Tutorial: Sync Player Characters

Steps to sync each player’s character position and RPCs in Unity without writing rules

In this tutorial, you build a small room in which each player who joins moves their own character, and it appears the same way on the other players’ screens.

  • When you connect, one character of your own is spawned.
  • Moving your character with the arrow keys (WASD) moves it smoothly on the other players’ screens too.
  • Pressing the Space key makes that character briefly grow larger on everyone’s screen (RPC).

You use the sync server type. You do not need to write room rules. For the differences between server types, see Server Types and How to Choose.

Workflow

StepWhere you workWhat you build
1. Configure the namespaceGS2The Realtime namespace settings
2. Create the character PrefabUnity EditorA Prefab with movement and RPC scripts
3. Connect to the roomUnity EditorA Prefab Catalog, and a scene that connects and spawns your character
4. Verify the behaviorUnity (2 clients)—
5. Restrict before releaseGS2The object catalog

You work in a Unity project (Unity 2021.3 or later) with the GS2 SDK for Unity. The sync components are included in the SDK, so no additional packages are required.

1. Configure the Namespace

In the GS2-Realtime namespace, specify the server type.

SettingFieldValue
Server typeserverTypesync

Preparing the room (GS2-Matchmaking and RoomWant) is the same as for the other server types.

2. Create the Character Prefab

Prepare the Scripts

This script moves your own character. You only move the Transform, and NetworkTransform sends the position to the other participants. The Space key sends an RPC to everyone.

using Gs2.Realtime.Sync;
using UnityEngine;

// Registered in NetworkEntity's Controlled Behaviours so it runs only on your own character
public sealed class PlayerMover : MonoBehaviour
{
    [SerializeField] float speed = 3f;

    void Update()
    {
        var move = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"));
        // Just move the Transform; NetworkTransform sends it to the other participants
        transform.position += move.normalized * (speed * Time.deltaTime);

        if (Input.GetKeyDown(KeyCode.Space))
            GetComponent<NetworkEntity>().Rpc(nameof(PlayerEmote.Wave), RpcTarget.All);
    }
}

Add [NetworkRpc] to methods called by RPC. A method that returns IEnumerator runs as a coroutine.

using System.Collections;
using Gs2.Realtime.Sync;
using UnityEngine;

public sealed class PlayerEmote : MonoBehaviour
{
    // The character briefly grows larger on everyone's screen
    [NetworkRpc]
    public IEnumerator Wave(NetworkRpcInfo info)
    {
        Debug.Log("Wave from connection " + info.Sender);
        transform.localScale = Vector3.one * 1.5f;
        yield return new WaitForSeconds(0.3f);
        transform.localScale = Vector3.one;
    }
}

info.Sender is the connection ID of the player who sent the RPC. The server sets it, so the sender cannot fake it.

Create the Prefab

  1. Create a Prefab for the character’s appearance (a Capsule, for example), and add the following components to its root.

    ComponentSetting
    NetworkEntityLeave Schema empty (only position is synced)
    NetworkTransformPosition Axes is All, Rotation Axes is None
    PlayerMoverThe script above
    PlayerEmoteThe script above
  2. Register PlayerMover in NetworkEntity > Controlled Behaviours. PlayerMover is then enabled only on characters you own. If you do not register it, your key input is also applied to the other players’ characters on your screen.

The SDK has standard fields for position and rotation, so they can be synced without a sync schema asset. To sync custom values such as HP, see Display Custom Values.

3. Connect to the Room

Place a NetworkWorld

  1. In the Project window, create a catalog with Create > GS2 > Realtime > Prefab Catalog.
  2. In the catalog’s Prefabs, register Id 1 and the Prefab from step 2. The Prefab ID is a number that represents the kind of object; other clients use this number to choose which Prefab to display.
  3. Create an empty GameObject in the scene, add NetworkWorld, and specify the catalog for Prefab Catalog.
  4. Place a Plane for the ground and a Camera looking down from above.

Add the Connection Script

Add NetworkSession and the following script to the same GameObject as the NetworkWorld. Obtain the room’s connection information (IP address, port, encryption key) and the access token as described in Obtaining connection information for the game server, and pass them in.

using Gs2.Realtime.Sync;
using UnityEngine;

[RequireComponent(typeof(NetworkWorld), typeof(NetworkSession))]
public sealed class PlayerRoomConnection : MonoBehaviour
{
    const uint PlayerPrefab = 1; // The Id registered in the Prefab Catalog

    NetworkWorld world;
    NetworkSession session;

    // Subscribe once: subscribing on each connection repeats the handlers after a reconnect
    void Awake()
    {
        world = GetComponent<NetworkWorld>();
        session = GetComponent<NetworkSession>();
        // Once the room state is applied, spawn your character.
        // The character is removed on disconnect, so create it again on each reconnect
        session.Ready += () => world.Instantiate(
            PlayerPrefab,
            new Vector3(Random.Range(-3f, 3f), 0, Random.Range(-3f, 3f)),
            Quaternion.identity);
        session.Disconnected += reason => Debug.Log("Disconnected: " + reason);
    }

    public void Connect(string accessToken, string ipAddress, int port, string encryptionKey)
    {
        session.Connect(accessToken, ipAddress, port, encryptionKey);
    }
}

There are three key points.

  • Create your own character with world.Instantiate. GameObjects created with Unity’s Instantiate are not shared with the other participants. The return value of world.Instantiate can be moved right away as your own (IsMine). Movement and RPCs before the room confirms the spawn are held and sent after it is confirmed.
  • NetworkWorld creates the other players’ characters. When it receives a spawn from the room, it creates a GameObject from the Prefab in the catalog and interpolates it toward the received positions.
  • When you connect, you receive the current state. Players who join later also see the characters of players already in the room at their current positions. Ready is raised once it has been applied.

4. Verify the Behavior

Connect to the same room from two clients. Connecting with the same userId replaces the earlier connection, so use access tokens for different userIds.

  1. Confirm that both screens show two characters.
  2. Move a character on one client, and confirm that the same character moves smoothly on the other screen too. Key input on the other client moves only its own character.
  3. Press the Space key on one client, and confirm that the character briefly grows larger on both screens.
  4. Disconnect one client, and confirm that its character disappears from the other screen. When it reconnects, a new character is spawned.

5. Restrict Before Release

With the settings so far, the room does not check the Prefabs players spawn or the RPCs they call. To restrict spawns and RPCs from modified clients, set an object catalog in the namespace setting syncSetting.objectCatalog.

  1. Right-click the Prefab Catalog asset and select Copy Object Catalog JSON. The synced fields and RPC IDs of the registered Prefabs are copied to the clipboard as JSON.
  2. Set that JSON in syncSetting.objectCatalog.

Once it is set, only registered Prefabs can be spawned, and owners can call only the RPCs registered in methods. RPC IDs are derived from method names, so regenerate the JSON when you rename a method.