Search code examples
c#unity-game-enginenetwork-programmingmultiplayer

Unity Fishnet Replicate Not Called On None Owned Clients


I'm using Unity with Fishnet networking and trying to make client side prediction work.

I have been asking this question on the discord help channel for 3 days now and I haven't received any response. So I'm posting here in hopes that someone can help me track down what's going on.

I have a [Reconcile] and [Replicate] function setup inside a player controller script. I'm using the Unity starter assets and have been referencing this: https://github.com/RidefortGames/FishNet-ThirdPersonPrediction/blob/main/Assets/Script/PredictionMotor.cs

to try and setup a multiplayer client side prediction controller. I have setup a NetworkTransform, NetworkObserver, and PredictedObject on my player controller.

I have registered to the OnTick Event like so:

private void Awake()
{
    InstanceFinder.TimeManager.OnTick += TimeManager_OnTick; // Called right before physics sim, liked FixedUpdate
    InstanceFinder.TimeManager.OnUpdate += TimeManager_OnUpdate;
    _controller = GetComponent<CharacterController>();
}

My OnTick Looks like the following:

private void TimeManager_OnTick()
        {
            if (base.IsOwner)
            {
                Reconciliation(default, false);
                CheckInput(out MoveData md);
                Move(md, false);
            }
            if (base.IsServer)
            {
                Move(default, true);
                ReconcileData rd = new ReconcileData()
                {
                    Position = transform.position,
                    Rotation = transform.rotation,
                    VerticalVelocity = _verticalVelocity,
                    FallTimeout = _fallTimeoutDelta,
                    JumpTimeout = _jumpTimeoutDelta,
                    Grounded = Grounded
                };
                Reconciliation(rd, true);
            }
        }

I have my Move(... setup like:

[Replicate]
        private void Move(MoveData md, bool asServer, Channel channel = Channel.Unreliable, bool replaying = false)
        {
            if (!base.IsOwner) Debug.Log("REPLICATE");
            if (asServer || replaying)
            {
                float delta = (float)base.TimeManager.TickDelta;
                JumpAndGravity(md, delta);
                GroundedCheck();
                MoveWithData(md, delta);
            }
            else if (!asServer)
                _clientMoveData = md;
        }

I decided to move my character inside the TimeManager_Update like the following because it produced smoother movement:

private void TimeManager_OnUpdate()
        {
            if (base.IsOwner)
            {
                // NOTE: The "_clientMoveData" is set inside the Tick, but played here.
                JumpAndGravity(_clientMoveData, Time.deltaTime);
                GroundedCheck();
                MoveWithData(_clientMoveData, Time.deltaTime);
            }
        }

This all works great when I move the owner client and all the animations and movement play on the server. However, if I move a Owner client on the server and look at it on the other client (that isn't also the server client) it will not play any animations.

So I added the following into my [Replicate] function:

if (!base.IsOwner) Debug.Log("REPLICATE");

and it is never called.

My thought was that replicate and reconcile work like the following:
[Replicate]

  1. The owner client sends movedata to the server
  2. The server will take that move data and play it
  3. It will then tell all other clients to play that same function with the same move data

[Reconcile]

  1. The server will send data values to all clients
  2. All clients compare their local values to make sure they match, if not will reset them to received values

From what I'm seeing it looks like [Replicate] and [Reconcile] are only ever called on the Owner and Server, but never NoneOwners.

Is this right? Am I doing something wrong? Any help on this one would be greatly appreciated as I'm super confused.

Here are some helpful videos demonstrating my issue.

Server Client To None Server Client: enter image description here

None Server Client To Server Client: enter image description here


Solution

  • Okay I did get some answer and it doesn't work like I thought it did. Basically this is how it works.

    [Replicate]
    This will ONLY send data from the owning client to the server. The owning client will run the functions first with the data it sends then the server will receive that data and run the function. Unfortunately the server WILL NOT send that data on to other clients.

    [Reconcile]
    This will ONLY send from the server back to the owning client. It will not send this data to none-owned clients.

    So basically there are a couple of options for me to pursue. You can add a NetworkTransform component to sync position/rotation/scale, and a NetworkAnimator to sync the animations. The other alternative is to run your own RPC methods, excluding the owner, to get the clients to perform the actions.

    Since the NetworkTransform and NetworkAnimator are fairly optimized I will be sticking with this method. I just hope it will work if I plan on running 100 players on a server.