Skip to content

Commit c28f3da

Browse files
update
Adding new NetworkVariable door example used for documentation.
1 parent 871ccbc commit c28f3da

1 file changed

Lines changed: 273 additions & 0 deletions

File tree

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
using System.Runtime.CompilerServices;
2+
using Unity.Netcode;
3+
using UnityEngine;
4+
5+
/// <summary>
6+
/// Example of using a <see cref="NetworkVariable{T}"/> to drive changes
7+
/// in state.
8+
/// </summary>
9+
/// <remarks>
10+
/// This is a simple state driven door example.
11+
/// This script was written with recommended usages patterns in mind.
12+
/// </remarks>
13+
public class Door : NetworkBehaviour, INetworkUpdateSystem
14+
{
15+
/// <summary>
16+
/// The two door states.
17+
/// </summary>
18+
public enum DoorStates
19+
{
20+
Closed,
21+
Open
22+
}
23+
24+
/// <summary>
25+
/// Initializes the door to a specific state (server side) when first spawned.
26+
/// </summary>
27+
[Tooltip("Configures the door's initial state when 1st spawned.")]
28+
public DoorStates InitialState = DoorStates.Closed;
29+
30+
/// <summary>
31+
/// Used for <see cref="CanPlayerToggleState"/> example purposes.
32+
/// When true, only the server can open and close the door.
33+
/// Clients will receive a console log saying they could not open the door.
34+
/// </summary>
35+
public bool IsLocked;
36+
37+
/// <summary>
38+
/// A simple door state where the server has write permissions and everyone has read permissions.
39+
/// </summary>
40+
private NetworkVariable<DoorStates> m_State = new NetworkVariable<DoorStates>(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
41+
42+
/// <summary>
43+
/// The current state of the door.
44+
/// </summary>
45+
public DoorStates CurrentState => m_State.Value;
46+
47+
/// <summary>
48+
/// Invoked while the <see cref="NetworkObject"/> is in the middle of
49+
/// being spawned.
50+
/// </summary>
51+
public override void OnNetworkSpawn()
52+
{
53+
// The write authority (server) does not need to know about its
54+
// own changes (for this example) since it is the "single point
55+
// of truth" for the door instance.
56+
if (IsServer)
57+
{
58+
// Host/Server:
59+
// Applies the configurable state upon spawning.
60+
m_State.Value = InitialState;
61+
}
62+
else
63+
{
64+
// Clients:
65+
// Subscribe to changes in the door's state.
66+
m_State.OnValueChanged += OnStateChanged;
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Invoked once the door and all associated components
72+
/// have finished the spawn process.
73+
/// </summary>
74+
protected override void OnNetworkPostSpawn()
75+
{
76+
// Everyone updates their door state when finished spawning the door
77+
// in order to assure the door reflects (visually) its current state.
78+
UpdateFromState();
79+
80+
// Begin to start updating this NetworkBehaviour instance once all
81+
// netcode related components have finished the spawn process.
82+
NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.Update);
83+
base.OnNetworkPostSpawn();
84+
}
85+
86+
/// <summary>
87+
/// Example of using the <see cref="INetworkUpdateSystem"/> usage pattern
88+
/// where it only updates while spawned.
89+
/// </summary>
90+
/// <param name="updateStage">The current update stage being invoked.</param>
91+
public void NetworkUpdate(NetworkUpdateStage updateStage)
92+
{
93+
switch (updateStage)
94+
{
95+
case NetworkUpdateStage.Update:
96+
{
97+
if (Input.GetKeyDown(KeyCode.Space))
98+
{
99+
Interact();
100+
}
101+
break;
102+
}
103+
}
104+
}
105+
106+
/// <summary>
107+
/// Invoked just before this instance runs through its de-spawn
108+
/// sequence. A good time to unsubscribe from things.
109+
/// </summary>
110+
public override void OnNetworkPreDespawn()
111+
{
112+
if (!IsServer)
113+
{
114+
m_State.OnValueChanged -= OnStateChanged;
115+
}
116+
117+
// Stop updating this NetworkBehaviour instance prior to running
118+
// through the de-spawn process.
119+
NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.Update);
120+
base.OnNetworkPreDespawn();
121+
}
122+
123+
/// <summary>
124+
/// Server makes changes to the state.
125+
/// Clients receive the changes in state.
126+
/// </summary>
127+
/// <remarks>
128+
/// When the previous state equals the current state, we are a client
129+
/// that is doing its 1st synchronization of this door instance.
130+
/// </remarks>
131+
/// <param name="previous">The previous <see cref="DoorStates"/> state.</param>
132+
/// <param name="current">The current <see cref="DoorStates"/> state.</param>
133+
public void OnStateChanged(DoorStates previous, DoorStates current)
134+
{
135+
UpdateFromState();
136+
}
137+
138+
/// <summary>
139+
/// Invoke when the state is updated in order to apply the change
140+
/// in door state to the door asset itself.
141+
/// </summary>
142+
private void UpdateFromState()
143+
{
144+
switch(m_State.Value)
145+
{
146+
case DoorStates.Closed:
147+
{
148+
// door is open:
149+
// - rotate door transform
150+
// - play animations, sound etc.
151+
/// <see cref="Netcode.Components.Helpers.ComponentCont"
152+
break;
153+
}
154+
case DoorStates.Open:
155+
{
156+
// door is closed:
157+
// - rotate door transform
158+
// - play animations, sound etc.
159+
break;
160+
}
161+
}
162+
Debug.Log($"[{name}] Door is currently {m_State.Value}.");
163+
}
164+
165+
/// <summary>
166+
/// Override to apply specific checks (like a player having the right
167+
/// key to open the door) or make it a non-virtual class and add logic
168+
/// directly to this method.
169+
/// </summary>
170+
/// <param name="player">The player attempting to open the door.</param>
171+
/// <returns></returns>
172+
protected virtual bool CanPlayerToggleState(NetworkObject player)
173+
{
174+
// For this example, if the door "is locked" then clients will
175+
// not be able to open the door but the host-client's player can.
176+
return !IsLocked || player.IsOwnedByServer;
177+
}
178+
179+
/// <summary>
180+
/// Invoked by either a Host or clients to interact with the door.
181+
/// </summary>
182+
public void Interact()
183+
{
184+
// Optional:
185+
// This is only if you want clients to be able to
186+
// interact with doors. A dedicated server would not
187+
// be able to do this since it does not have a player.
188+
if (IsServer && !IsHost)
189+
{
190+
// Optional to log a warning about this.
191+
return;
192+
}
193+
194+
if (IsHost)
195+
{
196+
ToggleState(NetworkManager.LocalClientId);
197+
}
198+
else
199+
{
200+
// Clients send an RPC to server (write authority) who applies the
201+
// change in state that will be synchronized with all client observers.
202+
ToggleStateRpc();
203+
}
204+
}
205+
206+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
207+
private DoorStates NextToggleState()
208+
{
209+
return m_State.Value == DoorStates.Open ? DoorStates.Closed : DoorStates.Open;
210+
}
211+
212+
/// <summary>
213+
/// Invoked only server-side
214+
/// Primary method to handle toggling the door state.
215+
/// </summary>
216+
/// <param name="clientId">The client toggling the door state.</param>
217+
private void ToggleState(ulong clientId)
218+
{
219+
// Get the server-side client player instance
220+
var playerObject = NetworkManager.SpawnManager.GetPlayerNetworkObject(clientId);
221+
if (playerObject != null)
222+
{
223+
var nextToggleState = NextToggleState();
224+
if (CanPlayerToggleState(playerObject))
225+
{
226+
// Host toggles the state
227+
m_State.Value = nextToggleState;
228+
UpdateFromState();
229+
}
230+
else
231+
{
232+
ToggleStateFailRpc(nextToggleState, RpcTarget.Single(clientId, RpcTargetUse.Temp));
233+
}
234+
}
235+
else
236+
{
237+
// Optional as to how you handle this. Since ToggleState is only invoked by
238+
// sever-side only script, this could mean many things depending upon whether
239+
// or not a client could interact with something and not have a player object.
240+
// If that is the case, then don't even bother checking for a player object.
241+
// If that is not the case, then there could be a timing issue between when
242+
// something can be "interacted with" and when a player is about to be de-spawned.
243+
// For this example, we just log a warning as this example was built with
244+
// the requirement that a client has a spawned player object that is used for
245+
// reference to determine if the client's player can toggle the state of the
246+
// door or not.
247+
NetworkLog.LogWarningServer($"Client-{clientId} has no spawned player object!");
248+
}
249+
}
250+
251+
/// <summary>
252+
/// Invoked by clients.
253+
/// Re-directs to the common <see cref="ToggleState(ulong)"/> method.
254+
/// </summary>
255+
/// <param name="rpcParams">includes <see cref="RpcReceiveParams.SenderClientId"/> that is automatically populated for you.</param>
256+
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
257+
private void ToggleStateRpc(RpcParams rpcParams = default)
258+
{
259+
ToggleState(rpcParams.Receive.SenderClientId);
260+
}
261+
262+
/// <summary>
263+
/// Optional:
264+
/// Handling when a player cannot open a door.
265+
/// </summary>
266+
/// <param name="rpcParams">includes <see cref="RpcReceiveParams.SenderClientId"/> that is automatically populated for you.</param>
267+
[Rpc(SendTo.SpecifiedInParams, InvokePermission = RpcInvokePermission.Server)]
268+
private void ToggleStateFailRpc(DoorStates doorState, RpcParams rpcParams = default)
269+
{
270+
// Provide player feedback that toggling failed.
271+
Debug.Log($"Failed to {doorState} the door!");
272+
}
273+
}

0 commit comments

Comments
 (0)