using System.Collections; using UnityEngine; public class EnemyTarget : MonoBehaviour, IDamageable { public static int RemainingObjectives { get; private set; } public static event System.Action ObjectiveCountChanged; [Header("Health")] public float maxHealth = 60f; public bool countsAsObjective = true; [Header("Combat")] public bool canAttack = true; public float attackRange = 28f; public float attackDamage = 12f; public float attackCooldown = 1.3f; public Color tracerColor = new Color(1f, 0.35f, 0.25f); [Header("Movement")] public Vector3 patrolAxis = Vector3.zero; public float patrolAmplitude = 0f; public float patrolSpeed = 1f; [Header("References")] public Transform muzzlePoint; private static Material _sharedTracerMaterial; private float _currentHealth; private float _nextAttackTime; private Vector3 _startPosition; private PlayerHealth _playerHealth; private Transform _playerTarget; private Renderer[] _renderers; private Color[] _baseColors; private bool _destroyed; public static void ResetObjectives() { RemainingObjectives = 0; ObjectiveCountChanged?.Invoke(RemainingObjectives); } public static void RecalculateObjectivesInScene() { EnemyTarget[] targets = FindObjectsOfType(true); int total = 0; for (int i = 0; i < targets.Length; i++) { if (targets[i] != null && targets[i].countsAsObjective) { total++; } } RemainingObjectives = total; ObjectiveCountChanged?.Invoke(RemainingObjectives); } public void Initialize(PlayerHealth playerHealth, Transform playerTarget) { _playerHealth = playerHealth; _playerTarget = playerTarget; } private void Awake() { _currentHealth = maxHealth; _startPosition = transform.position; _renderers = GetComponentsInChildren(); _baseColors = new Color[_renderers.Length]; for (int i = 0; i < _renderers.Length; i++) { _baseColors[i] = _renderers[i].material.color; } if (countsAsObjective) { RemainingObjectives++; ObjectiveCountChanged?.Invoke(RemainingObjectives); } } private void Start() { SnapToSurface(); _startPosition = transform.position; if (_playerHealth == null) { _playerHealth = FindObjectOfType(); } if (_playerTarget == null && _playerHealth != null) { Camera playerCamera = _playerHealth.GetComponentInChildren(true); _playerTarget = playerCamera != null ? playerCamera.transform : _playerHealth.transform; } } private void SnapToSurface() { if (_renderers == null || _renderers.Length == 0) { return; } Bounds bounds = _renderers[0].bounds; for (int i = 1; i < _renderers.Length; i++) { bounds.Encapsulate(_renderers[i].bounds); } float currentBottomY = bounds.min.y; Vector3 rayOrigin = bounds.center + Vector3.up * 20f; RaycastHit[] hits = Physics.RaycastAll(rayOrigin, Vector3.down, 60f, ~0, QueryTriggerInteraction.Ignore); if (hits.Length == 0) { return; } System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance)); for (int i = 0; i < hits.Length; i++) { if (hits[i].collider == null || hits[i].collider.transform.IsChildOf(transform)) { continue; } float offsetY = hits[i].point.y - currentBottomY; if (Mathf.Abs(offsetY) > 0.01f) { transform.position += Vector3.up * offsetY; } break; } } private void Update() { if (_destroyed) { return; } if (patrolAmplitude > 0.01f && patrolAxis.sqrMagnitude > 0.001f) { Vector3 offset = patrolAxis.normalized * Mathf.Sin(Time.time * patrolSpeed) * patrolAmplitude; transform.position = _startPosition + offset; } if (!canAttack || _playerHealth == null || _playerHealth.IsDead || _playerTarget == null) { return; } Vector3 aimPoint = _playerTarget.position; Vector3 flatDirection = aimPoint - transform.position; flatDirection.y = 0f; if (flatDirection.sqrMagnitude > 0.001f) { Quaternion targetRotation = Quaternion.LookRotation(flatDirection.normalized, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f); } Vector3 fireOrigin = muzzlePoint != null ? muzzlePoint.position : transform.position + transform.forward * 0.8f + Vector3.up * 1.2f; Vector3 direction = (aimPoint - fireOrigin).normalized; if (Vector3.Distance(fireOrigin, aimPoint) > attackRange || Time.time < _nextAttackTime) { return; } if (Physics.Raycast(fireOrigin, direction, out RaycastHit hit, attackRange, ~0, QueryTriggerInteraction.Ignore)) { PlayerHealth hitPlayer = hit.collider.GetComponentInParent(); if (hitPlayer != null) { _nextAttackTime = Time.time + attackCooldown; hitPlayer.TakeDamage(attackDamage); ShowTracer(fireOrigin, hit.point, tracerColor); } } } public void TakeDamage(float amount, Vector3 hitPoint, Vector3 hitNormal) { if (_destroyed) { return; } _currentHealth -= amount; StartCoroutine(FlashHit()); if (_currentHealth <= 0f) { DestroyTarget(); } } private IEnumerator FlashHit() { for (int i = 0; i < _renderers.Length; i++) { _renderers[i].material.color = Color.white; } yield return new WaitForSeconds(0.08f); for (int i = 0; i < _renderers.Length; i++) { if (_renderers[i] != null) { _renderers[i].material.color = _baseColors[i]; } } } private void DestroyTarget() { _destroyed = true; if (countsAsObjective) { RemainingObjectives = Mathf.Max(0, RemainingObjectives - 1); ObjectiveCountChanged?.Invoke(RemainingObjectives); } GameObject burst = GameObject.CreatePrimitive(PrimitiveType.Sphere); Destroy(burst.GetComponent()); burst.transform.position = transform.position + Vector3.up; burst.transform.localScale = Vector3.one * 0.75f; Renderer burstRenderer = burst.GetComponent(); burstRenderer.material = new Material(Shader.Find("Standard")); burstRenderer.material.color = new Color(1f, 0.45f, 0.15f); Destroy(burst, 0.18f); Destroy(gameObject); } private void ShowTracer(Vector3 start, Vector3 end, Color color) { GameObject tracer = new GameObject("EnemyTracer"); LineRenderer renderer = tracer.AddComponent(); renderer.material = GetTracerMaterial(); renderer.positionCount = 2; renderer.SetPosition(0, start); renderer.SetPosition(1, end); renderer.startWidth = 0.045f; renderer.endWidth = 0.015f; renderer.startColor = color; renderer.endColor = new Color(color.r, color.g, color.b, 0.05f); renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; renderer.receiveShadows = false; Destroy(tracer, 0.07f); } private static Material GetTracerMaterial() { if (_sharedTracerMaterial == null) { _sharedTracerMaterial = new Material(Shader.Find("Sprites/Default")); } return _sharedTracerMaterial; } }