using UnityEngine; using UnityEngine.Rendering; public class EnemyVisionVisualizer : MonoBehaviour { public EnemyVision vision; public EnemyStateMachine stateMachine; public Material visionMaterialTemplate; public int segmentCount = 28; public float groundOffset = 0.05f; private Transform meshRoot; private MeshFilter meshFilter; private MeshRenderer meshRenderer; private Mesh visionMesh; private Material runtimeMaterial; private void Awake() { vision = vision != null ? vision : GetComponent(); stateMachine = stateMachine != null ? stateMachine : GetComponent(); EnsureMeshObjects(); RebuildMesh(); UpdateVisualState(); } private void LateUpdate() { EnsureMeshObjects(); UpdateVisualState(); } private void OnValidate() { segmentCount = Mathf.Max(6, segmentCount); if (!Application.isPlaying) { EnsureMeshObjects(); RebuildMesh(); UpdateVisualState(); } } private void EnsureMeshObjects() { int visualizationLayer = LayerMask.NameToLayer("Vision"); if (visualizationLayer < 0) { visualizationLayer = LayerMask.NameToLayer("Ignore Raycast"); } if (meshRoot == null) { Transform existing = transform.Find("VisionCone"); if (existing != null) { meshRoot = existing; } else { GameObject root = new GameObject("VisionCone"); root.transform.SetParent(transform, false); root.transform.localPosition = new Vector3(0f, groundOffset, 0f); root.transform.localRotation = Quaternion.identity; root.layer = visualizationLayer; meshRoot = root.transform; } } meshRoot.localPosition = new Vector3(0f, groundOffset, 0f); meshRoot.gameObject.layer = visualizationLayer; if (meshFilter == null) { meshFilter = meshRoot.GetComponent(); if (meshFilter == null) { meshFilter = meshRoot.gameObject.AddComponent(); } } if (meshRenderer == null) { meshRenderer = meshRoot.GetComponent(); if (meshRenderer == null) { meshRenderer = meshRoot.gameObject.AddComponent(); meshRenderer.shadowCastingMode = ShadowCastingMode.Off; meshRenderer.receiveShadows = false; } } if (visionMesh == null) { visionMesh = new Mesh { name = "EnemyVisionCone" }; meshFilter.sharedMesh = visionMesh; } if (runtimeMaterial == null) { runtimeMaterial = visionMaterialTemplate != null ? new Material(visionMaterialTemplate) : BuildTransparentMaterial(); runtimeMaterial.name = "EnemyVisionRuntimeMaterial"; meshRenderer.sharedMaterial = runtimeMaterial; } } private void RebuildMesh() { if (vision == null || visionMesh == null) { return; } float radius = vision.viewRadius; float halfAngle = vision.viewAngle * 0.5f; Vector3[] vertices = new Vector3[segmentCount + 2]; int[] triangles = new int[segmentCount * 3]; Vector2[] uv = new Vector2[vertices.Length]; vertices[0] = Vector3.zero; uv[0] = new Vector2(0.5f, 0f); for (int i = 0; i <= segmentCount; i++) { float progress = i / (float)segmentCount; float angle = -halfAngle + progress * vision.viewAngle; float radians = Mathf.Deg2Rad * angle; Vector3 direction = new Vector3(Mathf.Sin(radians), 0f, Mathf.Cos(radians)); vertices[i + 1] = direction * radius; uv[i + 1] = new Vector2(progress, 1f); if (i == segmentCount) { continue; } int triangleIndex = i * 3; triangles[triangleIndex] = 0; triangles[triangleIndex + 1] = i + 2; triangles[triangleIndex + 2] = i + 1; } visionMesh.Clear(); visionMesh.vertices = vertices; visionMesh.triangles = triangles; visionMesh.uv = uv; visionMesh.RecalculateNormals(); visionMesh.RecalculateBounds(); } private void UpdateVisualState() { if (runtimeMaterial == null || stateMachine == null) { return; } Color stateColor = new Color(0.2f, 0.95f, 0.35f, 0.22f); switch (stateMachine.CurrentState) { case EnemyStateMachine.State.Suspicion: stateColor = new Color(1f, 0.85f, 0.2f, 0.24f); break; case EnemyStateMachine.State.Alert: stateColor = new Color(1f, 0.25f, 0.2f, 0.28f); break; } runtimeMaterial.color = stateColor; if (runtimeMaterial.HasProperty("_Color")) { runtimeMaterial.SetColor("_Color", stateColor); } } private Material BuildTransparentMaterial() { Shader shader = Shader.Find("Standard"); Material material = new Material(shader); material.SetFloat("_Mode", 3f); material.SetInt("_SrcBlend", (int)BlendMode.SrcAlpha); material.SetInt("_DstBlend", (int)BlendMode.OneMinusSrcAlpha); material.SetInt("_ZWrite", 0); material.DisableKeyword("_ALPHATEST_ON"); material.EnableKeyword("_ALPHABLEND_ON"); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.renderQueue = (int)RenderQueue.Transparent; material.color = new Color(0.2f, 0.95f, 0.35f, 0.22f); return material; } private void OnDrawGizmosSelected() { if (vision == null) { vision = GetComponent(); } if (vision == null) { return; } Gizmos.color = Color.cyan; Vector3 origin = transform.position + Vector3.up * 0.1f; Gizmos.DrawWireSphere(origin, vision.viewRadius); Quaternion leftRotation = Quaternion.Euler(0f, -vision.viewAngle * 0.5f, 0f); Quaternion rightRotation = Quaternion.Euler(0f, vision.viewAngle * 0.5f, 0f); Gizmos.DrawLine(origin, origin + leftRotation * transform.forward * vision.viewRadius); Gizmos.DrawLine(origin, origin + rightRotation * transform.forward * vision.viewRadius); } }