271 lines
8.0 KiB
C#
271 lines
8.0 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
|
|
public class EnemyTarget : MonoBehaviour, IDamageable
|
|
{
|
|
public static int RemainingObjectives { get; private set; }
|
|
public static event System.Action<int> 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<EnemyTarget>(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<Renderer>();
|
|
_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<PlayerHealth>();
|
|
}
|
|
|
|
if (_playerTarget == null && _playerHealth != null)
|
|
{
|
|
Camera playerCamera = _playerHealth.GetComponentInChildren<Camera>(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<PlayerHealth>();
|
|
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<Collider>());
|
|
burst.transform.position = transform.position + Vector3.up;
|
|
burst.transform.localScale = Vector3.one * 0.75f;
|
|
Renderer burstRenderer = burst.GetComponent<Renderer>();
|
|
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<LineRenderer>();
|
|
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;
|
|
}
|
|
}
|