215 lines
6.6 KiB
C#
215 lines
6.6 KiB
C#
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<EnemyVision>();
|
|
stateMachine = stateMachine != null ? stateMachine : GetComponent<EnemyStateMachine>();
|
|
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<MeshFilter>();
|
|
if (meshFilter == null)
|
|
{
|
|
meshFilter = meshRoot.gameObject.AddComponent<MeshFilter>();
|
|
}
|
|
}
|
|
|
|
if (meshRenderer == null)
|
|
{
|
|
meshRenderer = meshRoot.GetComponent<MeshRenderer>();
|
|
if (meshRenderer == null)
|
|
{
|
|
meshRenderer = meshRoot.gameObject.AddComponent<MeshRenderer>();
|
|
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<EnemyVision>();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|