using Components; using System.Collections.Generic; using GeometryTD.Definition; using GeometryTD.Entity.EntityData; using UnityEngine; using UnityGameFramework.Runtime; namespace GeometryTD.Entity { public class EnemyEntity : EntityBase, IDamageReceiver { private const float WaypointReachDistance = 0.05f; private static readonly List _activeEnemies = new(); private static readonly HashSet _killedEnemyEntityIds = new(); private Transform _target; private float _speed; private int _maxHealth; private int _currentHealth; private MovementComponent _movementComponent; private readonly List _pathPoints = new(); private int _pathPointIndex; private bool _isDespawnRequested; public static IReadOnlyList ActiveEnemies => _activeEnemies; public static bool TryConsumeKilledFlag(int entityId) { return _killedEnemyEntityIds.Remove(entityId); } protected override void OnInit(object userData) { base.OnInit(userData); _movementComponent = GetComponent(); } protected override void OnShow(object userData) { base.OnShow(userData); _target = null; _pathPoints.Clear(); _pathPointIndex = 0; _isDespawnRequested = false; _maxHealth = 1; _currentHealth = 1; if (userData is EnemyData enemyData) { _speed = enemyData.Speed; _target = enemyData.Player; _maxHealth = Mathf.Max(1, enemyData.MaxHealth); _currentHealth = _maxHealth; if (enemyData.HasPath) { IReadOnlyList pathPoints = enemyData.PathPoints; for (int i = 0; i < pathPoints.Count; i++) { _pathPoints.Add(pathPoints[i]); } } } _movementComponent.OnInit(_speed, CachedTransform); _movementComponent.SetMove(true); _killedEnemyEntityIds.Remove(Id); if (!_activeEnemies.Contains(this)) { _activeEnemies.Add(this); } } protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) { base.OnUpdate(elapseSeconds, realElapseSeconds); if (_pathPoints.Count > 0) { UpdatePathMovement(elapseSeconds, realElapseSeconds); return; } } protected override void OnHide(bool isShutdown, object userData) { _movementComponent.SetMove(false); _pathPoints.Clear(); _pathPointIndex = 0; _isDespawnRequested = false; _maxHealth = 0; _currentHealth = 0; _activeEnemies.Remove(this); base.OnHide(isShutdown, userData); } private void OnDestroy() { _activeEnemies.Remove(this); } public void TakeDamage(int damage, AttackPropertyType attackPropertyType) { _ = attackPropertyType; if (_isDespawnRequested || damage <= 0 || _currentHealth <= 0) { return; } int previousHealth = _currentHealth; _currentHealth = Mathf.Max(0, _currentHealth - damage); if (_maxHealth > 0) { GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth, (float)_currentHealth / _maxHealth); Log.Info($"ShowBar: {_currentHealth}/{_maxHealth}"); } if (_currentHealth <= 0) { Log.Info("Enemy Dead"); _killedEnemyEntityIds.Add(Id); RequestDespawn(); } } private void UpdatePathMovement(float elapseSeconds, float realElapseSeconds) { if (_isDespawnRequested) { return; } if (_pathPointIndex >= _pathPoints.Count) { DespawnOnReachHouse(); return; } Vector3 direction = GetDirectionToPathPoint(_pathPoints[_pathPointIndex], out float distanceSquared); if (distanceSquared <= WaypointReachDistance * WaypointReachDistance) { _pathPointIndex++; if (_pathPointIndex >= _pathPoints.Count) { DespawnOnReachHouse(); return; } direction = GetDirectionToPathPoint(_pathPoints[_pathPointIndex], out _); } if (direction.sqrMagnitude <= Mathf.Epsilon) { _movementComponent.SetMove(false); return; } _movementComponent.SetMove(true); _movementComponent.SetDirection(direction); _movementComponent.OnUpdate(elapseSeconds, realElapseSeconds); } private Vector3 GetDirectionToPathPoint(Vector3 worldPoint, out float distanceSquared) { Vector3 delta = worldPoint - CachedTransform.position; distanceSquared = delta.sqrMagnitude; if (distanceSquared <= Mathf.Epsilon) { return Vector3.zero; } return delta.normalized; } private void DespawnOnReachHouse() { RequestDespawn(); } private void RequestDespawn() { if (_isDespawnRequested) { return; } _isDespawnRequested = true; _movementComponent.SetMove(false); GameEntry.Entity.HideEntity(Entity); } } }