On this page
Build your game

Multiplayer

CCDS multiplayer runs on Photon PUN2: rooms, networked vehicles, synchronised missions, chat and nametags.

Prerequisites

  1. Photon PUN2 imported (free tier is sufficient).
  2. A PUN App ID from https://dashboard.photonengine.com, entered via the PUN Wizard or the CCDS Setup Wizard.
  3. The PHOTON_UNITY_NETWORKING scripting symbol defined — PUN2 sets it on import.

Without PUN2, all networked code compiles out and CCDS runs single-player. That is a supported configuration, not a broken one.

Single-player and multiplayer are the same scenes

There is no separate multiplayer scene or mission set. CCDS_NetworkManager.networkMode decides the mode:

C#
CCDS_NetworkManager.Instance.SetSinglePlayerMode();
CCDS_NetworkManager.Instance.SetMultiplayerMode();

// Anywhere in gameplay code, prefer this over raw Photon checks
if (CCDS.IsMultiplayer()) { /* ... */ }

CCDS.IsMultiplayer() is the check to use. Hand-rolled PhotonNetwork.IsConnectedAndReady && InRoom tests drift out of agreement with the rest of CCDS.

The player's preference persists:

C#
CCDS.SetPreferMultiplayer(true);
bool prefers = CCDS.GetPreferMultiplayer();

Room configuration

On CCDS_NetworkManager:

Field Type Range Default Purpose
networkMode NetworkMode — SinglePlayer Offline or connected
maxPlayersPerRoom int 2–20 8 Room capacity
playerName string — "Player" Displayed name
roomName string — "" Current or target room
roomIsVisible bool — true Listed in the lobby
roomIsOpen bool — true Accepts new players
missionType string — "FreeRoam" Current room's mission type
defaultMissionType string — "FreeRoam" For new rooms

Read-only state getters: IsInRoomAndReady, InLobby, InRoom, IsMultiplayer, IsMasterClient.

Connection flow

Plain text
ConnectToPhoton()
  → OnConnectedToMaster
  → JoinLobby()
  → CreateRoom() / JoinRoom() / JoinOrCreateRoom() / JoinRandomRoom()
  → OnJoinedRoom
  → PhotonNetwork.Instantiate spawns the player vehicle

The public entry points:

Method Does
ConnectToPhoton() Connect using the configured App ID
JoinLobby() Enter the default lobby
CreateRoom(name) Create a room
CreateRoomWithOptions(name, roomOptions, typedLobby) Create with explicit RoomOptions
JoinRoom(name) Join a named room
JoinOrCreateRoom(name) Join if it exists, otherwise create
JoinRandomRoom() Matchmake
LeaveRoom() Leave
DisconnectFromPhoton() Disconnect
AutoStartMultiplayer() Connect and matchmake in one call
ConnectToRegion(regionCode) Pin to a Photon region
DisconnectAndLoadScene(sceneIndex) Leave and load a scene cleanly
SignalPlayerReady() Mark this player ready
IsPlayerLoaded(player) Has that player finished loading

Network rates

In CCDS_Settings.asset:

Setting Range Default Meaning
photonSendRate 10–60 30 Updates sent per second
photonSerializationRate 10–60 20 Serializations per second

Keep serialization below send rate. Raising both smooths remote vehicles and costs bandwidth; the shipped 30/20 pair is tuned for vehicles.

Networked vehicles

A player vehicle needs PhotonView, CCDS_Player and RCCP_PhotonSync. The vehicle setup prompt adds all of them and wires the observed components — see Add Your Own Vehicle.

Authority follows Photon: the owner simulates, remotes interpolate. CCDS adds a control lock on top, so it can hold authority across mission transitions:

C#
vehicle.LockControl();
vehicle.UnlockControl();
vehicle.SetControlWithLock(true);
bool locked = vehicle.ControlLockedByCCDS;

While locked, RCCP_PhotonSync will not override canControl or externalControl. Use SetControlWithLock rather than touching RCCP.SetControl directly whenever CCDS owns the decision — otherwise the sync component silently reverts you next frame.

