Multiplayer
CCDS runs single-player without Photon PUN2. Install and configure the optional PUN2 integration to enable rooms, networked vehicles, synchronised missions, chat and nametags in the same project.
Prerequisites
- Import a complete supported Photon PUN2 SDK and allow Unity to finish importing and compiling.
- Configure a Photon Realtime App ID in
PhotonServerSettings. - Open Tools → BoneCracker Games → CCDS → Multiplayer Setup to check the installation/configuration status. When PUN is installed but unconfigured, Select Photon Settings selects its settings asset.
The integration has been tested with the complete PUN2 2.55 SDK layout. Revalidate SDK removal, reinstall and multiplayer when upgrading Photon.
CCDS's dependency bootstrap manages the BCG_CCDS_PUN scripting symbol automatically from the SDK's source files, assembly definitions and client plugin on disk. An incomplete import or multiple SDK copies do not enable the integration. Do not add scripting symbols manually to bypass detection; repair the SDK import instead.
| SDK state | Startup and UI |
|---|---|
| Absent | Local gameplay remains available. Multiplayer entry controls and online panels are hidden or disabled, while their authored components and references remain. |
| Installed, App ID unconfigured | Startup stays local even if the saved preference is multiplayer. The editor status directs you to configure Photon settings. |
| Installed and configured | Local gameplay and multiplayer entry points are available. Online startup follows the saved preference; actual multiplayer gameplay still requires joining a room. |
Removing the optional SDK does not require deleting CCDS network bindings, scene IDs, UI references or multiplayer save fields. Keep this authoring data so reinstalling the SDK can use it again. Installing the SDK does not by itself connect the player to a room.
Single-player and multiplayer are the same scenes
There is no separate single-player project, scene set, mission set or save format. CCDS_NetworkManager.networkMode records the selected mode:
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.
For capability checks, use the SDK-independent CCDS_NetworkAvailability API:
| Member | Meaning |
|---|---|
IsInstalled |
The optional PUN integration is compiled into this build |
IsConfigured |
A nonempty, valid Realtime App ID is configured |
IsInRoom |
Photon is connected and ready inside a room |
ShouldStartOnline(savedPreference) |
Installed, configured and the saved preference is true |
Reading these properties does not start a connection. SetMultiplayerMode() cannot enable online mode without a configured integration.
The player's preference persists:
CCDS.SetPreferMultiplayer(true);
bool prefers = CCDS.GetPreferMultiplayer();
Unavailable networking changes the effective startup choice, not the stored preference. Vehicle ownership, upgrades and multiplayer progression remain in the same save data while playing locally.
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
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; only available under BCG_CCDS_PUN |
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 Photon Player finished loading; only available under BCG_CCDS_PUN |
Wrap calls to SDK-specific overloads, Photon types and PUN callback overrides in #if BCG_CCDS_PUN. The two entries marked above are absent when the integration is not compiled; checking IsInstalled at runtime cannot make an SDK-only signature compile.
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's authored setup uses CCDS_NetworkBinding, CCDS_Player and RCCP_PhotonSync. The vehicle setup prompt adds the components and records the ordered observers in the binding — see Add Your Own Vehicle.
The binding stores the network view's settings without an SDK component. During Play Mode, when BCG_CCDS_PUN is compiled, CCDS creates the real PhotonView and restores its settings and observer order. Without the SDK, the binding and CCDS component types remain in the prefab. Do not mix an authored PhotonView with an unprepared binding on the same object.
Prefab bindings keep sceneId = 0. Placed scene bindings need persistent IDs and an active CCDS_NetworkSceneBootstrap; use Multiplayer Setup → Register Active Scene and Assign IDs after saving a new scene. See scene registration.
Authority follows Photon: the owner simulates, remotes interpolate. CCDS adds a control lock on top, so it can hold authority across mission transitions:
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 their logical state and remainingCooldown through OnPhotonSerializeView, with the master client supplying the state. The same marker and mission classes retain their local behavior without the SDK.
Writing networked components
Derive from ACCDS_PunComponent for shared components. With BCG_CCDS_PUN, it extends MonoBehaviourPunCallbacks and exposes the cached PhotonViewComp. Without that symbol, it extends MonoBehaviour and retains virtual local lifecycle hooks. Its scene-manager access is available in both states, and it no longer requires an authored PhotonView.
using BoneCrackerGames.CCDS;
public class MyNetworkedThing : ACCDS_PunComponent {
public override void OnEnable() {
base.OnEnable();
CCDS_Events.OnNetworkPlayerJoined += HandleJoined;
}
public override void OnDisable() {
CCDS_Events.OnNetworkPlayerJoined -= HandleJoined;
base.OnDisable();
}
void HandleJoined(string playerName) {
CCDS.DebugLog($"{playerName} joined");
}
}
Keep OnEnable and OnDisable as public override methods that call their base implementations in both SDK states. PUN registers its callback targets there when installed; the local base hooks let the same subscription code compile without PUN. Guard direct PhotonViewComp access and SDK-specific callback methods with #if BCG_CCDS_PUN.
If your component needs a network view, author a CCDS_NetworkBinding on its object. Add serializing components to the binding's ordered observed list; a component that only uses RPCs still needs the view but does not automatically need an observation entry. Keep bindings and scene registration with the content even when developing without PUN.
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.
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.
These save fields and preferMultiplayer remain part of CCDS_SaveData without PUN. Removing or reinstalling the SDK does not automatically erase them.
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 |
|---|---|
| Multiplayer controls are hidden | The optional PUN integration is absent; single-player remains available. Check Multiplayer Setup for status. |
| Startup stays local despite a multiplayer preference | PUN is absent or the Realtime App ID is unconfigured; the saved preference is retained. |
| PUN files exist but integration stays disabled | Complete the SDK import and remove duplicate SDK copies; do not force scripting symbols manually. |
| Connection never completes | Check the Realtime App ID in PhotonServerSettings and the connection status |
| 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 | Check the binding's ordered observers and RCCP_PhotonSync, plus scene IDs for placed content |
| 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 |
| SDK-specific example fails to compile without PUN | Guard Photon types and SDK-only methods with #if BCG_CCDS_PUN |
| Scene loads out of sync | Use CCDS scene-loading entry points; direct PhotonNetwork.LoadLevel calls require BCG_CCDS_PUN and an active room |