by Pivec Labs
⏱ 90–120 min GC2 Character Tutorial

Game Creator 2 — Multiplayer Character

Build a full multiplayer third-person game using Game Creator 2 and NetCrafter. GC2's character system handles all locomotion locally; NetCrafter handles the server protocol. Only the Unity-side scripts differ from the standard Character tutorial — the schema, server extension, and zone config are identical.

90–120 min Unity 6.3 LTS URP Game Creator 2 SFS3 NetCrafter 1.1.0

Compatibility note

  • This tutorial demonstrates NetCrafter's compatibility with Game Creator 2.
  • The network schema, server extension, and zone config are identical to the Character tutorial.
  • Only the Unity-side C# scripts differ — they use GC2's API instead of Starter Assets.
  • If you completed the Character tutorial first, you can skip B1–B2 and re-use the generated code.

Prerequisites

🎮
Unity 6.3 LTS

Unity Editor with URP (Universal Render Pipeline) template.

✓ Required — use URP for correct material colour support.

🕹️
Game Creator 2

GC2 Core + Characters package installed via Package Manager.

✓ Required — this tutorial uses GC2's Character and locomotion system.

📦
NetCrafter

NetCrafter 1.1.0+ imported into the Unity project.

✓ Required — open the NetCrafter window via Tools → Pivec Labs.

🖥️
SmartFoxServer 3

SFS3 running locally on port 9977. Java 21 + Maven installed.

✓ See Deployment Guide for setup steps.

About the Examples folder

Assets/PivecLabs/Examples/GC2/ ships with a prebuilt reference scene (GC2DemoScene.unity) for comparison once you're done — it is not a shortcut. Build your own scene from scratch by following the steps below.

The example's scripts are wrapped in NETCRAFTER_GC2_EXAMPLE/NETCRAFTER_GC2_GENERATED compile guards, which are off by default — they only compile once Game Creator 2 is actually installed and you've added those define symbols yourself (Edit → Project Settings → Player → Scripting Define Symbols). Until then, components referencing them will show as "missing script" in the Inspector — that's expected.

Placeholder note: the example's RemoteGC2CharacterPrefab uses a plain capsule, not a skinned GC2 model, so GC2CharacterColorManager's per-player tinting (which only affects SkinnedMeshRenderers) won't visibly do anything until you swap in a real rigged character.

A1

Project Setup §

Before writing any scripts, configure the project so GC2 and NetCrafter example scripts can coexist without compile errors.

Step 1Add the compile define

Go to Edit → Project Settings → Player → Other Settings → Scripting Define Symbols and add:

define
NETCRAFTER_GC2_EXAMPLE

This guard wraps all GC2 example scripts in the source project. In this tutorial project the scripts are clean — no guard needed.

⚠️
No assembly definition needed. Do not add a PivecLabs.Examples.asmdef to the Examples/GC2/ folder. Example scripts must compile into Assembly-CSharp so they can see the generated types (SmartFoxConnectionManager, PlayerJoinedEvent, etc.).
A2

Use the GC2 Sample Scene §

Step 1Open the GC2 character example scene

In the Project window, navigate to:

path
Assets/Plugins/GameCreator/Installs/GameCreator.Examples@1.11.28/1 - Characters/1_PlayerMovement.unity

Open it, then save a working copy via File → Save As:

path
Assets/Scenes/GC2CharacterScene.unity

Step 2Verify single-player movement

