Skip to content

Commit 4f1dba4

Browse files
pdeschainSamuelBellomoBriancoughlins-omeilia-unity
authored
feat: local iteration workflows - testing multiplayer games locally doc (#263)
* Adding a local iteration guide doc that describes how to use local builds and parrelsync * admonition instead of quote * a less chonky gif * sidebar * Apply suggestions from code review Co-authored-by: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> * MTTDOC-317 updates to enable building of site * Changes based on PR feedback p1 * Update local-Iteration-testing-multiplayer-games-locally.md * addressing comments * linked to parrelsync issue ticket * # * Update docs/tutorials/local_iteration_series/local-Iteration-testing-multiplayer-games-locally.md Co-authored-by: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Co-authored-by: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Co-authored-by: Briancoughlin <76997526+Briancoughlin@users.noreply.github.com> Co-authored-by: Brian Coughlin <brian.coughlin@unity3d.com> Co-authored-by: Sara [Unity] <89528471+s-omeilia-unity@users.noreply.github.com>
1 parent d608ba5 commit 4f1dba4

File tree

7 files changed

+311
-2
lines changed

7 files changed

+311
-2
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
---
2+
id: networktime-ticks
3+
title: NetworkTime and Ticks
4+
sidebar_label: NetworkTime & Ticks
5+
---
6+
7+
## LocalTime and ServerTime
8+
9+
Why are there two different time values and which one should be used?
10+
11+
Netcode for Gameobjects uses a star topology. That means all communications happen between the clients and the server/host and never between clients directly. Messages take time to transmit over the network. That's why `RPCs` and `NetworkVariable` will not happen immediately on other machines. `NetworkTime` allows to use time while considering those transmission delays.
12+
13+
- `LocalTime` on a client is ahead of the server. If a server RPC is sent at `LocalTime` from a client it will roughly arrive at `ServerTime` on the server.
14+
- `ServerTime` on clients is behind the server. If a client RPC is sent at `ServerTime` from the server to clients it will roughly arrive at `ServerTime` on the clients.
15+
16+
17+
18+
<Mermaid chart={`
19+
sequenceDiagram
20+
participant Owner as Client LocalTime
21+
participant Server as Server ServerTime & LocalTime
22+
participant Receiver as Client ServerTime
23+
Note over Owner: Send message to server at LocalTime.
24+
Owner->>Server: Delay when sending message
25+
Note over Server: Message arrives at ServerTime.
26+
Note over Server: On server: ServerTime == LocalTime.
27+
Note over Server: Send message to clients at LocalTime.
28+
Server->>Receiver: Delay when sending message
29+
Note over Receiver: Message arrives at ServerTime.
30+
`}/>
31+
32+
33+
34+
35+
`LocalTime`
36+
- Use for player objects with client authority.
37+
- Use if just a general time value is needed.
38+
39+
`ServerTime`:
40+
- For player objects with server authority (E.g. by sending inputs to the server via RPCs)
41+
- In sync with position updates of NetworkTransform for all NetworkObjects where the client is not authoritative over the transform.
42+
- For everything on non client controlled NetworkObjects.
43+
44+
## Examples
45+
46+
### Example 1: Using network time to synchronize environments
47+
48+
Many games have environmental objects which move in a fixed pattern. By using network time these objects can be moved without having to synchronize their positions with a `NetworkTransform`.
49+
50+
For instance the following code could be used to create a moving elevator platform for a client authoritative game:
51+
52+
```csharp
53+
using Unity.Netcode;
54+
using UnityEngine;
55+
56+
public class MovingPlatform : MonoBehaviour
57+
{
58+
public void Update()
59+
{
60+
// Move up and down by 5 meters and change direction every 3 seconds.
61+
var positionY = Mathf.PingPong(NetworkManager.Singleton.LocalTime.TimeAsFloat / 3f, 1f) * 5f;
62+
transform.position = new Vector3(0, positionY, 0);
63+
}
64+
}
65+
```
66+
67+
### Example 2: Using network time to create a synced event
68+
69+
Most of the time aligning an effect precisely to time is not needed. But in some cases for important effects or gameplay events it can help to improve consistency especially for clients with bad network connections.
70+
71+
```csharp
72+
using System.Collections;
73+
using Unity.Netcode;
74+
using UnityEngine;
75+
using UnityEngine.Assertions;
76+
77+
public class SyncedEventExample : NetworkBehaviour
78+
{
79+
public GameObject ParticleEffect;
80+
81+
// Called by the client to create a synced particle event at its own position.
82+
public void ClientCreateSyncedEffect()
83+
{
84+
Assert.IsTrue(IsOwner);
85+
var time = NetworkManager.LocalTime.Time;
86+
CreateSyncedEffectServerRpc(time);
87+
StartCoroutine(WaitAndSpawnSyncedEffect(0)); // Create the effect immediately locally.
88+
}
89+
90+
private IEnumerator WaitAndSpawnSyncedEffect(float timeToWait)
91+
{
92+
// Note sometimes the timeToWait will be negative on the server or the receiving clients if a message got delayed by the network for a long time. This usually happens only in rare cases. Custom logic could be implemented to deal with that scenario.
93+
if (timeToWait > 0)
94+
{
95+
yield return new WaitForSeconds(timeToWait);
96+
}
97+
98+
Instantiate(ParticleEffect, transform.position, Quaternion.identity);
99+
}
100+
101+
[ServerRpc]
102+
private void CreateSyncedEffectServerRpc(double time)
103+
{
104+
CreateSyncedEffectClientRpc(time); // Call a client RPC to also create the effect on each client.
105+
var timeToWait = time - NetworkManager.ServerTime.Time;
106+
StartCoroutine(WaitAndSpawnSyncedEffect((float)timeToWait)); // Create the effect on the server but wait for the right time.
107+
}
108+
109+
[ClientRpc]
110+
private void CreateSyncedEffectClientRpc(double time)
111+
{
112+
// The owner already created the effect so skip them.
113+
if (IsOwner == false)
114+
{
115+
var timeToWait = time - NetworkManager.ServerTime.Time;
116+
StartCoroutine(WaitAndSpawnSyncedEffect((float)timeToWait)); // Create the effect on the client but wait for the right time.
117+
}
118+
}
119+
}
120+
```
121+
122+
<Mermaid chart={`
123+
sequenceDiagram
124+
participant Owner as Owner
125+
participant Server as Server
126+
participant Receiver as Other Client
127+
Note over Owner: LocalTime = 10.0
128+
Note over Owner: ClientCreateSyncedEffect()
129+
Note over Owner: Instantiate effect immediately (LocalTime = 10)
130+
Owner->>Server: CreateSyncedEffectServerRpc
131+
Server->>Receiver: CreateSyncedEffectClientRpc
132+
Note over Server: ServerTime = 9.95 #38; timeToWait = 0.05
133+
Note over Server: StartCoroutine(WaitAndSpawnSyncedEffect(0.05))
134+
Server->>Server: WaitForSeconds(0.05);
135+
Note over Server: Instantiate effect at ServerTime = 10.0
136+
Note over Receiver: ServerTime = 9.93 #38; timeToWait = 0.07
137+
Note over Receiver: StartCoroutine(WaitAndSpawnSyncedEffect(0.07))
138+
Receiver->>Receiver: WaitForSeconds(0.07);
139+
Note over Receiver: Instantiate effect at ServerTime = 10.0
140+
`}/>
141+
142+
143+
144+
:::note
145+
Some components such as `NetworkTransform` add additional buffering. When trying to align an RPC event like in this example, an additional delay would need to be added.
146+
:::
147+
148+
## Network Ticks
149+
150+
Network ticks are run at a fixed rate. The 'Tick Rate' field on the NetworkManager can be used to set the tick rate.
151+
152+
What does changing the network tick affect? Changes to `NetworkVariables` are not sent immediately. Instead during each network tick changes to `NetworkVariables` are collected and sent out to other peers.
153+
154+
To run custom code once per network tick (before `NetworkVariable` changes are collected) the `Tick` event on the `NetworkTickSystem` can be used.
155+
```cs
156+
public override void OnNetworkSpawn()
157+
{
158+
NetworkManager.NetworkTickSystem.Tick += Tick;
159+
}
160+
161+
private void Tick()
162+
{
163+
Debug.Log($"Tick: {NetworkManager.LocalTime.Tick}");
164+
}
165+
166+
public override void OnNetworkDespawn() // don't forget to unsubscribe
167+
{
168+
NetworkManager.NetworkTickSystem.Tick -= Tick;
169+
}
170+
```
171+
172+
:::tip
173+
When using `FixedUpdate` or physics in your game, set the network tick rate to the same rate as the fixed update rate. The `FixedUpdate` rate can be changed in `Edit > Project Settings > Time > Fixed Timestep`
174+
:::
175+
176+
## Network FixedTime
177+
178+
`Network FixedTime` can be used to get a time value representing the time during a network tick. This works similar to `FixedUpdate` where `Time.fixedTime` represents the time during the `FixedUpdate`.
179+
180+
```cs
181+
public void Update()
182+
{
183+
double time = NetworkManager.Singleton.LocalTime.Time; // time during this Update
184+
double fixedTime = NetworkManager.Singleton.LocalTime.FixedTime; // time during the previous network tick
185+
}
186+
```
187+
188+
## NetworkTime Precision
189+
190+
Network time values are calculated using double precisions. This allows time to stay accurate on long running servers. For game servers which run sessions for a long time (multiple hours or days) do not convert this value in a float and always use doubles for time related calculations.
191+
192+
For games with short play sessions casting the time to float is safe or `TimeAsFloat` can be used.
193+
194+
## NetworkTimeSystem Configuration
195+
196+
:::caution
197+
The properties of the `NetworkTimeSystem` should be left untouched on the server/host. Changing the values on the client is sufficient to change the behavior of the time system.
198+
:::
199+
200+
The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. See the API docs (TODO LINK) for information about the properties which can be modified. All properties can be safely adjusted at runtime. For instance buffer values could be increased for a player with a bad connection.
201+
202+
<!-- On page code -->
203+
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
id: local_iteration_testing_locally
3+
title: Local Iteration - Testing multiplayer games locally
4+
description: Guide covering the available workflows for testing multiplayer games locally.
5+
---
6+
- [Player Builds](#player-builds)
7+
- [Local iteration using Player Builds](#local-iteration-using-player-builds)
8+
- [ParrelSync](#parrelsync)
9+
- [Installation](#installation)
10+
- [Usage](#usage)
11+
- [Known issues and workarounds](#known-issues-and-workarounds)
12+
- [General tips](#general-tips)
13+
14+
Testing a multiplayer game presents unique challenges to developers:
15+
- We need to run multiple instances of the game in order to test multiplayer scenarios.
16+
- We also need to iterate quickly on our custom code and asset changes and validate our work in a multiplayer scenario.
17+
- We need to be able to debug our work in a multiplayer scenario using editor tools.
18+
19+
Currently, Unity does not provide any workflow that covers all of these requirements. (See our [roadmap here](https://unity.com/roadmap/unity-platform/multiplayer-networking))
20+
21+
There will always be a need to validate our work in the target distribution format (ie. on platform) and the way to do it is by creating [Player Builds](#player-builds).
22+
23+
However, player builds do not meet the requirement of quick iteration and easy debuggability using editor tools. As such our current recommended workflow for local iteration [ParrelSync](#parrelsync).
24+
25+
## Player Builds
26+
27+
:::hint
28+
29+
This approach is great when we need to verify our work on the target platform or with a wider group of testers.
30+
31+
:::
32+
33+
First we need to build an executable. The default way of doing that is via `File->Build Settings` in the menu bar, and then pressing `Build` button.
34+
35+
Then the build can be shared among the testers.
36+
37+
### Local iteration using Player Builds
38+
39+
Once the build has completed you can launch several instances of the built executable in order to both host and join a game.
40+
41+
It is also possible to run the builds along with an editor that produced said build, which could be useful during iterations.
42+
43+
> Mac users: to run multiple instances of the same app, you need to use the command line.
44+
> Run `open -n YourAppName.app`
45+
46+
:::hint
47+
48+
Though functional, we find this approach to be somewhat slow for the purposes of local iteration. Head on to the [ParrelSync](#parrelsync) section for our suggested workflow for local iteration.
49+
50+
:::
51+
52+
## ParrelSync
53+
![parrelsync-bossroom-demo](../../../static/img/parrelsync-bossroom-demo.gif)
54+
55+
[**ParrelSync**](https://github.com/VeriorPies/ParrelSync) is an open-source Unity editor extension that allows users to **test multiplayer gameplay without building the project** by having another Unity editor window opened and mirror the changes from the original project.
56+
57+
**ParrelSync** works by making a copy of the original project folder and creating symbolic links to the `Asset` and `Project Settings` folders back from the original project.
58+
59+
We use **ParrelSync** for local iteration in [BossRoom sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/).
60+
61+
:::important
62+
63+
**ParrelSync** relies on symbolic links and partial copies of the original project folder structure - generally it is completely safe.
64+
65+
Yet, just to be sure that no bug in any of the software you use can destroy your work - it's a good idea to consistently backup your project or use a version control system such as [Git](https://git-scm.com/), [SVN](https://subversion.apache.org/), [Plastic](https://www.plasticscm.com/) or any other.
66+
67+
:::
68+
69+
### Installation
70+
71+
Follow the installation instructions on **ParrelSync** repo [page](https://github.com/VeriorPies/ParrelSync#installation)
72+
73+
### Usage
74+
- Open the `ParrelSync->Preferences` menu in the menu bar to open the preferences window
75+
- Verify that your settings are set to the following: ![parrelsync-preferences](../../../static/img/parrelsync-preferences.png)
76+
77+
:::important
78+
79+
By default **ParrelSync** prevents asset serialization in all clone instances and changes can only be made from the original project editor. This is a **very important setting** that prevents issues with multiple editors accessing the same `Library` folder (which is not supported and breaks basic assumptions in Unity design).
80+
81+
:::
82+
83+
- Open the `ParrelSync->Clones Manager` from which you can launch, create and remove clone editors.
84+
- Advanced usage is to utilize **ParrelSync's** capability of passing [Arguments](https://github.com/VeriorPies/ParrelSync/wiki/Argument) to clones, thus allowing to run custom logic on a per-clone basis.
85+
86+
### Known issues and workarounds
87+
- An important nuance is that **ParrelSync** does not sync changes made to packages. `Packages` folder is synced on clone opening, so if you made package changes - you should close and re-open your clones.
88+
- [Relevant GitHub issue](https://github.com/VeriorPies/ParrelSync/issues/48)
89+
- If you encounter a Netcode error that mentions `soft sync` - that generally means that prefabs or scenes are not in sync between editors. You should save the project in the main editor via `File->Save Project` and refresh the projects in the clone editors by pressing `Ctrl + R` (which is by default done automatically) or reimport networked prefabs in the main editor.
90+
- More information and general **ParrelSync** FAQ: https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs
91+
- The ultimate workaround in case nothing helps - deleting and re-creating the clone instance via `ParrelSync->Clones Manager` window.
92+
93+
## General tips
94+
- Bigger screens or multi-screen setups allow for more screen real estate, which is handy when one has to have multiple instances of an app opened at the same time.
95+
- **ParrelSync** has to copy and update separate `Packages` and `Library` folders for every clone, and in certain cases a fix for misbehaving clone is re-creation - a good SSD makes this process quite a bit faster.
96+
- Creating a fork of any git repository that your project relies upon in production could help avoid bad surprises if the repo gets taken down or introduces an undesirable change. You should fork **ParrelSync** before using it in your live project.

sidebars.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ module.exports = {
6969
],
7070

7171
},
72+
{
73+
"collapsed": true,
74+
"type": "category",
75+
"label": "Local Iteration ",
76+
"items": [
77+
{
78+
"type": "doc",
79+
"id": "tutorials/local_iteration_series/local_iteration_testing_locally"
80+
}
81+
],
82+
},
7283
{
7384
"collapsed": true,
7485
"type": "category",
39.8 MB
Loading

static/img/parrelsync-preferences.png

9.52 KB
Loading

versioned_docs/version-0.1.0/advanced-topics/network-update-loop-system/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ In all `NetworkUpdateStages`, it iterates over a static array and calls the `Net
5050

5151
## References
5252

53-
See [Network Update Loop Reference](network-update-loop-reference.md) for process flow diagrams and code.
53+
See [Network Update Loop Reference](network-update-loop-reference.md) for process flow diagrams and code.

versioned_docs/version-0.1.0/reference/glossary/network-latency-management.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,4 @@ A tick or simulation rate of 60Hz will cause less delay than a tick rate of 30Hz
152152

153153
When a server gets close to the limit, or even fails to process a tick inside that timeframe, then you will instantly notice the results: all sorts of strange gameplay issues like rubber banding, players teleporting, hits getting rejected, and physics failing.
154154

155-
156155
import ImageSwitcher from '@site/src/ImageSwitcher.js';

0 commit comments

Comments
 (0)