enemy spawner

This commit is contained in:
2025-10-26 14:17:31 +01:00
parent 20d3b46834
commit 40a62b5b5a
2102 changed files with 1255290 additions and 70 deletions

View File

@@ -1,4 +1,7 @@
using System.Collections.Generic;
using Game.Scripts.Runtime.Data;
using Game.Scripts.Runtime.Pooling;
using Game.Scripts.Runtime.Navigation;
using MegaKoop.Game.Combat;
using MegaKoop.Game.Networking;
using MegaKoop.Game.WeaponSystem;
@@ -27,6 +30,7 @@ namespace MegaKoop.Game.Enemy
[SerializeField] private float moveSpeed = 3.5f;
[SerializeField] private float turnSpeed = 6f;
[SerializeField] private float stoppingDistance = 4f;
[SerializeField] private float attackStoppingDistance = 1.25f;
[SerializeField] private float detectionRadius = 30f;
[SerializeField] private float leashDistance = 60f;
[SerializeField] private float retargetInterval = 0.4f;
@@ -40,6 +44,14 @@ namespace MegaKoop.Game.Enemy
[SerializeField] private NetworkIdentity identity;
[SerializeField] private SteamNetworkTransform networkTransform;
[Header("Spawner Integration")]
[SerializeField] private EnemyDefinition fallbackDefinition;
[SerializeField] private bool applyDefinitionStats = true;
[SerializeField] private float healthMultiplier = 1f;
[SerializeField] private float moveSpeedMultiplier = 1f;
[SerializeField] private float damageMultiplier = 1f;
[SerializeField] private float deathDespawnDelay = 0f;
private SteamCoopNetworkManager networkManager;
private Transform currentTargetTransform;
private NetworkIdentity currentTargetIdentity;
@@ -49,11 +61,17 @@ namespace MegaKoop.Game.Enemy
private float retargetTimer;
private Vector3 spawnPosition;
private bool cachedNavAgentState;
private PooledInstance pooledInstance;
private EnemyDefinition activeDefinition;
private float baseMoveSpeed;
private bool pendingDespawn;
private float despawnTimer;
private void Awake()
{
EnsureIdentity();
spawnPosition = transform.position;
baseMoveSpeed = moveSpeed;
if (navMeshAgent == null)
{
@@ -75,11 +93,21 @@ namespace MegaKoop.Game.Enemy
networkTransform = GetComponent<SteamNetworkTransform>();
}
if (pooledInstance == null)
{
pooledInstance = GetComponent<PooledInstance>();
}
var navMeshAvailable = NavMeshRuntimeUtility.HasNavMeshData();
if (navMeshAgent != null)
{
navMeshAgent.updateRotation = false;
navMeshAgent.stoppingDistance = stoppingDistance;
navMeshAgent.speed = moveSpeed;
if (!navMeshAvailable && navMeshAgent.enabled)
{
navMeshAgent.enabled = false;
}
cachedNavAgentState = navMeshAgent.enabled;
}
@@ -115,7 +143,33 @@ namespace MegaKoop.Game.Enemy
private void OnEnable()
{
networkManager = SteamCoopNetworkManager.Instance;
pooledInstance ??= GetComponent<PooledInstance>();
pendingDespawn = false;
despawnTimer = 0f;
spawnPosition = transform.position;
lastKnownTargetPosition = spawnPosition;
lastTargetPosition = Vector3.positiveInfinity;
ClearTarget();
retargetTimer = Random.Range(0f, Mathf.Max(0.05f, retargetInterval));
SyncNavMeshAgentState(ShouldSimulate());
if (health != null)
{
health.NormalizedHealthChanged += HandleHealthChanged;
}
ApplyDefinitionStats();
}
private void OnDisable()
{
if (health != null)
{
health.NormalizedHealthChanged -= HandleHealthChanged;
}
pendingDespawn = false;
weaponController?.ClearDamageOverride();
}
private void Update()
@@ -130,6 +184,12 @@ namespace MegaKoop.Game.Enemy
weaponController.enabled = simulate;
}
if (pendingDespawn)
{
TickDespawn(Time.deltaTime);
return;
}
if (!simulate)
{
return;
@@ -137,7 +197,7 @@ namespace MegaKoop.Game.Enemy
if (health != null && !health.IsAlive)
{
StopAgentMovement();
BeginDespawnCountdown();
return;
}
@@ -179,6 +239,16 @@ namespace MegaKoop.Game.Enemy
return;
}
if (!NavMeshRuntimeUtility.HasNavMeshData())
{
if (navMeshAgent.enabled)
{
navMeshAgent.enabled = false;
}
cachedNavAgentState = false;
return;
}
if (cachedNavAgentState == simulate)
{
return;
@@ -289,10 +359,11 @@ namespace MegaKoop.Game.Enemy
return;
}
float desiredStopDistance = GetDesiredStoppingDistance();
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
{
navMeshAgent.speed = moveSpeed;
navMeshAgent.stoppingDistance = stoppingDistance;
navMeshAgent.stoppingDistance = desiredStopDistance;
if (!navMeshAgent.hasPath || (targetPosition - lastTargetPosition).sqrMagnitude >= repathThreshold * repathThreshold)
{
@@ -302,26 +373,27 @@ namespace MegaKoop.Game.Enemy
}
else
{
ManualMove(targetPosition, deltaTime);
ManualMove(targetPosition, deltaTime, desiredStopDistance);
lastTargetPosition = targetPosition;
}
}
private void ManualMove(Vector3 targetPosition, float deltaTime)
private void ManualMove(Vector3 targetPosition, float deltaTime, float stopDistance)
{
Vector3 currentPosition = transform.position;
Vector3 direction = targetPosition - currentPosition;
direction.y = 0f;
float distance = direction.magnitude;
if (distance <= Mathf.Max(0.1f, stoppingDistance))
float resolvedStop = Mathf.Max(0.1f, stopDistance);
if (distance <= resolvedStop)
{
return;
}
Vector3 normalized = direction / distance;
float step = moveSpeed * deltaTime;
Vector3 newPosition = currentPosition + normalized * Mathf.Min(step, distance - stoppingDistance);
Vector3 newPosition = currentPosition + normalized * Mathf.Min(step, distance - resolvedStop);
transform.position = newPosition;
}
@@ -357,13 +429,14 @@ namespace MegaKoop.Game.Enemy
return;
}
float stopDistance = Mathf.Max(0.1f, stoppingDistance);
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
{
navMeshAgent.SetDestination(spawnPosition);
}
else
{
ManualMove(spawnPosition, Time.deltaTime);
ManualMove(spawnPosition, Time.deltaTime, stopDistance);
FacePoint(spawnPosition, Time.deltaTime);
}
}
@@ -430,6 +503,113 @@ namespace MegaKoop.Game.Enemy
}
}
private void HandleHealthChanged(float normalized)
{
if (normalized > 0f || pendingDespawn)
{
return;
}
BeginDespawnCountdown();
}
private void BeginDespawnCountdown()
{
if (pendingDespawn)
{
return;
}
pendingDespawn = true;
despawnTimer = Mathf.Max(0f, deathDespawnDelay);
StopAgentMovement();
}
private void TickDespawn(float deltaTime)
{
if (!pendingDespawn)
{
return;
}
despawnTimer -= deltaTime;
if (despawnTimer <= 0f)
{
CompleteDespawn();
}
}
private void CompleteDespawn()
{
pendingDespawn = false;
if (pooledInstance != null)
{
pooledInstance.ReturnToPool();
}
else
{
gameObject.SetActive(false);
}
}
private void ApplyDefinitionStats()
{
if (health == null && weaponController == null && navMeshAgent == null && !applyDefinitionStats)
{
return;
}
activeDefinition = ResolveDefinition();
if (health != null)
{
float resolvedHp = health.MaxHealth;
if (applyDefinitionStats && activeDefinition != null && activeDefinition.BaseHP > 0f)
{
resolvedHp = activeDefinition.BaseHP;
}
resolvedHp *= Mathf.Max(0.01f, healthMultiplier);
health.Revive(resolvedHp);
}
float resolvedMoveSpeed = baseMoveSpeed;
if (applyDefinitionStats && activeDefinition != null && activeDefinition.MoveSpeed > 0f)
{
resolvedMoveSpeed = activeDefinition.MoveSpeed;
}
resolvedMoveSpeed *= Mathf.Max(0.01f, moveSpeedMultiplier);
moveSpeed = resolvedMoveSpeed;
if (navMeshAgent != null)
{
navMeshAgent.speed = moveSpeed;
}
if (weaponController != null)
{
if (applyDefinitionStats && activeDefinition != null && activeDefinition.Damage > 0f)
{
float resolvedDamage = activeDefinition.Damage * Mathf.Max(0.01f, damageMultiplier);
weaponController.SetDamageOverride(resolvedDamage);
}
else
{
weaponController.ClearDamageOverride();
}
}
}
private EnemyDefinition ResolveDefinition()
{
if (pooledInstance != null && pooledInstance.Definition != null)
{
return pooledInstance.Definition;
}
return fallbackDefinition;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1f, 0.25f, 0f, 0.2f);
@@ -448,10 +628,26 @@ namespace MegaKoop.Game.Enemy
moveSpeed = Mathf.Max(0f, moveSpeed);
turnSpeed = Mathf.Max(0f, turnSpeed);
stoppingDistance = Mathf.Max(0f, stoppingDistance);
attackStoppingDistance = Mathf.Max(0f, attackStoppingDistance);
detectionRadius = Mathf.Max(0f, detectionRadius);
leashDistance = Mathf.Max(0f, leashDistance);
retargetInterval = Mathf.Max(0.05f, retargetInterval);
repathThreshold = Mathf.Max(0.1f, repathThreshold);
}
private float GetDesiredStoppingDistance()
{
if (HasAggroTarget())
{
return Mathf.Max(0.05f, attackStoppingDistance);
}
return Mathf.Max(0.05f, stoppingDistance);
}
private bool HasAggroTarget()
{
return currentTargetTransform != null || currentTargetIdentity != null || currentTargetHealth != null;
}
}
}