diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3353565e01..516d548156 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,10 +13,12 @@ Additional documentation and release notes are available at [Multiplayer Documen - It is now possible to control which port clients will bind to using the `UnityTransport.ConnectionData.ClientBindPort` field. If not set, clients will bind to an ephemeral port (same as before this change). (#3764) - Added a flag to override command-line arguments (port and ip) in `SetConnectionData`. (#3760) - Added a command-line singleton to parse environment command-line arguments. (#3760) - +- Added `NetworkAnimator.AuthorityMode` which allows you to select whether the `NetworkAnimator` will use a server or owner authority model for state updates (like `NetworkTransform`). (#3586) +- Added the ability to select which `Animator` parameters the authority `NetworkAnimator` instance should synchronize. This can be done via the inspector view interface or during runtime via `NetworkAnimator.EnableParameterSynchronization`. (#3586) ### Changed +- Changed NetworkAnimator to use the `RpcAttribute` along with the appropriate `SendTo` parameter. (#3586) ### Deprecated @@ -30,6 +32,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Fixed issue where invoking an RPC, on another `NetworkBehaviour` associated with the same `NetworkObject` that is ordered before the `NetworkBehaviour` invoking the RPC, during `OnNetworkSpawn` could throw an exception if scene management is disabled. (#3782) - Fixed issue where the `Axis to Synchronize` toggles didn't work with multi object editing in `NetworkTransform`. (#3781) - Fixed issue where using the dedicated server package would override all attempts to change the port by code. (#3760) +- Fixed issue with authority animator instance sending itself RPCs. (#3586) ### Security diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md index 5f1bd1371b..d2681785e8 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkanimator.md @@ -3,36 +3,32 @@ The NetworkAnimator component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. * Players joining an existing network session will be synchronized with: - * All the `Animator`'s current properties and states. - * *With exception to `Animator` trigger properties. These are only synchronized with already connected clients.* - * Any in progress transition + * All the `Animator`'s current parameters and states. + * With the following exceptions: + * `Animator` trigger parameters. + * _These are only synchronized with already connected clients._ + * _However, late joining clients will get synchronized with the `Animator`'s current state_. + * Any `Animator` parameter specifically excluded from being synchronized. + * Any in progress transition. * Players already connected will be synchronized with changes to `Animator`: - * States - * Transitions - * Properties - * NetworkAnimator will only synchronize properties that have changed since the earlier frame property values. - * Since triggers are similar to an "event," when an `Animator` property is set to `true` it will always be synchronized. + * States + * Transitions + * Parameters + * NetworkAnimator will only synchronize parameters that have changed since the previous frame's parameter values. + * Since triggers are similar to an "event," when an `Animator` parameter is set to `true` it will always be synchronized. -NetworkAnimator can operate in two authoritative modes: - -* Server Authoritative (default): Server initiates animation state changes. - * Owner's can still invoke `NetworkAnimator.SetTrigger`. -* Client Authoritative: Client owners start animation state changes. +NetworkAnimator can operate in two authority modes: +* Server Authoritative (default): Server dictates changes to `Animator` state(s) and/or parameters. + * _Owner's can still invoke `NetworkAnimator.SetTrigger`._ +* Client Authoritative: The owner of the spawned `NetworkObject` dictates changes to `Animator` state(s) and/or parameters. > [!NOTE] -> You need to use `Unity.Netcode.Components` to reference components such as NetworkAnimator. - -## Animator Trigger Property - -The `Animator` trigger property type ("trigger") is basically nothing more than a Boolean value that, when set to `true`, will get automatically reset back to `false` after the `Animator` component has processed the trigger. Usually, a trigger is used to start a transition between `Animator` layer states. In this sense, one can think of a trigger as a way to signal the "beginning of an event." Because trigger properties have this unique behavior, they **require** that you to set the trigger value via the `NetworkAnimator.SetTrigger` method. - -> [!NOTE] -> If you set a trigger property using `Animator.SetTrigger` then this won't be synchronized with non-owner clients. +> You need to include `Unity.Netcode.Components` as a using directive in order to reference components such as NetworkAnimator. ## Server Authoritative Mode -The default setting for NetworkAnimator is server authoritative mode. When operating in server authoritative mode, any animation state changes that are set (triggers) or detected (change in layer, state, or any `Animator` properties excluding triggers) on the server side will be synchronized with all clients. Because the server initiates any synchronization of changes to an `Animator` 's state, a client that's the owner of the NetworkObject associated with the NetworkAnimator can lag by roughly the full round trip time (RTT). Below is a timing diagram to show this: +The default setting for NetworkAnimator is server authoritative mode. When operating in server authoritative mode, any animation state changes that are set (triggers) or detected (change in layer, state, or any `Animator` parameters excluding triggers) on the server side will be synchronized with all clients. Because the server initiates any synchronization of changes to an `Animator` 's state, the owner of the NetworkObject associated with the NetworkAnimator can lag by roughly the full round trip time (RTT). Below is a timing diagram to show this: ![ServerAuthMode](../../images/NetworkAnimatorServerAuthTiming.png) @@ -77,7 +73,7 @@ In the above diagram, it shows that the owner client has an `Animator` state cha * A host lags behind the owner client's animation by roughly half RTT. > [!NOTE] -> The same rule for setting trigger properties still applies to owner clients. As such, if you want to programmatically set a trigger then you still need to use `NetworkAnimator.SetTrigger`. +> The same rule for setting trigger parameters still applies to owner clients. As such, if you want to programmatically set a trigger then you still need to use `NetworkAnimator.SetTrigger`. ## Using NetworkAnimator @@ -86,41 +82,360 @@ Using NetworkAnimator is a pretty straight forward approach with the only subtle > [!NOTE] > NetworkAnimator is one of several possible ways to synchronize animations during a network session. Netcode for GameObjects provides you with the building blocks (RPCs, NetworkVariables, and Custom Messages) needed to create a completely unique animation synchronization system that has a completely different and potentially more optimized approach. NetworkAnimator is a straight forward approach provided for users already familiar with the `Animator` component and, depending upon your project's design requirements, might be all that you need. -### Server Authoritative +### Changing meshes + +When swapping a skinned mesh with another re-parented skinned mesh, you should invoke the `Rebind` method on the `Animator` component (`Animator.Rebind()`). + +### Assigning the animator -If you decide you want to use the server authoritative model, then you can add a NetworkAnimator component to either the same GameObject that has the NetworkObject component attached to it or any child GameObject. In the below screenshot, you can see a network Prefab that houses two authoritative models. The `NetworkAnimatorCube-Server` GameObject has an `Animator` component, an `AnimatedCubeController` component (used for manual testing), and the NetworkAnimator component that has a reference to the `Animator` component. +![Usage-1](../../images/networkanimator/usingnetworkanimator.png) -![Usage-1](../../images/NetworkAnimatorUsage-1.png) +Upon adding a `NetowrkAnimator` component to a network prefab, you will need to drag and drop the `Animator` component onto the **Animator** field within the inspector view. The `Animator` component can be on the root `GameObject` of the network prefab or a child under the root `GameObject`. -### Owner Authoritative +### Selecting the authority mode -If you decide you want to use the owner authoritative model, then (for example purposes) you would use your derived `OwnerNetworkAnimator` component as opposed to the default NetworkAnimator component like in the screenshot below: +The `NetworkAnimator` authority mode determines which instance of a spawned network prefab pushes updates to the `Animator`'s state. -![Usage-1](../../images/NetworkAnimatorUsage-2.png) +> [!NOTE] +> If you are upgrading from an older version of Netcode for GameObjects, then using this legacy/alternate approach still works and will be honored. The value returned from an overridden `NetworkAnimator.OnIsServerAuthoritative` will be used as opposed to the more recent update where you can just select the authority model within the inspector view. + +In earlier versions of Netcode for GameObjects, to change the authority mode of a `NetworkAnimator` you would need to: + +* Derive from the `NetworkAnimator` class. +* Override the `NetworkAnimator.OnIsServerAuthoritative` method. + * Returning `true` would make the server the animator authority. + * Returning `false` would make the owner the animator authority. + +With the updated NetworkAnimator, you now can just select which authority model you want to use from within the inspector view: + +![Usage-1](../../images/networkanimator/animatorauthority.png) > [!NOTE] -> While it isn't advised to have different NetworkAnimator authoritative models "under the same root network Prefab GameObject, " you can have multiple children that each have their own `Animator` and NetworkAnimator all housed under a single NetworkObject and all use the same authoritative model. However, you should always consider the balance between performance (CPU or bandwidth consumption) and convenience/modularity. +> Using the `NetworkAnimator.OnIsServerAuthoritative` still works and will supersede the NetworkAnimator's **Authority Mode** setting. + +### Changing Animator parameters + +For all `Animator` parameters (except for triggers), you can set them directly via the `Animator` class. As an example, you might want to incorporate a player jumping and need to be able to handle transitioning out of the later portion of the sequence where the player is "falling" from the jump (or falling when walking off of the edge of a platform). You might have a `bool` parameter called "Grounded" that you need to set when the player is not grounded. The straight forward way would be to set the value on the authoritative instance (server or owner) like such: -### Changing Animator Properties +```csharp +m_Animator.SetFloat("Grounded", false); +``` -For all `Animator` properties (except for triggers), you can set them directly via the `Animator` class. As an example, you might use the player's normalized velocity as a way to control the walking or running animation of a player. You might have an `Animator` `float` property called "AppliedMotion" that you would set on the authoritative instance (server or owner) like such: +_(In the above script, `m_Animator` is a reference to the `Animator` component.)_ + +The above example works, but in reality you would want to pre-calculate the hash value of the parameter's name and use that pre-calculated value to apply updates to parameters. Below provides an example of how you can accomplish this: ```csharp -public void ApplyMotion(Vector3 playerVelocity) +private int m_GroundedParameterId; +private bool m_WasGrounded; +private Animator m_Animator; +private CharacterController m_CharacterController; + +protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) +{ + // Pre-calculate the hash for quick lookup. + m_GroundedParameterId = Animator.StringToHash("Grounded"); + // Get the CharacterController. + m_CharacterController = GetComponent(); +} + +private void CheckForFalling() { - m_Animator.SetFloat("AppliedMotion", playerVelocity.normalized.magnitude); + // If the last status of being grounded is not the current. + if (m_CharacterController.isGrounded != m_WasGrounded) + { + // Set the Grounded parameter to match the change in the grounded state. + m_Animator.SetBool(m_GroundedParameterId, m_CharacterController.isGrounded); + // Update to be able to detect when it changes back. + m_WasGrounded = m_CharacterController.isGrounded; + } } ``` -For triggers you always want to use NetworkAnimator. One example might be that you use a trigger, called it " `IsJumping`, " to start a blended transition between the player's walking/running animation and the jumping animation: +## Animator trigger parameter + +The `Animator` trigger parameter type ("trigger") is basically nothing more than a Boolean value that, when set to `true`, will get automatically reset back to `false` after the `Animator` component has processed the trigger. Usually, a trigger is used to start a transition between `Animator` layer states. In this sense, one can think of a trigger as a way to signal the "beginning of an event". Because trigger parameters have this unique behavior, they **require** that you to set the trigger value via the `NetworkAnimator.SetTrigger` method. + +> [!NOTE] +> If you set a trigger parameter using `Animator.SetTrigger` then this trigger sequence/transition won't be properly synchronized with the non-authority clone instances. + +An example might be that you use a trigger parameter called `IsJumping` to start a blended transition between the player's walking/running animation and the jumping animation. The below script adds **m_NetworkAnimator** which is assigned during `OnNetworkPreSpawn` (_unless you need to access it in `Start` it is recommended to handle getting components within `OnNetworkPreSpawn` as this will be invoked prior to `Start` when first spawning an instance_). ```csharp +private int m_GroundedParameterId; +private int m_JumpingParameterId; +private bool m_WasGrounded; +private Animator m_Animator; +private NetworkAnimator m_NetworkAnimator; +private CharacterController m_CharacterController; + +protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) +{ + // Pre-calculate the hash values for performance purposes. + m_GroundedParameterId = Animator.StringToHash("Grounded"); + m_JumpingParameterId = Animator.StringToHash("IsJumping"); + + // Get the CharacterController. + m_CharacterController = GetComponent(); + // Get the NetworkAnimator component. + m_NetworkAnimator = GetComponent(); +} + +private void CheckForFalling() +{ + // If the last status of being grounded is not the current. + if (m_CharacterController.isGrounded != m_WasGrounded) + { + // Set the Grounded parameter to match the change in the grounded state. + m_Animator.SetBool(m_GroundedParameterId, m_CharacterController.isGrounded); + // Update to be able to detect when it changes back. + m_WasGrounded = m_CharacterController.isGrounded; + } +} + public void SetPlayerJumping(bool isJumping) { - m_NetworkAnimator.SetTrigger("IsJumping"); + // You only need to pass in the parameters hash/id to set the trigger + m_NetworkAnimator.SetTrigger(m_JumpingParameterId); } ``` +## Excluding parameters from being synchronized + +Now that you know about setting the authority mode and changing parameters, it is time to think about which parameters you want synchronized. Initially you might feel that all parameters need synchronization, however NetworkAnimator will synchronize any changes to any parameters marked for synchronization. + +This can become most problematic with `float` parameters that change often. As an example, you might have a `float` parameter called "Speed" which dictates the speed at which the current animation is played. This value could be directly set from a player's actual linear velocity which would most definitively be different each frame while a player is walking or running around. The end result is that for each spawned authority instance of any given network prefab that uses this kind of approach would generate at least one RPC per frame and that can exhaust transport resources and generate a bunch of network traffic. + > [!NOTE] -> Changing meshes
-> When swapping a skinned mesh with another reparented skinned mesh, you can invoke the `Rebind ` method on the `Animator` components: `Animator.Rebind()`. +> Taking the above scenario into consideration, if you had 300 spawned instances where each authority instance generated 1 RPC per instance per frame you would generate 300 messages per frame or (if running at 60hz) 18,000 messages per second. While Netcode for GameObjects will batch messages by combining them into a single message, it would still generate (at a minimum) a reliable fragmented sequenced message that is fragmented across ~5 UTP messages per frame. This would lead to latency and performance issues. + +Fortunately, NetworkAnimator provides you with the ability to enable or disable any animator parameter from being synchronized within the **Animator Parameter Entries** field: + +![Usage-1](../../images/networkanimator/parametersynchronization.png) + +When expanding this list of parameters, you will note that it also provides the hash value of the parameter's name (which can be copied), the parameter type, and a **Synchronize** field. By default, all parameters are marked for synchronization. + +### Non-synchronized parameters + +How you want to handle updating parameters that are not synchronized is really dependent upon what kind of approach best fits your project's goals. Fortunately, there are two ways to handle this: + +* Synchronize the values using your own custom solution + * You can opt to send the values at a specific interval via RPC or use a NetworkVariable that will synchronize any delta each network tick. + * _This still contributes to the over-all bandwidth and processing time, but can provide you with the ability to lerp between the previous and current value over (n) period of time._ +* Update the parameters locally based on values that you already have access to. + * This is the "bandwidth free" approach, but does require some additional script to handle this. + +#### Updating locally + +Since this option is basically "bandwidth free" and is most likely the first area you might be interested in investigating, we will dive a bit deeper to provide one common parameter that can be handled locally: **Speed**. + +The scenario: + +* You are using a modified version of the ThirdPersonController or are using a similar approach where you have two parameters that determine how quickly a player might play a walking or running animation: + * Speed parameter: + * Updated each frame based on the player's input. + * MotionSpeed: + * Basically determines the magnitude of the player's input. + * _Most of the time this ends up being either 1.0 or 0.0 without an analog device, so the below example assumes you are not using an analog device to control the "amount of speed" to apply over time._ +* You are using a NetworkTransform to synchronize changes in position and rotation or you have written your own custom NetworkBehaviour that accomplishes the same thing. + * You have interpolation enabled or your custom solution uses some form of interpolation where a single state update is applied over (n) period of time (typically a network tick). + +Since each non-authority instance will be receiving delta transform state updates from the authority instance, we know that if the authority instance is moving then it is most likely animating and it is playing those animations based on the "speed" (linear velocity) of the authority player instance. + +This means we should be able to come up with a way, on the non-authority side, to "mock/calculate" some values based on changes to the transform's position on a frame by frame basis. The first thing we would want to do is mark the two parameters as not being synchronized within the NetworkAnimator's **Animator Parameter Entries** list (for this example): + +![Usage-1](../../images/networkanimator/parametersnosynch.png) + +This means the authority will no longer send updates when these two parameters are marked to not be synchronized. The next thing that needs to be done is to come up with a way to calculate these values on non-authority instances based on the non-authority instances movement over time. + +Below is a simple example pseudo/partial script that shows how to accomplishes this: + +```csharp +[Range(0.0001f, 1.0f)] +public float m_NonAuthorityMotionThreshold = 0.01f; +private Vector3 m_LastPosition; +private float m_UnitsPerSecond; +private bool m_WasMoving; + +// Can be used to toggle parameter synchronization during runtime +private NetworkVariable m_SynchronizeSpeedParameter = new NetworkVariable(false); + +protected override void OnNetworkPostSpawn() +{ + _controller.enabled = IsLocalPlayer; + _hasAnimator = TryGetComponent(out _animator); + if (IsLocalPlayer) + { + // Register the authority for both the Update and PostLateUpdate player loop stages + // Update used to handle input and apply motion. + NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.Update); + + // PostLateUpdate handles camera rotation adjustments + NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PostLateUpdate); + _input.enabled = true; + _playerInput.enabled = true; + m_SynchronizeSpeedParameter.Value = false; + m_NetworkAnimator.EnableParameterSynchronization("Speed", m_SynchronizeSpeedParameter.Value); + m_NetworkAnimator.EnableParameterSynchronization("MotionSpeed", m_SynchronizeSpeedParameter.Value); + } + else + { + // When a non-authority instance is spawned, it initializes the last known position + m_LastPosition = transform.position; + + // Non-authority instances register for the pre-late update to assure any adjustments to + // position have been applied before calculating the animation speed. + NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreLateUpdate); + + } + base.OnNetworkPostSpawn(); +} + +public override void OnNetworkPreDespawn() +{ + _controller.enabled = false; + // Before de-spawning, unregister from all updates for this instance + NetworkUpdateLoop.UnregisterAllNetworkUpdates(this); + base.OnNetworkPreDespawn(); +} + +// This class implements INetworkUpdateSystem +public void NetworkUpdate(NetworkUpdateStage updateStage) +{ + if (!IsSpawned) + { + return; + } + + switch (updateStage) + { + + case NetworkUpdateStage.Update: + { + // Authority only + AuthorityUpdate(); + break; + } + case NetworkUpdateStage.PreLateUpdate: + { + if (!m_SynchronizeSpeedParameter.Value) + { + // Non-authority only + NonAuthorityUpdate(); + } + else if (m_WasMoving) + { + // If synchronizing speed and we were moving, then + // reset the fields used to calculate speed + m_WasMoving = false; + m_UnitsPerSecond = 0.0f; + _animationBlend = 0.0f; + _speed = 0.0f; + } + break; + } + case NetworkUpdateStage.PostLateUpdate: + { + // Authority only + CameraRotation(); + break; + } + } +} + +private void NonAuthorityUpdate() +{ + // Get the delta from last frame + var deltaVector3 = transform.position - m_LastPosition; + // An approximated calculation of the potential unity world space units per second by getting the quotient of delta time divided into 1. We are only interested in x and z deltas, so use a Vector2, and then obtain the magnitude of the quotient times the Vector2. + var unitsPerSecond = (new Vector2(deltaVector3.x, deltaVector3.z) * (1.0f / Time.deltaTime)).magnitude; + + // Only trigger when the delta per frame exceeds the non-authority motion threshold + if (unitsPerSecond > m_NonAuthorityMotionThreshold) + { + // if the new delta is > or < the last value stored + if (unitsPerSecond != m_UnitsPerSecond) + { + // Lerp towards the new delta to mock the player input + m_UnitsPerSecond = Mathf.Lerp(m_UnitsPerSecond, unitsPerSecond, Time.deltaTime * SpeedChangeRate); + + // Clamp to the maximum world space units per second + m_UnitsPerSecond = Mathf.Clamp(m_UnitsPerSecond, 0.0f, SprintSpeed); + + // round speed to 3 decimal places like it does with player input + _speed = (float)System.Math.Round(m_UnitsPerSecond, 3); + + // Track that we are now moving + m_WasMoving = true; + } + else + { + // Maintain the current speed + _speed = m_UnitsPerSecond; + } + + // If we are half of the non-authority motion threshold then come to a stop + if (_speed < (m_NonAuthorityMotionThreshold * 0.5f)) + { + _speed = 0f; + m_UnitsPerSecond = 0f; + // Reset the magnitude to zero + _animator.SetFloat(_animIDMotionSpeed, 0.0f); + } + else + { + // Set maximum magnitude + _animator.SetFloat(_animIDMotionSpeed, 1.0f); + } + // Apply the calculated speed value + _animator.SetFloat(_animIDSpeed, _speed); + + } + else if (m_WasMoving) + { + // Reset everything until next motion + m_WasMoving = false; + _animator.SetFloat(_animIDSpeed, 0.0f); + _animator.SetFloat(_animIDMotionSpeed, 0.0f); + m_UnitsPerSecond = 0.0f; + _animationBlend = 0.0f; + _speed = 0.0f; + } + m_LastPosition = transform.position; +} +``` + +**The walk through:** + +* It keeps track of the last known position. + * This is initialized on non-authority instances during post spawn. +* The delta between the last known position and current position is used to determine what our world units per second would be if we maintained that same delta for one second. + * _This could be improved, but provides a reasonably close approximation._ +* We then make sure the (world) units per second exceeds a specific threshold to avoid edge cases. +* If the units per second is larger than the threshold: + * Lerping from the last known units per second value towards the new/current units per second value. + * _This handles "accelerating" towards or away from the current value._ + * Clamp the calculated value to the maximum speed. + * Round the result and assign it to the "_speed" field (from ThirdPersonController). +* Check if the currently known speed is less than a predetermined minimum value. + * _If so, then set the speed and motion speed to zero._ +* Update the local Animator's **Speed** and **MotionSpeed** parameters. + +The end result is that (NetworkAnimator relative) the only time this particular setup would send RPCs would be if the player jumps or falls since speed dictates the idle, walking, and running animations: + +![Usage-1](../../images/networkanimator/animator-idle-walk-run.png) + +### Update a parameter's synchronize during runtime + +You might have noticed a mention of toggling the **Speed** parameter's synchronize value during runtime. Perhaps you have a lot of already existing network prefab assets that might be too time intensive to adjust or you might only want certain instances adjusted. Either case, you can update the synchronization status of a parameter on the authority instance by invoking `NetworkAnimator.EnableParameterSynchronization`. + +Below is an example script that does this when the backslash key is pressed: + +```csharp +if (Input.GetKeyDown(KeyCode.Backslash)) +{ + m_SynchronizeSpeedParameter.Value = !m_SynchronizeSpeedParameter.Value; + m_NetworkAnimator.EnableParameterSynchronization(_animIDSpeed, m_SynchronizeSpeedParameter.Value); + m_NetworkAnimator.EnableParameterSynchronization(_animIDMotionSpeed, m_SynchronizeSpeedParameter.Value); +} +``` diff --git a/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/animator-idle-walk-run.png b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/animator-idle-walk-run.png new file mode 100644 index 0000000000..f063a158af Binary files /dev/null and b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/animator-idle-walk-run.png differ diff --git a/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/animatorauthority.png b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/animatorauthority.png new file mode 100644 index 0000000000..c909b9ce3a Binary files /dev/null and b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/animatorauthority.png differ diff --git a/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/parametersnosynch.png b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/parametersnosynch.png new file mode 100644 index 0000000000..d1891a2fdd Binary files /dev/null and b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/parametersnosynch.png differ diff --git a/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/parametersynchronization.png b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/parametersynchronization.png new file mode 100644 index 0000000000..c305ddffdc Binary files /dev/null and b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/parametersynchronization.png differ diff --git a/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/usingnetworkanimator.png b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/usingnetworkanimator.png new file mode 100644 index 0000000000..94ed20cb29 Binary files /dev/null and b/com.unity.netcode.gameobjects/Documentation~/images/networkanimator/usingnetworkanimator.png differ diff --git a/com.unity.netcode.gameobjects/Editor/NetworkAnimatorParameterEntryDrawer.cs b/com.unity.netcode.gameobjects/Editor/NetworkAnimatorParameterEntryDrawer.cs new file mode 100644 index 0000000000..9060b8993c --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/NetworkAnimatorParameterEntryDrawer.cs @@ -0,0 +1,96 @@ + +#if COM_UNITY_MODULES_ANIMATION +using Unity.Netcode.Components; +using UnityEditor; +using UnityEngine; + +namespace Unity.Netcode.Editor +{ + [CustomPropertyDrawer(typeof(NetworkAnimator.AnimatorParametersListContainer))] + internal class NetworkAnimatorParameterEntryDrawer : PropertyDrawer + { + // Draw the property inside the given rect + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUI.BeginProperty(position, label, property); + + // Draw the foldout for the list + SerializedProperty items = property.FindPropertyRelative(nameof(NetworkAnimator.AnimatorParameterEntries.ParameterEntries)); + position.height = EditorGUIUtility.singleLineHeight; + property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, label); + + if (property.isExpanded) + { + // Set the indention level down + EditorGUI.indentLevel++; + for (int i = 0; i < items.arraySize; i++) + { + position.y += EditorGUIUtility.singleLineHeight + 2; + SerializedProperty element = items.GetArrayElementAtIndex(i); + var nameField = element.FindPropertyRelative(nameof(NetworkAnimator.AnimatorParameterEntry.name)); + // Draw the foldout for the item + element.isExpanded = EditorGUI.Foldout(position, element.isExpanded, nameField.stringValue); + if (!element.isExpanded) + { + continue; + } + // Draw the contents of the item + position.y += EditorGUIUtility.singleLineHeight + 2; + // Set the indention level down + EditorGUI.indentLevel++; + // Calculate rects + var nameHashRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + position.y += EditorGUIUtility.singleLineHeight + 2; + var paramRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + position.y += EditorGUIUtility.singleLineHeight + 2; + var syncRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + + // Get the three properties we want to visualize in the inspector view + var synchronizeField = element.FindPropertyRelative(nameof(NetworkAnimator.AnimatorParameterEntry.Synchronize)); + var nameHashField = element.FindPropertyRelative(nameof(NetworkAnimator.AnimatorParameterEntry.NameHash)); + var parameterTypeField = element.FindPropertyRelative(nameof(NetworkAnimator.AnimatorParameterEntry.ParameterType)); + + // Draw the read only fields + GUI.enabled = false; + EditorGUI.PropertyField(nameHashRect, nameHashField); + EditorGUI.PropertyField(paramRect, parameterTypeField); + GUI.enabled = true; + // Draw the read/write fields + EditorGUI.PropertyField(syncRect, synchronizeField); + // Set the indention level up + EditorGUI.indentLevel--; + } + // Set the indention level up + EditorGUI.indentLevel--; + } + EditorGUI.EndProperty(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + var totalHeight = EditorGUIUtility.singleLineHeight; + if (!property.isExpanded) + { + return totalHeight; + } + var singleLineWithSpace = EditorGUIUtility.singleLineHeight + 2; + SerializedProperty items = property.FindPropertyRelative(nameof(NetworkAnimator.AnimatorParameterEntries.ParameterEntries)); + + totalHeight += singleLineWithSpace; + for (int i = 0; i < items.arraySize; i++) + { + SerializedProperty element = items.GetArrayElementAtIndex(i); + if (element.isExpanded) + { + totalHeight += (singleLineWithSpace * 4); + } + else + { + totalHeight += EditorGUIUtility.singleLineHeight; + } + } + return totalHeight; + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Editor/NetworkAnimatorParameterEntryDrawer.cs.meta b/com.unity.netcode.gameobjects/Editor/NetworkAnimatorParameterEntryDrawer.cs.meta new file mode 100644 index 0000000000..f3adbfd688 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/NetworkAnimatorParameterEntryDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3ab75768bda56e545a8ebd0324eecbaf \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index e5f8d7f9f1..db2edff3df 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -1,6 +1,7 @@ #if COM_UNITY_MODULES_ANIMATION using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Netcode.Runtime; @@ -33,7 +34,7 @@ private void FlushMessages() } else { - m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams); + m_NetworkAnimator.SendClientAnimStateRpc(animationUpdate.AnimationMessage, animationUpdate.RpcParams); } } @@ -47,7 +48,7 @@ private void FlushMessages() } else { - m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams); + m_NetworkAnimator.SendClientParametersUpdateRpc(sendEntry.ParametersUpdateMessage, sendEntry.RpcParams); } } m_SendParameterUpdates.Clear(); @@ -62,17 +63,24 @@ private void FlushMessages() { if (!sendEntry.SendToServer) { - m_NetworkAnimator.SendAnimTriggerClientRpc(sendEntry.AnimationTriggerMessage, sendEntry.ClientRpcParams); + m_NetworkAnimator.SendClientAnimTriggerRpc(sendEntry.AnimationTriggerMessage, sendEntry.RpcParams); } else { - m_NetworkAnimator.SendAnimTriggerServerRpc(sendEntry.AnimationTriggerMessage); + m_NetworkAnimator.SendServerAnimTriggerRpc(sendEntry.AnimationTriggerMessage); } } } m_SendTriggerUpdates.Clear(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasAuthority() + { + var isServerAuthority = m_NetworkAnimator.IsServerAuthoritative(); + return (!isServerAuthority && m_NetworkAnimator.IsOwner) || (isServerAuthority && (m_NetworkAnimator.IsServer || m_NetworkAnimator.IsOwner)); + } + /// public void NetworkUpdate(NetworkUpdateStage updateStage) { @@ -80,25 +88,32 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) { case NetworkUpdateStage.PreUpdate: { - // Only the owner or the server send messages - if (m_NetworkAnimator.IsOwner || m_IsServer) + // NOTE: This script has an order of operations requirement where + // the authority and/or server will flush messages first, parameter updates are applied + // for all instances, and then only the authority will check for animator changes. Changing + // the order could cause timing related issues. + + var hasAuthority = HasAuthority(); + // Only the authority or the server will send messages + if (hasAuthority || m_IsServer) { // Flush any pending messages FlushMessages(); } // Everyone applies any parameters updated - for (int i = 0; i < m_ProcessParameterUpdates.Count; i++) + if (m_ProcessParameterUpdates.Count > 0) { - var parameterUpdate = m_ProcessParameterUpdates[i]; - m_NetworkAnimator.UpdateParameters(ref parameterUpdate); + for (int i = 0; i < m_ProcessParameterUpdates.Count; i++) + { + var parameterUpdate = m_ProcessParameterUpdates[i]; + m_NetworkAnimator.UpdateParameters(ref parameterUpdate); + } + m_ProcessParameterUpdates.Clear(); } - m_ProcessParameterUpdates.Clear(); - var isServerAuthority = m_NetworkAnimator.IsServerAuthoritative(); - // owners when owner authoritative or the server when server authoritative are the only instances that - // checks for Animator changes - if ((!isServerAuthority && m_NetworkAnimator.IsOwner) || (isServerAuthority && m_NetworkAnimator.IsServer)) + // Only the authority checks for Animator changes + if (hasAuthority) { m_NetworkAnimator.CheckForAnimatorChanges(); } @@ -112,7 +127,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) /// private struct AnimationUpdate { - public ClientRpcParams ClientRpcParams; + public RpcParams RpcParams; public NetworkAnimator.AnimationMessage AnimationMessage; } @@ -121,14 +136,14 @@ private struct AnimationUpdate /// /// Invoked when a server needs to forwarding an update to the animation state /// - internal void SendAnimationUpdate(NetworkAnimator.AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default) + internal void SendAnimationUpdate(NetworkAnimator.AnimationMessage animationMessage, RpcParams rpcParams = default) { - m_SendAnimationUpdates.Add(new AnimationUpdate() { ClientRpcParams = clientRpcParams, AnimationMessage = animationMessage }); + m_SendAnimationUpdates.Add(new AnimationUpdate() { RpcParams = rpcParams, AnimationMessage = animationMessage }); } private struct ParameterUpdate { - public ClientRpcParams ClientRpcParams; + public RpcParams RpcParams; public NetworkAnimator.ParametersUpdateMessage ParametersUpdateMessage; } @@ -137,9 +152,9 @@ private struct ParameterUpdate /// /// Invoked when a server needs to forwarding an update to the parameter state /// - internal void SendParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage, ClientRpcParams clientRpcParams = default) + internal void SendParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage, RpcParams rpcParams = default) { - m_SendParameterUpdates.Add(new ParameterUpdate() { ClientRpcParams = clientRpcParams, ParametersUpdateMessage = parametersUpdateMessage }); + m_SendParameterUpdates.Add(new ParameterUpdate() { RpcParams = rpcParams, ParametersUpdateMessage = parametersUpdateMessage }); } private List m_ProcessParameterUpdates = new List(); @@ -151,7 +166,7 @@ internal void ProcessParameterUpdate(NetworkAnimator.ParametersUpdateMessage par private struct TriggerUpdate { public bool SendToServer; - public ClientRpcParams ClientRpcParams; + public RpcParams RpcParams; public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage; } @@ -160,9 +175,9 @@ private struct TriggerUpdate /// /// Invoked when a server needs to forward an update to a Trigger state /// - internal void QueueTriggerUpdateToClient(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) + internal void QueueTriggerUpdateToClient(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, RpcParams clientRpcParams = default) { - m_SendTriggerUpdates.Add(new TriggerUpdate() { ClientRpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage }); + m_SendTriggerUpdates.Add(new TriggerUpdate() { RpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage }); } internal void QueueTriggerUpdateToServer(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage) @@ -190,6 +205,12 @@ internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) [HelpURL(HelpUrls.NetworkAnimator)] public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver { +#if UNITY_EDITOR + [HideInInspector] + [SerializeField] + internal bool NetworkAnimatorExpanded; +#endif + [Serializable] internal class TransitionStateinfo { @@ -202,6 +223,49 @@ internal class TransitionStateinfo public int TransitionIndex; } + /// + /// Determines if the server or client owner pushes animation state updates. + /// + public enum AuthorityModes + { + /// + /// Server pushes animator state updates. + /// + Server, + /// + /// Client owner pushes animator state updates. + /// + Owner, + } + + /// + /// Determines whether this instance will have state updates pushed by the server or the client owner. + /// + /// +#if MULTIPLAYER_SERVICES_SDK_INSTALLED + [Tooltip("Selects who has authority(sends state updates) over the instance.When the network topology is set to distributed authority, this always defaults to owner authority.If server (the default), then only server-side adjustments to the " + + " instance will be synchronized with clients. If owner (or client), then only the owner-side adjustments to the instance will be synchronized with both the server and other clients.")] +#else + [Tooltip("Selects who has authority (sends state updates) over the instance. If server (the default), then only server-side adjustments to the instance will be synchronized with clients. If owner (or client), " + + "then only the owner-side adjustments to the instance will be synchronized with both the server and other clients.")] +#endif + public AuthorityModes AuthorityMode; + + [Tooltip("The animator that this NetworkAnimator component will be synchronizing.")] + [SerializeField] private Animator m_Animator; + + /// + /// The associated with this instance. + /// + public Animator Animator + { + get { return m_Animator; } + set + { + m_Animator = value; + } + } + /// /// Used to build the destination state to transition info table /// @@ -237,8 +301,36 @@ private void BuildDestinationToTransitionInfoTable() } } + [Serializable] + internal class AnimatorParameterEntry + { +#pragma warning disable IDE1006 + [HideInInspector] + public string name; +#pragma warning restore IDE1006 + public int NameHash; + public bool Synchronize; + public AnimatorControllerParameterType ParameterType; + } + + [Serializable] + internal class AnimatorParametersListContainer + { + public List ParameterEntries = new List(); + } + + [SerializeField] + internal AnimatorParametersListContainer AnimatorParameterEntries; + + internal Dictionary AnimatorParameterEntryTable = new Dictionary(); #if UNITY_EDITOR + [HideInInspector] + [SerializeField] + internal bool AnimatorParametersExpanded; + + internal Dictionary ParameterToNameLookup = new Dictionary(); + private void ParseStateMachineStates(int layerIndex, ref AnimatorController animatorController, ref AnimatorStateMachine stateMachine) { for (int y = 0; y < stateMachine.states.Length; y++) @@ -329,6 +421,73 @@ private void BuildTransitionStateInfoList() } } + internal void ProcessParameterEntries() + { + if (!Animator) + { + if (AnimatorParameterEntries != null && AnimatorParameterEntries.ParameterEntries.Count > 0) + { + AnimatorParameterEntries.ParameterEntries.Clear(); + } + return; + } + + var parameters = Animator.parameters; + + var parametersToRemove = new List(); + ParameterToNameLookup.Clear(); + foreach (var parameter in parameters) + { + ParameterToNameLookup.Add(parameter.nameHash, parameter); + } + + // Rebuild the parameter entry table for the inspector view + AnimatorParameterEntryTable.Clear(); + foreach (var parameterEntry in AnimatorParameterEntries.ParameterEntries) + { + // Check for removed parameters. + if (!ParameterToNameLookup.ContainsKey(parameterEntry.NameHash)) + { + parametersToRemove.Add(parameterEntry); + // Skip this removed entry + continue; + } + + // Build the list of known parameters + if (!AnimatorParameterEntryTable.ContainsKey(parameterEntry.NameHash)) + { + AnimatorParameterEntryTable.Add(parameterEntry.NameHash, parameterEntry); + } + + var parameter = ParameterToNameLookup[parameterEntry.NameHash]; + parameterEntry.name = parameter.name; + parameterEntry.ParameterType = parameter.type; + } + + // Update for removed parameters + foreach (var parameterEntry in parametersToRemove) + { + AnimatorParameterEntries.ParameterEntries.Remove(parameterEntry); + } + + // Update any newly added parameters + foreach (var parameterLookUp in ParameterToNameLookup) + { + if (!AnimatorParameterEntryTable.ContainsKey(parameterLookUp.Value.nameHash)) + { + var animatorParameterEntry = new AnimatorParameterEntry() + { + name = parameterLookUp.Value.name, + NameHash = parameterLookUp.Value.nameHash, + ParameterType = parameterLookUp.Value.type, + Synchronize = true, + }; + AnimatorParameterEntries.ParameterEntries.Add(animatorParameterEntry); + AnimatorParameterEntryTable.Add(parameterLookUp.Value.nameHash, animatorParameterEntry); + } + } + } + /// /// In-Editor Only /// Virtual OnValidate method for custom derived NetworkAnimator classes. @@ -336,6 +495,7 @@ private void BuildTransitionStateInfoList() protected virtual void OnValidate() { BuildTransitionStateInfoList(); + ProcessParameterEntries(); } #endif @@ -489,24 +649,19 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } } - [SerializeField] private Animator m_Animator; - - public Animator Animator - { - get { return m_Animator; } - set - { - m_Animator = value; - } - } - - internal bool IsServerAuthoritative() + /// + /// Determines whether the is or based on the field. + /// Optionally, you can still derive from and override the method. + /// + /// or + public bool IsServerAuthoritative() { return OnIsServerAuthoritative(); } /// - /// Override this method and return false to switch to owner authoritative mode. + /// Override this method and return false to switch to owner authoritative mode.
+ /// Alternately, you can update the field within the inspector view to select the authority mode. ///
/// /// When using a distributed authority network topology, this will default to @@ -514,12 +669,11 @@ internal bool IsServerAuthoritative() /// protected virtual bool OnIsServerAuthoritative() { - if (!m_LocalNetworkManager) + if (DistributedAuthorityMode) { - return true; + return false; } - - return !DistributedAuthorityMode; + return AuthorityMode == AuthorityModes.Server; } private int[] m_TransitionHash; @@ -527,8 +681,8 @@ protected virtual bool OnIsServerAuthoritative() private float[] m_LayerWeights; private static byte[] s_EmptyArray = new byte[] { }; private List m_ParametersToUpdate; - private List m_ClientSendList; - private ClientRpcParams m_ClientRpcParams; + private RpcParams m_RpcParams; + private RpcTargetGroup m_TargetGroup; private AnimationMessage m_AnimationMessage; private NetworkAnimatorStateChangeHandler m_NetworkAnimatorStateChangeHandler; @@ -539,6 +693,7 @@ protected virtual bool OnIsServerAuthoritative() private unsafe struct AnimatorParamCache { + internal bool Exclude; internal int Hash; internal int Type; internal fixed byte Value[4]; // this is a max size of 4 bytes @@ -583,6 +738,8 @@ public override void OnDestroy() { SpawnCleanup(); + m_TargetGroup?.Dispose(); + if (m_CachedAnimatorParameters != null && m_CachedAnimatorParameters.IsCreated) { m_CachedAnimatorParameters.Dispose(); @@ -605,6 +762,11 @@ protected virtual void Awake() return; } + foreach (var parameterEntry in AnimatorParameterEntries.ParameterEntries) + { + AnimatorParameterEntryTable.TryAdd(parameterEntry.NameHash, parameterEntry); + } + int layers = m_Animator.layerCount; // Initializing the below arrays for everyone handles an issue // when running in owner authoritative mode and the owner changes. @@ -648,11 +810,17 @@ protected virtual void Awake() for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; + var synchronizeParameter = true; + if (AnimatorParameterEntryTable.ContainsKey(parameter.nameHash)) + { + synchronizeParameter = AnimatorParameterEntryTable[parameter.nameHash].Synchronize; + } var cacheParam = new AnimatorParamCache { Type = UnsafeUtility.EnumToInt(parameter.type), - Hash = parameter.nameHash + Hash = parameter.nameHash, + Exclude = !synchronizeParameter }; unsafe @@ -736,12 +904,12 @@ public override void OnNetworkSpawn() NetworkLog.LogWarningServer($"[{gameObject.name}][{nameof(NetworkAnimator)}] {nameof(Animator)} is not assigned! Animation synchronization will not work for this instance!"); } - m_ClientSendList = new List(128); - m_ClientRpcParams = new ClientRpcParams + m_TargetGroup = RpcTarget.Group(new List(128), RpcTargetUse.Persistent) as RpcTargetGroup; + m_RpcParams = new RpcParams() { - Send = new ClientRpcSendParams + Send = new RpcSendParams() { - TargetClientIds = m_ClientSendList + Target = m_TargetGroup } }; @@ -766,6 +934,10 @@ private void WriteSynchronizationData(ref BufferSerializer serializer) whe m_ParametersToUpdate.Clear(); for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) { + if (m_CachedAnimatorParameters[i].Exclude) + { + continue; + } m_ParametersToUpdate.Add(i); } // Write, apply, and serialize @@ -1009,27 +1181,27 @@ internal void CheckForAnimatorChanges() else if (!IsServer && IsOwner) { - SendAnimStateServerRpc(m_AnimationMessage); + SendServerAnimStateRpc(m_AnimationMessage); } else { // Just notify all remote clients and not the local server - m_ClientSendList.Clear(); + m_TargetGroup.Clear(); foreach (var clientId in m_LocalNetworkManager.ConnectionManager.ConnectedClientIds) { if (clientId == m_LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId)) { continue; } - m_ClientSendList.Add(clientId); + m_TargetGroup.Add(clientId); } - m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; - SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams); + m_RpcParams.Send.Target = m_TargetGroup; + SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams); } } } - private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, bool sendDirect = false) + private void SendParametersUpdate(RpcParams rpcParams = default, bool sendDirect = false) { WriteParameters(ref m_ParameterWriter); @@ -1052,17 +1224,17 @@ private void SendParametersUpdate(ClientRpcParams clientRpcParams = default, boo { if (!IsServer) { - SendParametersUpdateServerRpc(parametersMessage); + SendServerParametersUpdateRpc(parametersMessage); } else { if (sendDirect) { - SendParametersUpdateClientRpc(parametersMessage, clientRpcParams); + SendClientParametersUpdateRpc(parametersMessage, rpcParams); } else { - m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, clientRpcParams); + m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersMessage, rpcParams); } } } @@ -1093,6 +1265,11 @@ private unsafe bool CheckParametersChanged() { ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), i); + if (cacheValue.Exclude) + { + continue; + } + // If a parameter gets controlled by a curve during runtime after initialization of NetworkAnimator // then ignore changes to this parameter. We are not removing the parameter in the event that // it no longer is controlled by a curve. @@ -1149,6 +1326,13 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) foreach (var parameterIndex in m_ParametersToUpdate) { ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), parameterIndex); + + if (cacheValue.Exclude) + { + Debug.LogWarning($"Parameter hash:{cacheValue.Hash} should be excluded but is in the parameters to update list when writing parameter values!"); + continue; + } + var hash = cacheValue.Hash; BytePacker.WriteValuePacked(writer, (uint)parameterIndex); if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt) @@ -1317,8 +1501,8 @@ internal void UpdateAnimationState(AnimationState animationState) /// Server-side animator parameter update request /// The server sets its local parameters and then forwards the message to the remaining clients ///
- [ServerRpc] - private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parametersUpdate, ServerRpcParams serverRpcParams = default) + [Rpc(SendTo.Server, AllowTargetOverride = true, InvokePermission = RpcInvokePermission.Owner)] + private unsafe void SendServerParametersUpdateRpc(ParametersUpdateMessage parametersUpdate, RpcParams rpcParams = default) { if (IsServerAuthoritative()) { @@ -1326,7 +1510,7 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame } else { - if (serverRpcParams.Receive.SenderClientId != OwnerClientId) + if (rpcParams.Receive.SenderClientId != OwnerClientId) { return; } @@ -1337,26 +1521,26 @@ private unsafe void SendParametersUpdateServerRpc(ParametersUpdateMessage parame return; } - m_ClientSendList.Clear(); + m_TargetGroup.Clear(); foreach (var clientId in connectedClientIds) { - if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) + if (clientId == rpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) { continue; } - m_ClientSendList.Add(clientId); + m_TargetGroup.Add(clientId); } - m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; - m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams); + m_RpcParams.Send.Target = m_TargetGroup; + m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_RpcParams); } } /// /// Distributed Authority: Updates the client's animator's parameters /// - [Rpc(SendTo.NotAuthority)] - internal void SendParametersUpdateRpc(ParametersUpdateMessage parametersUpdate) + [Rpc(SendTo.NotAuthority, AllowTargetOverride = true, InvokePermission = RpcInvokePermission.Owner)] + internal void SendParametersUpdateRpc(ParametersUpdateMessage parametersUpdate, RpcParams rpcParams = default) { m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate); } @@ -1364,11 +1548,11 @@ internal void SendParametersUpdateRpc(ParametersUpdateMessage parametersUpdate) /// /// Client-Server: Updates the client's animator's parameters /// - [ClientRpc] - internal void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUpdate, ClientRpcParams clientRpcParams = default) + [Rpc(SendTo.NotMe, AllowTargetOverride = true)] + internal void SendClientParametersUpdateRpc(ParametersUpdateMessage parametersUpdate, RpcParams rpcParams = default) { var isServerAuthoritative = IsServerAuthoritative(); - if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) + if ((!isServerAuthoritative && !IsOwner) || (isServerAuthoritative && !IsServer)) { m_NetworkAnimatorStateChangeHandler.ProcessParameterUpdate(parametersUpdate); } @@ -1378,8 +1562,8 @@ internal void SendParametersUpdateClientRpc(ParametersUpdateMessage parametersUp /// Server-side animation state update request /// The server sets its local state and then forwards the message to the remaining clients /// - [ServerRpc] - private void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpcParams serverRpcParams = default) + [Rpc(SendTo.Server, AllowTargetOverride = true)] + private void SendServerAnimStateRpc(AnimationMessage animationMessage, RpcParams rcParams = default) { if (IsServerAuthoritative()) { @@ -1387,7 +1571,7 @@ private void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpc } else { - if (serverRpcParams.Receive.SenderClientId != OwnerClientId) + if (rcParams.Receive.SenderClientId != OwnerClientId) { return; } @@ -1403,25 +1587,26 @@ private void SendAnimStateServerRpc(AnimationMessage animationMessage, ServerRpc return; } - m_ClientSendList.Clear(); + m_TargetGroup.Clear(); + foreach (var clientId in connectedClientIds) { - if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) + if (clientId == rcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) { continue; } - m_ClientSendList.Add(clientId); + m_TargetGroup.Add(clientId); } - m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList; - m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams); + m_RpcParams.Send.Target = m_TargetGroup; + m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_RpcParams); } } /// /// Client-Server: Internally-called RPC client-side receiving function to update animation states /// - [ClientRpc] - internal void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default) + [Rpc(SendTo.NotServer, AllowTargetOverride = true)] + internal void SendClientAnimStateRpc(AnimationMessage animationMessage, RpcParams rpcParams = default) { ProcessAnimStates(animationMessage); } @@ -1429,12 +1614,16 @@ internal void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRp /// /// Distributed Authority: Internally-called RPC non-authority receiving function to update animation states /// - [Rpc(SendTo.NotAuthority)] - internal void SendAnimStateRpc(AnimationMessage animationMessage) + [Rpc(SendTo.NotAuthority, AllowTargetOverride = true, InvokePermission = RpcInvokePermission.Owner)] + internal void SendAnimStateRpc(AnimationMessage animationMessage, RpcParams rpcParams = default) { ProcessAnimStates(animationMessage); } + /// + /// Process incoming . + /// + /// The message to process. private void ProcessAnimStates(AnimationMessage animationMessage) { if (HasAuthority) @@ -1454,17 +1643,15 @@ private void ProcessAnimStates(AnimationMessage animationMessage) } } - - /// /// Server-side trigger state update request /// The server sets its local state and then forwards the message to the remaining clients /// - [ServerRpc] - internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default) + [Rpc(SendTo.Server, AllowTargetOverride = true)] + internal void SendServerAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage, RpcParams rpcParams = default) { // Ignore if a non-owner sent this. - if (serverRpcParams.Receive.SenderClientId != OwnerClientId) + if (rpcParams.Receive.SenderClientId != OwnerClientId) { if (m_LocalNetworkManager.LogLevel == LogLevel.Developer) { @@ -1478,23 +1665,22 @@ internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerM var connectedClientIds = m_LocalNetworkManager.ConnectionManager.ConnectedClientIds; - m_ClientSendList.Clear(); + m_TargetGroup.Clear(); foreach (var clientId in connectedClientIds) { if (clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId)) { continue; } - m_ClientSendList.Add(clientId); + m_TargetGroup.Add(clientId); } if (IsServerAuthoritative()) { - m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_RpcParams); } else if (connectedClientIds.Count > (IsHost ? 2 : 1)) { - m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId); - m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams); + m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_RpcParams); } } @@ -1507,12 +1693,12 @@ private void InternalSetTrigger(int hash, bool isSet = true) } /// - /// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the server wants to forward - /// a trigger to a client + /// Distributed Authority: Internally-called RPC client receiving function to update a trigger when the authority wants + /// to forward a trigger to a client /// /// the payload containing the trigger data to apply - [Rpc(SendTo.NotAuthority)] - internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage) + [Rpc(SendTo.NotAuthority, AllowTargetOverride = true, InvokePermission = RpcInvokePermission.Owner)] + internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage, RpcParams rpcParams = default) { InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); } @@ -1523,8 +1709,8 @@ internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage /// /// the payload containing the trigger data to apply /// unused - [ClientRpc] - internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) + [Rpc(SendTo.NotServer, AllowTargetOverride = true)] + internal void SendClientAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage, RpcParams rpcParams = default) { InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); } @@ -1567,10 +1753,7 @@ public void SetTrigger(int hash, bool setTrigger = true) { /// as to why we queue m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage); - if (!IsHost) - { - InternalSetTrigger(hash, setTrigger); - } + InternalSetTrigger(hash, setTrigger); } else { @@ -1599,6 +1782,39 @@ public void ResetTrigger(int hash) { SetTrigger(hash, false); } + + /// + /// Allows for the enabling or disabling the synchronization of a specific parameter. + /// + /// The name of the parameter. + /// Whether to enable or disable the synchronization of the parameter. + public void EnableParameterSynchronization(string parameterName, bool isEnabled) + { + EnableParameterSynchronization(Animator.StringToHash(parameterName), isEnabled); + } + + /// + /// Allows for the enabling or disabling the synchronization of a specific parameter. + /// + /// The hash value (from using ) of the parameter name. + /// Whether to enable or disable the synchronization of the parameter. + public void EnableParameterSynchronization(int parameterNameHash, bool isEnabled) + { + var serverAuthoritative = OnIsServerAuthoritative(); + if (!IsSpawned || serverAuthoritative && IsServer || !serverAuthoritative && IsOwner) + { + for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) + { + var cachedParameter = m_CachedAnimatorParameters[i]; + if (cachedParameter.Hash == parameterNameHash) + { + cachedParameter.Exclude = !isEnabled; + m_CachedAnimatorParameters[i] = cachedParameter; + break; + } + } + } + } } } // COM_UNITY_MODULES_ANIMATION diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs index 5efbb54fa4..4ed476874f 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using Unity.Netcode; using Unity.Netcode.Components; @@ -12,19 +11,11 @@ public class AnimatedCubeController : NetworkBehaviour private Animator m_Animator; private bool m_Rotate; private NetworkAnimator m_NetworkAnimator; - private bool m_IsServerAuthoritative = true; + private bool m_IsServerAuthoritative => m_NetworkAnimator ? m_NetworkAnimator.IsServerAuthoritative() : true; private void DetermineNetworkAnimatorComponentType() { m_NetworkAnimator = GetComponent(); - if (m_NetworkAnimator != null) - { - m_IsServerAuthoritative = m_NetworkAnimator.GetType() != typeof(OwnerNetworkAnimator); - } - else - { - throw new Exception($"{nameof(AnimatedCubeController)} requires that it is paired with either a {nameof(NetworkAnimator)} or {nameof(OwnerNetworkAnimator)}. Neither of the two components were found!"); - } } public override void OnNetworkSpawn() @@ -136,8 +127,8 @@ private IEnumerator TestAnimatorRoutine() counter = 0.0f; while (counter < 100) { - m_Animator.SetFloat("TestFloat", UnityEngine.Random.Range(0.0f, 100.0f)); - m_Animator.SetInteger("TestInt", UnityEngine.Random.Range(0, 100)); + m_Animator.SetFloat("TestFloat", Random.Range(0.0f, 100.0f)); + m_Animator.SetInteger("TestInt", Random.Range(0, 100)); counter++; yield return waitForSeconds; } diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller index 3434711a59..edc31e90ab 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/CubeAnimatorController.controller @@ -761,6 +761,12 @@ AnimatorController: m_DefaultInt: 0 m_DefaultBool: 0 m_Controller: {fileID: 9100000} + - m_Name: ExcludeFromSync + m_Type: 1 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} m_AnimatorLayers: - serializedVersion: 5 m_Name: Base Layer diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity index efd820815d..568acb89e8 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorServerOwnerTest.unity @@ -13,7 +13,7 @@ OcclusionCullingSettings: --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 - serializedVersion: 9 + serializedVersion: 10 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 @@ -38,13 +38,12 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 - m_GIWorkflowMode: 1 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 m_GISettings: serializedVersion: 2 m_BounceScale: 1 @@ -67,9 +66,6 @@ LightmapSettings: m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 - m_FinalGather: 0 - m_FinalGatherFiltering: 1 - m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 1 @@ -104,7 +100,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +113,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -153,21 +149,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} m_Name: m_EditorClassIdentifier: - RunInBackground: 1 - LogLevel: 1 + NetworkManagerExpanded: 0 NetworkConfig: ProtocolVersion: 0 NetworkTransport: {fileID: 57527694} PlayerPrefab: {fileID: 3214090169675393154, guid: 978fc8cd63a1294438ebf3b352814970, type: 3} - NetworkPrefabs: - - Override: 0 - Prefab: {fileID: 3214090169675393154, guid: 978fc8cd63a1294438ebf3b352814970, - type: 3} - SourcePrefabToOverride: {fileID: 442217489085244684, guid: 5eca8a21314fe4278ba2571c289a9773, - type: 3} - SourceHashToOverride: 0 - OverridingTargetPrefab: {fileID: 0} + Prefabs: + NetworkPrefabsLists: [] TickRate: 30 ClientConnectionBufferTimeout: 10 ConnectionApproval: 0 @@ -181,8 +170,22 @@ MonoBehaviour: NetworkIdRecycleDelay: 120 RpcHashSize: 0 LoadSceneTimeOut: 120 - SpawnTimeout: 1 + SpawnTimeout: 10 EnableNetworkLogs: 1 + NetworkTopology: 0 + UseCMBService: 0 + AutoSpawnPlayerPrefabClientSide: 1 + NetworkProfilingMetrics: 1 + OldPrefabList: + - Override: 0 + Prefab: {fileID: 3214090169675393154, guid: 978fc8cd63a1294438ebf3b352814970, + type: 3} + SourcePrefabToOverride: {fileID: 442217489085244684, guid: 5eca8a21314fe4278ba2571c289a9773, + type: 3} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + RunInBackground: 1 + LogLevel: 1 --- !u!4 &57527693 Transform: m_ObjectHideFlags: 0 @@ -190,13 +193,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 57527690} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: -4.4, y: 0, z: 3.53} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &57527694 MonoBehaviour: @@ -211,6 +214,8 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_ProtocolType: 0 + m_UseWebSockets: 0 + m_UseEncryption: 0 m_MaxPacketQueueSize: 128 m_MaxPayloadSize: 80000 m_HeartbeatTimeoutMS: 500 @@ -220,7 +225,7 @@ MonoBehaviour: ConnectionData: Address: 127.0.0.1 Port: 7777 - ServerListenAddress: + ServerListenAddress: 127.0.0.1 DebugSimulator: PacketDelayMS: 0 PacketJitterMS: 0 @@ -234,8 +239,8 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 84683368} - - component: {fileID: 84683367} - component: {fileID: 84683366} + - component: {fileID: 84683367} m_Layer: 0 m_Name: Stats m_TagString: Untagged @@ -256,9 +261,18 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: GlobalObjectIdHash: 3133660421 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 0 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!114 &84683367 MonoBehaviour: m_ObjectHideFlags: 0 @@ -271,6 +285,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: cb5f3e55f5dd247129d8a4979b80ebbb, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ClientServerToggle: {fileID: 0} m_TrackSceneEvents: 0 --- !u!4 &84683368 @@ -280,13 +295,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 84683365} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 318.45444, y: 110.697815, z: 216.79077} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &146283178 GameObject: @@ -319,7 +334,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 343841036} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} @@ -444,7 +458,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 1 m_TargetDisplay: 0 @@ -462,7 +478,6 @@ RectTransform: m_Children: - {fileID: 146283179} m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -529,13 +544,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 809184351} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &825254415 GameObject: @@ -562,9 +577,8 @@ Light: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 825254415} m_Enabled: 1 - serializedVersion: 10 + serializedVersion: 11 m_Type: 1 - m_Shape: 0 m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} m_Intensity: 1 m_Range: 10 @@ -614,8 +628,12 @@ Light: m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 m_ShadowRadius: 0 m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 --- !u!4 &825254417 Transform: m_ObjectHideFlags: 0 @@ -623,13 +641,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 825254415} + serializedVersion: 2 m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} --- !u!224 &1537133403 stripped RectTransform: @@ -677,9 +695,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -713,13 +739,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1543554891} + serializedVersion: 2 m_LocalRotation: {x: 0.28182787, y: -0, z: -0, w: 0.959465} m_LocalPosition: {x: 5.7, y: 90, z: -136} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 32.739, y: 0, z: 0} --- !u!1 &1740491936 GameObject: @@ -797,7 +823,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -816,7 +844,6 @@ RectTransform: - {fileID: 1820369379} - {fileID: 1537133403} m_Father: {fileID: 0} - m_RootOrder: 5 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -828,6 +855,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 1740491940} m_Modifications: - target: {fileID: 2848221156307247792, guid: 3200770c16e3b2b4ebe7f604154faac7, @@ -962,6 +990,9 @@ PrefabInstance: objectReference: {fileID: 11400000, guid: 4a3cdce12e998384f8aca207b5a2c700, type: 2} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 3200770c16e3b2b4ebe7f604154faac7, type: 3} --- !u!224 &1820369379 stripped RectTransform: @@ -974,6 +1005,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 1740491940} m_Modifications: - target: {fileID: 6633621479308595792, guid: d725b5588e1b956458798319e6541d84, @@ -1087,4 +1119,18 @@ PrefabInstance: value: ConnectionModeButtons objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: d725b5588e1b956458798319e6541d84, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1543554894} + - {fileID: 825254417} + - {fileID: 57527693} + - {fileID: 343841036} + - {fileID: 809184354} + - {fileID: 1740491940} + - {fileID: 84683368} diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab index 293a5c28f9..ecb2989dd7 100644 --- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab +++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorTestPrefab.prefab @@ -26,6 +26,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 344968812738254812} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} @@ -34,7 +35,6 @@ Transform: - {fileID: 6945271965867870025} - {fileID: 7346803634269932765} m_Father: {fileID: 3078684837582866037} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &1862129067553351562 MeshFilter: @@ -61,6 +61,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -94,9 +97,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 344968812738254812} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1.0000002, y: 1, z: 1.0000002} m_Center: {x: 0.0000019073493, y: 0.000000007450581, z: -0.000001907349} --- !u!1 &2187136729444773472 @@ -125,13 +136,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2187136729444773472} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0.505} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2505406658094773853} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &3821723076754617548 MeshFilter: @@ -158,6 +169,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -191,9 +205,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2187136729444773472} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &2963963144167575062 @@ -227,7 +249,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 3214090169133903717} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -287,7 +308,7 @@ GameObject: - component: {fileID: 3078684837582866037} - component: {fileID: 3078684837575715027} - component: {fileID: 5295167409644547614} - - component: {fileID: 3632991713004262889} + - component: {fileID: 6991069935266812612} m_Layer: 0 m_Name: NetworkAnimatedCube-Owner m_TagString: Untagged @@ -302,6 +323,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 3078684837583100501} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 15, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -309,11 +331,10 @@ Transform: m_Children: - {fileID: 7635925556881204871} m_Father: {fileID: 3214090169675393152} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!95 &3078684837575715027 Animator: - serializedVersion: 4 + serializedVersion: 7 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -327,10 +348,12 @@ Animator: m_ApplyRootMotion: 0 m_LinearVelocityBlending: 0 m_StabilizeFeet: 0 + m_AnimatePhysics: 0 m_WarningMessage: m_HasTransformHierarchy: 1 m_AllowConstantClipSamplingOptimization: 1 - m_KeepAnimatorControllerStateOnDisable: 0 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 --- !u!114 &5295167409644547614 MonoBehaviour: m_ObjectHideFlags: 0 @@ -343,8 +366,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b322be05414199349ae9d1aea28228a7, type: 3} m_Name: m_EditorClassIdentifier: - TestIterations: 10 ---- !u!114 &3632991713004262889 + ShowTopMostFoldoutHeaderGroup: 1 +--- !u!114 &6991069935266812612 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -353,29 +376,103 @@ MonoBehaviour: m_GameObject: {fileID: 3078684837583100501} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f6a3556fb5f3bee4e8a0fb88acff87ff, type: 3} + m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 1 + m_Animator: {fileID: 3078684837575715027} TransitionStateInfoList: - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -570305638 TransitionDuration: 0.30227518 TriggerNameHash: 1033918907 TransitionIndex: 1 - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -1509639022 TransitionDuration: 0.25 TriggerNameHash: 1033918907 TransitionIndex: 2 - - Layer: 1 + - IsCrossFadeExit: 0 + Layer: 1 OriginatingState: 2081823275 DestinationState: -623385122 TransitionDuration: 2.2832582 TriggerNameHash: -623385122 TransitionIndex: 0 - m_Animator: {fileID: 3078684837575715027} + - IsCrossFadeExit: 0 + Layer: 3 + OriginatingState: 2081823275 + DestinationState: -1829531531 + TransitionDuration: 0.25 + TriggerNameHash: 1080829965 + TransitionIndex: 0 + - IsCrossFadeExit: 0 + Layer: 3 + OriginatingState: 2081823275 + DestinationState: 705160537 + TransitionDuration: 0.25 + TriggerNameHash: 1080829965 + TransitionIndex: 1 + - IsCrossFadeExit: 0 + Layer: 3 + OriginatingState: 2081823275 + DestinationState: 801385362 + TransitionDuration: 0.25 + TriggerNameHash: 1080829965 + TransitionIndex: 2 + AnimatorParameterEntries: + ParameterEntries: + - name: Rotate + NameHash: 807753530 + Synchronize: 1 + ParameterType: 4 + - name: Pulse + NameHash: -623385122 + Synchronize: 1 + ParameterType: 9 + - name: TestFloat + NameHash: -758535706 + Synchronize: 1 + ParameterType: 1 + - name: TestInt + NameHash: -1682086748 + Synchronize: 1 + ParameterType: 3 + - name: TestBool + NameHash: 953368263 + Synchronize: 1 + ParameterType: 4 + - name: TestTrigger + NameHash: 1033918907 + Synchronize: 1 + ParameterType: 9 + - name: LateJoinTest + NameHash: -1545343146 + Synchronize: 1 + ParameterType: 4 + - name: Attack + NameHash: 1080829965 + Synchronize: 1 + ParameterType: 9 + - name: WeaponType + NameHash: -1936256502 + Synchronize: 1 + ParameterType: 3 + - name: Weapon + NameHash: 1855955664 + Synchronize: 1 + ParameterType: 3 + - name: ExcludeFromSync + NameHash: -1993481368 + Synchronize: 0 + ParameterType: 1 + AnimatorParametersExpanded: 0 --- !u!1 &3214090169133903718 GameObject: m_ObjectHideFlags: 0 @@ -412,7 +509,6 @@ RectTransform: - {fileID: 8890957386270744200} - {fileID: 4304100640823132132} m_Father: {fileID: 3214090169675393152} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -436,7 +532,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -506,6 +604,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 3214090169675393154} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -515,7 +614,6 @@ Transform: - {fileID: 3078684837582866037} - {fileID: 3604323723684300772} m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &3214090169675393153 MonoBehaviour: @@ -529,10 +627,19 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 951099334 + GlobalObjectIdHash: 3334901199 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!114 &5628721071472145353 MonoBehaviour: m_ObjectHideFlags: 0 @@ -545,6 +652,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 65054225619821f498f40221095d527e, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 m_ServerAuthText: {fileID: 554345019093650043} m_OwnerAuthText: {fileID: 6408045601871479648} --- !u!114 &2564457647300903827 @@ -559,6 +667,21 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkTransformExpanded: 0 + AutoOwnerAuthorityTickOffset: 1 + PositionInterpolationType: 0 + RotationInterpolationType: 0 + ScaleInterpolationType: 0 + PositionLerpSmoothing: 1 + PositionMaxInterpolationTime: 0.1 + RotationLerpSmoothing: 1 + RotationMaxInterpolationTime: 0.1 + ScaleLerpSmoothing: 1 + ScaleMaxInterpolationTime: 0.1 + AuthorityMode: 0 + TickSyncChildren: 0 + UseUnreliableDeltas: 0 SyncPositionX: 1 SyncPositionY: 1 SyncPositionZ: 1 @@ -571,8 +694,13 @@ MonoBehaviour: PositionThreshold: 0.001 RotAngleThreshold: 0.01 ScaleThreshold: 0.01 + UseQuaternionSynchronization: 0 + UseQuaternionCompression: 0 + UseHalfFloatPrecision: 0 InLocalSpace: 0 + SwitchTransformSpaceWhenParented: 0 Interpolate: 0 + SlerpPosition: 0 --- !u!1 &3953046479900177945 GameObject: m_ObjectHideFlags: 0 @@ -599,6 +727,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 3953046479900177945} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: -15, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -606,11 +735,10 @@ Transform: m_Children: - {fileID: 2505406658094773853} m_Father: {fileID: 3214090169675393152} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!95 &6047057957339659638 Animator: - serializedVersion: 4 + serializedVersion: 7 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -624,10 +752,12 @@ Animator: m_ApplyRootMotion: 0 m_LinearVelocityBlending: 0 m_StabilizeFeet: 0 + m_AnimatePhysics: 0 m_WarningMessage: m_HasTransformHierarchy: 1 m_AllowConstantClipSamplingOptimization: 1 - m_KeepAnimatorControllerStateOnDisable: 0 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 --- !u!114 &8104648428997222210 MonoBehaviour: m_ObjectHideFlags: 0 @@ -640,7 +770,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: b322be05414199349ae9d1aea28228a7, type: 3} m_Name: m_EditorClassIdentifier: - TestIterations: 20 + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &1245349943772079751 MonoBehaviour: m_ObjectHideFlags: 0 @@ -653,26 +783,100 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 0 + m_Animator: {fileID: 6047057957339659638} TransitionStateInfoList: - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -570305638 TransitionDuration: 0.30227518 TriggerNameHash: 1033918907 TransitionIndex: 1 - - Layer: 0 + - IsCrossFadeExit: 0 + Layer: 0 OriginatingState: 2081823275 DestinationState: -1509639022 TransitionDuration: 0.25 TriggerNameHash: 1033918907 TransitionIndex: 2 - - Layer: 1 + - IsCrossFadeExit: 0 + Layer: 1 OriginatingState: 2081823275 DestinationState: -623385122 TransitionDuration: 2.2832582 TriggerNameHash: -623385122 TransitionIndex: 0 - m_Animator: {fileID: 6047057957339659638} + - IsCrossFadeExit: 0 + Layer: 3 + OriginatingState: 2081823275 + DestinationState: -1829531531 + TransitionDuration: 0.25 + TriggerNameHash: 1080829965 + TransitionIndex: 0 + - IsCrossFadeExit: 0 + Layer: 3 + OriginatingState: 2081823275 + DestinationState: 705160537 + TransitionDuration: 0.25 + TriggerNameHash: 1080829965 + TransitionIndex: 1 + - IsCrossFadeExit: 0 + Layer: 3 + OriginatingState: 2081823275 + DestinationState: 801385362 + TransitionDuration: 0.25 + TriggerNameHash: 1080829965 + TransitionIndex: 2 + AnimatorParameterEntries: + ParameterEntries: + - name: Rotate + NameHash: 807753530 + Synchronize: 1 + ParameterType: 4 + - name: Pulse + NameHash: -623385122 + Synchronize: 1 + ParameterType: 9 + - name: TestFloat + NameHash: -758535706 + Synchronize: 1 + ParameterType: 1 + - name: TestInt + NameHash: -1682086748 + Synchronize: 1 + ParameterType: 3 + - name: TestBool + NameHash: 953368263 + Synchronize: 1 + ParameterType: 4 + - name: TestTrigger + NameHash: 1033918907 + Synchronize: 1 + ParameterType: 9 + - name: LateJoinTest + NameHash: -1545343146 + Synchronize: 1 + ParameterType: 4 + - name: Attack + NameHash: 1080829965 + Synchronize: 1 + ParameterType: 9 + - name: WeaponType + NameHash: -1936256502 + Synchronize: 1 + ParameterType: 3 + - name: Weapon + NameHash: 1855955664 + Synchronize: 1 + ParameterType: 3 + - name: ExcludeFromSync + NameHash: -1993481368 + Synchronize: 0 + ParameterType: 1 + AnimatorParametersExpanded: 1 --- !u!1 &4033769488171516769 GameObject: m_ObjectHideFlags: 0 @@ -699,13 +903,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4033769488171516769} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -0.514} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 7635925556881204871} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &6303887162499072930 MeshFilter: @@ -732,6 +936,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -765,9 +972,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4033769488171516769} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &4988239203845480744 @@ -796,13 +1011,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4988239203845480744} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0.505} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 7635925556881204871} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &6969857922933789850 MeshFilter: @@ -829,6 +1044,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -862,9 +1080,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4988239203845480744} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &8225275286638841897 @@ -898,7 +1124,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 3214090169133903717} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -978,7 +1203,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 3214090169133903717} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1053,6 +1277,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8656650671630400802} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 10, y: 10, z: 10} @@ -1061,7 +1286,6 @@ Transform: - {fileID: 4145180303732600870} - {fileID: 1514700077442365156} m_Father: {fileID: 3604323723684300772} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &4441112472850329336 MeshFilter: @@ -1088,6 +1312,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1121,9 +1348,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8656650671630400802} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1.0000002, y: 1, z: 1.0000002} m_Center: {x: 0.0000019073493, y: 0.000000007450581, z: -0.000001907349} --- !u!1 &8778627084610397836 @@ -1152,13 +1387,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8778627084610397836} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -0.514} m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2505406658094773853} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &5462393744046406360 MeshFilter: @@ -1185,6 +1420,9 @@ MeshRenderer: m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1218,9 +1456,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8778627084610397836} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &9079811648883086489 @@ -1254,7 +1500,6 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 3214090169133903717} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} diff --git a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs index e8130c68b4..6f5b60b227 100644 --- a/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs +++ b/testproject/Assets/Tests/Runtime/Animation/AnimatorTestHelper.cs @@ -108,6 +108,16 @@ public void UpdateParameters(ParameterValues parameterValues) m_Animator.SetBool("TestBool", parameterValues.BoolValue); } + public void UpdateExcludedParameter(float value) + { + m_Animator.SetFloat("ExcludeFromSync", value); + } + + public float GetExcludedParameter() + { + return m_Animator.GetFloat("ExcludeFromSync"); + } + public bool GetCurrentTriggerState() { return m_Animator.GetBool("TestTrigger"); diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index df4124e5eb..474c9dabbe 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -157,6 +157,39 @@ private bool ParameterValuesMatch(OwnerShipMode ownerShipMode, AuthoritativeMode return true; } + private bool ExcludedParameterValuesDoNotMatch() + { + var objectToUpdate = AnimatorTestHelper.ServerSideInstance; + var excludedParameterValue = objectToUpdate.GetExcludedParameter(); + if (m_AuthoritativeMode == AuthoritativeMode.OwnerAuth) + { + objectToUpdate = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + excludedParameterValue = objectToUpdate.GetExcludedParameter(); + if (m_OwnerShipMode == OwnerShipMode.ClientOwner) + { + if (excludedParameterValue == AnimatorTestHelper.ServerSideInstance.GetExcludedParameter()) + { + return false; + } + } + } + + foreach (var animatorTestHelper in AnimatorTestHelper.ClientSideInstances) + { + if (objectToUpdate == animatorTestHelper.Value) + { + continue; + } + var clientExcludedParameter = animatorTestHelper.Value.GetExcludedParameter(); + if (clientExcludedParameter == excludedParameterValue) + { + return false; + } + } + return true; + } + + public enum OwnerShipMode { ServerOwner, @@ -268,6 +301,42 @@ public void ParameterUpdateTests() VerboseDebug($" ------------------ Parameter Test [{m_OwnerShipMode}] Stopping ------------------ "); } + [Test] + public void ParameterExcludedTests() + { + VerboseDebug($" ++++++++++++++++++ Parameter Excluded Test [{m_OwnerShipMode}] Starting ++++++++++++++++++ "); + + // Spawn our test animator object + var objectInstance = SpawnPrefab(m_OwnerShipMode == OwnerShipMode.ClientOwner, m_AuthoritativeMode); + + // Wait for it to spawn server-side + var success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ServerSideInstance != null); + Assert.True(success, $"Timed out waiting for the server-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + + // Wait for it to spawn client-side + success = WaitForConditionOrTimeOutWithTimeTravel(() => AnimatorTestHelper.ClientSideInstances.ContainsKey(m_ClientNetworkManagers[0].LocalClientId)); + Assert.True(success, $"Timed out waiting for the client-side instance of {GetNetworkAnimatorName(m_AuthoritativeMode)} to be spawned!"); + + if (m_AuthoritativeMode == AuthoritativeMode.OwnerAuth) + { + var objectToUpdate = m_OwnerShipMode == OwnerShipMode.ClientOwner ? AnimatorTestHelper.ClientSideInstances[m_ClientNetworkManagers[0].LocalClientId] : AnimatorTestHelper.ServerSideInstance; + // Set the excluded parameter value via the owner instance + objectToUpdate.UpdateExcludedParameter(Random.Range(1.5f, 100.0f)); + } + else + { + // Set the excluded parameter value via the server instance + AnimatorTestHelper.ServerSideInstance.UpdateExcludedParameter(Random.Range(1.5f, 100.0f)); + } + + TimeTravel(0.5, 60); + // Wait for the client side to update to the new parameter values + success = WaitForConditionOrTimeOutWithTimeTravel(ExcludedParameterValuesDoNotMatch); + Assert.True(success, $"The excluded parameter was synchronized!"); + VerboseDebug($" ------------------ Parameter Test [{m_OwnerShipMode}] Stopping ------------------ "); + } + + private bool AllTriggersDetected(OwnerShipMode ownerShipMode) { diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab index 9ef650701f..e7caee94cc 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/AnimatorObject.prefab @@ -386,6 +386,8 @@ MonoBehaviour: SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!95 &6515743261518512780 Animator: serializedVersion: 7 @@ -420,6 +422,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 0 + m_Animator: {fileID: 6515743261518512780} TransitionStateInfoList: - IsCrossFadeExit: 0 Layer: 0 @@ -463,7 +469,53 @@ MonoBehaviour: TransitionDuration: 0.25 TriggerNameHash: 1080829965 TransitionIndex: 2 - m_Animator: {fileID: 6515743261518512780} + AnimatorParameterEntries: + ParameterEntries: + - name: Rotate + NameHash: 807753530 + Synchronize: 1 + ParameterType: 4 + - name: Pulse + NameHash: -623385122 + Synchronize: 1 + ParameterType: 9 + - name: TestFloat + NameHash: -758535706 + Synchronize: 1 + ParameterType: 1 + - name: TestInt + NameHash: -1682086748 + Synchronize: 1 + ParameterType: 3 + - name: TestBool + NameHash: 953368263 + Synchronize: 1 + ParameterType: 4 + - name: TestTrigger + NameHash: 1033918907 + Synchronize: 1 + ParameterType: 9 + - name: LateJoinTest + NameHash: -1545343146 + Synchronize: 1 + ParameterType: 4 + - name: Attack + NameHash: 1080829965 + Synchronize: 1 + ParameterType: 9 + - name: WeaponType + NameHash: -1936256502 + Synchronize: 1 + ParameterType: 3 + - name: Weapon + NameHash: 1855955664 + Synchronize: 1 + ParameterType: 3 + - name: ExcludeFromSync + NameHash: -1993481368 + Synchronize: 0 + ParameterType: 1 + AnimatorParametersExpanded: 0 --- !u!114 &-8876216387850298050 MonoBehaviour: m_ObjectHideFlags: 0 @@ -476,6 +528,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 48b31fae64dc3c14c98e3c5d09cb1269, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &5194088446451573146 MonoBehaviour: m_ObjectHideFlags: 0 @@ -488,3 +541,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab index 56b679ebb2..d737209912 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/OwnerAnimatorObject.prefab @@ -386,6 +386,8 @@ MonoBehaviour: SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!95 &6515743261518512780 Animator: serializedVersion: 7 @@ -420,6 +422,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f6a3556fb5f3bee4e8a0fb88acff87ff, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 1 + m_Animator: {fileID: 6515743261518512780} TransitionStateInfoList: - IsCrossFadeExit: 0 Layer: 0 @@ -463,7 +469,53 @@ MonoBehaviour: TransitionDuration: 0.25 TriggerNameHash: 1080829965 TransitionIndex: 2 - m_Animator: {fileID: 6515743261518512780} + AnimatorParameterEntries: + ParameterEntries: + - name: Rotate + NameHash: 807753530 + Synchronize: 1 + ParameterType: 4 + - name: Pulse + NameHash: -623385122 + Synchronize: 1 + ParameterType: 9 + - name: TestFloat + NameHash: -758535706 + Synchronize: 1 + ParameterType: 1 + - name: TestInt + NameHash: -1682086748 + Synchronize: 1 + ParameterType: 3 + - name: TestBool + NameHash: 953368263 + Synchronize: 1 + ParameterType: 4 + - name: TestTrigger + NameHash: 1033918907 + Synchronize: 1 + ParameterType: 9 + - name: LateJoinTest + NameHash: -1545343146 + Synchronize: 1 + ParameterType: 4 + - name: Attack + NameHash: 1080829965 + Synchronize: 1 + ParameterType: 9 + - name: WeaponType + NameHash: -1936256502 + Synchronize: 1 + ParameterType: 3 + - name: Weapon + NameHash: 1855955664 + Synchronize: 1 + ParameterType: 3 + - name: ExcludeFromSync + NameHash: -1993481368 + Synchronize: 0 + ParameterType: 1 + AnimatorParametersExpanded: 0 --- !u!114 &-8876216387850298050 MonoBehaviour: m_ObjectHideFlags: 0 @@ -476,6 +528,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 48b31fae64dc3c14c98e3c5d09cb1269, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &5194088446451573146 MonoBehaviour: m_ObjectHideFlags: 0 @@ -488,3 +541,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerOwnerAuth.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerOwnerAuth.prefab index c243ddc5b4..273adf7194 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerOwnerAuth.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerOwnerAuth.prefab @@ -377,6 +377,8 @@ MonoBehaviour: SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!114 &2033189052978358381 MonoBehaviour: m_ObjectHideFlags: 0 @@ -389,6 +391,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f6a3556fb5f3bee4e8a0fb88acff87ff, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 0 TransitionStateInfoList: - IsCrossFadeExit: 0 Layer: 0 @@ -404,6 +409,15 @@ MonoBehaviour: TransitionDuration: 0.25 TriggerNameHash: 2081823275 TransitionIndex: 0 + AnimatorParameterEntries: + - Synchronize: 1 + NameHash: -813790127 + Name: Cheer + ParameterType: 9 + - Synchronize: 1 + NameHash: 2081823275 + Name: Idle + ParameterType: 9 m_Animator: {fileID: 4217478864398958195} --- !u!114 &6813822210364307126 MonoBehaviour: @@ -417,6 +431,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 77114a4761fbbf04ebafd6a884d3d312, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &-126005763244726388 MonoBehaviour: m_ObjectHideFlags: 0 @@ -429,6 +444,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &8698027682878108555 MonoBehaviour: m_ObjectHideFlags: 0 @@ -441,3 +457,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 48b31fae64dc3c14c98e3c5d09cb1269, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 diff --git a/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerServerAuth.prefab b/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerServerAuth.prefab index 3b8fc9dd68..6cb3dde6ae 100644 --- a/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerServerAuth.prefab +++ b/testproject/Assets/Tests/Runtime/Animation/Resources/PlayerCheerServerAuth.prefab @@ -269,6 +269,8 @@ MonoBehaviour: SpawnWithObservers: 1 DontDestroyWithOwner: 0 AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 --- !u!114 &6975563805585028340 MonoBehaviour: m_ObjectHideFlags: 0 @@ -281,6 +283,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 + NetworkAnimatorExpanded: 0 + AuthorityMode: 0 TransitionStateInfoList: - IsCrossFadeExit: 0 Layer: 0 @@ -296,6 +301,15 @@ MonoBehaviour: TransitionDuration: 0.25 TriggerNameHash: 2081823275 TransitionIndex: 0 + AnimatorParameterEntries: + - Synchronize: 1 + NameHash: -813790127 + Name: Cheer + ParameterType: 9 + - Synchronize: 1 + NameHash: 2081823275 + Name: Idle + ParameterType: 9 m_Animator: {fileID: 7612458143371274009} --- !u!114 &9047519417049631294 MonoBehaviour: @@ -309,6 +323,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 77114a4761fbbf04ebafd6a884d3d312, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &-967079540368916851 MonoBehaviour: m_ObjectHideFlags: 0 @@ -321,6 +336,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a915cfb2e4f748e4f9526a8bf5ee84f2, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!114 &707713165077007192 MonoBehaviour: m_ObjectHideFlags: 0 @@ -333,6 +349,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 48b31fae64dc3c14c98e3c5d09cb1269, type: 3} m_Name: m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!1 &8445200702028649601 GameObject: m_ObjectHideFlags: 0