This commit is contained in:
2025-10-24 20:18:24 +02:00
parent db45509eaa
commit 4229355a32
5 changed files with 265 additions and 13 deletions

View File

@@ -38,9 +38,11 @@ namespace MegaKoop.Game.Enemy
[SerializeField] private SteamNetworkTransform networkTransform;
private SteamCoopNetworkManager networkManager;
private Transform currentTarget;
private Transform currentTargetTransform;
private NetworkIdentity currentTargetIdentity;
private Health currentTargetHealth;
private Vector3 lastTargetPosition;
private Vector3 lastKnownTargetPosition;
private float retargetTimer;
private Vector3 spawnPosition;
private bool cachedNavAgentState;
@@ -83,6 +85,7 @@ namespace MegaKoop.Game.Enemy
}
lastTargetPosition = Vector3.positiveInfinity;
lastKnownTargetPosition = spawnPosition;
retargetTimer = Random.Range(0f, Mathf.Max(0.05f, retargetInterval));
}
@@ -121,7 +124,7 @@ namespace MegaKoop.Game.Enemy
AcquireTarget();
}
if (currentTarget == null)
if (!TryGetTargetPosition(out _))
{
HandleIdle();
return;
@@ -164,7 +167,7 @@ namespace MegaKoop.Game.Enemy
private bool IsTargetValid()
{
if (currentTarget == null)
if (currentTargetTransform == null && currentTargetIdentity == null && currentTargetHealth == null)
{
return false;
}
@@ -174,7 +177,12 @@ namespace MegaKoop.Game.Enemy
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)
{
return false;
@@ -182,7 +190,7 @@ namespace MegaKoop.Game.Enemy
if (leashDistance > 0f)
{
float sqrLeash = (currentTarget.position - spawnPosition).sqrMagnitude;
float sqrLeash = (targetPosition - spawnPosition).sqrMagnitude;
if (sqrLeash > leashDistance * leashDistance)
{
return false;
@@ -195,8 +203,7 @@ namespace MegaKoop.Game.Enemy
private void AcquireTarget()
{
retargetTimer = retargetInterval;
currentTarget = null;
currentTargetHealth = null;
ClearTarget();
float bestScore = float.MaxValue;
SharedHealthBuffer.Clear();
@@ -215,7 +222,18 @@ namespace MegaKoop.Game.Enemy
}
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)
{
continue;
@@ -224,12 +242,15 @@ namespace MegaKoop.Game.Enemy
if (sqrDistance < bestScore)
{
bestScore = sqrDistance;
currentTarget = candidateTransform;
currentTargetTransform = candidateTransform;
currentTargetIdentity = candidateIdentity;
currentTargetHealth = candidate;
lastKnownTargetPosition = candidatePosition;
lastTargetPosition = Vector3.positiveInfinity;
}
}
if (currentTarget == null)
if (currentTargetTransform == null && currentTargetIdentity == null)
{
lastTargetPosition = Vector3.positiveInfinity;
}
@@ -239,7 +260,11 @@ namespace MegaKoop.Game.Enemy
private void TickMovement(float deltaTime)
{
Vector3 targetPosition = currentTarget.position;
if (!TryGetTargetPosition(out Vector3 targetPosition))
{
ClearTarget();
return;
}
if (navMeshAgent != null && navMeshAgent.enabled && navMeshAgent.isOnNavMesh)
{
@@ -255,6 +280,7 @@ namespace MegaKoop.Game.Enemy
else
{
ManualMove(targetPosition, deltaTime);
lastTargetPosition = targetPosition;
}
}
@@ -278,7 +304,12 @@ namespace MegaKoop.Game.Enemy
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;
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)
{
Vector3 toPoint = point - transform.position;

View File

@@ -109,6 +109,8 @@ namespace MegaKoop.Game.Networking
Debug.Log($"[NetworkIdRegistry] Unregistered Network ID {networkId}");
}
SteamCharacterStateCache.RemoveState(networkId);
// Also remove from Steam ID mapping if present
ulong steamIdToRemove = 0;
foreach (var kvp in instance.steamIdToNetworkId)

View File

@@ -211,6 +211,7 @@ namespace MegaKoop.Game.Networking
var unityController = GetComponent<CharacterController>();
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);
byte[] payload = CharacterTransformMessage.Serialize(message);
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);

View 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);
}
}
}

View File

@@ -152,9 +152,10 @@ namespace MegaKoop.Game.Networking
}
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);
byte[] payload = CharacterTransformMessage.Serialize(message);
networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);