Search Unity

Third Party Mirror NetworkAnimator not syncing animations from Animator.Play()

Discussion in 'Multiplayer' started by jack_107, Sep 6, 2021.

  1. jack_107

    jack_107

    Joined:
    May 21, 2016
    Posts:
    7
    A while ago I switched from using triggers, bools, floats, ect, to manage my animator's states in favor of doing
    Code (CSharp):
    1. AnimatorName.Play("AnimationName");
    I did this because I was finding the triggers to be inconsistent and hard to manage whereas I could just play the animation I want to happen at the time I want to happen.

    See this video on an explaination:


    Anyways, that worked really great... until I tried to make the game multiplayer. I'm currently using mirror with the basic Kcp transport (standing in for steam transport for ease of testing purposes). My player has an identity, a netTransform, and a netAnimator. The movement is synced across and the idle animation is synced across, but when I move the local player the other client doesn't see the running animation or attacking animations or anything.

    I suspect its due to my Animator.Play() approach but I'm lost on how to fix it. I cant find anything online even about Animator.Play() and its relation to multiplayer let alone my specific issue.

    Here's the player animation script (condensed since its long):
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using Mirror;
    4.  
    5.  
    6. //
    7. public class Player_Animation : NetworkBehaviour
    8. {
    9.     //Animators
    10.     public Animator player, helmet, chestplate, boot, weapon;
    11.  
    12.  
    13.     public void setSwordSwingAnimation()
    14.     {
    15.         player.Play(PLAYER_SWORD);
    16.         helmet.Play(HELMET_SWORD);
    17.         chestplate.Play(CHESTPLATE_SWORD);
    18.         boot.Play(BOOT_SWORD);
    19.         weapon.Play(WEAPON_SWORD);
    20.     }
    21.  
    22. }
    PLAYER_SWORD and alike are const strings, the other methods for the other animations are almost identical to this one.

    This is what the animator looks like (very clean!)
    upload_2021-9-6_12-33-25.png

    When I run the game, from Client A's perspective, Client B's animator just plays the Player_Idle animation indefinitely. And on Client B's perspective its animator functions as expected (each animation plays when called by Animator.Play() )

    Perhaps the solution requires using ClientRPC, TargetRPC, or one of those attributes, but I'm still lost in understanding that so any guidance or help is greatly appreciated.
     
    Michael4545 likes this.
  2. jack_107

    jack_107

    Joined:
    May 21, 2016
    Posts:
    7
    Figured it out.
    I set all of the animation set methods like setSwordSwingAnimation() to Commands.
    Changed all the animators to ClientAuthority=false in the editor.
    - Why?
    Here's why:
    https://answers.unity.com/questions/1725421/animations-playing-only-in-the-host-and-not-on-the.html
    "And btw rememebr to take off the client authority checkbox in the animator component since the command directive makes the update method a server method."


    Also I moved them from under child gameobjects to the parent object referencing the animators in the children.

    Then it turned out that the animation was playing it was just being interrupted by the idle state instantly. Due to:
    Code (CSharp):
    1.  
    2.     [Command]
    3.     public void CmdupdateAnimations(bool running, bool holdingShield)
    4.     {
    5.         if (holdingShield)
    6.         {
    7.             player.Play(PLAYER_SHIELD);
    8.             helmet.Play(HELMET_SHEILD);
    9.             chestplate.Play(CHESTPLATE_SHEILD);
    10.             boot.Play(BOOT_SHIELD);
    11.             weapon.Play(WEAPON_SHIELD);
    12.         }
    13.         else
    14.         {
    15.  
    16.             //Set all the bools
    17.             if (running)
    18.             {
    19.                 if (!playerCombat.attacking) //REQUIRES ATTACKING TO BE SYNCED!!
    20.                 {
    21.                     player.Play(PLAYER_RUN);
    22.                     helmet.Play(HELMET_RUN);
    23.                     chestplate.Play(CHESTPLATE_RUN);
    24.                     boot.Play(BOOT_RUN);
    25.                     weapon.Play(WEAPON_RUN);
    26.                 }
    27.              
    28.             }
    29.             else
    30.             {
    31.              
    32.                 if (!playerCombat.attacking)
    33.                 {
    34.                     player.Play(PLAYER_IDLE);
    35.                     helmet.Play(HELMET_IDLE);
    36.                     chestplate.Play(CHESTPLATE_IDLE);
    37.                     boot.Play(BOOT_IDLE);
    38.                     weapon.Play(WEAPON_IDLE);
    39.                 }
    40.             }
    41.         }
    42.     }
    Then in the player Combat class
    //do action and skills from input
    void FixedUpdate()
    {
    doAction(playerInput.getAction());
    doSkills(playerInput.getSkill());

    CmdsetAttackState(sword_attack || bow_attack || staff_attack || holding_shield);
    }
    //Synchronize attack state across network
    [Command]
    public void CmdsetAttackState(bool attackingBool)
    {
    attacking = attackingBool;
    }
    Code (CSharp):
    1.  //do action and skills from input
    2.     void FixedUpdate()
    3.     {
    4.         doAction(playerInput.getAction());
    5.         doSkills(playerInput.getSkill());
    6.  
    7.         CmdsetAttackState(sword_attack || bow_attack || staff_attack || holding_shield);
    8.     }
    9.     //Synchronize attack state across network
    10.     [Command]
    11.     public void CmdsetAttackState(bool attackingBool)
    12.     {
    13.         attacking = attackingBool;
    14.     }
    What was happening is that the attack animation was playing but the attacking bool wasnt getting synced across the network so the idle animation was playing right when I tried to play any attack animation.


    TLDR: In order for animations to be synced across network in mirror with the network animator you must:
    1. Ensure all the netanimators are on the parent object (they can reference animators in children)
    2. Set the client authority of the netanimators to false.
    3. Make the methods that set the animations (Or bools, floats, ints, triggers) Commands and make sure if you use any variables (in my case bool: attacking) that they are SyncVars that must be set in Cmds.
    - FYI the running bool and holdingShield bool are NOT synced and dont need to be because they are parameters in the Cmd.
     
    Michael4545 likes this.
  3. jack_107

    jack_107

    Joined:
    May 21, 2016
    Posts:
    7
    I've recently found out that calling commands in Update is a poor practice because you are sending traffic over the network every Update.

    A way to fix this is only send the command if the data has changed or use the animator parameters instead of animator.Play (where I assume they send commands only when the data changes). If I were you I'd suggest using animator parameters. But I dont want to give up my animation logic.
     
    Michael4545 likes this.
  4. marshmallowboy

    marshmallowboy

    Joined:
    Jan 5, 2020
    Posts:
    5
    Bro really setup a problem. Fixed his own problem. Then gave advice on the problem. Based.
     
    Michael4545 likes this.