Multiplayer - pirate-games/LabRats GitHub Wiki

Multiplayer

This game is entirely multiplayer and currently uses Unity's Netcode for GameObjects v1.7.1. We also make use of Unity's relay.

Here is the link to the main documentation page for NetCode.

Server Connection

In short, the server connection is handled by the script 'NetworkConnector'. The Create() script handles hosting the server connection. The Join(string joinCode) makes sure players can join a server once they put in a correct code.

Built-in Scripts

Netcode provides us with a few built-in scripts that we can use to synchronize the object's data in both instances (client-side & host-side) of the scene. These two scripts are:

  • NetworkObject

  • NetworkTransform

All interactable objects need to have the NetworkObject script in order to work. The NetworkTransform script makes sure that the object's transform (the position, rotation and scale of the object), gets synchronized in both instances.

Important

When adding objects to a hierarchy, it is important to note that when you add an interactive object as a child to another GameObject in the scene hierarchy, you will have to add the NetworkObject script to the parent of that interactive object, all the way to the root object. If you fail to do this, you will get an error in the console (we got this way too many times at the start).

NetworkVariables

To sync other variables like booleans or floats that do not represent the object's transform, it's best to use NetworkVariables, which are set with the method ServerRpc and are retrieved with the method ClientRpc. These two methods are also provided by Netcode.

Code from Lab Rats/Assets/Lab/Materials/General/Liquid/Wobble.cs

    //NetworkVariables
    NetworkVariable thisColor = new NetworkVariable();
    NetworkVariable fillHeight = new NetworkVariable();
    NetworkVariable intensity = new NetworkVariable();

    //When spawning the object in the scene, set these networkvariables in this method
    public override void OnNetworkSpawn()
    {
        base.OnNetworkSpawn();

        if (IsHost)
        {
            CalculateValuesServerRpc();
            SetValue();
        }
        else
        {
            Debug.Log("SetMaterial");
            SetValue();
        }
    }

    private void SetValue()
    {
        Debug.Log("SetValue");
        rend.material.SetFloat("_EmissionIntemsity", intensity.Value);
        rend.material.SetFloat("_Fill", fillHeight.Value);
        rend.material.SetColor("_EmissonColor", thisColor.Value);  // Fix the typo here
        rend.material.SetColor("_LiquidColor", thisColor.Value);
        rend.material.SetColor("_SurfaceColor", thisColor.Value);
    }

    [ServerRpc]
    private void CalculateValuesServerRpc()
    {
        float randomHue = Random.Range(0f, 1f);
         intensity.Value = Random.Range(3, 9);
         fillHeight.Value = Random.Range(0.485f, 0.52f);
        // Set saturation and value to their maximum for full color strength
        thisColor.Value = Color.HSVToRGB(randomHue, 1f, 1f);
    }

ServerRpc is a method that the client can invoke. It will run code on the server side of the network. Useful when you have some methods that require the server's permission.

ClientRpc is a method that the server can invoke. It will run code on the client side of the network.

Important

When using any methods like ServerRpc or ClientRpc, any methods that uses the ServerRpc must have the ServerRpc or ClientRpc Suffix in the method name, and [ServerRpc] or [ClientRpc] methods on top of the method. The class that holds these functions must also inherit from the NetworkBehaviour class instead of the MonoBehaviour.

Example (SynchKnob.cs):

    [ServerRpc(RequireOwnership = false)]
    public void ChangeValueServerRpc(float value, ServerRpcParams serverRpcParams = default)
    {
        if (knob == null) return;
        var clientId = serverRpcParams.Receive.SenderClientId;
        if (NetworkManager.ConnectedClients.ContainsKey(clientId))
        {
            ChangeValueClientRpc(value, clientId);
        }
    }

    [ClientRpc]
    private void ChangeValueClientRpc(float value, ulong clientId)
    {
        knob.value = value;
    }

Grab Interactbles

Normally, grabbing objects in VR is only permissible on the server-side of the network, which is in our case the player that hosts. Clients are therefore unable to interact with objects without measures being taken to help remedy this issue. In order to share this permission, you will have to add the XRMultiplayerScript to the object in question.

The XRMultiplayerScript is a script we wrote that allows for ownership to be transferred from objects over the server. This is done by sending server rpc's when an object is grabbed and released. In order to make this work in Unity you will need to add the following events in the xr grab interactable of the object:

image

Custom Sockets & Knobs

The original XRKnob from the Unity XR library does not work with netcode so we wrote an extra script that can be attached to the knob to synch the value variable between clients. The value is synched by sending rpcs to the clients when the value is changed. Unfortunately we were unable to chenge the value to a NetworkVariable because the XRKnob already inherits from the XRBaseInteractible and can not inherit from a network behaviour.

For the socket interactor only the select entered event works, when grabbing an item out the object gets desynchronised, to avoid this we wrote a new socket script that snaps the position of an object to a given transform, because the new socket doesn't hold on to the object it does not cause a desynch.

public void Enter(T enteringObject)
{
   enteringObject.transform.SetPositionAndRotation(attachTransform.position, attachTransform.rotation);

   if (!enteringObject.TryGetComponent (out var m_Interactable)
      || m_Interactable.firstInteractorSelecting == null) return;
        
   m_Interactable.interactionManager.SelectExit(m_Interactable.firstInteractorSelecting, m_Interactable);
}

The socket is used in the conductivity puzzle and the Enter method is called by a TriggerDetector.

Sources

https://docs-multiplayer.unity3d.com/netcode/current/advanced-topics/message-system/serverrpc/

https://docs-multiplayer.unity3d.com/netcode/current/advanced-topics/message-system/clientrpc/

https://docs-multiplayer.unity3d.com/netcode/current/basics/networkvariable/

⚠️ **GitHub.com Fallback** ⚠️