enemy spawner
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user