// Actually destroying the symbol objects can cause garbage collection and other delays that might lead to desyncs.
// Disabling the networked object can also cause issues, so instead, just move the object, and it will be cleaned up once the NetworkManager is destroyed.
// (If object pooling, this is where to instead return it to the pool.)
// (If we used object pooling, this is where we would instead return it to the pool.)
// Note: The SymbolObjects, which will be children of this object, need their NetworkTransforms to have IsLocalSpace set to true. Otherwise, they might get desynced.
// (This would manifest as packet loss errors.)
// Also note: The initial position of the SymbolObject prefab is set to be outside the camera view in the Z-direction, so that it doesn't interpolate past the actual
// position when it spawns on a client (as opposed to in the Y-direction, since this SymbolContainer is also moving downward).
/// <summary>
/// Rather than track movement data for every symbol object, the symbols will all be parented under one container that will move.
/// It will not begin that movement until it both has been Spawned on the network and it has been informed that the game has started.
[SerializeField]privatefloatm_speed=1;
privateboolm_isConnected=false;
privateboolm_hasGameStarted=false;
/// <summary>
/// Verify both that the game has started and that the network connection is working before moving the symbols.
/// Handles selecting the randomized sequence of symbols to spawn. This also selects a subset of the selected symbols to be the target
/// sequence that each player needs to select in order.
/// Handles selecting the randomized sequence of symbols to spawn, choosing a subset to be the ordered target sequence that each player needs to select.
/// This also handles selecting randomized positions for the symbols, and it sets up the target sequence animation for the instruction sequence.
privateboolm_hasReceivedTargetSequence=false;// TODO: Perhaps split up members by client vs. host?
privateboolm_hasReceivedTargetSequence=false;
privateDictionary<ulong,int>m_targetSequenceIndexPerPlayer=newDictionary<ulong,int>();// Also owned by the host, indexed by client ID.
privateDictionary<ulong,int>m_targetSequenceIndexPerPlayer=newDictionary<ulong,int>();// Each player's current target. Also owned by the host, indexed by client ID.
publicvoidAwake()
{
publicoverridevoidOnNetworkSpawn()
{
if(IsHost)
ChooseSymbols();
m_localId=NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_localId);
}
privatevoidChooseSymbols()
{
// Choose some subset of the list of symbols to be present in this game, along with a target sequence.
m_fullSequence.Add(m_targetSequence[2]);// We want a List instead of a Queue or Stack for faster insertion, but we will remove indices backwards so as to not resize other entries.
m_fullSequence.Add(m_targetSequence[1]);
m_fullSequence.Add(m_targetSequence[0]);
}
// Then, fill in with a good mix of the remaining symbols.
for(intn=3;n<numSymbolTypes-1;n++)
AddHalfRemaining(n,2);
AddHalfRemaining(numSymbolTypes-1,1);// 1 as the divider ensures all remaining symbols get an index.
// Then, ensure that the target sequence is present in order throughout most of the full set of symbols to spawn.
intnumTargetSequences=(int)(k_symbolCount*2/3f)/3;// About 2/3 of the symbols will be definitely part of the target sequence.
for(;numTargetSequences>=0;numTargetSequences--)
{m_fullSequence.Add(m_targetSequence[2]);// We want a List instead of a Queue or Stack for faster insertion, but we will remove indices backwards so as to not reshift other entries.
m_fullSequence.Add(m_targetSequence[1]);
m_fullSequence.Add(m_targetSequence[0]);
}
// Then, fill in with a good mix of the remaining symbols.
/// Used for the binary space partition (BSP) algorithm, which makes alternating "cuts" to subdivide rectangles.
/// Used for the binary space partition (BSP) algorithm, which makes alternating "cuts" to subdivide rectangles while maintaining a buffer of space between them.
/// This ensures all symbols will be randomly (though not uniformly) distributed without overlapping each other.
/// </summary>
privatestructRectCut
{
points.Clear();
rects.Enqueue(newRectCut(bounds,-1));// Start with an extra horizontal cut since the space is so tall.
// For each rect, subdivide it with an alternating cut, and then enqueue for recursion until enough points are chosen or the rects are all too small.
// This ensures a reasonable distribution of points which won't cause overlaps, though it will not necessarily be uniform.
// For each rect, subdivide it with an alternating cut, and then enqueue the two smaller rects for recursion until enough points are chosen or the rects are all too small.
// TODO: I'm using host and server interchangeably...which in part I have to since it's ServerRpc but I think IsHost vs. IsServer yield different results in some places?
// TODO: Most of the ints could be bytes?
[Tooltip("When the game ends, this will be provided the results in order of rank (1st-place first, and so on).")]
[Tooltip("When the game ends, this will be called once for each player in order of rank (1st-place first, and so on).")]
publicclassRelayUtpNGOSetupHost:MonoBehaviour// If this is a MonoBehaviour, it can be added to the InGameRunner object for easier cleanup on game end.
publicclassRelayUtpNGOSetupHost:MonoBehaviour// This is a MonoBehaviour so that it can be added to the InGameRunner object for easier cleanup on game end.
{
privateSetupInGamem_setupInGame;
privateLocalLobbym_localLobby;
privatevoidOnJoin(JoinAllocationjoinAllocation)
{
if(joinAllocation==null||this==null)// The returned JoinAllocation is null if allocation failed. this would be destroyed already if you quit the lobby while Relay is connecting.
if(joinAllocation==null||this==null)// The returned JoinAllocation is null if allocation failed. This would be destroyed already if you quit the lobby while Relay is connecting.
privateNetworkVariable<Vector3>m_position=newNetworkVariable<Vector3>(NetworkVariableReadPermission.Everyone,Vector3.zero);// (Using a NetworkTransform to sync position would also work.)
// If the local player cursor spawns before this cursor's owner, the owner's data won't be available yet. This is used to retrieve the data later.
/// A place to store data needed by networked behaviors. Each client has an instance, but the server's instance stores the actual data.
/// A place to store data needed by networked behaviors. Each client has an instance so they can retrieve data, but the server's instance stores the actual data.
NetworkObjectplayerCursor=NetworkObject.Instantiate(m_playerCursorPrefab);// Note that the client will not receive the cursor object reference, so the cursor must handle initializing itself.
/// The game will begin either when all players have connected successfully or after a timeout.
/// </summary>
privatevoidBeginGame()
{
m_canSpawnInGameObjects=true;
{
EndGame_ClientRpc();
yieldreturnnull;
SendEndGameSignal();
SendLocalEndGameSignal();
}
[ClientRpc]
return;
SendEndGameSignal();
SendLocalEndGameSignal();
privatevoidSendEndGameSignal()
privatevoidSendLocalEndGameSignal()
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame,null);// We only send this message if the game completes, since the player remains in the lobby. If the player leaves with the back button, that instead sends them to the menu.
Locator.Get.Messenger.OnReceiveMessage(MessageType.EndGame,null);// We only send this message if the game completes, since the player remains in the lobby in that case. If the player leaves with the back button, that instead sends them to the menu.