Press Play. The GC2 character should:

  • Move with WASD / left stick
  • Jump with Space
  • Have the camera following from behind (Cinemachine or GC2's follow camera)

If movement works, single-player setup is confirmed. Stop Play and continue.

A3

GC2 Components to Sync §

Before writing network code, understand what GC2 exposes for multiplayer use.

Player hierarchy

hierarchy
Player                          ← root; has Character (GC2) component
└── Mannequin                   ← GC2 mannequin pivot (y = -1 offset)
      └── Mannequin             ← has Animator (CompleteLocomotion controller)
            ├── Armature        ← bone hierarchy root (Hips → Spine → …)
            └── Character       ← SkinnedMeshRenderer (mesh + materials)

Key GC2 API for networking

ExpressionTypePurpose
character.IsPlayerboolSet false on remote clones — disables ALL GC2 player-input units
character.Player.IsControllableboolSecondary input gate (not needed for remote clones)
character.Animim.AnimatorAnimatorThe Unity Animator on the inner Mannequin
character.Driver.IsGroundedboolWhether the character is touching the ground
character.Driver.WorldMoveDirectionVector3Current world-space velocity direction
character.Motion.IsJumpingboolWhether a jump is active

CompleteLocomotion animator parameters

All 17 parameters in the CompleteLocomotion.controller are Float — there are no Bool parameters. Key ones for network sync:

ParameterDefaultUsage
Speed0Overall movement speed — set on remote characters to drive blend tree
Speed-XZ0Horizontal speed — drive idle/walk/run blend on remote characters
Grounded11 = on ground, 0 = airborne (float, not bool)
Intent-X/Y/Z0Input intent direction
Stand1Stand level (1 = standing)
Phase-0…30Animation phase channels

No CharacterController to disable

GC2 uses its own UnitDriverController (inside the Character component) for physics. There is no separate CharacterController, ThirdPersonController, or StarterAssetsInputs to disable on remote clones. Setting character.IsPlayer = false is the single required step.

B1

NetCrafter Schema §

Create a new schema named GC2Character using the Definition Tools:

  1. Go to Tools → Pivec Labs → Definition Tools.
  2. Click Create Definition.
  3. Name it: GC2Character.
  4. Click Create.

Then configure the schema fields as described below.

SmartFox Settings

Go to NetCrafter → Settings → SmartFox Settings and configure:

FieldValue
Host127.0.0.1
ZoneGC2Zone
Extension NameGC2Character
Extension Classcom.piveclabs.gc2.GC2CharacterExtension
TCP Port9977
WS Port9977

Click Save Settings after making changes.

⚠️
These settings are used by the Config Generator and Server Code Generator. The Extension Class must match exactly what is in your generated Java extension.

Project Settings

FieldValue
NamespaceGC2Character
Overwrite Filestrue

ClientToServer Message

Create one message named PlayerUpdate with transport ExtensionRequest and handler HandlePlayerUpdate:

FieldTypeDescription
posXFloatWorld X position
posYFloatWorld Y position
posZFloatWorld Z position
rotYFloatY-axis rotation (euler degrees)
speedFloatAnimator "Speed" parameter
motionSpeedFloatAnimator "Speed-XZ" parameter
animStateIntDiscrete state (0=idle, 1=moving, 2=airborne), mirroring Character's stateHash / VR's animState
groundedFloatGC2's "Grounded" animator blend value
speedYFloatVertical velocity, for jump/fall blending
standFloatGC2's "Stand" animator blend value (crouch/stand)
colorIndexIntPlayer colour (0–7)

ServerToClient Events

Create three events (handler name: leave blank — the server broadcasts these):

PlayerStateUpdate

FieldType
playerIdInt
posXFloat
posYFloat
posZFloat
rotYFloat
speedFloat
motionSpeedFloat
animStateInt
groundedFloat
speedYFloat
standFloat

PlayerJoined

FieldType
playerIdInt
playerNameString
colorIndexInt
spawnIndexInt

PlayerLeft

FieldType
playerIdInt

Server Action

FieldValue
NameHandlePlayerUpdate
TriggerPlayerUpdate
Response EventPlayerStateUpdate
LogicBroadcastAll
B2

Generate All Code §

⚠️
Before generating: confirm the namespace is set correctly. Open NetCrafter → Settings → Project Settings and check the Namespace field. It must show GC2Character — not Character. If it shows Character, change it to GC2Character and save before generating. This prevents a namespace conflict with Game Creator 2's Character component.

Step 1Generate client code

In the NetCrafter window → Code tab → click Generate Client Code. This creates SmartFoxConnectionManager.cs, PlayerJoinedEvent.cs, PlayerStateUpdateEvent.cs, PlayerLeftEvent.cs, and related types under Definitions/GC2Character/Generated/Client/.

Step 2Generate and build server code

Click Generate Server Code then Deploy JAR. The extension JAR is named GC2CharacterExtension.jar and is copied to your SFS3 extensions/GC2Character/ folder.

Step 3Add the generated define

In Edit → Project Settings → Player → Scripting Define Symbols, add:

define
NETCRAFTER_GC2_GENERATED

This unlocks the #if NETCRAFTER_GC2_GENERATED blocks in GC2NetworkManager.cs that call SmartFoxConnectionManager and the generated event types.

🚨
Critical Step — Add Second Define Symbol

After generating client code you MUST add a second scripting define symbol or the multiplayer scripts will not compile correctly and Login() will never be called:

  1. Go to Edit → Project Settings → Player → Other Settings
  2. Find Scripting Define Symbols
  3. Add: NETCRAFTER_GC2_GENERATED
  4. Click Apply

⚠ Without this define, the connection will appear to work (you will see "Connected — waiting for Login()") but Login() will never be called and your game will be stuck forever.

B2 Checklist — before continuing

  • ☐ Client code generated ✓
  • ☐ NETCRAFTER_GC2_EXAMPLE defined ✓
  • ☐ NETCRAFTER_GC2_GENERATED defined ✓ ← Easy to miss!
  • ☐ Zero compile errors ✓
B3

Create the Remote GC2 Character Prefab §

Remote GC2 characters are instances of the same GC2 Player prefab, stripped of camera-follow components and extended with network sync scripts.

Step 1 — Locate the GC2 Player prefab

In the Project window, find the Player prefab added by the GC2 Characters package. It is typically at:

Path
Assets/Plugins/GameCreator/Installs/GameCreator.Characters@<version>/Player.prefab

Alternatively, use the Player GameObject already in your scene from Part A.

Step 2 — Duplicate and rename

In the Hierarchy, right-click Player → Duplicate. Name the duplicate RemoteGC2Character.

Step 3 — Remove camera components

Unlike the Starter Assets character, there are no input scripts to remove manually. RemoteGC2CharacterController calls character.IsPlayer = false in Awake(), which disables all GC2 input in one call.

You do need to remove camera-follow components so the scene camera does not try to track this remote character:

  • Remove or disable any GC2 Camera Motor child GameObject
  • Remove or disable any Cinemachine Virtual Camera that follows this character
  • Remove any camera target components that reference the main follow camera

Step 4 — Add network components

On RemoteGC2Character, use Add Component to attach:

  • RemoteGC2CharacterController
  • GC2CharacterColorManager
  • RemoteAnimationEventReceiver
⚠️
Do not add PlayerNameDisplay here. It must be on the NameCanvas child — see Step 5. Adding it to the root causes LookAt() to rotate the entire character toward the camera.

Step 5 — Set up the name display

5.1 Create the Canvas

  1. Right-click RemoteGC2Character in the Hierarchy → UI → Canvas.
  2. Name it NameCanvas.
  3. On the Canvas component: set Render Mode to World Space.
  4. On the NameCanvas Transform:
    • Position: X=0, Y=2.2, Z=0
    • Rotation: X=0, Y=0, Z=0
    • Scale: X=0.01, Y=0.01, Z=0.01
    ⚠️
    The default Canvas scale is 100× too large for world space. Setting scale to 0.01 makes it the right size relative to the character.
  5. On the Rect Transform: Width = 200, Height = 50.

5.2 Add the Text

  1. Right-click NameCanvas → UI → Text - TextMeshPro.
  2. Name it NameText.
  3. If prompted to import TMP Essentials, click Import TMP Essentials.
  4. On the Rect Transform: set Anchor to stretch/stretch (fill the canvas); set Left, Right, Top, Bottom offsets to 0.
  5. On the TMP_Text component:
    • Alignment: Center Middle
    • Font Size: 36
    • Color: White

5.3 Add and wire PlayerNameDisplay

  1. Select the NameCanvas child of RemoteGC2Character.
  2. Click Add Component and add PlayerNameDisplay.
    ⚠️
    PlayerNameDisplay must be on NameCanvas, not on the root RemoteGC2Character. The component calls transform.LookAt() in LateUpdate to billboard the label toward the camera. When placed on the root, this rotates the entire character. On NameCanvas it only rotates the name label.
  3. On the PlayerNameDisplay component:
    • Name Text: drag the NameText TMP child from the Hierarchy into this field.
    • Camera Transform: leave empty — GC2NetworkManager assigns this at runtime from the main camera.

Step 6 — Save as a prefab

Drag RemoteGC2Character from the Hierarchy into:
Assets/PivecLabs/Examples/GC2/Prefabs/RemoteGC2Character.prefab

(Create the Prefabs folder first if it does not exist.)

Step 7 — Remove from the Hierarchy

Delete RemoteGC2Character from the Hierarchy — it is now a prefab asset and no longer needs to live in the scene. You will assign it to GC2NetworkManager in Part B4.

Components on the final RemoteGC2Character prefab

ComponentStatus
Transform✓ Kept
Character (GC2)✓ Kept — IsPlayer = false is set at runtime to disable all GC2 input
Animator (on inner Mannequin)✓ Kept
RemoteGC2CharacterController✓ Added
GC2CharacterColorManager✓ Added
PlayerNameDisplay (on NameCanvas child)✓ Added to NameCanvas
RemoteAnimationEventReceiver✓ Added
GC2 Camera Motor / Cinemachine target✗ Remove
ℹ️
GC2 does not use ThirdPersonController, StarterAssetsInputs, or PlayerInput — there are no such components to remove. All GC2 player input is blocked by setting character.IsPlayer = false at runtime.
B4

Scene Setup §

B4.1Create the NetworkManager

  1. In the Hierarchy, right-click the scene name → Create Empty.
  2. Name it NetworkManager.
  3. Confirm its Transform Position is 0, 0, 0 (click ⋮ → Reset if not).
  4. With NetworkManager selected, click Add Component, search for GC2NetworkManager, and add it.

B4.2Add GC2UIManager

  1. Create a second empty GameObject named UIManager.
  2. Add the GC2UIManager component to it.
  3. (Or add both components to the same NetworkManager GameObject — either approach works.)

B4.3Assign the remote character prefab

  1. Select NetworkManager → GC2NetworkManager component.
  2. In the Remote Players section, drag RemoteGC2Character.prefab from:
    Assets/PivecLabs/Examples/GC2/Prefabs/RemoteGC2Character.prefab
    into the Remote Character Prefab field. Confirm it shows RemoteGC2Character (not None).

B4.4Add the ConnectionManager prefab

  1. In the NetCrafter window, go to Scenes → Prefab Generator → Create ConnectionManager Prefab.

B4.5Add to existing scene

  1. In the NetCrafter window, click Scene Assistant → Add to Existing Scene.
  2. This places ConnectionManager, NetCrafterSceneEventRouter, and NetCrafterEventDispatcher in the scene.

B4.6Wire Scene Events

In the NetCrafter window → Scenes → Scene Events, wire the three network events to GC2NetworkManager:

EventTarget Method
PlayerJoinedGC2NetworkManager.OnPlayerJoined()
PlayerLeftGC2NetworkManager.OnPlayerLeft()
PlayerStateUpdateGC2NetworkManager.OnPlayerStateUpdate()

GC2NetworkManager automatically subscribes to the RoomJoined event in Start() via NetCrafterEventDispatcher — no manual wiring needed for room join.

B4.7Run In Background

To keep the connection alive when the Unity Editor loses focus during multiplayer testing:

  1. Go to Edit → Project Settings → Player → Resolution and Presentation
  2. Enable Run In Background

B4.8Add GC2CharacterColorManager to the local Player

Select the Player GameObject in the scene and add the GC2CharacterColorManager component. GC2NetworkManager uses this at runtime to tint the local character with the player’s assigned colour.

B5

GC2NetworkManager §

GC2NetworkManager.cs is the top-level coordinator. It handles the connection lifecycle, spawns and removes remote GC2 characters, runs the 20 Hz send loop, and forwards typed events from the NetCrafter event router.

Key responsibilities

  • Awake() — finds the GC2 Character in the scene; sets character.IsPlayer = false to block all input during login
  • Connect() — called by the login button; passes the player name to SmartFoxConnectionManager
  • OnRoomJoined() — sets character.IsPlayer = true (re-enables GC2 input); gets the Animator via character.Animim.Animator; starts the send loop
  • SendPlayerUpdate() — runs at 20 Hz (50 Hz while jumping); reads position, rotation, and GC2 animator params; sends via SmartFoxConnectionManager
  • OnPlayerJoined() / OnPlayerLeft() — spawn/destroy remote character GameObjects
  • OnPlayerStateUpdate() — forwards state to the matching RemoteGC2CharacterController
csharp — GC2NetworkManager.cs (Awake)
private void Awake()
{
    // Find the GC2 Character in the scene (local Player has IsPlayer = true by default)
    _localCharacter = FindAnyObjectByType<Character>();
    if (_localCharacter != null)
        _localCharacter.IsPlayer = false; // block all GC2 input until room join
}
csharp — GC2NetworkManager.cs (OnRoomJoined)
public void OnRoomJoined()
{
    Cursor.lockState = CursorLockMode.Locked;
    Cursor.visible   = false;

    if (_localCharacter != null)
    {
        _localCharacter.IsPlayer = true;                  // re-enable all GC2 input
        _localAnimator = _localCharacter.Animim.Animator; // Animator lives on inner Mannequin
    }
#if NETCRAFTER_GC2_GENERATED
    int uid = SmartFoxConnectionManager.Instance?.LocalUserId ?? 0;
    _localPlayerId = uid >= 0 ? uid : 0;
#endif
    _localColorIndex = _localPlayerId % GC2CharacterColorManager.PlayerColors.Length;

    if (_localCharacter == null)
    {
        Debug.LogError("[GC2NetworkManager] No GC2 Character found in scene.");
        return;
    }
    _localCharacter.GetComponent<GC2CharacterColorManager>()?.SetColor(_localColorIndex);
    _localCharacter.GetComponentInChildren<PlayerNameDisplay>(true)?.Hide();

    _isConnected = true;
    uiManager?.ShowGamePanel();
    StartCoroutine(SendUpdateLoop());
}
csharp — GC2NetworkManager.cs (OnPlayerStateUpdate)
public void OnPlayerStateUpdate()
{
#if NETCRAFTER_GC2_GENERATED
    var payload = NetCrafterSceneEventRouter.Instance?.GetLastPayload("PlayerStateUpdate");
    if (!(payload is PlayerStateUpdateEvent ev)) return;
    if (!_remoteCharacters.TryGetValue(ev.playerId, out var remoteGo)) return;
    var ctrl = remoteGo?.GetComponent<RemoteGC2CharacterController>();
    ctrl?.SetNetworkState(
        new Vector3(ev.posX, ev.posY, ev.posZ),
        ev.rotY, ev.speed, ev.motionSpeed, ev.animState,
        ev.speedY, ev.grounded, ev.stand);
#endif
}

Key differences from CharacterNetworkManager

FeatureCharacterNetworkManagerGC2NetworkManager
Character lookupFindAnyObjectByType<ThirdPersonController>()FindAnyObjectByType<Character>()
Disable inputDisable ThirdPersonController, StarterAssetsInputs, PlayerInputcharacter.IsPlayer = false
Re-enable inputN/A (remote prefab lacks those components)character.IsPlayer = true
Animator accesscharacter.GetComponent<Animator>()character.Animim.Animator (nested inside Mannequin)
Velocity sourceThirdPersonController speedcharacter.Driver.WorldMoveDirection
Jump stateInternal TPC fieldcharacter.Motion.IsJumping
MotionSpeed param"MotionSpeed""Speed-XZ" (GC2 CompleteLocomotion)
Send rate (jumping)20 Hz constant50 Hz while jumping

Character type alias

using Character = GameCreator.Runtime.Characters.Character; is declared at the top of GC2NetworkManager.cs. This resolves the naming conflict that arises when using GameCreator.Runtime.Characters; makes Character ambiguous (GC2 uses it as both a class name and a sub-namespace prefix). The namespace for generated types is set to GC2Character (not Character) in Project Settings to avoid the same conflict.

B6

Login UI §

GC2UIManager.cs manages two panels: a login screen before the player connects, and a game panel showing the connected-players list. It is structurally identical to CharacterUIManager.

B6.1Create the Canvas

  1. Right-click the Hierarchy → UI → Canvas — Render Mode: Screen Space - Overlay
  2. Name it UICanvas

B6.2Create the Login Panel

  1. Right-click UICanvas → UI → Panel — name it LoginPanel
  2. Add a TMP Text child — title: GC2 Character Demo, font size 36, centre-aligned
  3. Add a TMP InputField — name it PlayerNameInput, placeholder: Enter your name...
  4. Add a TMP Button — name it ConnectButton, label: Connect
  5. Add a TMP Text — name it StatusText, small font, for connection messages

B6.3Create the Game Panel

  1. Right-click UICanvas → UI → Panel — name it GamePanel, set inactive in the Inspector
  2. Resize to a small corner panel (e.g. top-right, 200×200 px)
  3. Add a TMP Text child — name it PlayerListText, set alignment to top-left

B6.4Attach and wire GC2UIManager

  1. Select the UIManager GameObject (created in B4.2); confirm GC2UIManager is attached
  2. In the Inspector, wire all fields:
    • Login Panel → LoginPanel
    • Player Name Input → PlayerNameInput
    • Connect Button → ConnectButton
    • Status Text → StatusText
    • Game Panel → GamePanel
    • Player List Text → PlayerListText
  3. Wire ConnectButton’s On Click event → GC2NetworkManager.Connect()
⚠️
GC2 does not manage cursor lock at the engine level by default. GC2UIManager.ShowLoginPanel() unlocks the cursor so the player can use the login form. GC2NetworkManager.OnRoomJoined() locks it again for gameplay. No changes to GC2’s internal scripts are needed.
B7

SFS3 Zone Config §

In the NetCrafter window → Code → Config Generator → click Generate Zone Config. Copy the generated GC2Zone.zone.xml to your SFS3 server/zones/ folder and restart SFS3.

The generated config will match this structure (required stubs included to prevent SFS3 NullPointerExceptions):

xml — GC2Zone.zone.xml
<zone>
  <name>GC2Zone</name>
  <allowGuestUsers>true</allowGuestUsers>
  <guestUserNamePrefix>Guest#</guestUserNamePrefix>
  <maxUsers>100</maxUsers>
  <wordsFilter active="false"/>
  <floodFilter active="false"/>
  <rooms>
    <room>
      <name>GameRoom</name>
      <groupId>default</groupId>
      <password></password>
      <maxUsers>50</maxUsers>
      <maxSpectators>0</maxSpectators>
      <isDynamic>false</isDynamic>
      <isGame>false</isGame>
      <isHidden>false</isHidden>
      <autoRemoveMode>DEFAULT</autoRemoveMode>
      <permissions>
        <flags>PUBLIC_MESSAGES,ROOM_NAME_CHANGE</flags>
        <maxRoomVariablesAllowed>30</maxRoomVariablesAllowed>
      </permissions>
      <events>USER_ENTER_EVENT,USER_EXIT_EVENT</events>
      <badWordsFilter isActive="false"/>
      <roomVariables/>
      <extension>
        <name></name><type>JAVA</type><file></file>
        <propertiesFile></propertiesFile><reloadMode>AUTO</reloadMode>
      </extension>
      <mmoSettings>
        <isActive>false</isActive>
        <defaultAOI>100,100,0</defaultAOI>
        <lowerMapLimit></lowerMapLimit>
        <higherMapLimit></higherMapLimit>
        <forceFloats>false</forceFloats>
        <userMaxLimboSeconds>50</userMaxLimboSeconds>
        <proximityListUpdateMillis>500</proximityListUpdateMillis>
        <sendAOIEntryPoint>true</sendAOIEntryPoint>
      </mmoSettings>
      <allowOwnerInvitation>true</allowOwnerInvitation>
      <suppressUserList>false</suppressUserList>
      <allowAudioStreaming>false</allowAudioStreaming>
    </room>
  </rooms>
  <privilegeManager active="false"/>
  <extension>
    <name>GC2Character</name>
    <type>JAVA</type>
    <file>com.piveclabs.gc2.GC2CharacterExtension</file>
    <propertiesFile></propertiesFile>
    <reloadMode>AUTO</reloadMode>
  </extension>
  <buddyList active="false"/>
  <databaseManager active="false"/>
  <udpSettings>
    <keepAlive>true</keepAlive>
    <rdpQualitySetting>BEST_EFFORT</rdpQualitySetting>
    <rdpReliableChannelType>2</rdpReliableChannelType>
  </udpSettings>
</zone>
B8

Test §

Step 1Start SFS3

Launch SFS3 and confirm the GC2Zone appears in the AdminTool. The GC2CharacterExtension should be listed under Extensions.

Step 2First client

  1. Press Play in Unity Editor.
  2. The Login Panel appears. Enter a name (e.g. Alice) and press Connect.
  3. The Login Panel hides, cursor locks, and the GC2 character starts responding to WASD + Space.
  4. Alice's name should appear in the player list.

Step 3Second client

  1. Build and run a standalone player, or open a second Unity Editor instance.
  2. Enter a different name (e.g. Bob) and connect.
  3. In Alice's editor: Bob's GC2 character appears, tinted a different colour, with Bob's name tag above their head.
  4. Move Bob — Alice should see the animation playing correctly (idle → walk → run).
  5. Jump Bob — Alice should see the jump animation replicate.

Step 4Disconnect test

Stop Bob's client. Alice should see Bob's character disappear and the player list update automatically.

⚠️
If the remote character stands still but the local character moves: verify that SmartFoxConnectionManager.Instance?.SendExtensionRequest is being called (add a Debug.Log inside SendPlayerUpdate). Also confirm that the Scene Events are correctly wired on the NetCrafterSceneEventRouter component in the scene.
⚠️
If animation does not play on the remote character: confirm that RemoteGC2CharacterController is finding the Character component on Awake(). The Character component must be on the same root GameObject as the RemoteGC2CharacterController.
→

What's Next §

The GC2 tutorial establishes the same multiplayer foundation as the Character tutorial using GC2's API. Here are logical extensions:

GC2 Visual Scripting

Replace manual C# sync logic with GC2's Hotspot system — wire PlayerJoined scene events directly to GC2 Instructions (InstructionCharacterNavigationTarget) for zero-code remote movement.

GC2 States (Melee / Crouch)

Add stateIndex to the PlayerUpdate message. On the local side read from a GC2 State Machine; on remote side call InstructionCharacterStateEnter to trigger the same state.

GC2 Cinemachine Cameras

GC2's follow camera targets the local Player by name. Disable or repurpose the follow camera target on remote clone prefabs so the camera stays on the local player regardless of how many remote clones exist in the scene.

Character Selection

Add a characterIndex field to PlayerJoined. Swap the RemoteGC2Character prefab based on index — GC2's mannequin swap system lets you change the character mesh at runtime without rebuilding the animator graph.


This tutorial demonstrates NetCrafter's compatibility with Game Creator 2. The network schema, server extension, and zone config are identical to the Character Tutorial — only the Unity-side scripts differ. Any NetCrafter schema update applies equally to both tutorials without changes to the server layer.