Mission participation

Multiplayer missions derive from ACCDS_MultiplayerMissionBase.

Concept Held in
Who is playing missionPlayers
Where each player spawns playerSpawnAssignments — actor number → spawn index
Spawn slots child transforms under transportToThisLocation
Slot resolution GetPlayerSpawnPosition(), with blocked-slot fallbacks

An empty missionPlayers cancels the mission.

Mission AI activates locally on each participant. Non-participants never enable the mission, so they never see its AI. Do not "fix" this with an RPC — see Create a Mission.

Markers synchronise isOnCooldown, missionInProgress, remainingCooldown and their state enum through OnPhotonSerializeView, master-authoritative.

Writing networked components

Derive from ACCDS_PunComponent. It extends MonoBehaviourPunCallbacks, requires a PhotonView via [RequireComponent], and caches the view and scene manager.

C#
using BoneCrackerGames.CCDS;

public class MyNetworkedThing : ACCDS_PunComponent {

    void OnEnable() {
        CCDS_Events.OnNetworkPlayerJoined += HandleJoined;
    }

    void OnDisable() {
        CCDS_Events.OnNetworkPlayerJoined -= HandleJoined;
    }

    void HandleJoined(string playerName) {
        CCDS.DebugLog($"{playerName} joined");
    }
}

If you make an existing class inherit MonoBehaviourPunCallbacks, its OnEnable and OnDisable must be public override and call base.OnEnable() / base.OnDisable(). PUN registers its callback targets there. A plain private void OnEnable() hides the base method, no Photon callback ever fires, and the compiler gives at most a warning. This is the single most expensive mistake in networked CCDS code.

Traffic sync is the same story: the master client owns traffic and instantiates it over the network. Clients must not spawn their own.

Chat and nametags

Chat is CCDS_ChatManager plus CCDS_UI_ChatPanel. Return sends, Esc closes.

C#
CCDS.ShowChat();
CCDS.HideChat();
CCDS.ToggleChatVisibility();
CCDS.SetChatVisibilityMode(ChatVisibilityMode.ShowOnNewMessages);

ChatVisibilityMode is AlwaysVisible, ShowOnNewMessages or Hidden.

Nametags come from CCDS_UI_NametagManager using CCDS_Settings.nametagPrefab, which must contain a TextMeshProUGUI and a CanvasGroup.

Hold Tab for the scoreboard (CCDS_UI_MultiplayerIngame).

Progression

Persisted per player: multiplayerLevel (starts at CCDS_Settings.defaultMultiplayerLevel, default 1), multiplayerXP, multiplayerWins, multiplayerLosses, totalMultiplayerMatches, favoriteRoomName, preferredRegion, lastOnlineTime.

Testing two clients on one PC

Enable CCDS_Settings.useProcessIdForSaveFile. Each instance then writes its own save file, keyed by process ID, so two clients do not fight over one file.

Turn it off for production builds. Left on, every launch is a new save and players lose their progress.

Events

Sixteen network events are available — connection, room, player and master-client changes. See Events Catalog.

Common problems

Symptom Cause
Connection never completes No App ID, or PUN Wizard never run
Photon callbacks never fire OnEnable/OnDisable not public override calling base
Remote vehicles stutter Send/serialization rates too low, or sync component missing
Remote vehicles do not move PhotonView present but RCCP_PhotonSync absent
Traffic desynced between clients Clients spawning traffic locally; only the master may
Non-participants see mission AI Mission AI being spawned over the network
Both test clients share a save useProcessIdForSaveFile disabled
Player spawns inside another Blocked spawn slot — check transportToThisLocation slots
Scene loads out of sync Use PhotonNetwork.LoadLevel while IsConnectedAndReady && InRoom

Next

(c) 2014 - 2026 BoneCracker Games - City Car Driving Simulator 1.6Back to top ↑
Document details

Generated from: 02_Build_Your_Game/05_Multiplayer.md
Date: 2026-09-15 07:52
(c) 2014 - 2026 BoneCracker Games - City Car Driving Simulator 1.6