-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathServerAdditiveSceneLoader.cs
158 lines (140 loc) · 6.02 KB
/
ServerAdditiveSceneLoader.cs
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
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Multiplayer.Samples.Utilities
{
/// <summary>
/// This NetworkBehavior, when added to a GameObject containing a collider (or multiple colliders) with the
/// IsTrigger property On, allows the server to load or unload a scene additively according to the position of
/// player-owned objects. The scene is loaded when there is at least one NetworkObject with the specified tag that
/// enters its collider. It also unloads it when all such NetworkObjects leave the collider, after a specified
/// delay to prevent it from repeatedly loading and unloading the same scene.
/// </summary>
public class ServerAdditiveSceneLoader : NetworkBehaviour
{
[SerializeField]
float m_DelayBeforeUnload = 5.0f;
[SerializeField]
string m_SceneName;
/// <summary>
/// We assume that all NetworkObjects with this tag are player-owned
/// </summary>
[SerializeField]
string m_PlayerTag;
/// <summary>
/// We keep the clientIds of every player-owned object inside the collider's volume
/// </summary>
List<ulong> m_PlayersInTrigger;
bool IsActive => IsServer && IsSpawned;
enum SceneState
{
Loaded,
Unloaded,
Loading,
Unloading,
WaitingToUnload,
}
SceneState m_SceneState = SceneState.Unloaded;
Coroutine m_UnloadCoroutine;
public override void OnNetworkSpawn()
{
if (IsServer)
{
// Adding this to remove all pending references to a specific client when they disconnect, since objects
// that are destroyed do not generate OnTriggerExit events.
NetworkManager.OnClientDisconnectCallback += RemovePlayer;
NetworkManager.SceneManager.OnSceneEvent += OnSceneEvent;
m_PlayersInTrigger = new List<ulong>();
}
}
public override void OnNetworkDespawn()
{
if (IsServer)
{
NetworkManager.OnClientDisconnectCallback -= RemovePlayer;
NetworkManager.SceneManager.OnSceneEvent -= OnSceneEvent;
}
}
void OnSceneEvent(SceneEvent sceneEvent)
{
if (sceneEvent.SceneEventType == SceneEventType.LoadEventCompleted && sceneEvent.SceneName == m_SceneName)
{
m_SceneState = SceneState.Loaded;
}
else if (sceneEvent.SceneEventType == SceneEventType.UnloadEventCompleted && sceneEvent.SceneName == m_SceneName)
{
m_SceneState = SceneState.Unloaded;
}
}
void OnTriggerEnter(Collider other)
{
if (IsActive) // make sure that OnNetworkSpawn has been called before this
{
if (other.CompareTag(m_PlayerTag) && other.TryGetComponent(out NetworkObject networkObject))
{
m_PlayersInTrigger.Add(networkObject.OwnerClientId);
if (m_UnloadCoroutine != null)
{
// stopping the unloading coroutine since there is now a player-owned NetworkObject inside
StopCoroutine(m_UnloadCoroutine);
if (m_SceneState == SceneState.WaitingToUnload)
{
m_SceneState = SceneState.Loaded;
}
}
}
}
}
void OnTriggerExit(Collider other)
{
if (IsActive) // make sure that OnNetworkSpawn has been called before this
{
if (other.CompareTag(m_PlayerTag) && other.TryGetComponent(out NetworkObject networkObject))
{
m_PlayersInTrigger.Remove(networkObject.OwnerClientId);
}
}
}
void FixedUpdate()
{
if (IsActive) // make sure that OnNetworkSpawn has been called before this
{
if (m_SceneState == SceneState.Unloaded && m_PlayersInTrigger.Count > 0)
{
var status = NetworkManager.SceneManager.LoadScene(m_SceneName, LoadSceneMode.Additive);
// if successfully started a LoadScene event, set state to Loading
if (status == SceneEventProgressStatus.Started)
{
m_SceneState = SceneState.Loading;
}
}
else if (m_SceneState == SceneState.Loaded && m_PlayersInTrigger.Count == 0)
{
// using a coroutine here to add a delay before unloading the scene
m_UnloadCoroutine = StartCoroutine(WaitToUnloadCoroutine());
m_SceneState = SceneState.WaitingToUnload;
}
}
}
void RemovePlayer(ulong clientId)
{
// remove all references to this clientId. There could be multiple references if a single client owns
// multiple NetworkObjects with the m_PlayerTag, or if this script's GameObject has overlapping colliders
while (m_PlayersInTrigger.Remove(clientId)) { }
}
IEnumerator WaitToUnloadCoroutine()
{
yield return new WaitForSeconds(m_DelayBeforeUnload);
Scene scene = SceneManager.GetSceneByName(m_SceneName);
if (scene.isLoaded)
{
var status = NetworkManager.SceneManager.UnloadScene(SceneManager.GetSceneByName(m_SceneName));
// if successfully started an UnloadScene event, set state to Unloading, if not, reset state to Loaded so a new Coroutine will start
m_SceneState = status == SceneEventProgressStatus.Started ? SceneState.Unloading : SceneState.Loaded;
}
}
}
}