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