FirstGearGames / FishNet

FishNet: Unity Networking Evolved.
Other
1.28k stars 136 forks source link

[FN 3.5.8] AudioSource on an CSP object does play multiple times and sound gets cut off on each play when spawned via Predicted Spawn on client #304

Closed tedbarth closed 1 year ago

tedbarth commented 1 year ago

Unity version: 22 (and 21) Fish-Networking version: 3.5.8 (and 3.5.7, 3.5.5, 3.4.3)

Describe the bug A CSP object, launched via Predicted Spawn on the client with an AudioSource that either is played on awake via playOnAwake = true; or via audioSource.Play() in Awake() will play but gets cut off after some milliseconds. playOnAwake additionally will cause the object to play the sound multiple times.

It does work though when either:

The problem appears only when CSP is implemented and launched via Predicted Spawn on the client.

Awake() gets called only once, which is why using audioSource.Play() will sound only once. Still it gets cut off.

My guess is that it gets reconciled (replayed) at spawn somehow, leading to an active or enabled state toggling of the object or AudioSource component.

AudioSource must play while attached to the CSP object and not be separated from it in my usecase (among other reasons because the audio source must move with the object for 3D sound).

Video https://youtu.be/tm18RQxoO-Q

  1. First shooting a CSP projectile with Predicted Spawn on the client
  2. Then shooting a CSP projectile with [ServerRpc] and spawning on the server
  3. Then shooting a CSP projectile with Predicted Spawn on the client, again

Code The code for when on the AudioSource Play On Awake is set to true in the Editor:

using FishNet.Object.Prediction;
using FishNet.Transporting;
using Networking;
using UnityEngine;

namespace Weapon.Projectile {
  public class ProjectileCspController : TickedNetworkBehaviour {
    private Rigidbody2D _body;

    private void Awake() {
      _body = GetComponent<Rigidbody2D>();

     // "Play on Awake" is set to true on the AudioSource, so we do not have to Play() manually here.
     // Otherwise I would do:
     // getComponent<AudioSource>().Play();
    }

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

        // Where input is read and values for the forces to apply are calculated
        HandleInput();
      }

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

    protected override void OnNetworkPostTick() {
      if (IsServer) {
        ReconcileMoveData reconcileMoveData = new ReconcileMoveData {
          Position = transform.position,
          Rotation = transform.rotation.eulerAngles.z,
          LinearVelocity = _body.velocity,
          AngularVelocity = _body.angularVelocity,
        };
        Reconcile(reconcileMoveData, true);
      }
    }

    private void HandleInput() {
      EmptyReplicateData md = default;

      // Right now, no inputs collected, md stays default.
      // I will implement this when the current state works.

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

    [Replicate]
    private void Replicate(
      EmptyReplicateData moveData,
      bool asServer,
      Channel channel = Channel.Unreliable,
      bool replaying = false) {
      // Nothing to do right now
    }

    [Reconcile]
    private void Reconcile(
      ReconcileMoveData recData,
      bool asServer,
      Channel channel = Channel.Unreliable) {
      transform.position = recData.Position;
      transform.rotation = Quaternion.Euler(new Vector3(0, 0, recData.Rotation));
      _body.velocity = recData.LinearVelocity;
      _body.angularVelocity = recData.AngularVelocity;
    }
  }
}
Maclay74 commented 1 year ago

I might be wrong, but I don't see where you call your audioSource in the code?..

tedbarth commented 1 year ago

@Maclay74 In this example I don't. The AudioSource is set up with PlayOnAwake in the Editor (a checkbox). If I don't do so (which would be the other mentioned case) I'd call getComponent<AudioSource>().Play() in Awake().

Edit: I have improved the example above by including this information as comments. Thanks for the hint.

tedbarth commented 1 year ago

Added video with sound to the OP to illustrate the problem.

FirstGearGames commented 1 year ago

Acknowledging your report. Will update once able to review.

tedbarth commented 1 year ago

After I finally managed to host the game on a server I can notice that the problem is probably bigger: It is not just the audio but also the first view position update which suffer from (I think so) excessive reconciliation:

https://youtu.be/mRdmQtWIiy8 (I recommend to use YouTubes options to set the video play speed to 0.25)

In the video you see in the order:

  1. Shooting a CSP projectile with Predicted Spawn on the client
  2. Shooting a CSP projectile with [ServerRpc] and spawning on the server
  3. Shooting a CSP projectile with Predicted Spawn on the client
  4. Shooting a CSP projectile with [ServerRpc] and spawning on the server
  5. Shooting a CSP projectile with Predicted Spawn on the client
FirstGearGames commented 1 year ago

It's hard to say what's going on initially.

