@@ -148,14 +148,22 @@ The [synchronization and notification example](#synchronization-and-notification
148148The ` OnValueChanged ` example shows a simple server-authoritative ` NetworkVariable ` being used to track the state of a door (open or closed) using an RPC that's sent to the server. Each time the door is used by a client, the ` Door.ToggleStateRpc ` is invoked and the server-side toggles the state of the door. When the ` Door.State.Value ` changes, all connected clients are synchronized to the (new) current ` Value ` and the ` OnStateChanged ` method is invoked locally on each client.
149149
150150``` csharp
151+ using System .Runtime .CompilerServices ;
152+ using Unity .Netcode ;
153+ using UnityEngine ;
154+
151155/// <summary >
152- /// A basic NetworkVariable driven door state example.
156+ /// Example of using a <see cref =" NetworkVariable{T}" /> to drive changes
157+ /// in state.
153158/// </summary >
154- public class Door : NetworkBehaviour
159+ /// <remarks >
160+ /// This is a simple state driven door example.
161+ /// This script was written with recommended usages patterns in mind.
162+ /// </remarks >
163+ public class Door : NetworkBehaviour , INetworkUpdateSystem
155164{
156165 /// <summary >
157- /// Only for UI purposes.
158- /// This provides an initial configuration state for the door.
166+ /// The two door states.
159167 /// </summary >
160168 public enum DoorStates
161169 {
@@ -169,10 +177,22 @@ public class Door : NetworkBehaviour
169177 [Tooltip (" Configures the door's initial state when 1st spawned." )]
170178 public DoorStates InitialState = DoorStates .Closed ;
171179
180+ /// <summary >
181+ /// Used for <see cref =" CanPlayerToggleState" /> example purposes.
182+ /// When true, only the server can open and close the door.
183+ /// Clients will receive a console log saying they could not open the door.
184+ /// </summary >
185+ public bool IsLocked ;
186+
172187 /// <summary >
173188 /// A simple door state where the server has write permissions and everyone has read permissions.
174189 /// </summary >
175- public NetworkVariable <bool > State = new NetworkVariable <bool >(default , NetworkVariableReadPermission .Everyone , NetworkVariableWritePermission .Server );
190+ private NetworkVariable <DoorStates > m_State = new NetworkVariable <DoorStates >(default , NetworkVariableReadPermission .Everyone , NetworkVariableWritePermission .Server );
191+
192+ /// <summary >
193+ /// The current state of the door.
194+ /// </summary >
195+ public DoorStates CurrentState => m_State .Value ;
176196
177197 /// <summary >
178198 /// Invoked while the <see cref =" NetworkObject" /> is in the middle of
@@ -187,27 +207,52 @@ public class Door : NetworkBehaviour
187207 {
188208 // Host/Server:
189209 // Applies the configurable state upon spawning.
190- State .Value = InitialState == DoorStates . Open ;
210+ m_State .Value = InitialState ;
191211 }
192212 else
193213 {
194214 // Clients:
195215 // Subscribe to changes in the door's state.
196- State .OnValueChanged += OnStateChanged ;
216+ m_State .OnValueChanged += OnStateChanged ;
197217 }
198218 }
199219
200220 /// <summary >
201- /// Invoked once the door and all associated < see cref = " NetworkAnimator " />
202- /// components have finished the spawn process.
221+ /// Invoked once the door and all associated components
222+ /// have finished the spawn process.
203223 /// </summary >
204224 protected override void OnNetworkPostSpawn ()
205225 {
206- // Everyone updates their door state when finished spawning the door.
207- UpdateFromState (true );
226+ // Everyone updates their door state when finished spawning the door
227+ // in order to assure the door reflects (visually) its current state.
228+ UpdateFromState ();
229+
230+ // Begin to start updating this NetworkBehaviour instance once all
231+ // netcode related components have finished the spawn process.
232+ NetworkUpdateLoop .RegisterNetworkUpdate (this , NetworkUpdateStage .Update );
208233 base .OnNetworkPostSpawn ();
209234 }
210235
236+ /// <summary >
237+ /// Example of using the <see cref =" INetworkUpdateSystem" /> usage pattern
238+ /// where it only updates while spawned.
239+ /// </summary >
240+ /// <param name =" updateStage" >The current update stage being invoked.</param >
241+ public void NetworkUpdate (NetworkUpdateStage updateStage )
242+ {
243+ switch (updateStage )
244+ {
245+ case NetworkUpdateStage .Update :
246+ {
247+ if (Input .GetKeyDown (KeyCode .Space ))
248+ {
249+ Interact ();
250+ }
251+ break ;
252+ }
253+ }
254+ }
255+
211256 /// <summary >
212257 /// Invoked just before this instance runs through its de-spawn
213258 /// sequence. A good time to unsubscribe from things.
@@ -216,56 +261,55 @@ public class Door : NetworkBehaviour
216261 {
217262 if (! IsServer )
218263 {
219- State .OnValueChanged -= OnStateChanged ;
264+ m_State .OnValueChanged -= OnStateChanged ;
220265 }
266+
267+ // Stop updating this NetworkBehaviour instance prior to running
268+ // through the de-spawn process.
269+ NetworkUpdateLoop .RegisterNetworkUpdate (this , NetworkUpdateStage .Update );
221270 base .OnNetworkPreDespawn ();
222271 }
223272
224273 /// <summary >
225- /// Only clients invoke this.
226274 /// Server makes changes to the state.
275+ /// Clients receive the changes in state.
227276 /// </summary >
228277 /// <remarks >
229278 /// When the previous state equals the current state, we are a client
230279 /// that is doing its 1st synchronization of this door instance.
231280 /// </remarks >
232- /// <param name =" previous" >The previous state.</param >
233- /// <param name =" current" >The current state.</param >
234- public void OnStateChanged (bool previous , bool current )
281+ /// <param name =" previous" >The previous < see cref = " DoorStates " /> state.</param >
282+ /// <param name =" current" >The current < see cref = " DoorStates " /> state.</param >
283+ public void OnStateChanged (DoorStates previous , DoorStates current )
235284 {
236- // Update to the current state while also providing a catch for
237- // the first synchronization where previous == current.
238285 UpdateFromState ();
239286 }
240287
241288 /// <summary >
242- /// Common method used to update the actual door asset based on its current state.
289+ /// Invoke when the state is updated in order to apply the change
290+ /// in door state to the door asset itself.
243291 /// </summary >
244- /// <param name =" isFirstSynchronization" >only set upon first spawn by a client</param >
245- private void UpdateFromState (bool isFirstSynchronization = false )
292+ private void UpdateFromState ()
246293 {
247- if (State .Value )
248- {
249- // door is open:
250- // - rotate door transform
251- // - play animations, sound etc.
252- // if first sync, reset to open and don't play sound
253- }
254- else
294+ switch (m_State .Value )
255295 {
256- // door is closed:
257- // - rotate door transform
258- // - play animations, sound etc.
259- // if first sync, reset to closed and don't play sound
260- }
261-
262- // Don't log a message about a change in state when first
263- // synchronizing.
264- if (! isFirstSynchronization )
265- {
266- var openClosed = State .Value ? " open" : " closed" ;
267- Debug .Log ($" []The door is now {openClosed }." );
296+ case DoorStates .Closed :
297+ {
298+ // door is open:
299+ // - rotate door transform
300+ // - play animations, sound etc.
301+ // / <see cref="Netcode.Components.Helpers.ComponentCont"
302+ break ;
303+ }
304+ case DoorStates .Open :
305+ {
306+ // door is closed:
307+ // - rotate door transform
308+ // - play animations, sound etc.
309+ break ;
310+ }
268311 }
312+ Debug .Log ($" [{name }] Door is currently {m_State .Value }." );
269313 }
270314
271315 /// <summary >
@@ -277,8 +321,9 @@ public class Door : NetworkBehaviour
277321 /// <returns ></returns >
278322 protected virtual bool CanPlayerToggleState (NetworkObject player )
279323 {
280- // For this example, the door can always be toggled.
281- return true ;
324+ // For this example, if the door "is locked" then clients will
325+ // not be able to open the door but the host-client's player can.
326+ return ! IsLocked || player .IsOwnedByServer ;
282327 }
283328
284329 /// <summary >
@@ -308,6 +353,12 @@ public class Door : NetworkBehaviour
308353 }
309354 }
310355
356+ [MethodImpl (MethodImplOptions .AggressiveInlining )]
357+ private DoorStates NextToggleState ()
358+ {
359+ return m_State .Value == DoorStates .Open ? DoorStates .Closed : DoorStates .Open ;
360+ }
361+
311362 /// <summary >
312363 /// Invoked only server-side
313364 /// Primary method to handle toggling the door state.
@@ -319,15 +370,16 @@ public class Door : NetworkBehaviour
319370 var playerObject = NetworkManager .SpawnManager .GetPlayerNetworkObject (clientId );
320371 if (playerObject != null )
321372 {
373+ var nextToggleState = NextToggleState ();
322374 if (CanPlayerToggleState (playerObject ))
323375 {
324376 // Host toggles the state
325- State .Value = ! State . Value ;
377+ m_State .Value = nextToggleState ;
326378 UpdateFromState ();
327379 }
328380 else
329381 {
330- ToggleStateFailRpc (RpcTarget .Single (clientId , RpcTargetUse .Temp ));
382+ ToggleStateFailRpc (nextToggleState , RpcTarget .Single (clientId , RpcTargetUse .Temp ));
331383 }
332384 }
333385 else
@@ -363,11 +415,10 @@ public class Door : NetworkBehaviour
363415 /// </summary >
364416 /// <param name =" rpcParams" >includes <see cref =" RpcReceiveParams.SenderClientId" /> that is automatically populated for you.</param >
365417 [Rpc (SendTo .SpecifiedInParams , InvokePermission = RpcInvokePermission .Server )]
366- private void ToggleStateFailRpc (RpcParams rpcParams = default )
418+ private void ToggleStateFailRpc (DoorStates doorState , RpcParams rpcParams = default )
367419 {
368420 // Provide player feedback that toggling failed.
369- var openOrClose = State .Value ? " close" : " open" ;
370- Debug .Log ($" Failed to {openOrClose } the door!" );
421+ Debug .Log ($" Failed to {doorState } the door!" );
371422 }
372423}
373424```
0 commit comments