Skip to content

Commit e098e31

Browse files
update
Work in progress adjustments for usability under various scenarios.
1 parent abd75d0 commit e098e31

File tree

3 files changed

+156
-10
lines changed

3 files changed

+156
-10
lines changed

com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ namespace Unity.Netcode.Components
1818
/// The term "attach" is used in place of parenting in order to distinguish between <see cref="NetworkObject"/> parenting and
1919
/// <see cref="AttachableBehaviour"/> parenting ("attaching" and "detaching").<br />
2020
/// This component can be used along with one or more <see cref="ComponentController"/> in order to enable or disable different components depending
21-
/// upon the <see cref="AttachableBehaviour"/> instance's current state.
21+
/// upon the <see cref="AttachableBehaviour"/> instance's current state.<br />
22+
/// <see cref="AttachableNode"/> invocation order:
23+
/// - When attaching, the <see cref="AttachableNode"/>'s <see cref="AttachableNode.OnAttached(AttachableBehaviour)"/> is invoked just before the <see cref="OnAttachStateChanged"/> is invoked with the <see cref="AttachState.Attached"/> state.<br />
24+
/// - When detaching, the <see cref="AttachableNode"/>'s <see cref="AttachableNode.OnDetached(AttachableBehaviour)"/> is invoked right after the <see cref="OnAttachStateChanged"/> is invoked with the <see cref="AttachState.Detached"/> notification.<br />
2225
/// </remarks>
2326
public class AttachableBehaviour : NetworkBehaviour
2427
{
@@ -191,9 +194,11 @@ private void UpdateAttachedState()
191194
{
192195
// Run through the same process without being triggerd by a NetVar update.
193196
UpdateAttachState(AttachState.Detaching, m_AttachableNode);
197+
InternalDetach();
198+
UpdateAttachState(AttachState.Detached, m_AttachableNode);
199+
194200
m_AttachableNode.Detach(this);
195-
transform.parent = null;
196-
UpdateAttachState(AttachState.Detached, null);
201+
m_AttachableNode = null;
197202
}
198203
}
199204

@@ -211,6 +216,14 @@ private void UpdateAttachedState()
211216

212217
// Notify of the changed attached state
213218
UpdateAttachState(m_AttachState, m_AttachableNode);
219+
220+
// When detatching, we want to make our final action
221+
// the invocation of the AttachableNode's Detatch method.
222+
if (!shouldParent && m_AttachableNode)
223+
{
224+
m_AttachableNode.Detach(this);
225+
m_AttachableNode = null;
226+
}
214227
}
215228

