Networking a Clients float value through Photon Fusion

791 Views Asked by At

I am currently developing a multiplayer racing game in Unity that utilizes Photon Fusion. In all the documentation, Photon Fusion uses the Input methods to sync the positions of the players and everything under FixedUpdateNetwork(). But in my game, I just need to Network a float variable, that changes based on players input through a UI button. So basically, the car will move forward at a fixed speed at first. And based on the players input, the speed varies for each player.

I first attempted to sync the variable using [Networked]. The Host variable only gets synced across builds. But, not from clients to the host. The client's value never gets updated in either of the client's car or in the host game. I get an update that the value is applied to the client's car for a split second before it glitches and returns to its normal position. This only occurs in the client build. And then I tried the RPC method. It worked fine, but sometimes the client's car moves way faster than the hosts. It is recommended in the Photon Fusion documentation not to use RPC all of the time. So, how do I achieve this function perfectly?

This is the script I use to update the speed variable. Inside the car gameObject I have EntityScript, ControllerScript and other fusion mandatory scripts. EntityScript controls the UI and checks for UI inputs, while ControllerScripts controls the position of the gameObjects.

EnityScript.cs

public override void FixedUpdateNetwork()
{
base.FixedUpdateNetwork();
Debug.Log($"Applied Speed of {Car.name} : {AppliedSpeed}");
transform.position += AppliedSpeed * transform.forward * Runner.DeltaTime;
}

ControllerScript.cs

private void SpeedApplied()
{
if (Object.HasInputAuthority)
{
    if (Hud.answer.text == answerValue.ToString())
    {
        Controller.AppliedSpeed += 10;
        Debug.Log($"CheckAnswer AppliedSpeed: {Controller.AppliedSpeed}");
    }
    else
    {
        Controller.AppliedSpeed = 15f;
    }
}
}
1

There are 1 best solutions below

1
On BEST ANSWER

TL;DR

You are changing a [Networked] variable locally in the client, as seen in your Object.HasInputAuthority checking.

[Networked] variables can only be changed by the StateAuthority, then it will automatically be synced across clients.

You should find a way for the client to communicate with the host to change the [Networked] variable. The easiest way would be via RPCs from the client to the StateAuthority.

The long answer

I am going to make a few assumptions about the requirements of your game, my apologies if I have misunderstood, but I hope this may still help you.

I will then clear up some concepts, so that at least we are in the same grounds before we can discuss your situation.

Then I will diagnose the problems in your code, and give you a suggestion of how I would write the code, which could hopefully be of some help to you.

Assumptions

For your game, I assume that

  • Each player has a car
  • Each car first moves at a constant speed
  • Each player has a UI button
  • When an individual player presses the UI button, that player's car will move faster
  • You will eventually end up with different cars moving at different speeds
  • Except for the UI, you expect the speeds of the cars and the visuals of the cars are synced across all clients, including the host.

For your code, I assume that

  • Each car is a network object spawned by the host which has StateAuthority
  • Each car is given the InputAuthority of the player that controls the car
  • Each car has at least the following components
    • Transform
    • NetworkObject
    • NetworkTransform
    • EntityScript
    • ControllerScript
  • Each car has the visuals in a separate child game object and referenced in the InterpolationTarget of the NetworkObject
  • AppliedSpeed is a public [Networked] variable in the EntityScript

Concept Overview

Only StateAuthority can change the network state, including [Networked] variables. If you have a NetworkTransform component on the GameObject, the transform of that GameObject will then be also part of the network state. The transform of the GameObject in the host should be regarded as the single source of truth, and that will be synchronized across the clients.

So whether you are using NetworkInput or RPC, you have to ensure that it is the StateAuthority doing the changes to the network state. Weird bugs will happen if only the client tries to change the network state.

Diagnosis

AppliedSpeed is a [Networked] variable. You are trying to change this variable in a if (Object.HasInputAuthority). This means that you are asking the InputAuthority to change a [Networked] variable locally. As mentioned above, only StateAuthority can change [Networked] variables.

This would most likely explain why the value would be applied for a split second then returning back to its original value. This is most likely because after the client changes the value [Networked] variable locally, it becomes different than the value of the host. It will then self-correct itself to match the host, because the host is the absolute source of truth and only StateAuthority can change the network state.

My Suggestion

Keep note that since this method uses RPCs, RPCs are not tick aligned and there is no prediction involved. Clients would probably feel some delay after clicking the button and seeing the car move faster. You may want to refactor and use NetworkInput instead if it is critical to have everything tick-aligned and you would like to use prediction.

EntityScript.cs

public class EntityScript : NetworkBehaviour
{
    [Networked] public float AppliedSpeed {get; set;}

    // ...

    public override void Spawned()
    {
        // Set the initial speed
        AppliedSpeed = 15f;
    }

    public override void FixedUpdateNetwork()
    {
        transform.position += AppliedSpeed * transform.forward * Runner.DeltaTime;
    }

    // ...
}

ControllerScript.cs

public class ControllerScript : NetworkBehaviour
{
    // I assume you would reference the EntityScript component here
    [SerializedField] public EntityScript Controller;

    // ...

    // Called locally on the client from UI button press
    public void OnButtonPress()
    {
        RPC_ChangeAppliedSpeed();
    }

    // Only StateAuthority can change networked variables
    // So the client sends an RPC to the host to change the speed
    [Rpc(RpcSources.All, RpcTargets.StateAuthority)]
    private void RPC_ChangeAppliedSpeed()
    {
        // The networked variable will be synced automatically across all clients
        Controller.AppliedSpeed += 10;
    }

    // ....
}

Hope this helps!

By this all people will know that you are my disciples, if you have love for one another. John 13:35 (ESV)