Multiplayer Integration
Add multiplayer functionality to your RPG using Unity Netcode for GameObjects.
Multiplayer Overview
Transform your single-player RPG into a cooperative multiplayer experience.
👥 Cooperative Play
Up to 4 players can adventure together
🌐 Shared World
Synchronized game state across all clients
⚔️ Team Combat
Coordinated battles and shared resources
💬 Communication
Built-in chat and voice communication
Setting Up Unity Netcode
Step 1: Install Netcode Package
Add Unity Netcode for GameObjects to your project:
- Open Package Manager (Window > Package Manager)
- Select "Unity Registry" from dropdown
- Search for "Netcode for GameObjects"
- Click Install
Step 2: Network Manager Setup
Configure the core networking component:
1"keyword">using Unity.Netcode;
2"keyword">using UnityEngine;
3
4"keyword">public "keyword">class RPGNetworkManager : NetworkManager
5{
6 [Header("RPG Network Settings")]
7 "keyword">public GameObject playerPrefab;
8 "keyword">public Transform[] spawnPoints;
9 "keyword">public "keyword">int maxPlayers = 4;
10
11 "keyword">private "keyword">int currentSpawnIndex = 0;
12
13 "keyword">public "keyword">static RPGNetworkManager Instance { get; "keyword">private set; }
14
15 "keyword">void Awake()
16 {
17 "keyword">if (Instance == null)
18 {
19 Instance = "keyword">this;
20 DontDestroyOnLoad(gameObject);
21 }
22 "keyword">else
23 {
24 Destroy(gameObject);
25 }
26 }
27
28 "keyword">public "keyword">override "keyword">void OnServerStarted()
29 {
30 Debug.Log("Server started successfully");
31
32 "comment">// Initialize server-side systems
33 InitializeServerSystems();
34 }
35
36 "keyword">public "keyword">override "keyword">void OnClientConnectedCallback(ulong clientId)
37 {
38 Debug.Log($"Client {clientId} connected");
39
40 "keyword">if (IsServer)
41 {
42 "comment">// Spawn player "keyword">for the connected client
43 SpawnPlayerForClient(clientId);
44
45 "comment">// Sync world state to "keyword">new client
46 SyncWorldStateToClient(clientId);
47 }
48 }
49
50 "keyword">public "keyword">override "keyword">void OnClientDisconnectCallback(ulong clientId)
51 {
52 Debug.Log($"Client {clientId} disconnected");
53
54 "keyword">if (IsServer)
55 {
56 "comment">// Handle player disconnect
57 HandlePlayerDisconnect(clientId);
58 }
59 }
60
61 "keyword">void SpawnPlayerForClient(ulong clientId)
62 {
63 "comment">// Get spawn position
64 Vector3 spawnPosition = GetSpawnPosition();
65
66 "comment">// Spawn player object
67 GameObject playerInstance = Instantiate(playerPrefab, spawnPosition, Quaternion.identity);
68
69 "comment">// Get NetworkObject component and spawn it "keyword">for the client
70 NetworkObject networkObject = playerInstance.GetComponent<NetworkObject>();
71 networkObject.SpawnAsPlayerObject(clientId, true);
72
73 "comment">// Initialize player data
74 var networkPlayer = playerInstance.GetComponent<NetworkPlayer>();
75 networkPlayer.InitializePlayer(clientId);
76 }
77
78 Vector3 GetSpawnPosition()
79 {
80 "keyword">if (spawnPoints.Length == 0)
81 "keyword">return Vector3.zero;
82
83 Vector3 position = spawnPoints[currentSpawnIndex].position;
84 currentSpawnIndex = (currentSpawnIndex + 1) % spawnPoints.Length;
85
86 "keyword">return position;
87 }
88
89 "keyword">void InitializeServerSystems()
90 {
91 "comment">// Initialize shared systems
92 NetworkQuestManager.Instance.InitializeAsServer();
93 NetworkInventoryManager.Instance.InitializeAsServer();
94 NetworkCombatManager.Instance.InitializeAsServer();
95 }
96
97 "keyword">void SyncWorldStateToClient(ulong clientId)
98 {
99 "comment">// Send current world state to "keyword">new client
100 NetworkQuestManager.Instance.SyncQuestStateToClient(clientId);
101 NetworkWorldState.Instance.SyncWorldStateToClient(clientId);
102 }
103
104 "keyword">void HandlePlayerDisconnect(ulong clientId)
105 {
106 "comment">// Save player data before disconnect
107 var playerObject = NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject;
108 "keyword">if (playerObject != null)
109 {
110 var networkPlayer = playerObject.GetComponent<NetworkPlayer>();
111 networkPlayer?.SavePlayerData();
112 }
113
114 "comment">// Notify other systems about disconnect
115 NetworkQuestManager.Instance.HandlePlayerDisconnect(clientId);
116 }
117}
Networked Player System
Network Player Controller
Create a networked version of the player controller:
1"keyword">using Unity.Netcode;
2"keyword">using UnityEngine;
3
4"keyword">public "keyword">class NetworkPlayer : NetworkBehaviour
5{
6 [Header("Player Components")]
7 "keyword">public PlayerController playerController;
8 "keyword">public PlayerStats playerStats;
9 "keyword">public PlayerInventory playerInventory;
10
11 [Header("Network Variables")]
12 "keyword">public NetworkVariable<"keyword">int> networkHealth = "keyword">new NetworkVariable<"keyword">int>();
13 "keyword">public NetworkVariable<"keyword">int> networkMana = "keyword">new NetworkVariable<"keyword">int>();
14 "keyword">public NetworkVariable<"keyword">int> networkLevel = "keyword">new NetworkVariable<"keyword">int>();
15 "keyword">public NetworkVariable<Vector3> networkPosition = "keyword">new NetworkVariable<Vector3>();
16
17 [Header("Player Info")]
18 "keyword">public NetworkVariable<FixedString64Bytes> playerName = "keyword">new NetworkVariable<FixedString64Bytes>();
19
20 "keyword">private Camera playerCamera;
21 "keyword">private AudioListener audioListener;
22
23 "keyword">public "keyword">override "keyword">void OnNetworkSpawn()
24 {
25 "comment">// Setup based on ownership
26 "keyword">if (IsOwner)
27 {
28 SetupLocalPlayer();
29 }
30 "keyword">else
31 {
32 SetupRemotePlayer();
33 }
34
35 "comment">// Subscribe to network variable changes
36 networkHealth.OnValueChanged += OnHealthChanged;
37 networkMana.OnValueChanged += OnManaChanged;
38 networkLevel.OnValueChanged += OnLevelChanged;
39 networkPosition.OnValueChanged += OnPositionChanged;
40 playerName.OnValueChanged += OnPlayerNameChanged;
41
42 "comment">// Initialize UI
43 UpdatePlayerUI();
44 }
45
46 "keyword">public "keyword">override "keyword">void OnNetworkDespawn()
47 {
48 "comment">// Unsubscribe from network variables
49 networkHealth.OnValueChanged -= OnHealthChanged;
50 networkMana.OnValueChanged -= OnManaChanged;
51 networkLevel.OnValueChanged -= OnLevelChanged;
52 networkPosition.OnValueChanged -= OnPositionChanged;
53 playerName.OnValueChanged -= OnPlayerNameChanged;
54 }
55
56 "keyword">void SetupLocalPlayer()
57 {
58 "comment">// Enable local player components
59 playerController.enabled = true;
60
61 "comment">// Setup camera
62 playerCamera = GetComponentInChildren<Camera>();
63 "keyword">if (playerCamera != null)
64 {
65 playerCamera.enabled = true;
66 }
67
68 "comment">// Setup audio listener
69 audioListener = GetComponentInChildren<AudioListener>();
70 "keyword">if (audioListener != null)
71 {
72 audioListener.enabled = true;
73 }
74
75 "comment">// Setup UI
76 UIManager.Instance.SetLocalPlayer("keyword">this);
77
78 "comment">// Request player data from server
79 "keyword">if (!IsServer)
80 {
81 RequestPlayerDataServerRpc();
82 }
83 }
84
85 "keyword">void SetupRemotePlayer()
86 {
87 "comment">// Disable components that should only be active "keyword">for local player
88 playerController.enabled = false;
89
90 "keyword">if (playerCamera != null)
91 playerCamera.enabled = false;
92
93 "keyword">if (audioListener != null)
94 audioListener.enabled = false;
95 }
96
97 "keyword">void Update()
98 {
99 "keyword">if (IsOwner)
100 {
101 "comment">// Sync position regularly
102 "keyword">if (Vector3.Distance(transform.position, networkPosition.Value) > 0.1f)
103 {
104 UpdatePositionServerRpc(transform.position);
105 }
106
107 "comment">// Sync stats "keyword">if they changed
108 SyncStatsIfChanged();
109 }
110 }
111
112 "keyword">void SyncStatsIfChanged()
113 {
114 "keyword">if (playerStats.health.Value != networkHealth.Value)
115 {
116 UpdateHealthServerRpc(playerStats.health.Value);
117 }
118
119 "keyword">if (playerStats.mana.Value != networkMana.Value)
120 {
121 UpdateManaServerRpc(playerStats.mana.Value);
122 }
123
124 "keyword">if (playerStats.level != networkLevel.Value)
125 {
126 UpdateLevelServerRpc(playerStats.level);
127 }
128 }
129
130 [ServerRpc]
131 "keyword">void UpdatePositionServerRpc(Vector3 position)
132 {
133 networkPosition.Value = position;
134 }
135
136 [ServerRpc]
137 "keyword">void UpdateHealthServerRpc("keyword">int health)
138 {
139 networkHealth.Value = health;
140 }
141
142 [ServerRpc]
143 "keyword">void UpdateManaServerRpc("keyword">int mana)
144 {
145 networkMana.Value = mana;
146 }
147
148 [ServerRpc]
149 "keyword">void UpdateLevelServerRpc("keyword">int level)
150 {
151 networkLevel.Value = level;
152 }
153
154 [ServerRpc]
155 "keyword">void RequestPlayerDataServerRpc()
156 {
157 "comment">// Send current player data to requesting client
158 SendPlayerDataClientRpc(
159 playerStats.health.Value,
160 playerStats.mana.Value,
161 playerStats.level,
162 transform.position
163 );
164 }
165
166 [ClientRpc]
167 "keyword">void SendPlayerDataClientRpc("keyword">int health, "keyword">int mana, "keyword">int level, Vector3 position)
168 {
169 "keyword">if (IsOwner)
170 {
171 playerStats.health.SetCurrentValue(health);
172 playerStats.mana.SetCurrentValue(mana);
173 playerStats.SetLevel(level);
174 transform.position = position;
175 }
176 }
177
178 "comment">// Network variable change handlers
179 "keyword">void OnHealthChanged("keyword">int oldHealth, "keyword">int newHealth)
180 {
181 "keyword">if (!IsOwner)
182 {
183 playerStats.health.SetCurrentValue(newHealth);
184 }
185 UpdatePlayerUI();
186 }
187
188 "keyword">void OnManaChanged("keyword">int oldMana, "keyword">int newMana)
189 {
190 "keyword">if (!IsOwner)
191 {
192 playerStats.mana.SetCurrentValue(newMana);
193 }
194 UpdatePlayerUI();
195 }
196
197 "keyword">void OnLevelChanged("keyword">int oldLevel, "keyword">int newLevel)
198 {
199 "keyword">if (!IsOwner)
200 {
201 playerStats.SetLevel(newLevel);
202 }
203 UpdatePlayerUI();
204 }
205
206 "keyword">void OnPositionChanged(Vector3 oldPosition, Vector3 newPosition)
207 {
208 "keyword">if (!IsOwner)
209 {
210 "comment">// Smooth movement "keyword">for remote players
211 StartCoroutine(SmoothMoveTo(newPosition));
212 }
213 }
214
215 "keyword">void OnPlayerNameChanged(FixedString64Bytes oldName, FixedString64Bytes newName)
216 {
217 "comment">// Update name display
218 var nameDisplay = GetComponentInChildren<PlayerNameDisplay>();
219 "keyword">if (nameDisplay != null)
220 {
221 nameDisplay.SetPlayerName(newName.ToString());
222 }
223 }
224
225 System.Collections.IEnumerator SmoothMoveTo(Vector3 targetPosition)
226 {
227 Vector3 startPosition = transform.position;
228 "keyword">float journeyTime = 0.1f; "comment">// Smooth over 0.1 seconds
229 "keyword">float elapsedTime = 0;
230
231 "keyword">while (elapsedTime < journeyTime)
232 {
233 elapsedTime += Time.deltaTime;
234 "keyword">float fractionOfJourney = elapsedTime / journeyTime;
235 transform.position = Vector3.Lerp(startPosition, targetPosition, fractionOfJourney);
236 yield "keyword">return null;
237 }
238
239 transform.position = targetPosition;
240 }
241
242 "keyword">void UpdatePlayerUI()
243 {
244 "comment">// Update multiplayer UI elements
245 MultiplayerUI.Instance?.UpdatePlayerInfo(OwnerClientId, networkHealth.Value, networkMana.Value, networkLevel.Value);
246 }
247
248 "keyword">public "keyword">void InitializePlayer(ulong clientId)
249 {
250 "comment">// Set player name based on client ID or saved data
251 "keyword">if (IsServer)
252 {
253 "keyword">string defaultName = $"Player {clientId}";
254 playerName.Value = defaultName;
255
256 "comment">// Load saved player data "keyword">if available
257 LoadPlayerData(clientId);
258 }
259 }
260
261 "keyword">void LoadPlayerData(ulong clientId)
262 {
263 "comment">// Load player data from persistent storage
264 "comment">// This would integrate with your save system
265 var saveData = NetworkSaveManager.Instance.LoadPlayerData(clientId);
266 "keyword">if (saveData != null)
267 {
268 playerStats.health.SetCurrentValue(saveData.health);
269 playerStats.mana.SetCurrentValue(saveData.mana);
270 playerStats.SetLevel(saveData.level);
271 transform.position = saveData.position;
272
273 "comment">// Update network variables
274 networkHealth.Value = saveData.health;
275 networkMana.Value = saveData.mana;
276 networkLevel.Value = saveData.level;
277 networkPosition.Value = saveData.position;
278 }
279 }
280
281 "keyword">public "keyword">void SavePlayerData()
282 {
283 "keyword">if (IsServer)
284 {
285 var saveData = "keyword">new NetworkPlayerSaveData
286 {
287 clientId = OwnerClientId,
288 health = networkHealth.Value,
289 mana = networkMana.Value,
290 level = networkLevel.Value,
291 position = networkPosition.Value,
292 playerName = playerName.Value.ToString()
293 };
294
295 NetworkSaveManager.Instance.SavePlayerData(saveData);
296 }
297 }
298}
299
300[System.Serializable]
301"keyword">public "keyword">class NetworkPlayerSaveData
302{
303 "keyword">public ulong clientId;
304 "keyword">public "keyword">int health;
305 "keyword">public "keyword">int mana;
306 "keyword">public "keyword">int level;
307 "keyword">public Vector3 position;
308 "keyword">public "keyword">string playerName;
309}