using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; /// Reachable landmarks at the gaps between four repeated server clusters. [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 markers = new List(); private PortalServerGrid grid; private float period; private Material runtimeMaterial; private bool rebuild; private Vector3 initialCentre; private Material lastMaterial; private void OnEnable() { meadow = GetComponent(); grid = GetComponent(); 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().isTrigger = true; var renderer = marker.GetComponent(); 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); } }