There shouldn't be any active state changes unless you explicitly do it yourself. My guess is that because the RB is moved to a different physics scene to avoid incorrect simulation, then moved back, the audio source is firing awake twice.

I'd try disabling playOnAwake and write a script to ensure it only plays once. If this works let me know.

tedbarth commented 1 year ago

@FirstGearGames Did you see the video (https://youtu.be/mRdmQtWIiy8) from my last comment? There you can see, that the audio problem is just another symptom for a bigger problem: The position update is hold back, too, until suddenly up to date. You can see that when slowing down play speed in the Youtube video (Youtube has this feature in its video toolbar).

The only thing I do on the server after spawning is to update the velocity and mass of the object. Right now this is set to the velocity of the object from client in OnStartNetwork() (Server only) and in future will be the velocity determined on the server to block speed hacks. I will also do this with the position, which right now is only set on the client, right before calling Spawn() on the client. I hope I can benefit from Lag Compensation feature in the future when FN has proven that it works in this scenario (I currently hesitate to buy it because of this bug and the missing server validation of predicted spawns).

I'd try disabling playOnAwake and write a script to ensure it only plays once. If this works let me know.

I already did that, with the same result. This I have described in the original post:

That either is played on awake via playOnAwake = true; or via audioSource.Play() in Awake()

Additionally I also have tried to use a hasPlayed flag: This did cause the audio to play once, but the audio is still cut off.

tedbarth commented 1 year ago

I just created a project with isolated use case. Here I don't have the problem. :+1: I try to dig down now. Hopefully this is just another user error. Will inform you here as soon as I have isolated the cause.

tedbarth commented 1 year ago

@FirstGearGames I managed to isolate the cause: The problem appears when the CSP projectile is predicted spawned from off another CSP object (here the player object) that does reconciliation. When I just don't call the CSP Player's [Reconcile] method (e.g. on TimeManager.OnPostTick) on the server side...

The problem would also disappear when the projectile is not a CSP object itself. It does not matter what the reconcile data looks like or if it is set to default.

CSP Player object (predicted spawns the CSP projectile):

  protected override void OnNetworkPostTick() {
    if (IsServer) {
      ReconcileMoveData reconcileMoveData = new ReconcileMoveData {
        Position = transform.position,
        Rotation = transform.rotation.eulerAngles.z,
        LinearVelocity = _body.velocity,
        AngularVelocity = _body.angularVelocity,
      };

      // If I don't reconcile, the predicted spawned CSP projectile object plays sound only once
      // and is not hold back with position updates on the client.
      // Reconcile(reconcileMoveData, true);
    }
  }

The CSP projectile is predicted spawned in a method on the client side of the CSP player's component via ServerManager.Spawn(projectile); as part of another TimeManager.OnTick() delegate method.

My assumption is that the CSP Player's reconciliation causes it's launched CSP object - which is not a child object of the player - to get set back as part of the CSP players' reconciliation, too.

image

tedbarth commented 1 year ago

I switched from predicted spawning the CSP projectiles via ServerManager.Spawn(projectile); to predicted spawn via ServerManager.Spawn(projectile, ClientManager.Connection); (So with extra client connection argument) on the client. This solved the audio problem. But now, when shooting a projectiles the position update of the CSP player object is hold (or stepped) back, too:

https://youtu.be/225szFlQpY0 (I forgot to record the audio. Just believe me that the playOnAwake = true audio source is played only once and fully, now)

The same effect I see no matter if I spawn the projectile in the CSP Players' Update() or on TimeManager.OnTick:

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

      // Where input is read and values for the forces to apply are calculated (no forces are applied here)
      // Does call [Replicate] Move(moveData, false); with input data from client
      HandleAxesInput();

      if (_fire && !IsReconciling) {
        _weapon.Fire(); // Will instantiate the projectile and call ServerManager.Spawn(projectile, ClientManager.Connection);
      }
    }

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

I don't understand what is happening here.

FirstGearGames commented 1 year ago

I'm going to have to put this on hold for now given V2 is coming. Marking as 'wont fix'. If problem persist in v2 I'll look into it.

tedbarth commented 1 year ago

I just wanted to workaround the bug by launching the projectile on a different way but can't reproduce the working behavior anymore. Now, no matter how I spawn the CSP Projectile, whenever there is another CSP object in the scene doing some reconciliation the CSP projectile does this shit.

I'm totally stuck here. Could you give a hint on when the V2 arrives? Days, weeks, months, years? Is there a way for me to already test V2, to give an early feedback (especially on this issue)?

FirstGearGames commented 1 year ago

Maybe this will help? https://fish-networking.gitbook.io/docs/manual/guides/client-side-prediction/limitations-and-gotchas