Search Unity

Question Which delta time to use for movement?

Discussion in 'Netcode for GameObjects' started by babaliaris, Oct 31, 2022.

  1. babaliaris

    babaliaris

    Joined:
    Apr 21, 2013
    Posts:
    40
    I'm currently experimenting with Netcode for GameObjects and had a wonderful experience understanding how to code player movement using the ownership/permissions concept.

    My game uses the server controller model (the server is responsible for the game state) and I was wondering what is the proper way to implement object movement with delta-time.

    Let me give you an example:

    Code (CSharp):
    1. [ServerRpc]
    2. private void MovePlayerServerRpc(Vector3 moveDir)
    3. {
    4.     //speed is defined by the server and Time.deltaTime is the server's time between frames.
    5.     GetComponent<CharacterController>().Move(moveDir * speed * Time.deltaTime)
    6. }
    As you can see, I move the instance from within the server using its own Time.deltaTime. But of course, each client depending on their machine will probably have different delta times. So will this result in different movement speeds for each client?

    If yes how can I solve this problem?
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,909
    Since Netcode is tick-based I would assume you need the time between last and current tick which will be higher than deltaTime, and constant too. So you wouldn‘t even need to multiply by any time factor.

    But do cache the CharacterController reference rather than GetComponent it every time.
     
    babaliaris likes this.
  3. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    Time.deltaTime is the right thing to use here, assuming movement happens in Update or at the rate of Update. If it happens per FixedUpdate, use Time.fixedDeltaTime, if it happens per network tick, use whatever time corresponds to your chosen tick rate (e.g. tick rate 50 -> 1/50=0.02).

    The speed will not be different, even if the movement per frame were, because even if the frame rate is different the movement over time is still the same (which is exactly why we use Time.deltaTime in the first place).

    And yes, always cache components you need often, such as every frame. ;)
     
    babaliaris likes this.
  4. babaliaris

    babaliaris

    Joined:
    Apr 21, 2013
    Posts:
    40
    Hmm, this is still hard to understand... Let's talk about the following scenario:

    Server 60Fps,
    Client 1: 120FPS,
    Client 2: 30Fps

    When you move an object from within the server code with the server's delta-time:
    Code (CSharp):
    1. float serverDeltaSpeed = time.deltaTime * speed;
    Then client 1 should experience a faster movement since serverDeltaSpeed should be a constant translation to him
    and client 2 should experience slower movement.

    Maybe the NetworkTransform makes sure that the translation that happens in the server will look the same in both clients no matter their framerates?
     
  5. babaliaris

    babaliaris

    Joined:
    Apr 21, 2013
    Posts:
    40
    By the way, I just saw this for the first time. I joined my server using a standalone client (build) and a client from within the Unity editor, and the client in the Unity editor moves almost twice as fast as the standalone client!!! This is my actual code:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Netcode;
    5. using Cinemachine;
    6.  
    7. public class PlayerMovement : NetworkBehaviour
    8. {
    9.     public float playerSpeed = 10.0f;
    10.     public float gravitySpeed = 8.0f;
    11.  
    12.     private CinemachineVirtualCamera mVirtualCamera;
    13.     private CharacterController mCharacterController;
    14.     private Vector3 mMoveDir    = Vector3.zero;
    15.  
    16.     // Start is called before the first frame update
    17.     void Start()
    18.     {
    19.         mCharacterController = GetComponent<CharacterController>();
    20.         mVirtualCamera = GameObject.Find("PlayerVirtualCamera").GetComponent<CinemachineVirtualCamera>();
    21.  
    22.         if (IsOwner)
    23.             mVirtualCamera.Follow = transform;
    24.     }
    25.  
    26.     // Update is called once per frame
    27.     void Update()
    28.     {
    29.  
    30.         //Move the player.
    31.         if (IsOwner)
    32.         {
    33.             if (Input.GetKey(KeyCode.W))
    34.                 mMoveDir = transform.forward;
    35.  
    36.             else if (Input.GetKey(KeyCode.A))
    37.                 mMoveDir = -transform.right;
    38.  
    39.             else if (Input.GetKey(KeyCode.D))
    40.                 mMoveDir = transform.right;
    41.  
    42.             else if (Input.GetKey(KeyCode.S))
    43.                 mMoveDir = -transform.forward;
    44.  
    45.             else
    46.                 mMoveDir = Vector3.zero;
    47.  
    48.             if (mMoveDir.magnitude > 0)
    49.                 MoveServerRpc(mMoveDir);
    50.  
    51.         }
    52.  
    53.         //Apply gravity motion to the player.
    54.         if (IsServer)
    55.         {
    56.             mCharacterController.Move(-Vector3.up * gravitySpeed * Time.deltaTime);
    57.         }
    58.     }
    59.  
    60.     [ServerRpc]
    61.     public void MoveServerRpc(Vector3 moveDir)
    62.     {
    63.         mCharacterController.Move(moveDir * playerSpeed * Time.deltaTime);
    64.     }
    65. }
    66.  
     
  6. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    Yes, it should, roughly (with interpolation). However, that's only true if you apply the right translation, and in your code you do not: you are assuming implicitly that the client and server have the same framerate by using Time.deltaTime on the server.

    Your two simplest options here are: option A, send a translation (that is, a movement vector) to the server, not a direction – that is, send moveDir*playerSpeed*Time.deltaTime, which uses the client's deltaTime, and have the server move the player accordingly.
    Or option B, do it as you're doing now (though note that sending a vector for a direction is overkill), but use the time between ticks, and do not apply more than one movement per tick.
    Option A is simplest, but allows for cheating more easily, because the client could send the server any translation it likes (though note that this is already true since the magnitude of moveDir can be anything the client wants).
     
    babaliaris likes this.
  7. babaliaris

    babaliaris

    Joined:
    Apr 21, 2013
    Posts:
    40
    Thanks for the great reply!

    First I tried this (passing the client delta time to the server rpc):
    Code (CSharp):
    1. MoveServerRpc(mMoveDir, Time.deltaTime);
    Seems to work flawlessly.

    Regarding the second method that you mentioned (use the time between network ticks), I'm not really sure how to implement it.

    I tried the following:
    Code (CSharp):
    1. [ServerRpc]
    2.     public void MoveServerRpc(Vector3 moveDir)
    3.     {
    4.         mCharacterController.Move(moveDir * playerSpeed * NetworkManager.NetworkTickSystem.TickRate);
    5.     }
    But it made my character move close to light speed fast!!! playerSpeed = 10 and moveDir is normalized.
    Remember that MoveServerRpc is being called every frame. You said something about "one movement per tick", probably this is what I'm missing, but I don't know how to implement this. Can you show me?

    I also have some extra questions if you guys have the time to answer :p
    1)
    But what data can I send the server to tell him where I want to move?

    2) If the serverRPC for movement is MoveServerRpc(Vector3 moveDir), and I'm making a strategy game that highly depends on movement, could the player cheat by overriding the moveDir (using memory hooking for example) and move somewhere else? How do I deal with that since the client is responsible to tell me his new position?
     
  8. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    Remember that the time interval corresponding to the tick rate is 1/tickRate (see my previous example), hence the very large speed you're seeing in method B. Making sure you only have one update per tick is trickier though, so I would stick with method A.

    1) One way to cut down on the data transferred for the direction, for example just pass a single int (or short, or byte, or two bools, etc), for example 1 for forward, 2 for backward, 3 for left and 4 for right. The server should then move in the corresponding direction. Enums are good for this and natively supported by Netcode for serializable arguments.

    2) You can check that the input is reasonable on the server. For example, with Time.deltaTime your second variable, you could check that this is reasonably small (e.g. <0.05 or something) to avoid large jumps in movement.
     
    babaliaris likes this.