Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Third Party [Photon] Framerate drop when calling RPC

Discussion in 'Multiplayer' started by IroncladEarl, Nov 7, 2015.

  1. IroncladEarl

    IroncladEarl

    Joined:
    Jul 3, 2013
    Posts:
    87
    Hi all,

    This is my first project using Photon or any sort of networking. I'm trying to set a float within Mecanim each frame, using an RPC function. I call this when I check for my key inputs:

    Movement controller

    Code (CSharp):
    1.     void FixedUpdate() {
    2.         float moveX = Input.GetAxisRaw ("Horizontal");
    3.         float moveZ = Input.GetAxisRaw ("Vertical");
    4.  
    5.         photonView.RPC ("AnimationMovement", PhotonTargets.All, new Vector2 (moveX, moveZ));
    6.  
    7.        // More movement stuff below
    8.    }
    Animator script

    Code (CSharp):
    1. [PunRPC]
    2.     public void AnimationMovement(Vector2 movement) {
    3.         Debug.Log ("Setting movement animations");
    4.         model.GetComponent<Animator> ().SetFloat ("RunDirection", movement.y);
    5.         model.GetComponent<Animator> ().SetFloat ("StrafeDirection", movement.x);
    6.     }
    I get a huge framerate drop (3fps) when I do this. Is there a better way? or am I approaching this wrong?

    Any advice is appreciated! Thanks
     
    Last edited: Nov 10, 2015
  2. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Remote Call Procedure? :/
    You mean RPC.

    Anyway, it looks like 2 things:

    1. Debug.Log is seriously slow.
    2. GetComponent if abused is also slow.
    3. You're sending an awful lot of data, depending on fixed update frequency.
     
  3. IroncladEarl

    IroncladEarl

    Joined:
    Jul 3, 2013
    Posts:
    87
    Sorry I meant RPC. So I moved it to the regular Update method and the same thing is happening. How does every other game manage to get 100 different animations synced but I cant even send 2 accurately?

    Code (CSharp):
    1.    void Update() {
    2.         float moveX = Input.GetAxisRaw ("Horizontal");
    3.         float moveZ = Input.GetAxisRaw ("Vertical");
    4.        
    5.         photonView.RPC ("AnimationMovement", PhotonTargets.All, new Vector2 (moveX, moveZ));
    6.     }
    Code (CSharp):
    1.     [PunRPC]
    2.     public void AnimationMovement(Vector2 movement) {
    3.         model.GetComponent<Animator> ().SetFloat ("RunDirection", movement.y);
    4.         model.GetComponent<Animator> ().SetFloat ("StrafeDirection", movement.x);
    5.     }
     
  4. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,082
    Sending a RPC each frame will break your client really quickly.

    Your approach is to send the bool (let's say) 60 times a second. Do that for a few values and to 4 players, you easily break the message limit with that one value already. You can break mobile clients with this alone.
    The trick is to send updates when necessary only.

    How to do that exactly, is beyond a quick explanation here. We do have a PhotonAnimatorView component in PUN, that might help and do what you plan to do.

    Otherwise, I suggest you take a look at how we do position updates in the Marco Polo Tutorial (same principle), have a look at our page about synchronizing states and then take a look at the PhotonAnimatorView code. If it doesn't do what you need to, it can be a basis for your own code, too.
     
  5. IroncladEarl

    IroncladEarl

    Joined:
    Jul 3, 2013
    Posts:
    87
    Thanks tobiass, I figured out a way to do it properly after some digging. I am syncing the animation values with OnPhotonSerializeView(). This seems to work without a big performance drop.

    I appreciate for the reply though.
     
  6. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,082
    @Saladon: Happy to read that :)
    I would like to encourage you to take a few minutes and post your solution in a bit more detail. When you ask for help, its always nice to also show solutions you found, if time permits.
     
  7. IroncladEarl

    IroncladEarl

    Joined:
    Jul 3, 2013
    Posts:
    87
    Apologies, that was selfish. Below is the solution I got.

    ** SOLUTION **


    After much deliberation and breaking things, figured out better approach to this and it works without a big performance drop.

    Instead of calling an RPC method from each characters controller, as fast as possible in the Update() method, I decided to sync the animation states over the network and set them once they are received. Code below:

    NetworkPlayer Script (Attached to each player)

    Code (CSharp):
    1.  
    2. void Start ()
    3.     {
    4.         if (photonView.isMine) {
    5.             // Activate components that are needed for the controlling player
    6.         } else {
    7.             // Start updating player over the network, if not the controlling player
    8.             StartCoroutine("UpdateData");
    9.         }
    10.     }
    11.  
    12.     // Set animation state every iteration
    13.     IEnumerator UpdateData()
    14.     {
    15.         while (true) {
    16.             transform.position = Vector3.Lerp(transform.position, position, networkMovementSmoothing*Time.deltaTime);
    17.             transform.rotation = Quaternion.Lerp(transform.rotation, rotation, networkMovementSmoothing*Time.deltaTime);
    18.             animator.SetFloat("RunDirection", animRunDir);
    19.             animator.SetFloat("StrafeDirection", animStrafeDir);
    20.             animator.SetBool("isMoving", animIsRunning);
    21.             yield return null;
    22.         }
    23.     }
    24.  
    25. void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    26.     {
    27.         if (stream.isWriting) {
    28.             stream.SendNext (transform.position);
    29.             stream.SendNext (transform.rotation);
    30.  
    31.             // Write animation state to the network
    32.             stream.SendNext (animator.GetFloat("RunDirection"));
    33.             stream.SendNext (animator.GetFloat("StrafeDirection"));
    34.             stream.SendNext (animator.GetBool("isMoving"));
    35.  
    36.         } else {
    37.             position = (Vector3)stream.ReceiveNext();
    38.             rotation = (Quaternion)stream.ReceiveNext();
    39.  
    40.             // Recieve animation state from network
    41.             animRunDir = (float)stream.ReceiveNext();
    42.             animStrafeDir = (float)stream.ReceiveNext();
    43.             animIsRunning = (bool)stream.ReceiveNext();
    44.  
    45.         }
    46.     }
    So now when the controlling player changes the animation state in the player controller, it will sync across the network and update that player on all the other clients.

    Code (CSharp):
    1. void MoveControl() {
    2.         float moveX = Input.GetAxisRaw ("Horizontal");
    3.         float moveZ = Input.GetAxisRaw ("Vertical");
    4.  
    5.         if (moveX != 0 || moveZ != 0) {
    6.             animator.SetBool ("isMoving", true);
    7.         } else {
    8.             animator.SetBool ("isMoving", false);
    9.         }
    10.         animator.SetFloat ("RunDirection", moveX);
    11.         animator.SetFloat ("StrafeDirection", moveZ);
    12.     }
    I hope this helps someone.
     
    tobiass likes this.
  8. tobiass

    tobiass

    Joined:
    Apr 7, 2009
    Posts:
    3,082
    If I'm right, the coroutine is running every single frame. If so, that is going to be a lot of data.
    Keep in mind: We do not limit you in what you send but we also can't guarantee that things will work nicely, no matter what you send! It might end up as too much information for some devices, breaking in real world usage on a customer's device with lesser connections.

    I can only encourage you to rethink your approach. Usually, it's not a good thing to send each frame's values.
    Your game should be able to looks good with only 10..20 updates per second.
     
  9. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The best way to do this is don't send animation info directly at all. Drive that from what you know about movement and in response to other commands like 'jump', 'shoot', etc..

    I normally do this by having my character controller drive movement animations. What you want to send over the network is position of the character and heading. Maybe sync some state variables (as integers) to say if the character is running or walking. Then your animations are driven locally. If you get a remote position and it's not where your local character is, you know you need to move the character, and thus know you need a movement animation. Then you can check if the character state is set to walk or run to see which animation to use.

    Another issue with sending animations is those will be out of sync with actual movement. You are lerping, that means your movement is lagging behind your animations. And that will often be fairly visible when transitioning from stopped to moving and visa versa.
     
  10. IroncladEarl

    IroncladEarl

    Joined:
    Jul 3, 2013
    Posts:
    87
    I actually agree with that. By giving it a limit then performance can be controlled. I've modified my coroutine to delay the update so that it sends at a rate of about 20 per second. There is a bit more of a delay but it seems to work well.

    Code (CSharp):
    1. IEnumerator UpdateData()
    2.     {
    3.         while (true) {
    4.             transform.position = Vector3.Lerp(transform.position, position, networkMovementSmoothing*Time.deltaTime);
    5.             transform.rotation = Quaternion.Lerp(transform.rotation, rotation, networkMovementSmoothing*Time.deltaTime);
    6.             animator.SetFloat("RunDirection", animRunDir);
    7.             animator.SetFloat("StrafeDirection", animStrafeDir);
    8.             animator.SetBool("isMoving", animIsRunning);
    9.             yield return new WaitForSeconds(0.05f);
    10.         }
    11.     }
     
  11. IroncladEarl

    IroncladEarl

    Joined:
    Jul 3, 2013
    Posts:
    87
    In my case, I have to send floats for my animation states because I use blending. In my reply to Tobiass above, I mentioned that I limited the update rate by adding a 5 millisecond delay each iteration. I understand that the players won't be exactly accurate, but that is just the nature of networking and latency. I will handle my shooting logic in a special way so that I can fairly see if a player was shot or not (Average positioning between both clients, etc... Still working on it).