com.unity.netcode.gameobjects@1.0.0-pre.2
# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.0.0-pre.2] - 2020-12-20 ### Added - Associated Known Issues for the 1.0.0-pre.1 release in the changelog ### Changed - Updated label for `1.0.0-pre.1` changelog section ## [1.0.0-pre.1] - 2020-12-20 ### Added - Added `ClientNetworkTransform` sample to the SDK package (#1168) - Added `Bootstrap` sample to the SDK package (#1140) - Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913) - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons - Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101) - Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060) - Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727) - Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855) - Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670) - When a client tries to spawn a `NetworkObject` an exception is thrown to indicate unsupported behavior. (#981) - Added a `NetworkTime` and `NetworkTickSystem` which allows for improved control over time and ticks. (#845) - Added a `OnNetworkDespawn` function to `NetworkObject` which gets called when a `NetworkObject` gets despawned and can be overriden. (#865) - Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192) - Disabled by default for now, except spawn/despawn messages - Will leverage unreliable messages with eventual consistency - `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762) - Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878) - `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190) - Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175) - Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173) - Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872) ### Changed - Bumped minimum Unity version, renamed package as "Unity Netcode for GameObjects", replaced `MLAPI` namespace and its variants with `Unity.Netcode` namespace and per asm-def variants (#1007, #1009, #1015, #1017, #1019, #1025, #1026, #1065) - Minimum Unity version: - 2019.4 → 2020.3+ - Package rename: - Display name: `MLAPI Networking Library` → `Netcode for GameObjects` - Name: `com.unity.multiplayer.mlapi` → `com.unity.netcode.gameobjects` - Updated package description - All `MLAPI.x` namespaces are replaced with `Unity.Netcode` - `MLAPI.Messaging` → `Unity.Netcode` - `MLAPI.Connection` → `Unity.Netcode` - `MLAPI.Logging` → `Unity.Netcode` - `MLAPI.SceneManagement` → `Unity.Netcode` - and other `MLAPI.x` variants to `Unity.Netcode` - All assembly definitions are renamed with `Unity.Netcode.x` variants - `Unity.Multiplayer.MLAPI.Runtime` → `Unity.Netcode.Runtime` - `Unity.Multiplayer.MLAPI.Editor` → `Unity.Netcode.Editor` - and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants - Renamed `Prototyping` namespace and assembly definition to `Components` (#1145) - Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217) - Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080) - `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955) - `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080) - `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698) - `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865) - Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074) - `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788) - Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer<T>` instead of `NetworkSerializer` (#1187) - Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187) - Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196) ### Removed - Removed `NetworkNavMeshAgent` (#1150) - Removed `NetworkDictionary`, `NetworkSet` (#1149) - Removed `NetworkVariableSettings` (#1097) - Removed predefined `NetworkVariable<T>` types (#1093) - Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion` - Removed `NetworkChannel` and `MultiplexTransportAdapter` (#1133) - Removed ILPP backend for 2019.4, minimum required version is 2020.3+ (#895) - `NetworkManager.NetworkConfig` had the following properties removed: (#1080) - Scene Registrations no longer exists - Allow Runtime Scene Changes was no longer needed and was removed - Removed the NetworkObject.Spawn payload parameter (#1005) - Removed `ProfilerCounter`, the original MLAPI network profiler, and the built-in network profiler module (2020.3). A replacement can now be found in the Multiplayer Tools package. (#1048) - Removed UNet RelayTransport and related relay functionality in UNetTransport (#1081) - Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187) - Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187) - Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179) - Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101) - Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108) ### Fixed - Fixed ServerRpc ownership check to `Debug.LogError` instead of `Debug.LogWarning` (#1126) - Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092) - Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803) - Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678) - Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011) - Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s - Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676) - Fixed connection approval not being triggered for host (#675) - Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218) - Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266) - Improved runtime performance and reduced GC pressure (#1187) - Fixed #915 - clients are receiving data from objects not visible to them (#1099) - Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826) - Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844) ### Known Issues - `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation - `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions - `NetworkTransform` interpolates from the origin when switching Local Space synchronization - Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects - Cannot send an array of `INetworkSerializable` in RPCs - ILPP generation fails with special characters in project path ## [0.2.0] - 2021-06-03 WIP version increment to pass package validation checks. Changelog & final version number TBD. ## [0.1.1] - 2021-06-01 This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package. ### Changed - Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release. ## [0.1.0] - 2021-03-23 This is the initial experimental Unity MLAPI Package, v0.1.0. ### Added - Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)). - Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details. - `INetworkSerializable` interface replaces `IBitWritable`. - Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally. - Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details: - It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution. - You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation. - `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs. - Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately. - [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute. - Integrated MLAPI with the Unity Profiler for versions 2020.2 and later: - Added new profiler modules for MLAPI that report important network data. - Attached the profiler to a remote player to view network data over the wire. - A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject). - Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`. ### Changed - [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management. - Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s. - [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects. - GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings. For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514). | Previous MLAPI Versions | V 0.1.0 Name | | -- | -- | | `NetworkingManager` | `NetworkManager` | | `NetworkedObject` | `NetworkObject` | | `NetworkedBehaviour` | `NetworkBehaviour` | | `NetworkedClient` | `NetworkClient` | | `NetworkedPrefab` | `NetworkPrefab` | | `NetworkedVar` | `NetworkVariable` | | `NetworkedTransform` | `NetworkTransform` | | `NetworkedAnimator` | `NetworkAnimator` | | `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` | | `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` | | `SpawnManager` | `NetworkSpawnManager` | | `BitStream` | `NetworkBuffer` | | `BitReader` | `NetworkReader` | | `BitWriter` | `NetworkWriter` | | `NetEventType` | `NetworkEventType` | | `ChannelType` | `NetworkDelivery` | | `Channel` | `NetworkChannel` | | `Transport` | `NetworkTransport` | | `NetworkedDictionary` | `NetworkDictionary` | | `NetworkedList` | `NetworkList` | | `NetworkedSet` | `NetworkSet` | | `MLAPIConstants` | `NetworkConstants` | | `UnetTransport` | `UNetTransport` | ### Fixed - [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`. - Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame. - Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method. - [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode. - [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions. - [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits. - [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`. ### Removed With a new release of MLAPI in Unity, some features have been removed: - SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 --> - [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo. - [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes: - Removed `SecuritySendFlags` from all APIs. - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`. - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries. - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing. - Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later. - Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details. - [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer. ### Known Issues - `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination. - For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error. - For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work. - For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel. - `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them - `NetworkTransform` have the following issues: - Replicated objects may have jitter. - The owner is always authoritative about the object's position. - Scale is not synchronized. - Connection Approval is not called on the host client. - For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages. - For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly. ## [0.0.1-preview.1] - 2020-12-20 This was an internally-only-used version of the Unity MLAPI Package
This commit is contained in:
900
Components/NetworkTransform.cs
Normal file
900
Components/NetworkTransform.cs
Normal file
@@ -0,0 +1,900 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Unity.Netcode.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A component for syncing transforms
|
||||
/// NetworkTransform will read the underlying transform and replicate it to clients.
|
||||
/// The replicated value will be automatically be interpolated (if active) and applied to the underlying GameObject's transform
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Netcode/" + nameof(NetworkTransform))]
|
||||
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
|
||||
public class NetworkTransform : NetworkBehaviour
|
||||
{
|
||||
public const float PositionThresholdDefault = .001f;
|
||||
public const float RotAngleThresholdDefault = .01f;
|
||||
public const float ScaleThresholdDefault = .01f;
|
||||
public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale);
|
||||
public OnClientRequestChangeDelegate OnClientRequestChange;
|
||||
|
||||
internal struct NetworkTransformState : INetworkSerializable
|
||||
{
|
||||
private const int k_InLocalSpaceBit = 0;
|
||||
private const int k_PositionXBit = 1;
|
||||
private const int k_PositionYBit = 2;
|
||||
private const int k_PositionZBit = 3;
|
||||
private const int k_RotAngleXBit = 4;
|
||||
private const int k_RotAngleYBit = 5;
|
||||
private const int k_RotAngleZBit = 6;
|
||||
private const int k_ScaleXBit = 7;
|
||||
private const int k_ScaleYBit = 8;
|
||||
private const int k_ScaleZBit = 9;
|
||||
private const int k_TeleportingBit = 10;
|
||||
|
||||
// 11-15: <unused>
|
||||
private ushort m_Bitset;
|
||||
|
||||
public bool InLocalSpace
|
||||
{
|
||||
get => (m_Bitset & (1 << k_InLocalSpaceBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_InLocalSpaceBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_InLocalSpaceBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
// Position
|
||||
public bool HasPositionX
|
||||
{
|
||||
get => (m_Bitset & (1 << k_PositionXBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionXBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionXBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPositionY
|
||||
{
|
||||
get => (m_Bitset & (1 << k_PositionYBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionYBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionYBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPositionZ
|
||||
{
|
||||
get => (m_Bitset & (1 << k_PositionZBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionZBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionZBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
// RotAngles
|
||||
public bool HasRotAngleX
|
||||
{
|
||||
get => (m_Bitset & (1 << k_RotAngleXBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleXBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleXBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRotAngleY
|
||||
{
|
||||
get => (m_Bitset & (1 << k_RotAngleYBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleYBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleYBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasRotAngleZ
|
||||
{
|
||||
get => (m_Bitset & (1 << k_RotAngleZBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleZBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleZBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
// Scale
|
||||
public bool HasScaleX
|
||||
{
|
||||
get => (m_Bitset & (1 << k_ScaleXBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleXBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleXBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasScaleY
|
||||
{
|
||||
get => (m_Bitset & (1 << k_ScaleYBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleYBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleYBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasScaleZ
|
||||
{
|
||||
get => (m_Bitset & (1 << k_ScaleZBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleZBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleZBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTeleportingNextFrame
|
||||
{
|
||||
get => (m_Bitset & (1 << k_TeleportingBit)) != 0;
|
||||
set
|
||||
{
|
||||
if (value) { m_Bitset = (ushort)(m_Bitset | (1 << k_TeleportingBit)); }
|
||||
else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_TeleportingBit)); }
|
||||
}
|
||||
}
|
||||
|
||||
public float PositionX, PositionY, PositionZ;
|
||||
public float RotAngleX, RotAngleY, RotAngleZ;
|
||||
public float ScaleX, ScaleY, ScaleZ;
|
||||
public double SentTime;
|
||||
|
||||
public Vector3 Position
|
||||
{
|
||||
get { return new Vector3(PositionX, PositionY, PositionZ); }
|
||||
set
|
||||
{
|
||||
PositionX = value.x;
|
||||
PositionY = value.y;
|
||||
PositionZ = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Rotation
|
||||
{
|
||||
get { return new Vector3(RotAngleX, RotAngleY, RotAngleZ); }
|
||||
set
|
||||
{
|
||||
RotAngleX = value.x;
|
||||
RotAngleY = value.y;
|
||||
RotAngleZ = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Scale
|
||||
{
|
||||
get { return new Vector3(ScaleX, ScaleY, ScaleZ); }
|
||||
set
|
||||
{
|
||||
ScaleX = value.x;
|
||||
ScaleY = value.y;
|
||||
ScaleZ = value.z;
|
||||
}
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref SentTime);
|
||||
// InLocalSpace + HasXXX Bits
|
||||
serializer.SerializeValue(ref m_Bitset);
|
||||
// Position Values
|
||||
if (HasPositionX)
|
||||
{
|
||||
serializer.SerializeValue(ref PositionX);
|
||||
}
|
||||
|
||||
if (HasPositionY)
|
||||
{
|
||||
serializer.SerializeValue(ref PositionY);
|
||||
}
|
||||
|
||||
if (HasPositionZ)
|
||||
{
|
||||
serializer.SerializeValue(ref PositionZ);
|
||||
}
|
||||
|
||||
// RotAngle Values
|
||||
if (HasRotAngleX)
|
||||
{
|
||||
serializer.SerializeValue(ref RotAngleX);
|
||||
}
|
||||
|
||||
if (HasRotAngleY)
|
||||
{
|
||||
serializer.SerializeValue(ref RotAngleY);
|
||||
}
|
||||
|
||||
if (HasRotAngleZ)
|
||||
{
|
||||
serializer.SerializeValue(ref RotAngleZ);
|
||||
}
|
||||
|
||||
// Scale Values
|
||||
if (HasScaleX)
|
||||
{
|
||||
serializer.SerializeValue(ref ScaleX);
|
||||
}
|
||||
|
||||
if (HasScaleY)
|
||||
{
|
||||
serializer.SerializeValue(ref ScaleY);
|
||||
}
|
||||
|
||||
if (HasScaleZ)
|
||||
{
|
||||
serializer.SerializeValue(ref ScaleZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SyncPositionX = true, SyncPositionY = true, SyncPositionZ = true;
|
||||
public bool SyncRotAngleX = true, SyncRotAngleY = true, SyncRotAngleZ = true;
|
||||
public bool SyncScaleX = true, SyncScaleY = true, SyncScaleZ = true;
|
||||
|
||||
public float PositionThreshold = PositionThresholdDefault;
|
||||
public float RotAngleThreshold = RotAngleThresholdDefault;
|
||||
public float ScaleThreshold = ScaleThresholdDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether this transform should sync in local space or in world space.
|
||||
/// This is important to set since reparenting this transform could have issues,
|
||||
/// if using world position (depending on who gets synced first: the parent or the child)
|
||||
/// Having a child always at position 0,0,0 for example will have less possibilities of desync than when using world positions
|
||||
/// </summary>
|
||||
[Tooltip("Sets whether this transform should sync in local space or in world space")]
|
||||
public bool InLocalSpace = false;
|
||||
|
||||
public bool Interpolate = true;
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine who can write to this transform. Server only for this transform.
|
||||
/// Changing this value alone in a child implementation will not allow you to create a NetworkTransform which can be written to by clients. See the ClientNetworkTransform Sample
|
||||
/// in the package samples for how to implement a NetworkTransform with client write support.
|
||||
/// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing
|
||||
/// </summary>
|
||||
// This is public to make sure that users don't depend on this IsClient && IsOwner check in their code. If this logic changes in the future, we can make it invisible here
|
||||
public bool CanCommitToTransform;
|
||||
protected bool m_CachedIsServer;
|
||||
protected NetworkManager m_CachedNetworkManager;
|
||||
|
||||
private readonly NetworkVariable<NetworkTransformState> m_ReplicatedNetworkState = new NetworkVariable<NetworkTransformState>(new NetworkTransformState());
|
||||
|
||||
private NetworkTransformState m_LocalAuthoritativeNetworkState;
|
||||
|
||||
private NetworkTransformState m_PrevNetworkState;
|
||||
|
||||
private const int k_DebugDrawLineTime = 10;
|
||||
|
||||
private bool m_HasSentLastValue = false; // used to send one last value, so clients can make the difference between lost replication data (clients extrapolate) and no more data to send.
|
||||
|
||||
|
||||
private BufferedLinearInterpolator<float> m_PositionXInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_PositionYInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_PositionZInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<Quaternion> m_RotationInterpolator; // = new BufferedLinearInterpolatorQuaternion(); // rotation is a single Quaternion since each euler axis will affect the quaternion's final value
|
||||
private BufferedLinearInterpolator<float> m_ScaleXInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_ScaleYInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private BufferedLinearInterpolator<float> m_ScaleZInterpolator; // = new BufferedLinearInterpolatorFloat();
|
||||
private readonly List<BufferedLinearInterpolator<float>> m_AllFloatInterpolators = new List<BufferedLinearInterpolator<float>>(6);
|
||||
|
||||
private Transform m_Transform; // cache the transform component to reduce unnecessary bounce between managed and native
|
||||
private int m_LastSentTick;
|
||||
private NetworkTransformState m_LastSentState;
|
||||
|
||||
/// <summary>
|
||||
/// Tries updating the server authoritative transform, only if allowed.
|
||||
/// If this called server side, this will commit directly.
|
||||
/// If no update is needed, nothing will be sent. This method should still be called every update, it'll self manage when it should and shouldn't send
|
||||
/// </summary>
|
||||
/// <param name="transformToCommit"></param>
|
||||
/// <param name="dirtyTime"></param>
|
||||
protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime)
|
||||
{
|
||||
var isDirty = ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit);
|
||||
TryCommit(isDirty);
|
||||
}
|
||||
|
||||
private void TryCommitValuesToServer(Vector3 position, Vector3 rotation, Vector3 scale, double dirtyTime)
|
||||
{
|
||||
var isDirty = ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, dirtyTime, position, rotation, scale);
|
||||
|
||||
TryCommit(isDirty.isDirty);
|
||||
}
|
||||
|
||||
private void TryCommit(bool isDirty)
|
||||
{
|
||||
void Send(NetworkTransformState stateToSend)
|
||||
{
|
||||
if (m_CachedIsServer)
|
||||
{
|
||||
// server RPC takes a few frames to execute server side, we want this to execute immediately
|
||||
CommitLocallyAndReplicate(stateToSend);
|
||||
}
|
||||
else
|
||||
{
|
||||
CommitTransformServerRpc(stateToSend);
|
||||
}
|
||||
}
|
||||
|
||||
// if dirty, send
|
||||
// if not dirty anymore, but hasn't sent last value for limiting extrapolation, still set isDirty
|
||||
// if not dirty and has already sent last value, don't do anything
|
||||
// extrapolation works by using last two values. if it doesn't receive anything anymore, it'll continue to extrapolate.
|
||||
// This is great in case there's message loss, not so great if we just don't have new values to send.
|
||||
// the following will send one last "copied" value so unclamped interpolation tries to extrapolate between two identical values, effectively
|
||||
// making it immobile.
|
||||
if (isDirty)
|
||||
{
|
||||
Send(m_LocalAuthoritativeNetworkState);
|
||||
m_HasSentLastValue = false;
|
||||
m_LastSentTick = m_CachedNetworkManager.LocalTime.Tick;
|
||||
m_LastSentState = m_LocalAuthoritativeNetworkState;
|
||||
}
|
||||
else if (!m_HasSentLastValue && m_CachedNetworkManager.LocalTime.Tick >= m_LastSentTick + 1) // check for state.IsDirty since update can happen more than once per tick. No need for client, RPCs will just queue up
|
||||
{
|
||||
m_LastSentState.SentTime = m_CachedNetworkManager.LocalTime.Time; // time 1+ tick later
|
||||
Send(m_LastSentState);
|
||||
m_HasSentLastValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc(RequireOwnership = false)]
|
||||
private void CommitTransformServerRpc(NetworkTransformState networkState, ServerRpcParams serverParams = default)
|
||||
{
|
||||
if (serverParams.Receive.SenderClientId == OwnerClientId) // RPC call when not authorized to write could happen during the RTT interval during which a server's ownership change hasn't reached the client yet
|
||||
{
|
||||
CommitLocallyAndReplicate(networkState);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitLocallyAndReplicate(NetworkTransformState networkState)
|
||||
{
|
||||
m_ReplicatedNetworkState.Value = networkState;
|
||||
AddInterpolatedState(networkState);
|
||||
}
|
||||
|
||||
private void ResetInterpolatedStateToCurrentAuthoritativeState()
|
||||
{
|
||||
var serverTime = NetworkManager.ServerTime.Time;
|
||||
m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime);
|
||||
m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime);
|
||||
m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime);
|
||||
|
||||
m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.Rotation), serverTime);
|
||||
|
||||
m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime);
|
||||
m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime);
|
||||
m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime);
|
||||
}
|
||||
|
||||
// updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made
|
||||
// returned boolean would be useful to change encapsulating `NetworkVariable<NetworkState>`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty);
|
||||
internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
|
||||
{
|
||||
return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, transformToUse).isDirty;
|
||||
}
|
||||
|
||||
private (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse)
|
||||
{
|
||||
var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position;
|
||||
var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles;
|
||||
var scale = transformToUse.localScale;
|
||||
return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, position, rotAngles, scale);
|
||||
}
|
||||
|
||||
private (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Vector3 position, Vector3 rotAngles, Vector3 scale)
|
||||
{
|
||||
var isDirty = false;
|
||||
var isPositionDirty = false;
|
||||
var isRotationDirty = false;
|
||||
var isScaleDirty = false;
|
||||
|
||||
// hasPositionZ set to false when it should be true?
|
||||
|
||||
if (InLocalSpace != networkState.InLocalSpace)
|
||||
{
|
||||
networkState.InLocalSpace = InLocalSpace;
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
// we assume that if x, y or z are dirty then we'll have to send all 3 anyway, so for efficiency
|
||||
// we skip doing the (quite expensive) Math.Approximately() and check against PositionThreshold
|
||||
// this still is overly costly and could use more improvements.
|
||||
//
|
||||
// (ditto for scale components)
|
||||
if (SyncPositionX &&
|
||||
Mathf.Abs(networkState.PositionX - position.x) > PositionThreshold)
|
||||
{
|
||||
networkState.PositionX = position.x;
|
||||
networkState.HasPositionX = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionY &&
|
||||
Mathf.Abs(networkState.PositionY - position.y) > PositionThreshold)
|
||||
{
|
||||
networkState.PositionY = position.y;
|
||||
networkState.HasPositionY = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncPositionZ &&
|
||||
Mathf.Abs(networkState.PositionZ - position.z) > PositionThreshold)
|
||||
{
|
||||
networkState.PositionZ = position.z;
|
||||
networkState.HasPositionZ = true;
|
||||
isPositionDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleX &&
|
||||
Mathf.Abs(networkState.RotAngleX - rotAngles.x) > RotAngleThreshold)
|
||||
{
|
||||
networkState.RotAngleX = rotAngles.x;
|
||||
networkState.HasRotAngleX = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleY &&
|
||||
Mathf.Abs(networkState.RotAngleY - rotAngles.y) > RotAngleThreshold)
|
||||
{
|
||||
networkState.RotAngleY = rotAngles.y;
|
||||
networkState.HasRotAngleY = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncRotAngleZ &&
|
||||
Mathf.Abs(networkState.RotAngleZ - rotAngles.z) > RotAngleThreshold)
|
||||
{
|
||||
networkState.RotAngleZ = rotAngles.z;
|
||||
networkState.HasRotAngleZ = true;
|
||||
isRotationDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleX &&
|
||||
Mathf.Abs(networkState.ScaleX - scale.x) > ScaleThreshold)
|
||||
{
|
||||
networkState.ScaleX = scale.x;
|
||||
networkState.HasScaleX = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleY &&
|
||||
Mathf.Abs(networkState.ScaleY - scale.y) > ScaleThreshold)
|
||||
{
|
||||
networkState.ScaleY = scale.y;
|
||||
networkState.HasScaleY = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
if (SyncScaleZ &&
|
||||
Mathf.Abs(networkState.ScaleZ - scale.z) > ScaleThreshold)
|
||||
{
|
||||
networkState.ScaleZ = scale.z;
|
||||
networkState.HasScaleZ = true;
|
||||
isScaleDirty = true;
|
||||
}
|
||||
|
||||
isDirty |= isPositionDirty || isRotationDirty || isScaleDirty;
|
||||
|
||||
if (isDirty)
|
||||
{
|
||||
networkState.SentTime = dirtyTime;
|
||||
}
|
||||
|
||||
return (isDirty, isPositionDirty, isRotationDirty, isScaleDirty);
|
||||
}
|
||||
|
||||
private void ApplyInterpolatedNetworkStateToTransform(NetworkTransformState networkState, Transform transformToUpdate)
|
||||
{
|
||||
m_PrevNetworkState = networkState;
|
||||
|
||||
var interpolatedPosition = InLocalSpace ? transformToUpdate.localPosition : transformToUpdate.position;
|
||||
|
||||
// todo: we should store network state w/ quats vs. euler angles
|
||||
var interpolatedRotAngles = InLocalSpace ? transformToUpdate.localEulerAngles : transformToUpdate.eulerAngles;
|
||||
var interpolatedScale = transformToUpdate.localScale;
|
||||
|
||||
// InLocalSpace Read
|
||||
InLocalSpace = networkState.InLocalSpace;
|
||||
// Position Read
|
||||
if (SyncPositionX)
|
||||
{
|
||||
interpolatedPosition.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.x : m_PositionXInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncPositionY)
|
||||
{
|
||||
interpolatedPosition.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.y : m_PositionYInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncPositionZ)
|
||||
{
|
||||
interpolatedPosition.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Position.z : m_PositionZInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
// again, we should be using quats here
|
||||
if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ)
|
||||
{
|
||||
var eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles;
|
||||
if (SyncRotAngleX)
|
||||
{
|
||||
interpolatedRotAngles.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.x : eulerAngles.x;
|
||||
}
|
||||
|
||||
if (SyncRotAngleY)
|
||||
{
|
||||
interpolatedRotAngles.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.y : eulerAngles.y;
|
||||
}
|
||||
|
||||
if (SyncRotAngleZ)
|
||||
{
|
||||
interpolatedRotAngles.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.z : eulerAngles.z;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale Read
|
||||
if (SyncScaleX)
|
||||
{
|
||||
interpolatedScale.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.x : m_ScaleXInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncScaleY)
|
||||
{
|
||||
interpolatedScale.y = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.y : m_ScaleYInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
if (SyncScaleZ)
|
||||
{
|
||||
interpolatedScale.z = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Scale.z : m_ScaleZInterpolator.GetInterpolatedValue();
|
||||
}
|
||||
|
||||
// Position Apply
|
||||
if (SyncPositionX || SyncPositionY || SyncPositionZ)
|
||||
{
|
||||
if (InLocalSpace)
|
||||
{
|
||||
transformToUpdate.localPosition = interpolatedPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
transformToUpdate.position = interpolatedPosition;
|
||||
}
|
||||
|
||||
m_PrevNetworkState.Position = interpolatedPosition;
|
||||
}
|
||||
|
||||
// RotAngles Apply
|
||||
if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ)
|
||||
{
|
||||
if (InLocalSpace)
|
||||
{
|
||||
transformToUpdate.localRotation = Quaternion.Euler(interpolatedRotAngles);
|
||||
}
|
||||
else
|
||||
{
|
||||
transformToUpdate.rotation = Quaternion.Euler(interpolatedRotAngles);
|
||||
}
|
||||
|
||||
m_PrevNetworkState.Rotation = interpolatedRotAngles;
|
||||
}
|
||||
|
||||
// Scale Apply
|
||||
if (SyncScaleX || SyncScaleY || SyncScaleZ)
|
||||
{
|
||||
transformToUpdate.localScale = interpolatedScale;
|
||||
m_PrevNetworkState.Scale = interpolatedScale;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInterpolatedState(NetworkTransformState newState)
|
||||
{
|
||||
var sentTime = newState.SentTime;
|
||||
|
||||
if (newState.HasPositionX)
|
||||
{
|
||||
m_PositionXInterpolator.AddMeasurement(newState.PositionX, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasPositionY)
|
||||
{
|
||||
m_PositionYInterpolator.AddMeasurement(newState.PositionY, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasPositionZ)
|
||||
{
|
||||
m_PositionZInterpolator.AddMeasurement(newState.PositionZ, sentTime);
|
||||
}
|
||||
|
||||
m_RotationInterpolator.AddMeasurement(Quaternion.Euler(newState.Rotation), sentTime);
|
||||
|
||||
if (newState.HasScaleX)
|
||||
{
|
||||
m_ScaleXInterpolator.AddMeasurement(newState.ScaleX, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasScaleY)
|
||||
{
|
||||
m_ScaleYInterpolator.AddMeasurement(newState.ScaleY, sentTime);
|
||||
}
|
||||
|
||||
if (newState.HasScaleZ)
|
||||
{
|
||||
m_ScaleZInterpolator.AddMeasurement(newState.ScaleZ, sentTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransformState newState)
|
||||
{
|
||||
if (!NetworkObject.IsSpawned)
|
||||
{
|
||||
// todo MTT-849 should never happen but yet it does! maybe revisit/dig after NetVar updates and snapshot system lands?
|
||||
return;
|
||||
}
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
// we're the authority, we ignore incoming changes
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.DrawLine(newState.Position, newState.Position + Vector3.up + Vector3.left, Color.green, 10, false);
|
||||
|
||||
AddInterpolatedState(newState);
|
||||
|
||||
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
var pos = new Vector3(newState.PositionX, newState.PositionY, newState.PositionZ);
|
||||
Debug.DrawLine(pos, pos + Vector3.up + Vector3.left * Random.Range(0.5f, 2f), Color.green, k_DebugDrawLineTime, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// we only want to create our interpolators during Awake so that, when pooled, we do not create tons
|
||||
// of gc thrash each time objects wink out and are re-used
|
||||
m_PositionXInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_PositionYInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_PositionZInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); // rotation is a single Quaternion since each euler axis will affect the quaternion's final value
|
||||
m_ScaleXInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_ScaleYInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
m_ScaleZInterpolator = new BufferedLinearInterpolatorFloat();
|
||||
|
||||
if (m_AllFloatInterpolators.Count == 0)
|
||||
{
|
||||
m_AllFloatInterpolators.Add(m_PositionXInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_PositionYInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_PositionZInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_ScaleXInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_ScaleYInterpolator);
|
||||
m_AllFloatInterpolators.Add(m_ScaleZInterpolator);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// must set up m_Transform in OnNetworkSpawn because it's possible an object spawns but is disabled
|
||||
// and thus awake won't be called.
|
||||
// TODO: investigate further on not sending data for something that is not enabled
|
||||
m_Transform = transform;
|
||||
m_ReplicatedNetworkState.OnValueChanged += OnNetworkStateChanged;
|
||||
|
||||
CanCommitToTransform = IsServer;
|
||||
m_CachedIsServer = IsServer;
|
||||
m_CachedNetworkManager = NetworkManager;
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
|
||||
}
|
||||
m_LocalAuthoritativeNetworkState = m_ReplicatedNetworkState.Value;
|
||||
|
||||
// crucial we do this to reset the interpolators so that recycled objects when using a pool will
|
||||
// not have leftover interpolator state from the previous object
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
m_ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged;
|
||||
}
|
||||
|
||||
public override void OnGainedOwnership()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override void OnLostOwnership()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
ResetInterpolatedStateToCurrentAuthoritativeState(); // useful for late joining
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
m_ReplicatedNetworkState.SetDirty(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
|
||||
}
|
||||
}
|
||||
|
||||
#region state set
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets a state on the authoritative transform.
|
||||
/// This will override any changes made previously to the transform
|
||||
/// This isn't resistant to network jitter. Server side changes due to this method won't be interpolated.
|
||||
/// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb
|
||||
/// just the desired one(s)
|
||||
/// </summary>
|
||||
/// <param name="posIn"></param> new position to move to. Can be null
|
||||
/// <param name="rotIn"></param> new rotation to rotate to. Can be null
|
||||
/// <param name="scaleIn">new scale to scale to. Can be null</param>
|
||||
/// <param name="shouldGhostsInterpolate">Should other clients interpolate this change or not. True by default</param>
|
||||
/// new scale to scale to. Can be null
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool shouldGhostsInterpolate = true)
|
||||
{
|
||||
if (!IsOwner)
|
||||
{
|
||||
throw new Exception("Trying to set a state on a not owned transform");
|
||||
}
|
||||
|
||||
if (m_CachedNetworkManager && !(m_CachedNetworkManager.IsConnectedClient || m_CachedNetworkManager.IsListening))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 pos = posIn == null ? transform.position : (Vector3)posIn;
|
||||
Quaternion rot = rotIn == null ? transform.rotation : (Quaternion)rotIn;
|
||||
Vector3 scale = scaleIn == null ? transform.localScale : (Vector3)scaleIn;
|
||||
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
if (!m_CachedIsServer)
|
||||
{
|
||||
SetStateServerRpc(pos, rot, scale, shouldGhostsInterpolate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Transform.position = pos;
|
||||
m_Transform.rotation = rot;
|
||||
m_Transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldGhostsInterpolate;
|
||||
}
|
||||
}
|
||||
|
||||
[ServerRpc]
|
||||
private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport)
|
||||
{
|
||||
// server has received this RPC request to move change transform. Give the server a chance to modify or
|
||||
// even reject the move
|
||||
if (OnClientRequestChange != null)
|
||||
{
|
||||
(pos, rot, scale) = OnClientRequestChange(pos, rot, scale);
|
||||
}
|
||||
m_Transform.position = pos;
|
||||
m_Transform.rotation = rot;
|
||||
m_Transform.localScale = scale;
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport;
|
||||
}
|
||||
#endregion
|
||||
|
||||
// todo this is currently in update, to be able to catch any transform changes. A FixedUpdate mode could be added to be less intense, but it'd be
|
||||
// conditional to users only making transform update changes in FixedUpdate.
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (!NetworkObject.IsSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CanCommitToTransform)
|
||||
{
|
||||
if (m_CachedIsServer)
|
||||
{
|
||||
TryCommitTransformToServer(m_Transform, m_CachedNetworkManager.LocalTime.Time);
|
||||
}
|
||||
|
||||
m_PrevNetworkState = m_LocalAuthoritativeNetworkState;
|
||||
}
|
||||
|
||||
// apply interpolated value
|
||||
if (m_CachedNetworkManager.IsConnectedClient || m_CachedNetworkManager.IsListening)
|
||||
{
|
||||
// eventually, we could hoist this calculation so that it happens once for all objects, not once per object
|
||||
var cachedDeltaTime = Time.deltaTime;
|
||||
var serverTime = NetworkManager.ServerTime;
|
||||
var cachedServerTime = serverTime.Time;
|
||||
var cachedRenderTime = serverTime.TimeTicksAgo(1).Time;
|
||||
|
||||
foreach (var interpolator in m_AllFloatInterpolators)
|
||||
{
|
||||
interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
|
||||
}
|
||||
|
||||
m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
|
||||
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
#if NGO_TRANSFORM_DEBUG
|
||||
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
// TODO: This should be a component gizmo - not some debug draw based on log level
|
||||
var interpolatedPosition = new Vector3(m_PositionXInterpolator.GetInterpolatedValue(), m_PositionYInterpolator.GetInterpolatedValue(), m_PositionZInterpolator.GetInterpolatedValue());
|
||||
Debug.DrawLine(interpolatedPosition, interpolatedPosition + Vector3.up, Color.magenta, k_DebugDrawLineTime, false);
|
||||
|
||||
// try to update previously consumed NetworkState
|
||||
// if we have any changes, that means made some updates locally
|
||||
// we apply the latest ReplNetworkState again to revert our changes
|
||||
var oldStateDirtyInfo = ApplyTransformToNetworkStateWithInfo(ref m_PrevNetworkState, 0, m_Transform);
|
||||
|
||||
// there are several bugs in this code, as we the message is dumped out under odd circumstances
|
||||
// For Matt, it would trigger when an object's rotation was perturbed by colliding with another
|
||||
// object vs. explicitly rotating it
|
||||
if (oldStateDirtyInfo.isPositionDirty || oldStateDirtyInfo.isScaleDirty || (oldStateDirtyInfo.isRotationDirty && SyncRotAngleX && SyncRotAngleY && SyncRotAngleZ))
|
||||
{
|
||||
// ignoring rotation dirty since quaternions will mess with euler angles, making this impossible to determine if the change to a single axis comes
|
||||
// from an unauthorized transform change or euler to quaternion conversion artifacts.
|
||||
var dirtyField = oldStateDirtyInfo.isPositionDirty ? "position" : oldStateDirtyInfo.isRotationDirty ? "rotation" : "scale";
|
||||
Debug.LogWarning($"A local change to {dirtyField} without authority detected, reverting back to latest interpolated network state!", this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Apply updated interpolated value
|
||||
ApplyInterpolatedNetworkStateToTransform(m_ReplicatedNetworkState.Value, m_Transform);
|
||||
}
|
||||
}
|
||||
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleports the transform to the given values without interpolating
|
||||
/// </summary>
|
||||
public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale)
|
||||
{
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
throw new Exception("Teleport not allowed");
|
||||
}
|
||||
|
||||
var newRotationEuler = newRotation.eulerAngles;
|
||||
var stateToSend = m_LocalAuthoritativeNetworkState;
|
||||
stateToSend.IsTeleportingNextFrame = true;
|
||||
stateToSend.Position = newPosition;
|
||||
stateToSend.Rotation = newRotationEuler;
|
||||
stateToSend.Scale = newScale;
|
||||
ApplyInterpolatedNetworkStateToTransform(stateToSend, transform);
|
||||
// set teleport flag in state to signal to ghosts not to interpolate
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = true;
|
||||
// check server side
|
||||
TryCommitValuesToServer(newPosition, newRotationEuler, newScale, m_CachedNetworkManager.LocalTime.Time);
|
||||
m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user