FirstGearGames / FishNet

FishNet: Unity Networking Evolved.
Other
1.31k stars 139 forks source link

Client side prediction not working correctly with RigidBody2D physics #260

Closed tedbarth closed 1 year ago

tedbarth commented 1 year ago

Unity version: 2021.3.18f1 Fish-Networking version: 3.3.2

Describe the bug When enabling the Latency Simulator with some latency > 0 ms, CSP with RB2Ds prediction does not work because the latency seems to also delays the Physics simulation: For a latency of 1000ms the RB hosting object and its predicted Graphical Object is reacting to its forces applied at first after 1000ms have past.

I have checked the execution of network tick and post tick callbacks, they are fine at the setup tick rate. Rigid bodies are getting their forces applied on network tick correctly and unaffected of latency.

I'm unsure if the Latency simulator does affect the physics simulation steps or the physics simulation is not updated on the client at all.

To Reproduce Steps to reproduce the behavior:

  1. Object with RB2D with CSP:
    • GameObject has a RB2D component
    • GameObject has a child graphical object with a sprite renderer
    • GameObject has a NetworkObject component
    • GameObject has no NetworkTransform component
    • GameObject has a PredictedObject component referencing the graphical object as such and the GameObject as RB while Prediction Type is set to "RigidBody2D"
  2. TimerManager of NetworkManager object has Physics Mode set to "TimeManager"
  3. Tick 30 on Client and Server
  4. Start Game
  5. Enable Latency simulator
  6. Crank up latency to 1000ms to make this visible
  7. See RB and Graphical Object starting to get updated at first after 1000ms

Expected behavior The RB should react on its forces immediately and get corrected at first 1000ms (if necessary at all).

Relevant methods of my network motor script of the game object using CSP

  protected override void OnNetworkTick() {
    if (IsOwner) {
      Reconcile(default, false);
      HandleAxesInput();
    }

    if (IsServer) {
      Move(default, true);
    }
  }

  protected override void OnNetworkPostTick() {
    if (IsServer && TimeManager.Tick % 3 == 0) {
      Debug.Log("Sending Reconcile data");
      ReconcileMoveData reconcileMoveData = new ReconcileMoveData {
        Position = transform.position,
        Rotation = Utilities.limitToAmplitudeOf360(_body.rotation),
        LinearVelocity = _body.velocity,
        AngularVelocity = _body.angularVelocity,
        LinearAcceleration = _acceleration.linearAcceleration,
        AngularAcceleration = _acceleration.angularAcceleration
      };
      Reconcile(reconcileMoveData, true);
    }
  }

  private void HandleAxesInput() {
    float currentHorizontalInput = Input.GetAxisRaw("Horizontal");
    float currentVerticalInput = Input.GetAxisRaw("Vertical");

    MoveData md = default;
    md.Horizontal = currentHorizontalInput;
    md.Vertical = currentVerticalInput;

    // Will be replicated on the server
    Move(md, false);
  }

  [Replicate]
  private void Move(
    MoveData moveData,
    bool asServer,
    Channel channel = Channel.Unreliable,
    bool replaying = false) {

    if (moveData.Vertical != 0) {
      _body.AddForce(transform.up * thrustAccelerationForce * moveData.Vertical);
    }

    if (moveData.Horizontal != 0) {
      _body.AddTorque(angularAccelerationForce * -moveData.Horizontal);
    }
  }

  [Reconcile]
  private void Reconcile(
    ReconcileMoveData recData,
    bool asServer,
    Channel channel = Channel.Unreliable) {
    transform.position = recData.Position;
    _body.rotation = recData.Rotation;
    _body.velocity = recData.LinearVelocity; // FIXME: To scalar value in flight direction
    _body.angularVelocity = recData.AngularVelocity;
    _acceleration.linearAcceleration = recData.LinearAcceleration;
    _acceleration.angularAcceleration = recData.AngularAcceleration;
  }

Selection_277 Selection_278

Selection_276

tedbarth commented 1 year ago

I tested again. It is working when the latency is added on both sides, the client and server: The Client sees the direct response and the server handles it 1000 ms later. I'm pretty unsure if this is correct. This would require all peers to have the same latency. How would I simulate the situation where all clients have 100ms pings and just one 1000 ms?

Could it be that the latency simulator adds latency to one communication direction only, for instance, only outgoing packages, but not incoming?

tedbarth commented 1 year ago

EDIT: My apologizes, I have confused the bug issues. I will move this and all following comments into a separate issue, soon.

@FirstGearGames I uploaded an example project with video displaying the problem: https://github.com/tedbarth/FishNet-Bug-Doubled-Forces

tedbarth commented 1 year ago

I suspected RigedBody2d.inertia which affects the rotational reaction to an angular force, but printing them both per tick showed that they are the same.

Debug.Log($"Inertia: {_body.inertia}, Mass: {_body.mass}");
tedbarth commented 1 year ago

Another hint: If I do not call my [Reconcile] private void Reconcile() { ... } method for the owner, the problem disappears. Obviously this is not a solution: I want reconciliation. :-)