您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
276 行
11 KiB
276 行
11 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace LobbyRelaySample.ngo
|
|
{
|
|
/// <summary>
|
|
/// Once the NetworkManager has been spawned, we need something to manage the game state and setup other in-game objects
|
|
/// that is itself a networked object, to track things like network connect events.
|
|
/// </summary>
|
|
public class InGameRunner : NetworkBehaviour
|
|
{
|
|
[SerializeField]
|
|
private PlayerCursor m_playerCursorPrefab = default;
|
|
[SerializeField]
|
|
private SymbolContainer m_symbolContainerPrefab = default;
|
|
[SerializeField]
|
|
private SymbolObject m_symbolObjectPrefab = default;
|
|
[SerializeField]
|
|
private SequenceSelector m_sequenceSelector = default;
|
|
[SerializeField]
|
|
private Scorer m_scorer = default;
|
|
[SerializeField]
|
|
private SymbolKillVolume m_killVolume = default;
|
|
[SerializeField]
|
|
private IntroOutroRunner m_introOutroRunner = default;
|
|
[SerializeField]
|
|
private NetworkedDataStore m_dataStore = default;
|
|
[SerializeField]
|
|
private BoxCollider m_collider;
|
|
|
|
public Action onGameBeginning;
|
|
Action m_onConnectionVerified, m_onGameEnd;
|
|
private int
|
|
m_expectedPlayerCount; // Used by the host, but we can't call the RPC until the network connection completes.
|
|
private bool? m_canSpawnInGameObjects;
|
|
private Queue<Vector2> m_pendingSymbolPositions = new Queue<Vector2>();
|
|
private float m_symbolSpawnTimer = 0.5f; // Initial time buffer to ensure connectivity before loading objects.
|
|
private int m_remainingSymbolCount = 0; // Only used by the host.
|
|
private float m_timeout = 10;
|
|
private bool m_hasConnected = false;
|
|
|
|
[SerializeField]
|
|
private SymbolContainer m_symbolContainerInstance;
|
|
private PlayerData
|
|
m_localUserData; // This has an ID that's not necessarily the OwnerClientId, since all clients will see all spawned objects regardless of ownership.
|
|
|
|
public static InGameRunner Instance
|
|
{
|
|
get
|
|
{
|
|
if (s_Instance!) return s_Instance;
|
|
return s_Instance = FindObjectOfType<InGameRunner>();
|
|
}
|
|
}
|
|
|
|
static InGameRunner s_Instance;
|
|
|
|
public void Initialize(Action onConnectionVerified, int expectedPlayerCount, Action onGameBegin,
|
|
Action onGameEnd,
|
|
LocalPlayer localUser)
|
|
{
|
|
m_onConnectionVerified = onConnectionVerified;
|
|
m_expectedPlayerCount = expectedPlayerCount;
|
|
onGameBeginning = onGameBegin;
|
|
m_onGameEnd = onGameEnd;
|
|
m_canSpawnInGameObjects = null;
|
|
m_localUserData = new PlayerData(localUser.DisplayName.Value, 0);
|
|
}
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
if (IsHost)
|
|
FinishInitialize();
|
|
m_localUserData = new PlayerData(m_localUserData.name, NetworkManager.Singleton.LocalClientId);
|
|
VerifyConnection_ServerRpc(m_localUserData.id);
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
m_onGameEnd(); // As a backup to ensure in-game objects get cleaned up, if this is disconnected unexpectedly.
|
|
}
|
|
|
|
private void FinishInitialize()
|
|
{
|
|
m_symbolContainerInstance = Instantiate(m_symbolContainerPrefab);
|
|
m_symbolContainerInstance.NetworkObject.Spawn();
|
|
ResetPendingSymbolPositions();
|
|
m_killVolume.Initialize(OnSymbolDeactivated);
|
|
}
|
|
|
|
private void ResetPendingSymbolPositions()
|
|
{
|
|
m_pendingSymbolPositions.Clear();
|
|
Rect boxRext = new Rect(m_collider.bounds.min.x, m_collider.bounds.min.y, m_collider.bounds.size.x,
|
|
m_collider.bounds.size.y);
|
|
IList<Vector2> points = m_sequenceSelector.GenerateRandomSpawnPoints(boxRext, 2);
|
|
foreach (Vector2 point in points)
|
|
m_pendingSymbolPositions.Enqueue(point);
|
|
}
|
|
|
|
/// <summary>
|
|
/// To verify the connection, invoke a server RPC call that then invokes a client RPC call. After this, the actual setup occurs.
|
|
/// </summary>
|
|
[ServerRpc(RequireOwnership = false)]
|
|
private void VerifyConnection_ServerRpc(ulong clientId)
|
|
{
|
|
VerifyConnection_ClientRpc(clientId);
|
|
|
|
// While we could start pooling symbol objects now, incoming clients would be flooded with the Spawn calls.
|
|
// This could lead to dropped packets such that the InGameRunner's Spawn call fails to occur, so we'll wait until all players join.
|
|
// (Besides, we will need to display instructions, which has downtime during which symbol objects can be spawned.)
|
|
}
|
|
|
|
[ClientRpc]
|
|
private void VerifyConnection_ClientRpc(ulong clientId)
|
|
{
|
|
if (clientId == m_localUserData.id)
|
|
VerifyConnectionConfirm_ServerRpc(m_localUserData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Once the connection is confirmed, spawn a player cursor and check if all players have connected.
|
|
/// </summary>
|
|
[ServerRpc(RequireOwnership = false)]
|
|
private void VerifyConnectionConfirm_ServerRpc(PlayerData clientData)
|
|
{
|
|
// Note that the client will not receive the cursor object reference, so the cursor must handle initializing itself.
|
|
PlayerCursor playerCursor = Instantiate(m_playerCursorPrefab);
|
|
playerCursor.NetworkObject.SpawnWithOwnership(clientData.id);
|
|
playerCursor.name += clientData.name;
|
|
m_dataStore.AddPlayer(clientData.id, clientData.name);
|
|
// The game will begin at this point, or else there's a timeout for booting any unconnected players.
|
|
bool areAllPlayersConnected = NetworkManager.Singleton.ConnectedClients.Count >= m_expectedPlayerCount;
|
|
VerifyConnectionConfirm_ClientRpc(clientData.id, areAllPlayersConnected);
|
|
}
|
|
|
|
[ClientRpc]
|
|
private void VerifyConnectionConfirm_ClientRpc(ulong clientId, bool canBeginGame)
|
|
{
|
|
if (clientId == m_localUserData.id)
|
|
{
|
|
m_onConnectionVerified?.Invoke();
|
|
m_hasConnected = true;
|
|
}
|
|
|
|
if (canBeginGame && m_hasConnected)
|
|
{
|
|
m_timeout = -1;
|
|
BeginGame();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The game will begin either when all players have connected successfully or after a timeout.
|
|
/// </summary>
|
|
void BeginGame()
|
|
{
|
|
m_canSpawnInGameObjects = true;
|
|
GameManager.Instance.BeginGame();
|
|
onGameBeginning?.Invoke();
|
|
m_introOutroRunner.DoIntro(StartMovingSymbols);
|
|
}
|
|
|
|
void StartMovingSymbols()
|
|
{
|
|
m_sequenceSelector.SetTargetsAnimatable();
|
|
if(IsHost)
|
|
m_symbolContainerInstance.StartMovingSymbols(); //TODO fix this for
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
CheckIfCanSpawnNewSymbol();
|
|
if (m_timeout >= 0)
|
|
{
|
|
m_timeout -= Time.deltaTime;
|
|
if (m_timeout < 0)
|
|
BeginGame();
|
|
}
|
|
|
|
void CheckIfCanSpawnNewSymbol()
|
|
{
|
|
if (!m_canSpawnInGameObjects.GetValueOrDefault() ||
|
|
m_remainingSymbolCount >= SequenceSelector.symbolCount || !IsHost)
|
|
return;
|
|
if (m_pendingSymbolPositions.Count > 0)
|
|
{
|
|
m_symbolSpawnTimer -= Time.deltaTime;
|
|
if (m_symbolSpawnTimer < 0)
|
|
{
|
|
m_symbolSpawnTimer = 0.02f; // Space out the object spawning a little to prevent a lag spike.
|
|
SpawnNewSymbol();
|
|
if (m_remainingSymbolCount >= SequenceSelector.symbolCount)
|
|
m_canSpawnInGameObjects = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpawnNewSymbol()
|
|
{
|
|
int index = SequenceSelector.symbolCount - m_pendingSymbolPositions.Count;
|
|
Vector3 pendingPos = m_pendingSymbolPositions.Dequeue();
|
|
var symbolObj = Instantiate(m_symbolObjectPrefab);
|
|
symbolObj.NetworkObject.Spawn();
|
|
symbolObj.name = "Symbol" + index;
|
|
symbolObj.SetParentAndPosition_Server(m_symbolContainerInstance.NetworkObject, pendingPos);
|
|
symbolObj.SetSymbolIndex_Server(m_sequenceSelector.GetNextSymbol(index));
|
|
m_remainingSymbolCount++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called while on the host to determine if incoming input has scored or not.
|
|
/// </summary>
|
|
public void OnPlayerInput(ulong playerId, SymbolObject selectedSymbol)
|
|
{
|
|
if (selectedSymbol.Clicked)
|
|
return;
|
|
|
|
if (m_sequenceSelector.ConfirmSymbolCorrect(playerId, selectedSymbol.SymbolIndex))
|
|
{
|
|
selectedSymbol.ClickedSequence_ServerRpc(playerId);
|
|
m_scorer.ScoreSuccess(playerId);
|
|
OnSymbolDeactivated();
|
|
}
|
|
else
|
|
m_scorer.ScoreFailure(playerId);
|
|
}
|
|
|
|
void OnSymbolDeactivated()
|
|
{
|
|
if (--m_remainingSymbolCount <= 0)
|
|
WaitForEndingSequence_ClientRpc();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The server determines when the game should end. Once it does, it needs to inform the clients to clean up their networked objects first,
|
|
/// since disconnecting before that happens will prevent them from doing so (since they can't receive despawn events from the disconnected server).
|
|
/// </summary>
|
|
[ClientRpc]
|
|
private void WaitForEndingSequence_ClientRpc()
|
|
{
|
|
m_scorer.OnGameEnd();
|
|
m_introOutroRunner.DoOutro(EndGame);
|
|
}
|
|
|
|
private void EndGame()
|
|
{
|
|
if (IsHost)
|
|
StartCoroutine(EndGame_ClientsFirst());
|
|
}
|
|
|
|
private IEnumerator EndGame_ClientsFirst()
|
|
{
|
|
EndGame_ClientRpc();
|
|
yield return null;
|
|
SendLocalEndGameSignal();
|
|
}
|
|
|
|
[ClientRpc]
|
|
private void EndGame_ClientRpc()
|
|
{
|
|
if (IsHost)
|
|
return;
|
|
SendLocalEndGameSignal();
|
|
}
|
|
|
|
private void SendLocalEndGameSignal()
|
|
{
|
|
m_onGameEnd();
|
|
}
|
|
}
|
|
}
|