In NetworkCharacterController (Fusion's sample code) why is Velocity and IsGrounded Networked?

The whole answer can be found below.

Please note: The Photon forum is closed permanently. After many dedicated years of service we have made the decision to retire our forum and switch to read-only: we've saved the best to last! And we offer you support through these channels:

Try Our
Documentation

Please check if you can find an answer in our extensive documentation on Fusion.

Join Us
on Discord

Meet and talk to our staff and the entire Photon-Community via Discord.

Read More on
Stack Overflow

Find more information on Stack Overflow (for Circle members only).

Write Us
an E-Mail

Feel free to send your question directly to our developers.

In NetworkCharacterController (Fusion's sample code) why is Velocity and IsGrounded Networked?

ueharajohji
2022-10-07 11:52:07

I am trying to learn photon fusion through the sample codes. I am just wondering why is there a need for IsGrounded and Velocity to be networked when the transform is already sent across the other players? Does it have something to do with client predictions and stuff?

using System;    
using Fusion;    
using UnityEngine;    


[RequireComponent(typeof(CharacterController))]    
[OrderBefore(typeof(NetworkTransform))]    
[DisallowMultipleComponent]    
// ReSharper disable once CheckNamespace    
public class NetworkCharacterController : NetworkTransform {    
  [Header("Character Controller Settings")]    
  public float gravity       = -20.0f;    
  public float jumpImpulse   = 8.0f;    
  public float acceleration  = 10.0f;    
  public float braking       = 10.0f;    
  public float maxSpeed      = 2.0f;    
  public float rotationSpeed = 15.0f;    
  public float verticalRotationSpeed = 50.0f;    


  [Networked]    
  [HideInInspector]    
  public bool IsGrounded { get; set; }    


  [Networked]    
  [HideInInspector]    
  public Vector3 Velocity { get; set; }    


  /// <summary>    
  /// Sets the default teleport interpolation velocity to be the CC's current velocity.    
  /// For more details on how this field is used, see <see cref="NetworkTransform.TeleportToPosition"/>.    
  /// </summary>    
  protected override Vector3 DefaultTeleportInterpolationVelocity => Velocity;    


  /// <summary>    
  /// Sets the default teleport interpolation angular velocity to be the CC's rotation speed on the Z axis.    
  /// For more details on how this field is used, see <see cref="NetworkTransform.TeleportToRotation"/>.    
  /// </summary>    
  protected override Vector3 DefaultTeleportInterpolationAngularVelocity => new Vector3(0f, 0f, rotationSpeed);    


  public CharacterController Controller { get; private set; }    


  protected override void Awake() {    
    base.Awake();    
    CacheController();    
  }    


  public override void Spawned() {    
    base.Spawned();    
    CacheController();    


    // Caveat: this is needed to initialize the Controller's state and avoid unwanted spikes in its perceived velocity    
    // Controller.Move(transform.position);    
  }    


  private void CacheController() {    
    if (Controller == null) {    
      Controller = GetComponent<CharacterController>();    


      Assert.Check(Controller != null, $"An object with {nameof(NetworkCharacterControllerPrototype)} must also have a {nameof(CharacterController)} component.");    
    }    
  }    


  protected override void CopyFromBufferToEngine() {    
    // Trick: CC must be disabled before resetting the transform state    
    Controller.enabled = false;    


    // Pull base (NetworkTransform) state from networked data buffer    
    base.CopyFromBufferToEngine();    


    // Re-enable CC    
    Controller.enabled = true;    
  }    


  /// <summary>    
  /// Basic implementation of a jump impulse (immediately integrates a vertical component to Velocity).    
  /// <param name="ignoreGrounded">Jump even if not in a grounded state.</param>    
  /// <param name="overrideImpulse">Optional field to override the jump impulse. If null, <see cref="jumpImpulse"/> is used.</param>    
  /// </summary>    
  public virtual void Jump(bool ignoreGrounded = false, float? overrideImpulse = null) {    
    if (IsGrounded || ignoreGrounded) {    
      var newVel = Velocity;    
      newVel.y += overrideImpulse ?? jumpImpulse;    
      Velocity =  newVel;    
    }    
  }    


  public void Rotate(float rotationValue)    
    {    
        transform.Rotate(0, rotationValue * Runner.DeltaTime * rotationSpeed, 0);    
    }    


  /// <summary>    
  /// Basic implementation of a character controller's movement function based on an intended direction.    
  /// <param name="direction">Intended movement direction, subject to movement query, acceleration and max speed values.</param>    
  /// </summary>    
  public virtual void Move(Vector3 direction) {    
    var deltaTime    = Runner.DeltaTime;    
    var previousPos  = transform.position;    
    var moveVelocity = Velocity;    


    direction = direction.normalized;    


    if (IsGrounded && moveVelocity.y < 0) {    
      moveVelocity.y = 0f;    
    }    


    moveVelocity.y += gravity * Runner.DeltaTime;    


    var horizontalVel = default(Vector3);    
    horizontalVel.x = moveVelocity.x;    
    horizontalVel.z = moveVelocity.z;    


    if (direction == default) {    
      Debug.Log("direction == Default");    
      horizontalVel = Vector3.Lerp(horizontalVel, default, braking * deltaTime);    
    } else {    
      Debug.Log("direction else");    
      Debug.Log($"HorizontalVel {horizontalVel} + direcction {direction} * deltaTime {deltaTime}, maxSpeed {maxSpeed}");    
      horizontalVel      = Vector3.ClampMagnitude(horizontalVel + direction * acceleration * deltaTime, maxSpeed);    
      // LEFT RIGHT Movements will affect the rotation so commenting out is desirable    
      //transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Runner.DeltaTime);    
    }    


    moveVelocity.x = horizontalVel.x;    
    moveVelocity.z = horizontalVel.z;    


    Controller.Move(moveVelocity * deltaTime);    


    Debug.Log($"current pos {transform.position} - previous pos {previousPos} * runner tickrate {Runner.Simulation.Config.TickRate}");    
    Debug.Log($"current pos {transform.position} - previous pos {previousPos} = {transform.position - previousPos}");    


    Vector3 simulatedVelocity = (transform.position - previousPos) * Runner.Simulation.Config.TickRate;    
    //simulatedVelocity.y = simulatedVelocity.y * 0.5f;    
    Velocity = simulatedVelocity;    
    Debug.Log("Velocity:: " + Velocity);    
    IsGrounded = Controller.isGrounded;    
  }    
}    

Comments

emotitron
2022-10-08 14:02:55

Correct, the main reason typically for Networked values is for correct tick accuracy on clients and for client prediction.

Typically values are networked because they are considered "State", which for client prediction is important because these values need to correctly "rewind" during client reconciliation. The client needs to know when it is rewound what the exact server state was for that tick.

ueharajohji
2022-10-08 16:36:01

@emotitron Thanks for the clarification.

I am still reading tons of sample code and I think I am getting the grasps of networked variables.

I still haven't been able to wrap my head around this line

horizontalVel      = Vector3.ClampMagnitude(horizontalVel + direction * acceleration * deltaTime, maxSpeed);    

In my understanding the only variable that is affected by the networked "Velocity" is the horizontalVel which gets the x and y of the Velocity. The other variable that is going to change is direction which is controlled via **networkInputData.**Which i suspect can give a bug when a player is moving through stairs (or any uneven surface) with the code below.

Velocity = (transform.position - previousPos) * Runner.Simulation.Config.TickRate;

say for example a player is trying to walk into stairs which has a height that is too high for a player to walk into but walking into the edge of it is possible. this scenario might give the following result

Velocity = (1, 1.5, 1) - (1, 1.34, 1) * Runner.Simulation.Config.TickRate;

= (0, 9.6, 0)

which in this will make a player jump very high (this actually happens in fusion sample code, so I am just wondering if I am missing something or not).

emotitron
2022-10-08 18:51:07

I am not personally familiar enough with NCCP to comment, but someone else may who knows more about that implementation may chime in.

ueharajohji
2022-10-09 03:50:20

@emotitron Okay ! Thanks for sharing your thoughts!

Theoderek
2022-11-28 01:53:28

ueharajohji 2022-10-08T16:36:01+00:00

@emotitron Thanks for the clarification.

I am still reading tons of sample code and I think I am getting the grasps of networked variables.

I still haven't been able to wrap my head around this line

horizontalVel      = Vector3.ClampMagnitude(horizontalVel + direction * acceleration * deltaTime, maxSpeed);  

In my understanding the only variable that is affected by the networked "Velocity" is the horizontalVel which gets the x and y of the Velocity. The other variable that is going to change is direction which is controlled via networkInputData. Which i suspect can give a bug when a player is moving through stairs (or any uneven surface) with the code below.

Velocity = (transform.position - previousPos) * Runner.Simulation.Config.TickRate;

say for example a player is trying to walk into stairs which has a height that is too high for a player to walk into but walking into the edge of it is possible. this scenario might give the following result

Velocity = (1, 1.5, 1) - (1, 1.34, 1) * Runner.Simulation.Config.TickRate;

= (0, 9.6, 0)

which in this will make a player jump very high (this actually happens in fusion sample code, so I am just wondering if I am missing something or not).

Is there a solution to this? Because this happens to me all the time, the player bumps into something and goes flying into the air.

Back to top