754 lines
26 KiB
C#
754 lines
26 KiB
C#
using System;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.Globalization;
|
|
using RebootKit.Engine.Foundation;
|
|
using RebootKit.Engine.Main;
|
|
using RebootKit.Engine.Network;
|
|
using TriInspector;
|
|
using Unity.Collections;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
using Logger = RebootKit.Engine.Foundation.Logger;
|
|
|
|
namespace RebootKit.Engine.Simulation {
|
|
public interface IActorData : ISerializableEntity { }
|
|
|
|
public class NoActorData : IActorData {
|
|
public void Serialize(NetworkBufferWriter writer) { }
|
|
|
|
public void Deserialize(NetworkBufferReader reader) { }
|
|
|
|
public int GetMaxBytes() {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public struct ActorCommand : INetworkSerializable {
|
|
public ulong ActorID;
|
|
public ulong ClientID;
|
|
public ushort CommandID;
|
|
public NativeArray<byte> Data;
|
|
|
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
|
serializer.SerializeValue(ref ActorID);
|
|
serializer.SerializeValue(ref ClientID);
|
|
serializer.SerializeValue(ref CommandID);
|
|
|
|
if (serializer.IsWriter) {
|
|
bool hasData = Data.IsCreated;
|
|
serializer.SerializeValue(ref hasData);
|
|
|
|
if (hasData) {
|
|
serializer.SerializeValue(ref Data, Allocator.Temp);
|
|
}
|
|
} else if (serializer.IsReader) {
|
|
bool hasData = false;
|
|
serializer.SerializeValue(ref hasData);
|
|
|
|
if (hasData) {
|
|
serializer.SerializeValue(ref Data, Allocator.Temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// @NOTE: ActorEvent is used to send events from the server to clients and only clients.
|
|
// Server should not receive ActorEvents.
|
|
public struct ActorEvent : INetworkSerializable {
|
|
public ulong ActorID;
|
|
public ulong ClientID;
|
|
public ushort EventID;
|
|
public NativeArray<byte> Data;
|
|
|
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
|
serializer.SerializeValue(ref ActorID);
|
|
serializer.SerializeValue(ref ClientID);
|
|
serializer.SerializeValue(ref EventID);
|
|
|
|
if (serializer.IsWriter) {
|
|
bool hasData = Data.IsCreated;
|
|
serializer.SerializeValue(ref hasData);
|
|
|
|
if (hasData) {
|
|
serializer.SerializeValue(ref Data, Allocator.Temp);
|
|
}
|
|
} else if (serializer.IsReader) {
|
|
bool hasData = false;
|
|
serializer.SerializeValue(ref hasData);
|
|
|
|
if (hasData) {
|
|
serializer.SerializeValue(ref Data, Allocator.Temp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
enum ActorPhysicsFlags : byte {
|
|
None = 0,
|
|
IsKinematic = 1 << 0,
|
|
DisableColliders = 1 << 1,
|
|
}
|
|
|
|
struct ActorCoreStateSnapshot : ISerializableEntity {
|
|
public DateTime Timestamp;
|
|
|
|
// @NOTE: Position, Rotation, and Scale are in local space.
|
|
public Vector3 Position;
|
|
public Quaternion Rotation;
|
|
public Vector3 Scale;
|
|
|
|
public bool IsHidden;
|
|
public ActorPhysicsFlags Flags;
|
|
|
|
public ulong MasterActorID;
|
|
public FixedString32Bytes MasterSocketName;
|
|
|
|
public void Serialize(NetworkBufferWriter writer) {
|
|
writer.Write(Timestamp.Ticks);
|
|
writer.Write(Position);
|
|
writer.Write(Rotation);
|
|
writer.Write(Scale);
|
|
writer.Write(IsHidden);
|
|
writer.Write((byte) Flags);
|
|
writer.Write(MasterActorID);
|
|
writer.Write(MasterSocketName);
|
|
}
|
|
|
|
public void Deserialize(NetworkBufferReader reader) {
|
|
reader.Read(out long ticks);
|
|
Timestamp = new DateTime(ticks, DateTimeKind.Utc);
|
|
reader.Read(out Position);
|
|
reader.Read(out Rotation);
|
|
reader.Read(out Scale);
|
|
reader.Read(out IsHidden);
|
|
reader.Read(out byte flagsByte);
|
|
Flags = (ActorPhysicsFlags) flagsByte;
|
|
reader.Read(out MasterActorID);
|
|
reader.Read(out MasterSocketName);
|
|
}
|
|
|
|
public int GetMaxBytes() {
|
|
return sizeof(long) + // Timestamp
|
|
sizeof(float) * 3 + // Position
|
|
sizeof(float) * 4 + // Rotation (Quaternion)
|
|
sizeof(float) * 3 + // Scale
|
|
sizeof(bool) + // IsHidden
|
|
sizeof(byte) + // Flags
|
|
sizeof(ulong) + // MasterActorID
|
|
sizeof(byte) * 32; // MasterSocketName
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Represents the synchronization mode for actor transforms (and rigidbody).
|
|
/// @TODO: Might be a good idea to keep client-side actors rigidbody as kinematic and simulate physics only on the server.
|
|
/// IMPORTANT:
|
|
/// - Position, Rotation, and Scale are in local space.
|
|
/// - Velocity and AngularVelocity are only used if UsingRigidbody is set.
|
|
/// - When Actor is mounted to another actor, sync won't happen.
|
|
///
|
|
[Flags]
|
|
public enum ActorTransformSyncMode : byte {
|
|
None = 0,
|
|
Position = 1 << 0,
|
|
Rotation = 1 << 1,
|
|
Scale = 1 << 2,
|
|
// @NOTE: If this is set, Position and Rotation will be synced using Rigidbody's position and rotation.
|
|
UsingRigidbody = 1 << 3,
|
|
Velocity = 1 << 4, // @NOTE: Velocity is only used if UsingRigidbody is set.
|
|
AngularVelocity = 1 << 5 // @NOTE: AngularVelocity is only used if UsingRigidbody is set.
|
|
}
|
|
|
|
public struct ActorTransformSyncData : ISerializableEntity {
|
|
public ActorTransformSyncMode SyncMode;
|
|
|
|
public Vector3 Position;
|
|
public Quaternion Rotation;
|
|
public Vector3 Scale;
|
|
public Vector3 Velocity;
|
|
public Vector3 AngularVelocity;
|
|
|
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
|
|
serializer.SerializeValue(ref SyncMode);
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
serializer.SerializeValue(ref Position);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
serializer.SerializeValue(ref Rotation);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
serializer.SerializeValue(ref Scale);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
serializer.SerializeValue(ref Velocity);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
serializer.SerializeValue(ref AngularVelocity);
|
|
}
|
|
}
|
|
|
|
public void Serialize(NetworkBufferWriter writer) {
|
|
writer.Write((byte) SyncMode);
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
writer.Write(Position);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
writer.Write(Rotation);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
writer.Write(Scale);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
writer.Write(Velocity);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
writer.Write(AngularVelocity);
|
|
}
|
|
}
|
|
|
|
public void Deserialize(NetworkBufferReader reader) {
|
|
reader.Read(out byte syncModeByte);
|
|
SyncMode = (ActorTransformSyncMode) syncModeByte;
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
reader.Read(out Position);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
reader.Read(out Rotation);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
reader.Read(out Scale);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
reader.Read(out Velocity);
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
reader.Read(out AngularVelocity);
|
|
}
|
|
}
|
|
|
|
public int GetMaxBytes() {
|
|
int size = sizeof(byte); // SyncMode
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
size += sizeof(float) * 3; // Vector3
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
size += sizeof(float) * 4; // Quaternion
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
size += sizeof(float) * 3; // Vector3
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
size += sizeof(float) * 3; // Vector3
|
|
}
|
|
|
|
if ((SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
size += sizeof(float) * 3; // Vector3
|
|
}
|
|
|
|
return size;
|
|
}
|
|
}
|
|
|
|
public abstract class Actor : MonoBehaviour {
|
|
static readonly Logger s_ActorLogger = new Logger(nameof(Actor));
|
|
|
|
[field: SerializeField, TriInspector.ReadOnly] public string SourceActorPath { get; internal set; } = "";
|
|
[field: SerializeField, Unity.Collections.ReadOnly] public ulong ActorID { get; internal set; }
|
|
|
|
[NonSerialized] internal IActorData Data;
|
|
|
|
[SerializeField] string m_ActorName = "";
|
|
|
|
public string ActorName {
|
|
get {
|
|
return m_ActorName;
|
|
}
|
|
}
|
|
|
|
[SerializeField] internal Rigidbody actorRigidbody;
|
|
[InfoBox("If empty, will use GetComponentsInChildren<Collider>() to find colliders.")]
|
|
[SerializeField] Collider[] m_OverrideActorColliders;
|
|
|
|
[SerializeField] bool m_SetKinematicOnMount = true;
|
|
[SerializeField] bool m_DisableCollidersOnMount = true;
|
|
|
|
internal ActorPhysicsFlags PhysicsFlagsBeforeMount = ActorPhysicsFlags.None;
|
|
internal ActorPhysicsFlags PhysicsFlags = ActorPhysicsFlags.None;
|
|
|
|
// @NOTE: Sync won't happen if actor is mounted to another actor.
|
|
[SerializeField] internal ActorTransformSyncMode transformSyncMode = ActorTransformSyncMode.None;
|
|
|
|
[Serializable]
|
|
public struct AttachmentSocket {
|
|
[MaxLength(32)]
|
|
public string socketName;
|
|
public Transform root;
|
|
|
|
public Vector3 localPosition;
|
|
public Quaternion localRotation;
|
|
}
|
|
|
|
[SerializeField] AttachmentSocket[] m_AttachmentSockets;
|
|
|
|
// @NOTE: Master actor is the actor that this actor is attached to, if any.
|
|
internal Actor MasterActor;
|
|
internal FixedString32Bytes MasterSocketName;
|
|
|
|
internal bool IsCoreStateDirty;
|
|
public bool IsDataDirty { get; protected internal set; }
|
|
|
|
internal ActorsManager Manager;
|
|
internal DateTime LastCoreStateSyncTime = DateTime.MinValue;
|
|
|
|
//
|
|
// @MARK: Unity callbacks
|
|
//
|
|
void OnValidate() {
|
|
if (ActorID == 0) {
|
|
ActorID = UniqueID.NewULongFromGuid();
|
|
}
|
|
}
|
|
|
|
//
|
|
// @MARK: Callbacks to override in derived classes
|
|
//
|
|
|
|
protected abstract IActorData CreateActorData();
|
|
|
|
// @MARK: Server side
|
|
public virtual void OnServerTick(float deltaTime) { }
|
|
protected virtual void OnActorCommandServer(ActorCommand actorCommand) { }
|
|
|
|
// Override this method to implement client-side logic
|
|
public virtual void OnClientTick(float deltaTime) { }
|
|
protected virtual void OnActorEventClient(ActorEvent actorEvent) { }
|
|
protected virtual void OnClientFinishedInitialSync() { }
|
|
|
|
//
|
|
// @MARK: Server API
|
|
//
|
|
public void SetHidden(bool hidden) {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can set actor visibility. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
bool shouldBeActive = !hidden;
|
|
if (gameObject.activeSelf == shouldBeActive) {
|
|
s_ActorLogger
|
|
.Warning($"Actor {name} (ID: {ActorID}) is already in the desired visibility state: {shouldBeActive.ToString()}");
|
|
return;
|
|
}
|
|
|
|
gameObject.SetActive(shouldBeActive);
|
|
IsCoreStateDirty = true;
|
|
}
|
|
|
|
public void MountTo(Actor actor, string slotName) {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can mount actors. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (actor.TryGetAttachmentSocket(slotName, out AttachmentSocket _)) {
|
|
MasterActor = actor;
|
|
MasterSocketName = new FixedString32Bytes(slotName);
|
|
|
|
PhysicsFlagsBeforeMount = PhysicsFlags;
|
|
if (m_SetKinematicOnMount) {
|
|
PhysicsFlags |= ActorPhysicsFlags.IsKinematic;
|
|
}
|
|
|
|
if (m_DisableCollidersOnMount) {
|
|
PhysicsFlags |= ActorPhysicsFlags.DisableColliders;
|
|
}
|
|
|
|
UpdateLocalPhysicsState(PhysicsFlags);
|
|
UpdateMountedTransform();
|
|
IsCoreStateDirty = true;
|
|
}
|
|
}
|
|
|
|
public void UnMount() {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can unmount actors. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (MasterActor == null) {
|
|
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) is not mounted to any actor.");
|
|
return;
|
|
}
|
|
|
|
MasterActor = null;
|
|
MasterSocketName = default;
|
|
UpdateMountedTransform();
|
|
|
|
PhysicsFlags = PhysicsFlagsBeforeMount;
|
|
UpdateLocalPhysicsState(PhysicsFlags);
|
|
|
|
IsCoreStateDirty = true;
|
|
}
|
|
|
|
public void SetCollidersEnabled(bool enableColliders) {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can enable/disable colliders. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (actorRigidbody is null) {
|
|
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) has no Rigidbody to set colliders on.");
|
|
return;
|
|
}
|
|
|
|
if (enableColliders) {
|
|
PhysicsFlags &= ~ActorPhysicsFlags.DisableColliders;
|
|
} else {
|
|
PhysicsFlags |= ActorPhysicsFlags.DisableColliders;
|
|
}
|
|
|
|
UpdateLocalCollidersState(enableColliders);
|
|
IsCoreStateDirty = true;
|
|
}
|
|
|
|
public void SetKinematic(bool isKinematic) {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can set kinematic state. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (actorRigidbody is null) {
|
|
s_ActorLogger.Error($"Actor {name} (ID: {ActorID}) has no Rigidbody to set kinematic state on.");
|
|
return;
|
|
}
|
|
|
|
if (isKinematic) {
|
|
PhysicsFlags |= ActorPhysicsFlags.IsKinematic;
|
|
} else {
|
|
PhysicsFlags &= ~ActorPhysicsFlags.IsKinematic;
|
|
}
|
|
|
|
actorRigidbody.isKinematic = isKinematic;
|
|
IsCoreStateDirty = true;
|
|
}
|
|
|
|
//
|
|
// @MARK: Common API
|
|
//
|
|
public bool IsHidden() {
|
|
return !gameObject.activeSelf;
|
|
}
|
|
|
|
protected void SendActorCommand<TCmdData>(ushort commandID, ref TCmdData commandData)
|
|
where TCmdData : struct, ISerializableEntity {
|
|
NativeArray<byte> data = DataSerializationUtils.Serialize(commandData);
|
|
|
|
SendActorCommand(commandID, data);
|
|
}
|
|
|
|
protected void SendActorCommand(ushort commandID, NativeArray<byte> data = default) {
|
|
if (Manager is null) {
|
|
s_ActorLogger.Error($"Cannot send command because Manager is null for actor {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
ActorCommand command = new ActorCommand {
|
|
ActorID = ActorID,
|
|
ClientID = NetworkManager.Singleton.LocalClientId,
|
|
CommandID = commandID,
|
|
Data = data
|
|
};
|
|
|
|
Manager.SendActorCommandToServerRpc(command);
|
|
}
|
|
|
|
protected void SendActorEvent<TEventData>(ushort eventID, ref TEventData eventData)
|
|
where TEventData : struct, ISerializableEntity {
|
|
NativeArray<byte> data = DataSerializationUtils.Serialize(eventData);
|
|
SendActorEvent(eventID, data);
|
|
}
|
|
|
|
protected void SendActorEvent(ushort eventID, NativeArray<byte> data = default) {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can send actor events. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (Manager is null) {
|
|
s_ActorLogger.Error($"Cannot send event because Manager is null for actor {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
ActorEvent actorEvent = new ActorEvent {
|
|
ActorID = ActorID,
|
|
ClientID = NetworkManager.Singleton.LocalClientId,
|
|
EventID = eventID,
|
|
Data = data
|
|
};
|
|
|
|
Manager.SendActorEvent(actorEvent);
|
|
}
|
|
|
|
protected T DataAs<T>() where T : IActorData {
|
|
if (Data is T data) {
|
|
return data;
|
|
}
|
|
|
|
throw new InvalidCastException($"Actor data is not of type {typeof(T).Name}");
|
|
}
|
|
|
|
bool TryGetAttachmentSocket(string socketName, out AttachmentSocket socket) {
|
|
foreach (AttachmentSocket attachmentSocket in m_AttachmentSockets) {
|
|
if (attachmentSocket.socketName == socketName) {
|
|
socket = attachmentSocket;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
socket = default;
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// @MARK: Internal
|
|
//
|
|
internal ActorCoreStateSnapshot GetCoreStateSnapshot() {
|
|
ActorCoreStateSnapshot snapshot = new ActorCoreStateSnapshot();
|
|
snapshot.Timestamp = DateTime.UtcNow;
|
|
snapshot.Position = transform.localPosition;
|
|
snapshot.Rotation = transform.localRotation;
|
|
snapshot.Scale = transform.localScale;
|
|
|
|
snapshot.IsHidden = !gameObject.activeSelf;
|
|
snapshot.Flags = PhysicsFlags;
|
|
|
|
snapshot.MasterActorID = MasterActor != null ? MasterActor.ActorID : 0;
|
|
if (snapshot.MasterActorID != 0) {
|
|
snapshot.MasterSocketName = MasterSocketName;
|
|
} else {
|
|
snapshot.MasterSocketName = default;
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
internal void RestoreCoreState(ActorCoreStateSnapshot snapshot) {
|
|
if (snapshot.Timestamp < LastCoreStateSyncTime) {
|
|
s_ActorLogger.Warning($"Received an outdated core state snapshot for actor {name} (ID: {ActorID}). " +
|
|
$"Current time: {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}, Snapshot time: {snapshot.Timestamp}");
|
|
return;
|
|
}
|
|
LastCoreStateSyncTime = snapshot.Timestamp;
|
|
PhysicsFlags = snapshot.Flags;
|
|
|
|
if (snapshot.MasterActorID != 0) {
|
|
MasterActor = RR.FindSpawnedActor(snapshot.MasterActorID);
|
|
MasterSocketName = snapshot.MasterSocketName;
|
|
UpdateMountedTransform();
|
|
} else {
|
|
MasterActor = null;
|
|
MasterSocketName = default;
|
|
|
|
UpdateMountedTransform();
|
|
transform.localPosition = snapshot.Position;
|
|
transform.localRotation = snapshot.Rotation;
|
|
transform.localScale = snapshot.Scale;
|
|
}
|
|
|
|
if (snapshot.IsHidden) {
|
|
gameObject.SetActive(false);
|
|
} else {
|
|
gameObject.SetActive(true);
|
|
}
|
|
|
|
UpdateLocalPhysicsState(PhysicsFlags);
|
|
}
|
|
|
|
internal ActorTransformSyncData GetTransformSyncData() {
|
|
ActorTransformSyncData data = new ActorTransformSyncData {
|
|
SyncMode = transformSyncMode
|
|
};
|
|
|
|
bool useRigidbody = (data.SyncMode & ActorTransformSyncMode.UsingRigidbody) != 0;
|
|
|
|
if (useRigidbody && actorRigidbody == null) {
|
|
s_ActorLogger
|
|
.Error($"Actor {name} (ID: {ActorID.ToString()}) has no Rigidbody to sync transform. Ignoring transform sync.");
|
|
data.SyncMode = ActorTransformSyncMode.None;
|
|
return data;
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
if (useRigidbody) {
|
|
data.Position = actorRigidbody.position;
|
|
} else {
|
|
data.Position = transform.localPosition;
|
|
}
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
if (useRigidbody) {
|
|
data.Rotation = actorRigidbody.rotation;
|
|
} else {
|
|
data.Rotation = transform.localRotation;
|
|
}
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
data.Scale = transform.localScale;
|
|
}
|
|
|
|
if (useRigidbody && actorRigidbody != null) {
|
|
if ((data.SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
data.Velocity = actorRigidbody.linearVelocity;
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
data.AngularVelocity = actorRigidbody.angularVelocity;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
internal void RestoreTransformState(ActorTransformSyncData data) {
|
|
bool useRigidbody = (data.SyncMode & ActorTransformSyncMode.UsingRigidbody) != 0;
|
|
if (useRigidbody && actorRigidbody == null) {
|
|
s_ActorLogger
|
|
.Error($"Actor {name} (ID: {ActorID.ToString()}) has no Rigidbody to restore transform state. Ignoring transform sync.");
|
|
return;
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.Position) != 0) {
|
|
if (useRigidbody) {
|
|
actorRigidbody.position = data.Position;
|
|
} else {
|
|
transform.localPosition = data.Position;
|
|
}
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.Rotation) != 0) {
|
|
if (useRigidbody) {
|
|
actorRigidbody.rotation = data.Rotation;
|
|
} else {
|
|
transform.localRotation = data.Rotation;
|
|
}
|
|
}
|
|
|
|
if ((data.SyncMode & ActorTransformSyncMode.Scale) != 0) {
|
|
transform.localScale = data.Scale;
|
|
}
|
|
|
|
if (useRigidbody && (data.SyncMode & ActorTransformSyncMode.Velocity) != 0) {
|
|
actorRigidbody.linearVelocity = data.Velocity;
|
|
}
|
|
|
|
if (useRigidbody && (data.SyncMode & ActorTransformSyncMode.AngularVelocity) != 0) {
|
|
actorRigidbody.angularVelocity = data.AngularVelocity;
|
|
}
|
|
}
|
|
|
|
void UpdateLocalPhysicsState(ActorPhysicsFlags flags) {
|
|
if (actorRigidbody == null) {
|
|
return;
|
|
}
|
|
|
|
if ((flags & ActorPhysicsFlags.IsKinematic) != 0) {
|
|
actorRigidbody.isKinematic = true;
|
|
} else {
|
|
actorRigidbody.isKinematic = false;
|
|
}
|
|
|
|
bool enableColliders = (flags & ActorPhysicsFlags.DisableColliders) == 0;
|
|
UpdateLocalCollidersState(enableColliders);
|
|
}
|
|
|
|
void UpdateLocalCollidersState(bool enable) {
|
|
Collider[] colliders = m_OverrideActorColliders.Length > 0 ? m_OverrideActorColliders
|
|
: GetComponentsInChildren<Collider>();
|
|
|
|
foreach (Collider col in colliders) {
|
|
col.enabled = enable;
|
|
}
|
|
}
|
|
|
|
void UpdateMountedTransform() {
|
|
if (MasterActor == null) {
|
|
transform.SetParent(null);
|
|
return;
|
|
}
|
|
|
|
if (MasterActor.TryGetAttachmentSocket(MasterSocketName.Value, out AttachmentSocket socket)) {
|
|
transform.SetParent(socket.root);
|
|
transform.localPosition = socket.localPosition;
|
|
transform.localRotation = socket.localRotation;
|
|
} else {
|
|
s_ActorLogger
|
|
.Error($"Failed to update mounted transform: Socket {MasterSocketName} not found on {MasterActor.name}");
|
|
}
|
|
}
|
|
|
|
internal IActorData InternalCreateActorData() {
|
|
return CreateActorData();
|
|
}
|
|
|
|
internal void InitialSyncFinished() {
|
|
OnClientFinishedInitialSync();
|
|
}
|
|
|
|
internal void HandleActorCommand(ActorCommand actorCommand) {
|
|
if (!RR.IsServer()) {
|
|
s_ActorLogger.Error($"Only the server can handle actor commands. Actor: {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (Manager is null) {
|
|
s_ActorLogger.Error($"Cannot handle command because Manager is null for actor {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (actorCommand.ActorID != ActorID) {
|
|
s_ActorLogger
|
|
.Error($"Actor command ActorID {actorCommand.ActorID} does not match this actor's ID {ActorID}");
|
|
return;
|
|
}
|
|
|
|
OnActorCommandServer(actorCommand);
|
|
}
|
|
|
|
internal void HandleActorEvent(ActorEvent actorEvent) {
|
|
if (Manager is null) {
|
|
s_ActorLogger.Error($"Cannot handle event because Manager is null for actor {name} (ID: {ActorID})");
|
|
return;
|
|
}
|
|
|
|
if (actorEvent.ActorID != ActorID) {
|
|
s_ActorLogger
|
|
.Error($"Actor event ActorID {actorEvent.ActorID} does not match this actor's ID {ActorID}");
|
|
return;
|
|
}
|
|
|
|
OnActorEventClient(actorEvent);
|
|
}
|
|
}
|
|
} |