Root Motion and Photon Transform View



For my player character, I have a controller which utilized root motion animations that are enabled via triggers. What is the best way to go about syncing these root motion movements over the network? This is a multi factor question because:

1) The root motion animations are enabled using a trigger (which displays a Debug.Log saying that animation triggers should be sent using an RPC and not Photon Animator view?). How do I properly go about syncing triggers over the network?

2) Does using root motion animations have any unintended effects with the Photon Transform View? Should I create my own IPunObservable solution? If so what are the benefits of this, and how should I go about doing this?

Thank you so much!


  • Tobias
    Tobias admin
    edited November 2022

    Syncing Triggers can be tricky, as they might get cleared by Unity before PUN (anyone, really) notices.

    In best case, you sync the state that triggers the animations and run them, I would say.

    The PUN Basics Tutorial syncs Animations and there is a known issue paragraph about Triggers (and how to avoid some issues).

    Root Motion is likely going to cause issues, as it moves the character in ways that the network simulation didn't intend. Networked objects are controlled by one client and everyone else should follow along as best as possible, so root motion should not have additional control.

    Should I create my own IPunObservable solution?

    That depends on what you need and if you can achieve it. The nature of networking is that there is lag and (worse!) variance of lag. It does not always take the exact same time for packages to travel the internet, so updates are not perfectly paced. With some additional data, some of those effects can be hidden well (e.g. a Networked Rigidbody sends a velocity, so receiving clients can work with that)...

    If you can improve how the code works around it, then yes, your own solution will be good.

    Sorry for the vague answer.

  • Your answer is not vague at all! It actually has provided some great guidance for how to move forward.

    For my Character Controller, I use a finite state machine to transition between different player states. So for example, when the player left clicks, they enter their 'Attacking State', which triggers a root motion attack animation. When the attack is done, they re-enter their 'Normal Movement' state.

    To solve the problems I outlined above, I use a custom IPunObservable, which writes the users current keyboard/mouse inputs, and reads other players inputs. So, when I left click, it notifies all other players that I left clicked, ensuring that my character controller in other clients scenes executes all the logic required for its 'attack' state. When I press WASD, it notifies all other players of what keys I pressed, and it moves my Character Controller in their scene accordingly.

    The only problem I have right now is syncing the current transform of players in the room when a new player joins, although I think I will use some type of RPC in combination with OnPlayerEnteredRoom callback to solve this.

    Overall, do you see any potential flaws in this type of system? It will provide for me a syncing of not only transforms and animations, but also all relevant state information. I feel like this solution is too easy though. Am I missing something? As a little background, my multiplayer game is for a fast paced melee combat.

    Thanks again Tobias.

  • Tobias

    Using OnPlayerEnteredRoom to update joining players is a good idea. You can use RPCs or you could have optional data in your IPunObservable, which is only sent when a new player joined. A flag in your data will be enough to read this if present.

    Sending only the input should be fine, if you keep an eye on all the cases where gameplay diverges after a while. This won't be deterministic, so variable delay will cause small differences which may accumulate over time.

    Quantum is a better engine for melee combat, as it's deterministic. Fusion also just sends input but the Host / Server is the authoritative instance which sends the resulting game state to everyone. So clients just predict what happens locally but get the correct state asap from the Host.