add : unabomber scene
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
/// <summary>Reachable landmarks at the gaps between four repeated server clusters.</summary>
|
||||
[ExecuteAlways, DisallowMultipleComponent, RequireComponent(typeof(PortalMeadow)), DefaultExecutionOrder(205)]
|
||||
public sealed class PortalExitMarker : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Material exitMaterial;
|
||||
[SerializeField] private float rotationY = 45;
|
||||
[SerializeField] private Vector3 dimensions = new Vector3(1.5f, 3.2f, 0.2f);
|
||||
private PortalMeadow meadow;
|
||||
private readonly List<Transform> markers = new List<Transform>();
|
||||
private PortalServerGrid grid;
|
||||
private float period;
|
||||
private Material runtimeMaterial;
|
||||
private bool rebuild;
|
||||
private Vector3 initialCentre;
|
||||
private Material lastMaterial;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
meadow = GetComponent<PortalMeadow>(); grid = GetComponent<PortalServerGrid>(); rebuild = true;
|
||||
RenderPipelineManager.beginCameraRendering += Sync;
|
||||
}
|
||||
private void OnValidate() { rebuild = true; }
|
||||
private void Update()
|
||||
{
|
||||
if (!meadow || !exitMaterial || !grid || !grid.enabled || grid.RepetitionPeriod <= 0) { Clear(); return; }
|
||||
if (!rebuild && markers.Count > 0 && initialCentre == meadow.Center && lastMaterial == exitMaterial && period == grid.RepetitionPeriod) return;
|
||||
Clear(); rebuild = false; initialCentre = meadow.Center; lastMaterial = exitMaterial;
|
||||
period = grid.RepetitionPeriod;
|
||||
runtimeMaterial = new Material(exitMaterial) { hideFlags = HideFlags.DontSave, name = "Distant Exit (runtime)" };
|
||||
for (int z = -1; z <= 1; z++)
|
||||
for (int x = -1; x <= 1; x++)
|
||||
{
|
||||
var marker = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
marker.name = "Luminous Gap Exit (pooled)";
|
||||
marker.hideFlags = HideFlags.DontSave;
|
||||
marker.transform.SetParent(transform, false);
|
||||
marker.transform.localPosition = initialCentre + new Vector3((x + 0.5f) * period, 0, (z + 0.5f) * period)
|
||||
+ Vector3.up * Mathf.Max(0.1f, dimensions.y) * 0.5f;
|
||||
marker.transform.localRotation = Quaternion.Euler(0, rotationY, 0);
|
||||
marker.transform.localScale = new Vector3(Mathf.Max(0.1f, dimensions.x), Mathf.Max(0.1f, dimensions.y), Mathf.Max(0.01f, dimensions.z));
|
||||
marker.GetComponent<BoxCollider>().isTrigger = true;
|
||||
var renderer = marker.GetComponent<MeshRenderer>();
|
||||
renderer.sharedMaterial = runtimeMaterial;
|
||||
renderer.renderingLayerMask = meadow.RenderingLayers;
|
||||
renderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
renderer.allowOcclusionWhenDynamic = false;
|
||||
Bounds bounds = renderer.localBounds;
|
||||
float smallest = Mathf.Min(marker.transform.localScale.x, marker.transform.localScale.y, marker.transform.localScale.z);
|
||||
bounds.Expand(meadow.BoundsPadding * 2 / smallest);
|
||||
renderer.localBounds = bounds;
|
||||
markers.Add(marker.transform);
|
||||
}
|
||||
}
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!meadow || !meadow.Following || period <= 0) return;
|
||||
float poolPeriod = period * 3;
|
||||
foreach (Transform marker in markers)
|
||||
{
|
||||
Vector3 p = marker.localPosition;
|
||||
p.x += Mathf.Floor((meadow.SurfaceCenter.x - p.x + poolPeriod * 0.5f) / poolPeriod) * poolPeriod;
|
||||
p.z += Mathf.Floor((meadow.SurfaceCenter.z - p.z + poolPeriod * 0.5f) / poolPeriod) * poolPeriod;
|
||||
marker.localPosition = p;
|
||||
}
|
||||
}
|
||||
private void Sync(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (!runtimeMaterial || !meadow || !meadow.MeadowMaterial) return;
|
||||
runtimeMaterial.CopyPropertiesFromMaterial(exitMaterial);
|
||||
meadow.ApplyBend(runtimeMaterial);
|
||||
meadow.ConfigureRegion(runtimeMaterial);
|
||||
}
|
||||
private void OnDisable() { RenderPipelineManager.beginCameraRendering -= Sync; Clear(); }
|
||||
private void Clear()
|
||||
{
|
||||
foreach (Transform marker in markers)
|
||||
if (marker) { marker.gameObject.SetActive(false); Release(marker.gameObject); }
|
||||
markers.Clear();
|
||||
if (runtimeMaterial) Release(runtimeMaterial);
|
||||
runtimeMaterial = null;
|
||||
}
|
||||
private static void Release(Object value)
|
||||
{
|
||||
if (Application.isPlaying) Destroy(value); else DestroyImmediate(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9a18fc47d64b90b9c4131e4dfdcb62
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
/// <summary>Concentric curtains using Omnissiah's procedural fog and the meadow's bend.</summary>
|
||||
[ExecuteAlways, DisallowMultipleComponent, RequireComponent(typeof(PortalMeadow)), DefaultExecutionOrder(215)]
|
||||
public sealed class PortalFogRings : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Material fogMaterial;
|
||||
[SerializeField, Range(1, 8)] private int layers = 4;
|
||||
[SerializeField, Range(0.1f, 0.9f)] private float innerRadius = 0.35f;
|
||||
[SerializeField, Range(0.2f, 1.2f)] private float outerRadius = 0.98f;
|
||||
[SerializeField, Min(1)] private float height = 8;
|
||||
[SerializeField] private float baseOffset = -1;
|
||||
private PortalMeadow meadow;
|
||||
private GameObject generated;
|
||||
private Mesh mesh;
|
||||
private readonly List<Transform> rings = new List<Transform>();
|
||||
private readonly List<Material> materials = new List<Material>();
|
||||
private Material lastMaterial;
|
||||
private bool rebuild;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
meadow = GetComponent<PortalMeadow>(); rebuild = true;
|
||||
RenderPipelineManager.beginCameraRendering += Sync;
|
||||
}
|
||||
private void OnValidate() { rebuild = true; }
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!meadow || !fogMaterial) { Clear(); return; }
|
||||
if (rebuild || !generated || lastMaterial != fogMaterial) Build();
|
||||
generated.transform.localPosition = meadow.SurfaceCenter + Vector3.up * baseOffset;
|
||||
for (int i = 0; i < rings.Count; i++)
|
||||
{
|
||||
float t = rings.Count == 1 ? 1 : (float)i / (rings.Count - 1);
|
||||
float radius = meadow.Radius * Mathf.Lerp(innerRadius, Mathf.Max(innerRadius, outerRadius), t);
|
||||
rings[i].localScale = new Vector3(radius, Mathf.Max(1, height) * Mathf.Lerp(0.65f, 1, t), radius);
|
||||
var renderer = rings[i].GetComponent<MeshRenderer>();
|
||||
Bounds bounds = mesh.bounds;
|
||||
bounds.Expand(meadow.BoundsPadding * 2 / Mathf.Max(0.01f, Mathf.Min(radius, rings[i].localScale.y)));
|
||||
renderer.localBounds = bounds;
|
||||
}
|
||||
}
|
||||
private void Build()
|
||||
{
|
||||
Clear(); rebuild = false; lastMaterial = fogMaterial;
|
||||
generated = new GameObject("Atmospheric Fog Rings (generated)") { hideFlags = HideFlags.DontSave };
|
||||
generated.transform.SetParent(transform, false);
|
||||
const int segments = 128, rows = 8;
|
||||
var vertices = new Vector3[(segments + 1) * (rows + 1)];
|
||||
var triangles = new int[segments * rows * 6];
|
||||
int n = 0;
|
||||
for (int y = 0; y <= rows; y++)
|
||||
for (int x = 0; x <= segments; x++)
|
||||
{
|
||||
float angle = x * Mathf.PI * 2 / segments;
|
||||
vertices[y * (segments + 1) + x] = new Vector3(Mathf.Sin(angle), (float)y / rows, Mathf.Cos(angle));
|
||||
if (y == rows || x == segments) continue;
|
||||
int a = y * (segments + 1) + x, b = a + segments + 1;
|
||||
triangles[n++] = a; triangles[n++] = b; triangles[n++] = a + 1;
|
||||
triangles[n++] = a + 1; triangles[n++] = b; triangles[n++] = b + 1;
|
||||
}
|
||||
mesh = new Mesh { name = "Fog cylinder curtain", hideFlags = HideFlags.DontSave };
|
||||
mesh.vertices = vertices; mesh.triangles = triangles; mesh.RecalculateBounds();
|
||||
for (int i = 0; i < Mathf.Clamp(layers, 1, 8); i++)
|
||||
{
|
||||
var ring = new GameObject("Fog layer " + (i + 1)) { hideFlags = HideFlags.DontSave };
|
||||
ring.transform.SetParent(generated.transform, false);
|
||||
ring.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||||
var mat = new Material(fogMaterial) { name = "PortalB Fog (runtime)", hideFlags = HideFlags.DontSave };
|
||||
materials.Add(mat);
|
||||
var renderer = ring.AddComponent<MeshRenderer>();
|
||||
renderer.sharedMaterial = mat;
|
||||
renderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
renderer.receiveShadows = false;
|
||||
renderer.allowOcclusionWhenDynamic = false;
|
||||
rings.Add(ring.transform);
|
||||
}
|
||||
}
|
||||
private void Sync(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (!meadow || !meadow.MeadowMaterial || !fogMaterial) return;
|
||||
for (int i = 0; i < materials.Count; i++)
|
||||
{
|
||||
var mat = materials[i];
|
||||
mat.CopyPropertiesFromMaterial(fogMaterial);
|
||||
// From outside to inside, after the sky dome. The player is inside all curtains.
|
||||
mat.renderQueue = 3000 + materials.Count - 1 - i;
|
||||
meadow.ApplyBend(mat);
|
||||
meadow.ApplyPortalMask(mat);
|
||||
}
|
||||
}
|
||||
private void OnDisable() { RenderPipelineManager.beginCameraRendering -= Sync; Clear(); }
|
||||
private void Clear()
|
||||
{
|
||||
if (generated) { generated.SetActive(false); Release(generated); }
|
||||
foreach (Material mat in materials) if (mat) Release(mat);
|
||||
if (mesh) Release(mesh);
|
||||
generated = null; mesh = null; materials.Clear(); rings.Clear();
|
||||
}
|
||||
private static void Release(Object obj)
|
||||
{
|
||||
if (Application.isPlaying) Destroy(obj); else DestroyImmediate(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6d937b536e247789348f0994c873cb1
|
||||
@@ -0,0 +1,315 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
/// <summary>Deterministic meadow, generated in edit mode and at runtime. Wind and bending run on the GPU.</summary>
|
||||
[ExecuteAlways, DisallowMultipleComponent, DefaultExecutionOrder(100)]
|
||||
public sealed class PortalMeadow : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Material material;
|
||||
[SerializeField] private Vector2 size = new Vector2(32, 48);
|
||||
[SerializeField] private Vector3 center = new Vector3(0, -0.03f, 16);
|
||||
[SerializeField] private bool centerOnPlayerAtStart = true;
|
||||
[Tooltip("Optional player reference. Otherwise uses the Player tag, then the main camera.")]
|
||||
[SerializeField] private Transform initialPlayer;
|
||||
[SerializeField] private bool circular = true;
|
||||
[Tooltip("Keep the visible disk around the moving player and recycle existing grass tiles. No player teleportation.")]
|
||||
[SerializeField] private bool endless = true;
|
||||
[Tooltip("Disable while PortalB is only seen through a portal: the disk stays fixed until SetFollowPlayer(true).")]
|
||||
[SerializeField] private bool followPlayer = true;
|
||||
[SerializeField, Min(0.1f)] private float edgeFade = 4;
|
||||
[SerializeField] private Color edgeColor = new Color(0.36f, 0.43f, 0.46f, 1);
|
||||
[Tooltip("Thickness below the flat ground surface. Shader bending is visual only.")]
|
||||
[SerializeField, Min(0.01f)] private float colliderThickness = 0.2f;
|
||||
[SerializeField, Range(1, 60)] private int bladesPerSquareMetre = 30;
|
||||
[SerializeField] private Vector2 bladeHeight = new Vector2(0.25f, 0.65f);
|
||||
[SerializeField, Range(0.01f, 0.15f)] private float bladeWidth = 0.055f;
|
||||
[SerializeField] private int seed = 1978;
|
||||
[Tooltip("Extra bounds in every direction to accommodate camera-relative shader bending.")]
|
||||
[SerializeField, Min(1)] private float boundsPadding = 128;
|
||||
[Tooltip("Rendering layers of every generated PortalB renderer, so lights can be restricted to this world.")]
|
||||
[SerializeField] private RenderingLayerMask renderingLayers = 1u;
|
||||
[Tooltip("While not following the player, the bend is computed from this frame (e.g. the portal, looking along its forward axis) " +
|
||||
"instead of the camera, so it stays aligned and still behind the portal.")]
|
||||
[SerializeField] private Transform bendAnchor;
|
||||
[Tooltip("Blend from the anchor frame to the camera once the player follows.")]
|
||||
[SerializeField, Min(0)] private float bendFadeDuration = 1.5f;
|
||||
|
||||
private static readonly string[] BendProperties = { "_Curvature", "_StartDistance", "_HorizontalInfluence", "_BendAngle" };
|
||||
private static readonly int FrameId = Shader.PropertyToID("_CurvedWorldFrame");
|
||||
private static readonly int FrameInvId = Shader.PropertyToID("_CurvedWorldFrameInv");
|
||||
private static readonly int FrameFixedId = Shader.PropertyToID("_CurvedWorldFrameFixed");
|
||||
private float frameBlend;
|
||||
|
||||
private readonly List<Mesh> meshes = new List<Mesh>();
|
||||
private GameObject generated;
|
||||
private bool rebuild;
|
||||
private Vector3 initialCenterOffset;
|
||||
private bool centerCaptured;
|
||||
private bool wasPlaying;
|
||||
private Transform capturedPlayer;
|
||||
private Vector3 surfaceCenter;
|
||||
private Material renderedMaterial;
|
||||
private BoxCollider groundCollider;
|
||||
private readonly List<Transform> tiles = new List<Transform>();
|
||||
private readonly List<MeshRenderer> tileRenderers = new List<MeshRenderer>();
|
||||
private Vector2 patchSize;
|
||||
private Vector2 tileSize;
|
||||
|
||||
public Vector2 Size => size;
|
||||
public Vector3 Center => center + (centerOnPlayerAtStart ? initialCenterOffset : Vector3.zero);
|
||||
public Material MeadowMaterial => material;
|
||||
public float BoundsPadding => boundsPadding;
|
||||
public uint RenderingLayers => renderingLayers;
|
||||
public float Radius => Mathf.Min(size.x, size.y) * 0.5f;
|
||||
public bool Endless => endless && Application.isPlaying;
|
||||
/// <summary>Endless and currently recentering on the player (see SetFollowPlayer).</summary>
|
||||
public bool Following => Endless && followPlayer;
|
||||
public Vector3 SurfaceCenter => Following && capturedPlayer ? surfaceCenter : Center;
|
||||
|
||||
public void ConfigureRegion(Material target)
|
||||
{
|
||||
Vector3 p = SurfaceCenter;
|
||||
target.SetVector("_MeadowRegion", new Vector4(p.x, p.y, p.z, circular ? Radius : 0));
|
||||
target.SetMatrix("_MeadowWorldToLocal", transform.worldToLocalMatrix);
|
||||
target.SetFloat("_MeadowEdgeFade", Mathf.Min(edgeFade, Radius * 0.5f));
|
||||
target.SetColor("_MeadowEdgeColor", edgeColor);
|
||||
ApplyPortalMask(target);
|
||||
}
|
||||
|
||||
/// <summary>The meadow material's portal mask is the reference for every PortalB material.</summary>
|
||||
public void ApplyPortalMask(Material target)
|
||||
{
|
||||
if (material && material.HasProperty("_MaskID") && target.HasProperty("_MaskID"))
|
||||
target.SetFloat("_MaskID", material.GetFloat("_MaskID"));
|
||||
}
|
||||
|
||||
/// <summary>The meadow material's bend is the reference for every PortalB material.</summary>
|
||||
public void ApplyBend(Material target)
|
||||
{
|
||||
if (!material) return;
|
||||
foreach (string property in BendProperties)
|
||||
if (material.HasProperty(property) && target.HasProperty(property))
|
||||
target.SetFloat(property, material.GetFloat(property));
|
||||
}
|
||||
|
||||
public void SetFollowPlayer(bool follow) { followPlayer = follow; }
|
||||
|
||||
private void BeforeCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (!bendAnchor || frameBlend >= 1) { Shader.SetGlobalFloat(FrameFixedId, 0); return; }
|
||||
Transform view = camera.transform;
|
||||
Vector3 position = Vector3.Lerp(bendAnchor.position, view.position, frameBlend);
|
||||
Quaternion rotation = Quaternion.Slerp(bendAnchor.rotation, view.rotation, frameBlend);
|
||||
// Same convention as a camera view matrix: looks along -Z.
|
||||
Matrix4x4 frameToWorld = Matrix4x4.TRS(position, rotation, new Vector3(1, 1, -1));
|
||||
Shader.SetGlobalMatrix(FrameId, frameToWorld.inverse);
|
||||
Shader.SetGlobalMatrix(FrameInvId, frameToWorld);
|
||||
Shader.SetGlobalFloat(FrameFixedId, 1);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
frameBlend = !Following ? 0 : bendFadeDuration > 0 ? Mathf.MoveTowards(frameBlend, 1, Time.deltaTime / bendFadeDuration) : 1;
|
||||
surfaceCenter = Center;
|
||||
if (Following && capturedPlayer)
|
||||
{
|
||||
Vector3 p = transform.InverseTransformPoint(capturedPlayer.position);
|
||||
surfaceCenter.x = p.x; surfaceCenter.z = p.z;
|
||||
}
|
||||
if (groundCollider) groundCollider.center = SurfaceCenter + Vector3.down * (colliderThickness * 0.5f);
|
||||
if (renderedMaterial && material)
|
||||
{
|
||||
renderedMaterial.CopyPropertiesFromMaterial(material);
|
||||
ConfigureRegion(renderedMaterial);
|
||||
ApplyBend(renderedMaterial);
|
||||
}
|
||||
if (!generated) return;
|
||||
for (int i = 0; i < tiles.Count; i++)
|
||||
{
|
||||
Transform tile = tiles[i];
|
||||
Vector3 p = tile.localPosition;
|
||||
if (Following)
|
||||
{
|
||||
p.x += Mathf.Floor((SurfaceCenter.x - p.x - tileSize.x * 0.5f + patchSize.x * 0.5f) / patchSize.x) * patchSize.x;
|
||||
p.z += Mathf.Floor((SurfaceCenter.z - p.z - tileSize.y * 0.5f + patchSize.y * 0.5f) / patchSize.y) * patchSize.y;
|
||||
tile.localPosition = p;
|
||||
}
|
||||
float dx = Mathf.Max(0, Mathf.Abs(p.x + tileSize.x * 0.5f - SurfaceCenter.x) - tileSize.x * 0.5f);
|
||||
float dz = Mathf.Max(0, Mathf.Abs(p.z + tileSize.y * 0.5f - SurfaceCenter.z) - tileSize.y * 0.5f);
|
||||
tileRenderers[i].enabled = !circular || dx * dx + dz * dz <= (Radius + 0.3f) * (Radius + 0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable() { rebuild = true; centerCaptured = false; initialCenterOffset = Vector3.zero; wasPlaying = Application.isPlaying; frameBlend = 0; RenderPipelineManager.beginCameraRendering += BeforeCameraRendering; }
|
||||
private void OnValidate()
|
||||
{
|
||||
size.x = Mathf.Clamp(size.x, 1, 128);
|
||||
size.y = Mathf.Clamp(size.y, 1, 128);
|
||||
colliderThickness = Mathf.Max(0.01f, colliderThickness);
|
||||
bladeHeight.x = Mathf.Clamp(bladeHeight.x, 0.05f, 2);
|
||||
bladeHeight.y = Mathf.Clamp(bladeHeight.y, bladeHeight.x, 3);
|
||||
// Defer Unity object creation until Update, outside the serialization callback.
|
||||
rebuild = true;
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
// Capture again when entering Play even when domain/scene reload is disabled.
|
||||
if (wasPlaying != Application.isPlaying)
|
||||
{
|
||||
wasPlaying = Application.isPlaying;
|
||||
centerCaptured = false;
|
||||
initialCenterOffset = Vector3.zero;
|
||||
rebuild = true;
|
||||
}
|
||||
if ((centerOnPlayerAtStart || endless) && !centerCaptured) CaptureInitialCenter();
|
||||
if (!rebuild) return;
|
||||
rebuild = false;
|
||||
Build();
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering -= BeforeCameraRendering;
|
||||
Shader.SetGlobalFloat(FrameFixedId, 0);
|
||||
Clear();
|
||||
}
|
||||
|
||||
[ContextMenu("Regenerate meadow")]
|
||||
private void Regenerate() { rebuild = true; }
|
||||
|
||||
[ContextMenu("Recenter on player once")]
|
||||
private void Recenter() { centerCaptured = false; rebuild = true; }
|
||||
|
||||
private void CaptureInitialCenter()
|
||||
{
|
||||
Transform player = initialPlayer;
|
||||
if (!player)
|
||||
{
|
||||
foreach (GameObject candidate in GameObject.FindGameObjectsWithTag("Player"))
|
||||
if (candidate.scene == gameObject.scene) { player = candidate.transform; break; }
|
||||
}
|
||||
if (!player && Camera.main) player = Camera.main.transform;
|
||||
if (!player) return;
|
||||
capturedPlayer = player;
|
||||
Vector3 local = transform.InverseTransformPoint(player.position);
|
||||
initialCenterOffset = new Vector3(local.x - center.x, 0, local.z - center.z);
|
||||
surfaceCenter = Center;
|
||||
centerCaptured = true;
|
||||
rebuild = true;
|
||||
}
|
||||
|
||||
private void Clear()
|
||||
{
|
||||
if (generated) { generated.SetActive(false); Release(generated); }
|
||||
generated = null;
|
||||
foreach (Mesh mesh in meshes) if (mesh) Release(mesh);
|
||||
meshes.Clear();
|
||||
tiles.Clear();
|
||||
tileRenderers.Clear();
|
||||
if (renderedMaterial) Release(renderedMaterial);
|
||||
renderedMaterial = null;
|
||||
}
|
||||
private static void Release(Object value)
|
||||
{
|
||||
if (Application.isPlaying) Destroy(value);
|
||||
else DestroyImmediate(value);
|
||||
}
|
||||
|
||||
private void Build()
|
||||
{
|
||||
Clear();
|
||||
generated = new GameObject("Grass Ground (generated)") { hideFlags = HideFlags.DontSave, layer = gameObject.layer };
|
||||
generated.transform.SetParent(transform, false);
|
||||
// The top face matches the undeformed ground; X/Z match the full meadow footprint.
|
||||
groundCollider = generated.AddComponent<BoxCollider>();
|
||||
groundCollider.size = new Vector3(size.x, colliderThickness, size.y);
|
||||
groundCollider.center = Center + Vector3.down * (colliderThickness * 0.5f);
|
||||
if (!material) return;
|
||||
renderedMaterial = new Material(material) { name = "Meadow (circular)", hideFlags = HideFlags.DontSave };
|
||||
ConfigureRegion(renderedMaterial);
|
||||
ApplyBend(renderedMaterial);
|
||||
var random = new System.Random(seed);
|
||||
int nx = Mathf.CeilToInt(size.x / 8f), nz = Mathf.CeilToInt(size.y / 8f);
|
||||
float sx = size.x / nx, sz = size.y / nz;
|
||||
// One extra tile on each side keeps the disk covered while tiles wrap around it.
|
||||
nx += 2; nz += 2;
|
||||
patchSize = new Vector2(nx * sx, nz * sz);
|
||||
tileSize = new Vector2(sx, sz);
|
||||
for (int z = 0; z < nz; z++)
|
||||
for (int x = 0; x < nx; x++)
|
||||
BuildTile(new Vector3(Center.x - patchSize.x / 2 + x * sx, Center.y,
|
||||
Center.z - patchSize.y / 2 + z * sz), sx, sz, random, x, z);
|
||||
}
|
||||
|
||||
private void BuildTile(Vector3 origin, float sx, float sz, System.Random random, int tx, int tz)
|
||||
{
|
||||
var vertices = new List<Vector3>();
|
||||
var normals = new List<Vector3>();
|
||||
var uvs = new List<Vector2>();
|
||||
var colors = new List<Color>();
|
||||
var triangles = new List<int>();
|
||||
void Vertex(Vector3 position, Vector3 normal, Vector2 uv, Color color)
|
||||
{ vertices.Add(position); normals.Add(normal); uvs.Add(uv); colors.Add(color); }
|
||||
float Rand(float a, float b) { return Mathf.Lerp(a, b, (float)random.NextDouble()); }
|
||||
|
||||
// A subdivided ground is essential: a single quad cannot follow quadratic bending.
|
||||
int gx = Mathf.CeilToInt(sx * 2), gz = Mathf.CeilToInt(sz * 2);
|
||||
for (int z = 0; z <= gz; z++)
|
||||
for (int x = 0; x <= gx; x++)
|
||||
Vertex(new Vector3(sx * x / gx, 0, sz * z / gz), Vector3.up,
|
||||
Vector2.zero, new Color(0.5f, 0, 0, 0));
|
||||
for (int z = 0; z < gz; z++)
|
||||
for (int x = 0; x < gx; x++)
|
||||
{
|
||||
int a = z * (gx + 1) + x, b = a + gx + 1;
|
||||
triangles.AddRange(new[] { a, b, a + 1, a + 1, b, b + 1 });
|
||||
}
|
||||
|
||||
int count = Mathf.RoundToInt(sx * sz * bladesPerSquareMetre);
|
||||
for (int blade = 0; blade < count; blade++)
|
||||
{
|
||||
Vector3 root = new Vector3(Rand(0, sx), 0.005f, Rand(0, sz));
|
||||
float angle = Rand(0, Mathf.PI * 2), height = Rand(bladeHeight.x, bladeHeight.y);
|
||||
Vector3 side = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle));
|
||||
Vector3 normal = Vector3.Cross(side, Vector3.up);
|
||||
Vector3 lean = normal * Rand(-0.18f, 0.18f);
|
||||
Color color = new Color(Rand(0, 1), Rand(0, 1), 0, 1);
|
||||
float width = bladeWidth * Rand(0.65f, 1.35f);
|
||||
int first = vertices.Count;
|
||||
for (int level = 0; level < 3; level++)
|
||||
{
|
||||
float t = level / 3f;
|
||||
Vector3 p = root + Vector3.up * height * t + lean * t * t;
|
||||
Vector3 half = side * width * (1 - t) * 0.5f;
|
||||
Vertex(p - half, normal, new Vector2(0, t), color);
|
||||
Vertex(p + half, normal, new Vector2(1, t), color);
|
||||
}
|
||||
Vertex(root + Vector3.up * height + lean, normal, new Vector2(0.5f, 1), color);
|
||||
triangles.AddRange(new[] { first, first + 2, first + 1, first + 1, first + 2, first + 3,
|
||||
first + 2, first + 4, first + 3, first + 3, first + 4, first + 5, first + 4, first + 6, first + 5 });
|
||||
}
|
||||
|
||||
var mesh = new Mesh { name = $"Meadow {tx},{tz}", hideFlags = HideFlags.DontSave };
|
||||
if (vertices.Count > 65535) mesh.indexFormat = IndexFormat.UInt32;
|
||||
mesh.SetVertices(vertices); mesh.SetNormals(normals); mesh.SetUVs(0, uvs);
|
||||
mesh.SetColors(colors); mesh.SetTriangles(triangles, 0);
|
||||
mesh.RecalculateBounds();
|
||||
Bounds bounds = mesh.bounds;
|
||||
bounds.Expand(boundsPadding * 2);
|
||||
mesh.bounds = bounds;
|
||||
meshes.Add(mesh);
|
||||
var tile = new GameObject($"Meadow {tx},{tz}") { hideFlags = HideFlags.DontSave, layer = gameObject.layer };
|
||||
tile.transform.SetParent(generated.transform, false);
|
||||
tile.transform.localPosition = origin;
|
||||
tile.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||||
var renderer = tile.AddComponent<MeshRenderer>();
|
||||
renderer.sharedMaterial = renderedMaterial;
|
||||
renderer.renderingLayerMask = renderingLayers;
|
||||
tiles.Add(tile.transform);
|
||||
tileRenderers.Add(renderer);
|
||||
// Camera-relative bending must not be evaluated from a shadow light's view matrix.
|
||||
renderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
renderer.receiveShadows = false;
|
||||
renderer.allowOcclusionWhenDynamic = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db39d731f9ed4af998db7d9054ad7421
|
||||
@@ -0,0 +1,240 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
/// <summary>Fixed concentric server rings, facing the initial meadow centre. Only the shader bends them.</summary>
|
||||
[ExecuteAlways, DisallowMultipleComponent, RequireComponent(typeof(PortalMeadow)), DefaultExecutionOrder(200)]
|
||||
public sealed class PortalServerGrid : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject serverPrefab;
|
||||
[SerializeField] private Shader curvedServerShader;
|
||||
[Tooltip("X: minimum chord distance between neighbours. Y: distance between rings.")]
|
||||
[SerializeField] private Vector2 spacing = new Vector2(5, 6);
|
||||
[Tooltip("Empty border inside the terrain on X and Z.")]
|
||||
[SerializeField] private Vector2 edgeMargin = new Vector2(1, 1);
|
||||
[SerializeField, Min(1)] private float firstRingRadius = 8;
|
||||
[SerializeField, Range(0, 360)] private float angleOffset;
|
||||
[Tooltip("Pivot adjustment along each server's facing direction (positive = towards the centre).")]
|
||||
[SerializeField] private float offsetZ;
|
||||
[Tooltip("Additional height above the ground, after automatically aligning the model base.")]
|
||||
[SerializeField] private float heightOffset;
|
||||
[SerializeField] private Vector3 rotation = new Vector3(0, 180, 0);
|
||||
[Tooltip("Multiplier applied to the prefab's original scale.")]
|
||||
[SerializeField, Min(0.001f)] private float scaleMultiplier = 1;
|
||||
|
||||
private PortalMeadow meadow;
|
||||
private GameObject generated;
|
||||
private Vector2 lastSize;
|
||||
private Vector3 lastCenter;
|
||||
private bool rebuild;
|
||||
private readonly Dictionary<Material, Material> curvedMaterials = new Dictionary<Material, Material>();
|
||||
private readonly List<Transform> servers = new List<Transform>();
|
||||
private float wrapExtent;
|
||||
// Distance between the centres of two repeated server clusters.
|
||||
public float RepetitionPeriod => wrapExtent * 2;
|
||||
private bool lastEndless;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
meadow = GetComponent<PortalMeadow>();
|
||||
rebuild = true;
|
||||
RenderPipelineManager.beginCameraRendering += BeforeCameraRendering;
|
||||
}
|
||||
private void BeforeCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
// Match the terrain's current material, including changes made after Update.
|
||||
if (meadow) SyncBend();
|
||||
}
|
||||
private void OnValidate()
|
||||
{
|
||||
spacing.x = Mathf.Max(0.1f, spacing.x);
|
||||
spacing.y = Mathf.Max(0.1f, spacing.y);
|
||||
edgeMargin.x = Mathf.Max(0, edgeMargin.x);
|
||||
edgeMargin.y = Mathf.Max(0, edgeMargin.y);
|
||||
scaleMultiplier = Mathf.Max(0.001f, scaleMultiplier);
|
||||
firstRingRadius = Mathf.Max(1, firstRingRadius);
|
||||
rebuild = true;
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
if (!meadow) meadow = GetComponent<PortalMeadow>();
|
||||
if (!meadow) return;
|
||||
SyncBend();
|
||||
if (!rebuild && lastSize == meadow.Size && lastCenter == meadow.Center && lastEndless == meadow.Endless) return;
|
||||
rebuild = false;
|
||||
lastSize = meadow.Size;
|
||||
lastCenter = meadow.Center;
|
||||
lastEndless = meadow.Endless;
|
||||
Build();
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
RenderPipelineManager.beginCameraRendering -= BeforeCameraRendering;
|
||||
Clear();
|
||||
}
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!meadow || !meadow.Following || wrapExtent <= 0) return;
|
||||
Vector3 centre = meadow.SurfaceCenter;
|
||||
float period = wrapExtent * 2;
|
||||
foreach (Transform server in servers)
|
||||
{
|
||||
Vector3 p = server.localPosition;
|
||||
// Recycle only outside the visible disk. Nearby transforms/rotations stay fixed.
|
||||
p.x += Mathf.Floor((centre.x - p.x + wrapExtent) / period) * period;
|
||||
p.z += Mathf.Floor((centre.z - p.z + wrapExtent) / period) * period;
|
||||
server.localPosition = p;
|
||||
}
|
||||
}
|
||||
[ContextMenu("Regenerate server rings")]
|
||||
private void Regenerate() { rebuild = true; }
|
||||
|
||||
private static void Release(Object value)
|
||||
{
|
||||
if (Application.isPlaying) Destroy(value);
|
||||
else DestroyImmediate(value);
|
||||
}
|
||||
private void Clear()
|
||||
{
|
||||
if (generated) { generated.SetActive(false); Release(generated); }
|
||||
generated = null;
|
||||
foreach (Material material in curvedMaterials.Values) if (material) Release(material);
|
||||
curvedMaterials.Clear();
|
||||
servers.Clear();
|
||||
}
|
||||
private GameObject CreateServer()
|
||||
{
|
||||
var instance = Instantiate(serverPrefab, generated.transform, false);
|
||||
instance.transform.localPosition = Vector3.zero;
|
||||
instance.transform.localRotation = Quaternion.Euler(rotation) * serverPrefab.transform.localRotation;
|
||||
instance.transform.localScale = serverPrefab.transform.localScale * scaleMultiplier;
|
||||
foreach (Transform child in instance.GetComponentsInChildren<Transform>(true))
|
||||
child.gameObject.hideFlags = HideFlags.DontSave;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void Build()
|
||||
{
|
||||
Clear();
|
||||
if (!serverPrefab) return;
|
||||
// Inactive during setup: avoid briefly enabling all instances at the origin.
|
||||
generated = new GameObject("Server Rings (generated)") { hideFlags = HideFlags.DontSave };
|
||||
generated.SetActive(false);
|
||||
generated.transform.SetParent(transform, false);
|
||||
var prototype = CreateServer();
|
||||
if (!TryGetLocalBounds(prototype, out Bounds bounds))
|
||||
{
|
||||
Debug.LogWarning("Server grid: the prefab needs a Renderer to calculate its footprint.", this);
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
float footprint = new Vector2(bounds.size.x, bounds.size.z).magnitude;
|
||||
wrapExtent = meadow.Radius + footprint + 1;
|
||||
float pitch = Mathf.Max(spacing.x, footprint, 0.1f);
|
||||
float ringStep = Mathf.Max(spacing.y, footprint, 0.1f);
|
||||
float maxRadius = Mathf.Min(lastSize.x * 0.5f - edgeMargin.x,
|
||||
lastSize.y * 0.5f - edgeMargin.y) - footprint * 0.5f - Mathf.Abs(offsetZ);
|
||||
float startRadius = Mathf.Max(firstRingRadius, footprint * 0.5f + Mathf.Abs(offsetZ));
|
||||
var poses = new List<Pose>();
|
||||
int ring = 0;
|
||||
for (float radius = startRadius; radius <= maxRadius; radius += ringStep, ring++)
|
||||
{
|
||||
// Use chord length, not arc length, so neighbours cannot overlap on small rings.
|
||||
float placementRadius = radius - offsetZ;
|
||||
int around = pitch <= 2 * placementRadius
|
||||
? Mathf.Max(1, Mathf.FloorToInt(Mathf.PI / Mathf.Asin(pitch / (2 * placementRadius)))) : 1;
|
||||
if (poses.Count + around > 4096)
|
||||
{
|
||||
Debug.LogWarning("Server rings: more than 4096 instances requested. Increase Spacing.", this);
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < around; i++)
|
||||
{
|
||||
float angle = angleOffset * Mathf.Deg2Rad + (i + (ring % 2) * 0.5f) * Mathf.PI * 2 / around;
|
||||
Vector3 radial = new Vector3(Mathf.Sin(angle), 0, Mathf.Cos(angle));
|
||||
Quaternion facing = Quaternion.LookRotation(-radial, Vector3.up);
|
||||
Vector3 position = lastCenter + radial * radius + facing * Vector3.forward * offsetZ;
|
||||
// Bounds already include the prefab rotation, scale and user rotation correction.
|
||||
position -= facing * new Vector3(bounds.center.x, bounds.min.y, bounds.center.z);
|
||||
position.y += heightOffset;
|
||||
poses.Add(new Pose(position, facing));
|
||||
}
|
||||
}
|
||||
int count = 0;
|
||||
foreach (Pose pose in poses)
|
||||
{
|
||||
GameObject server = count == 0 ? prototype : CreateServer();
|
||||
server.transform.localPosition = pose.position;
|
||||
server.transform.localRotation = pose.rotation * Quaternion.Euler(rotation) * serverPrefab.transform.localRotation;
|
||||
server.name = $"Server {++count:000}";
|
||||
ApplyCurvedMaterials(server);
|
||||
servers.Add(server.transform);
|
||||
}
|
||||
if (count == 0) { Clear(); return; }
|
||||
SyncBend();
|
||||
generated.SetActive(true);
|
||||
}
|
||||
|
||||
private void ApplyCurvedMaterials(GameObject server)
|
||||
{
|
||||
if (!curvedServerShader) return;
|
||||
foreach (Renderer renderer in server.GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
Material[] materials = renderer.sharedMaterials;
|
||||
for (int i = 0; i < materials.Length; i++)
|
||||
{
|
||||
Material source = materials[i];
|
||||
if (!source) continue;
|
||||
if (!curvedMaterials.TryGetValue(source, out Material curved))
|
||||
{
|
||||
curved = new Material(source) { shader = curvedServerShader,
|
||||
name = source.name + " (Curved)", hideFlags = HideFlags.DontSave };
|
||||
curved.shaderKeywords = source.shaderKeywords;
|
||||
curvedMaterials.Add(source, curved);
|
||||
}
|
||||
materials[i] = curved;
|
||||
}
|
||||
renderer.sharedMaterials = materials;
|
||||
renderer.renderingLayerMask = meadow.RenderingLayers;
|
||||
renderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
renderer.allowOcclusionWhenDynamic = false;
|
||||
Bounds expanded = renderer.localBounds;
|
||||
Vector3 scale = renderer.transform.lossyScale;
|
||||
float minScale = Mathf.Max(0.001f, Mathf.Min(Mathf.Abs(scale.x), Mathf.Abs(scale.y), Mathf.Abs(scale.z)));
|
||||
expanded.Expand(meadow.BoundsPadding * 2 / minScale);
|
||||
renderer.localBounds = expanded;
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncBend()
|
||||
{
|
||||
if (!meadow.MeadowMaterial) return;
|
||||
foreach (Material material in curvedMaterials.Values)
|
||||
{
|
||||
meadow.ApplyBend(material);
|
||||
meadow.ConfigureRegion(material);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetLocalBounds(GameObject instance, out Bounds bounds)
|
||||
{
|
||||
bounds = default;
|
||||
bool found = false;
|
||||
foreach (Renderer renderer in instance.GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
Bounds local = renderer.localBounds;
|
||||
Matrix4x4 matrix = generated.transform.worldToLocalMatrix * renderer.transform.localToWorldMatrix;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3 corner = local.center + Vector3.Scale(local.extents,
|
||||
new Vector3((i & 1) == 0 ? -1 : 1, (i & 2) == 0 ? -1 : 1, (i & 4) == 0 ? -1 : 1));
|
||||
Vector3 point = matrix.MultiplyPoint3x4(corner);
|
||||
if (!found) { bounds = new Bounds(point, Vector3.zero); found = true; }
|
||||
else bounds.Encapsulate(point);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8aa0d5b728d34ab4b9fc5e4b23bedad0
|
||||
@@ -0,0 +1,98 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
/// <summary>A local sky, independent of RenderSettings.skybox, ready for the existing projection masks.</summary>
|
||||
[ExecuteAlways, DisallowMultipleComponent, RequireComponent(typeof(PortalMeadow)), DefaultExecutionOrder(210)]
|
||||
public sealed class PortalSkyDome : MonoBehaviour
|
||||
{
|
||||
public enum SkyVisibility { Preview, PortalOnly, Hidden }
|
||||
[SerializeField] private Material skyMaterial;
|
||||
[SerializeField] private SkyVisibility visibility = SkyVisibility.Preview;
|
||||
[SerializeField] private bool insidePortalB;
|
||||
[Tooltip("The sphere also expands automatically to contain the terrain and its bend bounds.")]
|
||||
[SerializeField, Min(10)] private float minimumRadius = 100;
|
||||
private PortalMeadow meadow;
|
||||
private GameObject dome;
|
||||
private Mesh mesh;
|
||||
private Material runtimeMaterial;
|
||||
private Material sourceMaterial;
|
||||
|
||||
private void OnEnable() { meadow = GetComponent<PortalMeadow>(); }
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!meadow) meadow = GetComponent<PortalMeadow>();
|
||||
if (!meadow || !skyMaterial) { Clear(); return; }
|
||||
if (!dome || sourceMaterial != skyMaterial) Build();
|
||||
// The centre is the same initial snapshot used by the terrain and the server rings.
|
||||
dome.transform.localPosition = meadow.SurfaceCenter;
|
||||
float radius = Mathf.Max(minimumRadius, meadow.Size.magnitude + meadow.BoundsPadding + 20);
|
||||
dome.transform.localScale = Vector3.one * radius;
|
||||
runtimeMaterial.CopyPropertiesFromMaterial(skyMaterial);
|
||||
runtimeMaterial.SetFloat("_Visibility", (float)visibility);
|
||||
runtimeMaterial.SetFloat("_InsidePortalB", insidePortalB ? 1 : 0);
|
||||
}
|
||||
|
||||
// Ready to connect to the future portal's enter/exit UnityEvents. No distance trigger is inferred.
|
||||
public void EnterPortalB() { SetInsidePortalB(true); }
|
||||
public void ExitPortalB() { SetInsidePortalB(false); }
|
||||
public void SetInsidePortalB(bool inside)
|
||||
{
|
||||
insidePortalB = inside;
|
||||
visibility = SkyVisibility.PortalOnly;
|
||||
if (runtimeMaterial)
|
||||
{
|
||||
runtimeMaterial.SetFloat("_Visibility", (float)visibility);
|
||||
runtimeMaterial.SetFloat("_InsidePortalB", inside ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void Build()
|
||||
{
|
||||
Clear();
|
||||
sourceMaterial = skyMaterial;
|
||||
runtimeMaterial = new Material(skyMaterial) { name = "PortalB Sky (runtime)", hideFlags = HideFlags.DontSave };
|
||||
dome = new GameObject("PortalB Sky Sphere (generated)") { hideFlags = HideFlags.DontSave, layer = gameObject.layer };
|
||||
dome.transform.SetParent(transform, false);
|
||||
const int segments = 64, rings = 32;
|
||||
var positions = new Vector3[(segments + 1) * (rings + 1)];
|
||||
var triangles = new int[segments * rings * 6];
|
||||
for (int y = 0; y <= rings; y++)
|
||||
for (int x = 0; x <= segments; x++)
|
||||
{
|
||||
float latitude = Mathf.PI * y / rings, longitude = 2 * Mathf.PI * x / segments;
|
||||
positions[y * (segments + 1) + x] = new Vector3(Mathf.Sin(latitude) * Mathf.Cos(longitude),
|
||||
Mathf.Cos(latitude), Mathf.Sin(latitude) * Mathf.Sin(longitude));
|
||||
}
|
||||
int index = 0;
|
||||
for (int y = 0; y < rings; y++)
|
||||
for (int x = 0; x < segments; x++)
|
||||
{
|
||||
int a = y * (segments + 1) + x, b = a + segments + 1;
|
||||
triangles[index++] = a; triangles[index++] = a + 1; triangles[index++] = b;
|
||||
triangles[index++] = b; triangles[index++] = a + 1; triangles[index++] = b + 1;
|
||||
}
|
||||
mesh = new Mesh { name = "Portal sky sphere", hideFlags = HideFlags.DontSave };
|
||||
mesh.vertices = positions;
|
||||
mesh.triangles = triangles;
|
||||
mesh.RecalculateBounds();
|
||||
dome.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||||
var renderer = dome.AddComponent<MeshRenderer>();
|
||||
renderer.sharedMaterial = runtimeMaterial;
|
||||
renderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
renderer.receiveShadows = false;
|
||||
renderer.allowOcclusionWhenDynamic = false;
|
||||
}
|
||||
private void OnDisable() { Clear(); }
|
||||
private void Clear()
|
||||
{
|
||||
if (dome) { dome.SetActive(false); Release(dome); }
|
||||
if (mesh) Release(mesh);
|
||||
if (runtimeMaterial) Release(runtimeMaterial);
|
||||
dome = null; mesh = null; runtimeMaterial = null; sourceMaterial = null;
|
||||
}
|
||||
private static void Release(Object value)
|
||||
{
|
||||
if (Application.isPlaying) Destroy(value);
|
||||
else DestroyImmediate(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfc7df93461546eea0d803683d7f3c78
|
||||
Reference in New Issue
Block a user