216229
/// <summary>
@@ -309,8 +322,6 @@ internal void InternalDetach()
309322
{
310323
if (m_AttachableNode)
311324
{
312-
m_AttachableNode.Detach(this);
313-
m_AttachableNode = null;
314325
if (m_DefaultParent)
315326
{
316327
// Set the original parent and origianl local position and rotation

com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
/// </remarks>
1515
public class AttachableNode : NetworkBehaviour
1616
{
17+
/// <summary>
18+
/// Returns true if the <see cref="AttachableNode"/> instance has one or more attached <see cref="AttachableBehaviour"/> components.
19+
/// </summary>
20+
public bool HasAttachments => m_AttachedBehaviours.Count > 0;
21+
1722
/// <summary>
1823
/// A <see cref="List{T}"/> of the currently attached <see cref="AttachableBehaviour"/>s.
1924
/// </summary>

com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs

Lines changed: 135 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.Reflection;
45
using UnityEngine;
@@ -16,8 +17,19 @@ public class ComponentControllerEntry
1617
/// When true, this component's enabled state will be the inverse of
1718
/// the value passed into <see cref="ComponentController.SetEnabled(bool)"/>.
1819
/// </summary>
20+
[Tooltip("When enabled, this component will inversely mirror the currently applied enable or disable state.")]
1921
public bool InvertEnabled;
2022

23+
/// <summary>
24+
/// The amount of time to delay enabling this component when the <see cref="ComponentController"/> has just transitioned from a disabled to enabled state.
25+
/// </summary>
26+
public float EnableDelay;
27+
28+
/// <summary>
29+
/// The amount of time to delay disabling this component when the <see cref="ComponentController"/> has just transitioned from an enabled to disabled state.
30+
/// </summary>
31+
public float DisableDelay;
32+
2133
/// <summary>
2234
/// The component to control.
2335
/// </summary>
@@ -27,8 +39,63 @@ public class ComponentControllerEntry
2739
/// and <see cref="InvertEnabled"/> properties will be applied to all components found on the <see cref="GameObject"/>.
2840
/// </remarks>
2941
public Object Component;
30-
3142
internal PropertyInfo PropertyInfo;
43+
44+
45+
internal bool TimeDeltaDelayInProgress;
46+
internal bool PendingState;
47+
internal float EnableDelayTimeDelta;
48+
internal float DisableDelayTimeDelta;
49+
50+
private bool GetRelativeEnabled(bool enabled)
51+
{
52+
return InvertEnabled ? !enabled : enabled;
53+
}
54+
55+
internal bool HasDelay(bool enabled)
56+
{
57+
return GetRelativeEnabled(enabled) ? EnableDelay > 0.0f : DisableDelay > 0.0f;
58+
}
59+
60+
internal void StartTimeDeltaTracking(bool isEnabled)
61+
{
62+
if (GetRelativeEnabled(isEnabled))
63+
{
64+
EnableDelayTimeDelta = Time.realtimeSinceStartup + EnableDelay;
65+
}
66+
else
67+
{
68+
DisableDelayTimeDelta = Time.realtimeSinceStartup + DisableDelay;
69+
}
70+
TimeDeltaDelayInProgress = true;
71+
PendingState = isEnabled;
72+
}
73+
74+
75+
internal void SetValue(bool isEnabled)
76+
{
77+
// If invert enabled is true, then use the inverted value passed in.
78+
// Otherwise, directly apply the value passed in.
79+
PropertyInfo.SetValue(Component, GetRelativeEnabled(isEnabled));
80+
}
81+
82+
internal bool CheckTimeDeltaDelay()
83+
{
84+
if (!TimeDeltaDelayInProgress)
85+
{
86+
return false;
87+
}
88+
89+
var isDeltaDelayInProgress = ((EnableDelayTimeDelta > Time.realtimeSinceStartup)
90+
|| (DisableDelayTimeDelta > Time.realtimeSinceStartup));
91+
92+
if (!isDeltaDelayInProgress)
93+
{
94+
SetValue(PendingState);
95+
}
96+
TimeDeltaDelayInProgress = isDeltaDelayInProgress;
97+
return TimeDeltaDelayInProgress;
98+
}
3299
}
33100

34101
/// <summary>
@@ -52,6 +119,17 @@ public class ComponentController : NetworkBehaviour
52119
[Tooltip("The initial state of the component controllers enabled status when instnatiated.")]
53120
public bool StartEnabled = true;
54121

122+
/// <summary>
123+
/// The coroutine yield time used to check on any pending delayed <see cref="ComponentControllerEntry"/> state transitions.
124+
/// </summary>
125+
/// <remarks>
126+
/// If there are any <see cref="ComponentControllerEntry"/> delays (enable, disable, or both), then upon changing the state of a <see cref="ComponentController"/> a coroutine will be started
127+
/// (if not already started) that will monitor any pending delayed transitions (<see cref="EnableDelay"/> or <see cref="DisableDelay"/>) and will
128+
/// handle changing each delayed <see cref="ComponentControllerEntry"/> until all instances have transitioned their value to the pending state change.
129+
/// When there are no more pending delayed <see cref="ComponentControllerEntry"/>s, the coroutine will stop.
130+
/// </remarks>
131+
public float PendingDelayYieldTime = 0.032f;
132+
55133
/// <summary>
56134
/// The list of <see cref="Components"/>s to be enabled and disabled.
57135
/// </summary>
@@ -214,6 +292,17 @@ public override void OnNetworkDespawn()
214292
base.OnNetworkDespawn();
215293
}
216294

295+
/// <inheritdoc/>
296+
public override void OnDestroy()
297+
{
298+
if (m_CoroutineObject.IsRunning)
299+
{
300+
StopCoroutine(m_CoroutineObject.Coroutine);
301+
m_CoroutineObject.IsRunning = false;
302+
}
303+
base.OnDestroy();
304+
}
305+
217306
private void OnEnabledChanged(bool previous, bool current)
218307
{
219308
ApplyEnabled(current);
@@ -239,13 +328,54 @@ private void InitializeComponents()
239328
/// <param name="enabled">the state update to apply</param>
240329
private void ApplyEnabled(bool enabled)
241330
{
331+
242332
foreach (var entry in ValidComponents)
243333
{
244-
// If invert enabled is true, then use the inverted value passed in.
245-
// Otherwise, directly apply the value passed in.
246-
var isEnabled = entry.InvertEnabled ? !enabled : enabled;
247-
entry.PropertyInfo.SetValue(entry.Component, isEnabled);
334+
if (entry.HasDelay(enabled))
335+
{
336+
if (!m_CoroutineObject.IsRunning)
337+
{
338+
m_CoroutineObject.Coroutine = StartCoroutine(PendingAppliedState());
339+
m_CoroutineObject.IsRunning = true;
340+
}
341+
entry.StartTimeDeltaTracking(enabled);
342+
}
343+
else
344+
{
345+
entry.SetValue(enabled);
346+
}
347+
}
348+
}
349+
350+
private class CoroutineObject
351+
{
352+
public Coroutine Coroutine;
353+
public bool IsRunning;
354+
}
355+
356+
private CoroutineObject m_CoroutineObject = new CoroutineObject();
357+
358+
359+
private IEnumerator PendingAppliedState()
360+
{
361+
var checkPendingDeltaDelays = true;
362+
var waitTime = new WaitForSeconds(PendingDelayYieldTime);
363+
364+
while (checkPendingDeltaDelays)
365+
{
366+
yield return waitTime;
367+
checkPendingDeltaDelays = false;
368+
foreach (var entry in ValidComponents)
369+
{
370+
if (!entry.CheckTimeDeltaDelay())
371+
{
372+
continue;
373+
}
374+
checkPendingDeltaDelays = true;
375+
}
248376
}
377+
m_CoroutineObject.IsRunning = false;
378+
yield break;
249379
}
250380

251381
/// <summary>

0 commit comments

Comments
 (0)