enemy ai
This commit is contained in:
@@ -38,9 +38,11 @@ namespace MegaKoop.Game.Enemy
|
|||||||
[SerializeField] private SteamNetworkTransform networkTransform;
|
[SerializeField] private SteamNetworkTransform networkTransform;
|
||||||
|
|
||||||
private SteamCoopNetworkManager networkManager;
|
private SteamCoopNetworkManager networkManager;
|
||||||
private Transform currentTarget;
|
private Transform currentTargetTransform;
|
||||||
|
private NetworkIdentity currentTargetIdentity;
|
||||||
private Health currentTargetHealth;
|
private Health currentTargetHealth;
|
||||||
private Vector3 lastTargetPosition;
|
private Vector3 lastTargetPosition;
|
||||||
|
private Vector3 lastKnownTargetPosition;
|
||||||
private float retargetTimer;
|
private float retargetTimer;
|
||||||
private Vector3 spawnPosition;
|
private Vector3 spawnPosition;
|
||||||
private bool cachedNavAgentState;
|
private bool cachedNavAgentState;
|
||||||
@@ -83,6 +85,7 @@ namespace MegaKoop.Game.Enemy
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastTargetPosition = Vector3.positiveInfinity;
|
lastTargetPosition = Vector3.positiveInfinity;
|
||||||
|
lastKnownTargetPosition = spawnPosition;
|
||||||
retargetTimer = Random.Range(0f, Mathf.Max(0.05f, retargetInterval));
|
retargetTimer = Random.Range(0f, Mathf.Max(0.05f, retargetInterval));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +124,7 @@ namespace MegaKoop.Game.Enemy
|
|||||||
AcquireTarget();
|
AcquireTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTarget == null)
|
if (!TryGetTargetPosition(out _))
|
||||||
{
|
{
|
||||||
HandleIdle();
|
HandleIdle();
|
||||||
return;
|
return;
|
||||||
@@ -164,7 +167,7 @@ namespace MegaKoop.Game.Enemy
|
|||||||
|
|
||||||
private bool IsTargetValid()
|
private bool IsTargetValid()
|
||||||
{
|
{
|
||||||
if (currentTarget == null)
|
if (currentTargetTransform == null && currentTargetIdentity == null && currentTargetHealth == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -174,7 +177,12 @@ namespace MegaKoop.Game.Enemy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float sqrDistance = (currentTarget.position - transform.position).sqrMagnitude;
|
if (!TryGetTargetPosition(out Vector3 targetPosition))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sqrDistance = (targetPosition - transform.position).sqrMagnitude;
|
||||||
if (detectionRadius > 0f && sqrDistance > detectionRadius * detectionRadius)
|
if (detectionRadius > 0f && sqrDistance > detectionRadius * detectionRadius)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -182,7 +190,7 @@ namespace MegaKoop.Game.Enemy
|
|||||||
|
|
||||||
if (leashDistance > 0f)
|
if (leashDistance > 0f)
|
||||||
{
|
{
|
||||||
float sqrLeash = (currentTarget.position - spawnPosition).sqrMagnitude;
|
float sqrLeash = (targetPosition - spawnPosition).sqrMagnitude;
|
||||||
if (sqrLeash > leashDistance * leashDistance)
|
if (sqrLeash > leashDistance * leashDistance)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -195,8 +203,7 @@ namespace MegaKoop.Game.Enemy
|
|||||||
private void AcquireTarget()
|
private void AcquireTarget()
|
||||||
{
|
{
|
||||||
retargetTimer = retargetInterval;
|
retargetTimer = retargetInterval;
|
||||||
currentTarget = null;
|
ClearTarget();
|
||||||
currentTargetHealth = null;
|
|
||||||
float bestScore = float.MaxValue;
|
float bestScore = float.MaxValue;
|
||||||
|
|
||||||
SharedHealthBuffer.Clear();
|
SharedHealthBuffer.Clear();
|
||||||
@@ -215,7 +222,18 @@ namespace MegaKoop.Game.Enemy
|
|||||||
}
|
}
|
||||||
|
|
||||||
Transform candidateTransform = candidate.transform;
|
Transform candidateTransform = candidate.transform;
|
||||||
float sqrDistance = (candidateTransform.position - transform.position).sqrMagnitude;
|
NetworkIdentity candidateIdentity = candidate.GetComponent<NetworkIdentity>();
|
||||||
|
Vector3 candidatePosition;
|
||||||
|
if (candidateIdentity != null && SteamCharacterStateCache.TryGetState(candidateIdentity.NetworkId, out var state))
|
||||||
|
{
|
||||||
|
candidatePosition = state.Position;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
candidatePosition = candidateTransform.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sqrDistance = (candidatePosition - transform.position).sqrMagnitude;
|
||||||
if (detectionRadius > 0f && sqrDistance > detectionRadius * detectionRadius)
|
if (detectionRadius > 0f && sqrDistance > detectionRadius * detectionRadius)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -224,12 +242,15 @@ namespace MegaKoop.Game.Enemy
|
|||||||
if (sqrDistance < bestScore)
|
if (sqrDistance < bestScore)
|
||||||
{
|
{
|
||||||
bestScore = sqrDistance;
|
bestScore = sqrDistance;
|
||||||
currentTarget = candidateTransform;
|
currentTargetTransform = candidateTransform;
|
||||||
|
currentTargetIdentity = candidateIdentity;
|
||||||
currentTargetHealth = candidate;
|
currentTargetHealth = candidate;
|
||||||
|
lastKnownTargetPosition = candidatePosition;
|
||||||
|
lastTargetPosition = Vector3.positiveInfinity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTarget == null)
|
if (currentTargetTransform == null && currentTargetIdentity == null)
|
||||||
{
|
{
|
||||||
lastTargetPosition = Vector3.positiveInfinity;
|
lastTargetPosition = Vector3.positiveInfinity;
|
||||||
}
|
}
|
||||||
@@ -239,7 +260,11 @@ namespace MegaKoop.Game.Enemy
|
|||||||
|
|
||||||
private void TickMovement(float deltaTime)
|
private void TickMovement(float deltaTime)
|
||||||
{
|
{
|
||||||
Vector3 targetPosition = currentTarget.position;
|
if (!TryGetTargetPosition(out Vector3 targetPosition))
|
||||||
|
{
|
||||||
|
ClearTarget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
|
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
|
||||||
{
|
{
|
||||||
@@ -255,6 +280,7 @@ namespace MegaKoop.Game.Enemy
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ManualMove(targetPosition, deltaTime);
|
ManualMove(targetPosition, deltaTime);
|
||||||
|
lastTargetPosition = targetPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +304,12 @@ namespace MegaKoop.Game.Enemy
|
|||||||
|
|
||||||
private void FaceTarget(float deltaTime)
|
private void FaceTarget(float deltaTime)
|
||||||
{
|
{
|
||||||
Vector3 toTarget = currentTarget.position - transform.position;
|
if (!TryGetTargetPosition(out Vector3 targetPosition))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 toTarget = targetPosition - transform.position;
|
||||||
toTarget.y = 0f;
|
toTarget.y = 0f;
|
||||||
if (toTarget.sqrMagnitude < 0.0001f)
|
if (toTarget.sqrMagnitude < 0.0001f)
|
||||||
{
|
{
|
||||||
@@ -314,6 +345,47 @@ namespace MegaKoop.Game.Enemy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryGetTargetPosition(out Vector3 position)
|
||||||
|
{
|
||||||
|
if (currentTargetIdentity != null && SteamCharacterStateCache.TryGetState(currentTargetIdentity.NetworkId, out var state))
|
||||||
|
{
|
||||||
|
position = state.Position;
|
||||||
|
lastKnownTargetPosition = position;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTargetTransform != null)
|
||||||
|
{
|
||||||
|
position = currentTargetTransform.position;
|
||||||
|
lastKnownTargetPosition = position;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTargetHealth != null)
|
||||||
|
{
|
||||||
|
position = currentTargetHealth.transform.position;
|
||||||
|
lastKnownTargetPosition = position;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastKnownTargetPosition != Vector3.positiveInfinity)
|
||||||
|
{
|
||||||
|
position = lastKnownTargetPosition;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
position = Vector3.zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearTarget()
|
||||||
|
{
|
||||||
|
currentTargetTransform = null;
|
||||||
|
currentTargetIdentity = null;
|
||||||
|
currentTargetHealth = null;
|
||||||
|
lastKnownTargetPosition = Vector3.positiveInfinity;
|
||||||
|
}
|
||||||
|
|
||||||
private void FacePoint(Vector3 point, float deltaTime)
|
private void FacePoint(Vector3 point, float deltaTime)
|
||||||
{
|
{
|
||||||
Vector3 toPoint = point - transform.position;
|
Vector3 toPoint = point - transform.position;
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ namespace MegaKoop.Game.Networking
|
|||||||
Debug.Log($"[NetworkIdRegistry] Unregistered Network ID {networkId}");
|
Debug.Log($"[NetworkIdRegistry] Unregistered Network ID {networkId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SteamCharacterStateCache.RemoveState(networkId);
|
||||||
|
|
||||||
// Also remove from Steam ID mapping if present
|
// Also remove from Steam ID mapping if present
|
||||||
ulong steamIdToRemove = 0;
|
ulong steamIdToRemove = 0;
|
||||||
foreach (var kvp in instance.steamIdToNetworkId)
|
foreach (var kvp in instance.steamIdToNetworkId)
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ namespace MegaKoop.Game.Networking
|
|||||||
|
|
||||||
var unityController = GetComponent<CharacterController>();
|
var unityController = GetComponent<CharacterController>();
|
||||||
Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero;
|
Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero;
|
||||||
|
SteamCharacterStateCache.ReportLocalState(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity);
|
||||||
var message = new CharacterTransformMessage(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity);
|
var message = new CharacterTransformMessage(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity);
|
||||||
byte[] payload = CharacterTransformMessage.Serialize(message);
|
byte[] payload = CharacterTransformMessage.Serialize(message);
|
||||||
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
||||||
|
|||||||
176
Game/Scripts/Networking/SteamCharacterStateCache.cs
Normal file
176
Game/Scripts/Networking/SteamCharacterStateCache.cs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Steamworks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MegaKoop.Game.Networking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lightweight cache of the latest character transform messages so systems (AI, prediction) can query peer positions.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultExecutionOrder(-900)]
|
||||||
|
public class SteamCharacterStateCache : MonoBehaviour
|
||||||
|
{
|
||||||
|
public readonly struct CharacterState
|
||||||
|
{
|
||||||
|
public readonly int NetworkId;
|
||||||
|
public readonly Vector3 Position;
|
||||||
|
public readonly Quaternion Rotation;
|
||||||
|
public readonly Vector3 Velocity;
|
||||||
|
public readonly float LastUpdateTime;
|
||||||
|
public readonly ulong SourceSteamId;
|
||||||
|
|
||||||
|
public CharacterState(int networkId, Vector3 position, Quaternion rotation, Vector3 velocity, float timestamp, ulong steamId)
|
||||||
|
{
|
||||||
|
NetworkId = networkId;
|
||||||
|
Position = position;
|
||||||
|
Rotation = rotation;
|
||||||
|
Velocity = velocity;
|
||||||
|
LastUpdateTime = timestamp;
|
||||||
|
SourceSteamId = steamId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SteamCharacterStateCache instance;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, CharacterState> states = new();
|
||||||
|
private SteamCoopNetworkManager networkManager;
|
||||||
|
private bool isRegistered;
|
||||||
|
|
||||||
|
public static SteamCharacterStateCache Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
var go = new GameObject("SteamCharacterStateCache");
|
||||||
|
instance = go.AddComponent<SteamCharacterStateCache>();
|
||||||
|
DontDestroyOnLoad(go);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (instance != null && instance != this)
|
||||||
|
{
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = this;
|
||||||
|
DontDestroyOnLoad(gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
EnsureSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
EnsureSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureSubscription()
|
||||||
|
{
|
||||||
|
var current = SteamCoopNetworkManager.Instance;
|
||||||
|
if (current == networkManager)
|
||||||
|
{
|
||||||
|
if (networkManager != null && !isRegistered)
|
||||||
|
{
|
||||||
|
networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage);
|
||||||
|
isRegistered = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unsubscribe();
|
||||||
|
networkManager = current;
|
||||||
|
|
||||||
|
if (networkManager != null)
|
||||||
|
{
|
||||||
|
networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage);
|
||||||
|
isRegistered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Unsubscribe()
|
||||||
|
{
|
||||||
|
if (networkManager != null && isRegistered)
|
||||||
|
{
|
||||||
|
networkManager.UnregisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage);
|
||||||
|
isRegistered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCharacterTransformMessage(NetworkMessage message)
|
||||||
|
{
|
||||||
|
CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload);
|
||||||
|
StoreState(transformMessage.NetworkId, transformMessage.Position, transformMessage.Rotation, transformMessage.Velocity, message.Sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StoreState(int networkId, Vector3 position, Quaternion rotation, Vector3 velocity, ulong steamId)
|
||||||
|
{
|
||||||
|
if (networkId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
states[networkId] = new CharacterState(networkId, position, rotation, velocity, Time.time, steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong GetLocalSteamId()
|
||||||
|
{
|
||||||
|
#if STEAMWORKSNET
|
||||||
|
if (MegaKoop.Steam.SteamManager.Initialized)
|
||||||
|
{
|
||||||
|
return SteamUser.GetSteamID().m_SteamID;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (SteamBootstrap.IsInitialized)
|
||||||
|
{
|
||||||
|
return SteamUser.GetSteamID().m_SteamID;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetState(int networkId, out CharacterState state)
|
||||||
|
{
|
||||||
|
if (Instance.states.TryGetValue(networkId, out state))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReportLocalState(int networkId, Vector3 position, Quaternion rotation, Vector3 velocity)
|
||||||
|
{
|
||||||
|
if (networkId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance.StoreState(networkId, position, rotation, velocity, GetLocalSteamId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoveState(int networkId)
|
||||||
|
{
|
||||||
|
if (networkId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance.states.Remove(networkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,9 +152,10 @@ namespace MegaKoop.Game.Networking
|
|||||||
}
|
}
|
||||||
else if (trackedRigidbody != null)
|
else if (trackedRigidbody != null)
|
||||||
{
|
{
|
||||||
velocity = trackedRigidbody.linearVelocity;
|
velocity = trackedRigidbody.velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SteamCharacterStateCache.ReportLocalState(identity.NetworkId, targetTransform.position, targetTransform.rotation, velocity);
|
||||||
var message = new CharacterTransformMessage(identity.NetworkId, targetTransform.position, targetTransform.rotation, velocity);
|
var message = new CharacterTransformMessage(identity.NetworkId, targetTransform.position, targetTransform.rotation, velocity);
|
||||||
byte[] payload = CharacterTransformMessage.Serialize(message);
|
byte[] payload = CharacterTransformMessage.Serialize(message);
|
||||||
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
||||||
|
|||||||
Reference in New Issue
Block a user