浏览代码

feat: rest of the lifecycle working

- back button
-Symbol Hints
- Messenger and Locator Reliance removal
/main/staging/2021_Upgrade/Async_Refactor
UnityJacob 2 年前
当前提交
a5704e9b
共有 15 个文件被更改,包括 233 次插入208 次删除
  1. 10
      Assets/Prefabs/NGO/InGameLogic.prefab
  2. 1
      Assets/Scripts/GameLobby/Auth/Auth.cs
  3. 20
      Assets/Scripts/GameLobby/Game/GameManager.cs
  4. 26
      Assets/Scripts/GameLobby/Lobby/LobbyManager.cs
  5. 106
      Assets/Scripts/GameLobby/NGO/InGameRunner.cs
  6. 10
      Assets/Scripts/GameLobby/NGO/IntroOutroRunner.cs
  7. 52
      Assets/Scripts/GameLobby/NGO/PlayerCursor.cs
  8. 135
      Assets/Scripts/GameLobby/NGO/SequenceSelector.cs
  9. 8
      Assets/Scripts/GameLobby/NGO/SetupInGame.cs
  10. 23
      Assets/Scripts/GameLobby/NGO/SymbolContainer.cs
  11. 2
      Assets/Scripts/GameLobby/NGO/SymbolData.cs
  12. 31
      Assets/Scripts/GameLobby/NGO/SymbolObject.cs
  13. 1
      Assets/Scripts/GameLobby/UI/BackButtonUI.cs
  14. 1
      Assets/Scripts/GameLobby/UI/JoinMenuUI.cs
  15. 15
      ProjectSettings/RiderScriptEditorPersistedState.asset

10
Assets/Prefabs/NGO/InGameLogic.prefab


m_Script: {fileID: 11500000, guid: e30fcbc5a3738e94a87d7a028a2a6fba, type: 3}
m_Name:
m_EditorClassIdentifier:
m_playerCursorPrefab: {fileID: -1321688216342888635, guid: 905594b4ee5bb864a84af916cc445d1b, type: 3}
m_symbolContainerPrefab: {fileID: 3984715711634906321, guid: f42ed38d10b57ec48870f76a7a63389e, type: 3}
m_playerCursorPrefab: {fileID: 4403165428829500588, guid: 905594b4ee5bb864a84af916cc445d1b, type: 3}
m_symbolContainerPrefab: {fileID: 210836793418873202, guid: f42ed38d10b57ec48870f76a7a63389e, type: 3}
m_symbolObjectPrefab: {fileID: -8192876538761676823, guid: e371ca3112f9e244ab574b472387b64b, type: 3}
m_sequenceSelector: {fileID: 6829526275642584874}
m_scorer: {fileID: 2250928641321586401}

m_collider: {fileID: 4478514896869440358}
m_symbolContainerInstance: {fileID: 0}
--- !u!114 &6829526275642584874
MonoBehaviour:
m_ObjectHideFlags: 0

m_Script: {fileID: 11500000, guid: 77910b5766924f044995dfe427e9eea3, type: 3}
m_Name:
m_EditorClassIdentifier:
m_symbolData: {fileID: 11400000, guid: 84a81a2f14d442a49b46eccefb73933f, type: 2}
m_targetSequenceOutput:
m_SymbolData: {fileID: 11400000, guid: 84a81a2f14d442a49b46eccefb73933f, type: 2}
m_TargetSequenceOutput:
- {fileID: 82602402459208677}
- {fileID: 2579125671427676891}
- {fileID: 6639276723346236408}

m_Script: {fileID: 11500000, guid: 9f7a308a72bf6ce4a862a246eaed82cb, type: 3}
m_Name:
m_EditorClassIdentifier:
m_inGameRunner: {fileID: 6673480979101889538}
m_animator: {fileID: 9205289577047239847}
--- !u!1 &7760914309000979352
GameObject:

1
Assets/Scripts/GameLobby/Auth/Auth.cs


profileOptions.SetProfile(profile);
await UnityServices.InitializeAsync(profileOptions);
await SignInAnonymouslyAsync(tries);
Debug.Log($"Auth attempts Finished : {AuthenticationState.ToString()}");
return AuthenticationState;
}

20
Assets/Scripts/GameLobby/Game/GameManager.cs


{
return;
}
SetCurrentLobbies(LobbyConverters.QueryToLocalList(qr));
}

m_LocalLobby.Locked.Value = true;
SendLocalLobbyData();
}
m_setupInGame?.MiniGameBeginning();
}
public void EndGame()

void SetGameState(GameState state)
{
var isQuittingGame = LocalGameState == GameState.Lobby &&
m_LocalLobby.LocalLobbyState.Value == LobbyState.InGame;
if (isQuittingGame)
{
//If we were in-game, make sure we stop by the lobby first
state = GameState.Lobby;
EndGame();
}
if (isLeavingLobby)
LeaveLobby();
onGameStateChanged.Invoke(LocalGameState);

foreach (var lobby in lobbies)
newLobbyDict.Add(lobby.LobbyID.Value, lobby);
LobbyList.CurrentLobbies = newLobbyDict;
LobbyList.CurrentLobbies = newLobbyDict;
}
async Task CreateLobby()

StartVivoxJoin();
}
void LeaveLobby()
public void LeaveLobby()
{
m_LocalUser.ResetState();
#pragma warning disable 4014

26
Assets/Scripts/GameLobby/Lobby/LobbyManager.cs


{
if (m_CurrentLobby == null)
{
Debug.LogError("LobbyManager not currently in a lobby. Did you CreateLobbyAsync or JoinLobbyAsync?");
Debug.LogWarning("LobbyManager not currently in a lobby. Did you CreateLobbyAsync or JoinLobbyAsync?");
return false;
}

}
await m_CreateCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Creating");
try
{

}
await m_JoinCooldown.QueueUntilCooldown();
Debug.Log($"{localUser.DisplayName.Value}({localUser.ID.Value}) Joining Lobby- {lobbyId} / {lobbyCode}");
string uasId = AuthenticationService.Instance.PlayerId;
var playerData = CreateInitialPlayerData(localUser);

}
await m_QuickJoinCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Quick Joining.");
var filters = LobbyColorToFilters(limitToColor);
string uasId = AuthenticationService.Instance.PlayerId;

Count = k_maxLobbiesToShow,
Filters = filters
};
Debug.Log("Retrieving Lobby List");
return await LobbyService.Instance.QueryLobbiesAsync(queryOptions);
}

{
var playerIndex = lobbyPlayerChanges.Key;
var localPlayer = localLobby.GetLocalPlayer(playerIndex);
Debug.Log($"{localPlayer} at index {playerIndex} data changed");
if (localPlayer == null)
continue;
$"ConnectionInfo for {localPlayer.DisplayName.Value} changed to {connectionInfo}");
$"ConnectionInfo for player {playerIndex} changed to {connectionInfo}");
if (playerChanges.LastUpdatedChanged.Changed)
{
var lastUpdated = playerChanges.LastUpdatedChanged.Value;
Debug.Log($"LastUpdated for {localPlayer.DisplayName.Value} changed to {lastUpdated}");
}
if (playerChanges.LastUpdatedChanged.Changed) { }
//There are changes on the Player
if (playerChanges.ChangedData.Changed)

m_LobbyEventCallbacks.KickedFromLobby += () =>
{
Debug.Log("Left Lobby");
Dispose();
};
await LobbyService.Instance.SubscribeToLobbyEventsAsync(lobbyID, m_LobbyEventCallbacks);
}

if (!InLobby())
return;
string playerId = AuthenticationService.Instance.PlayerId;
Debug.Log($"{playerId} leaving Lobby {m_CurrentLobby.Id}");
await LobbyService.Instance.RemovePlayerAsync(m_CurrentLobby.Id, playerId);
m_CurrentLobby = null;

if (m_UpdatePlayerCooldown.TaskQueued)
return;
await m_UpdatePlayerCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Updating Player Data");
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
{

if (m_UpdatePlayerCooldown.TaskQueued)
return;
await m_UpdatePlayerCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Relay Info (Player)");
UpdatePlayerOptions updateOptions = new UpdatePlayerOptions
{

if (m_UpdateLobbyCooldown.TaskQueued)
return;
await m_UpdateLobbyCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Updating Lobby Data");
UpdateLobbyOptions updateOptions = new UpdateLobbyOptions { Data = dataCurr, IsLocked = shouldLock };
m_CurrentLobby = await LobbyService.Instance.UpdateLobbyAsync(m_CurrentLobby.Id, updateOptions);

if (!InLobby())
return;
await m_DeleteLobbyCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Deleting Lobby");
await LobbyService.Instance.DeleteLobbyAsync(m_CurrentLobby.Id);
}

m_CurrentLobby = null;
m_LobbyEventCallbacks = new LobbyEventCallbacks();
}
#region HeartBeat

if (m_HeartBeatCooldown.IsCoolingDown)
return;
await m_HeartBeatCooldown.QueueUntilCooldown();
Debug.Log("Lobby - Heartbeat");
await LobbyService.Instance.SendHeartbeatPingAsync(m_CurrentLobby.Id);
}

106
Assets/Scripts/GameLobby/NGO/InGameRunner.cs


/// 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, IInGameInputHandler
public class InGameRunner : NetworkBehaviour
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;
private NetworkObject m_playerCursorPrefab = default;
private PlayerCursor m_playerCursorPrefab = default;
private NetworkObject m_symbolContainerPrefab = default;
private SymbolContainer m_symbolContainerPrefab = default;
[SerializeField]
private SymbolObject m_symbolObjectPrefab = default;
[SerializeField]

[SerializeField]
private BoxCollider m_collider;
private Transform 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 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 void Initialize(Action onConnectionVerified, int expectedPlayerCount, Action onGameEnd, LocalPlayer localUser)
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)
onGameBeginning = onGameBegin;
Locator.Get.Provide(this); // Simplifies access since some networked objects can't easily communicate locally (e.g. the host might call a ClientRpc without that client knowing where the call originated).
}
public override void OnNetworkSpawn()

private void FinishInitialize()
{
m_symbolContainerInstance = Instantiate(m_symbolContainerPrefab).transform;
m_symbolContainerInstance.GetComponent<NetworkObject>().Spawn();
m_symbolContainerInstance = Instantiate(m_symbolContainerPrefab);
m_symbolContainerInstance.NetworkObject.Spawn();
ResetPendingSymbolPositions();
m_killVolume.Initialize(OnSymbolDeactivated);
}

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);
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);

[ServerRpc(RequireOwnership = false)]
private void VerifyConnectionConfirm_ServerRpc(PlayerData clientData)
{
NetworkObject playerCursor = Instantiate(m_playerCursorPrefab); // Note that the client will not receive the cursor object reference, so the cursor must handle initializing itself.
playerCursor.SpawnWithOwnership(clientData.id);
// 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);
bool areAllPlayersConnected = NetworkManager.Singleton.ConnectedClients.Count >= m_expectedPlayerCount; // The game will begin at this point, or else there's a timeout for booting any unconnected players.
// 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);
}

/// <summary>
/// The game will begin either when all players have connected successfully or after a timeout.
/// </summary>
private void BeginGame()
void BeginGame()
m_introOutroRunner.DoIntro();
onGameBeginning?.Invoke();
m_introOutroRunner.DoIntro(StartMovingSymbols);
}
void StartMovingSymbols()
{
m_sequenceSelector.SetTargetsAnimatable();
if(IsHost)
m_symbolContainerInstance.StartMovingSymbols(); //TODO fix this for
if(IsServer||IsHost)
CheckIfCanSpawnNewSymbol();
CheckIfCanSpawnNewSymbol();
if (m_timeout >= 0)
{
m_timeout -= Time.deltaTime;

void CheckIfCanSpawnNewSymbol()
{
if (!m_canSpawnInGameObjects.GetValueOrDefault() || m_remainingSymbolCount >= SequenceSelector.k_symbolCount || !IsHost)
if (!m_canSpawnInGameObjects.GetValueOrDefault() ||
m_remainingSymbolCount >= SequenceSelector.symbolCount || !IsHost)
return;
if (m_pendingSymbolPositions.Count > 0)
{

m_symbolSpawnTimer = 0.02f; // Space out the object spawning a little to prevent a lag spike.
SpawnNewSymbol();
if (m_remainingSymbolCount >= SequenceSelector.k_symbolCount)
if (m_remainingSymbolCount >= SequenceSelector.symbolCount)
m_canSpawnInGameObjects = false;
}
}

{
int index = SequenceSelector.k_symbolCount - m_pendingSymbolPositions.Count;
int index = SequenceSelector.symbolCount - m_pendingSymbolPositions.Count;
var symbolObj = Instantiate(m_symbolObjectPrefab, m_symbolContainerInstance);
var symbolObj = Instantiate(m_symbolObjectPrefab);
// NetworkObject.TrySetParent(m_symbolContainerInstance, false);
symbolObj.SetPosition_Server(pendingPos);
symbolObj.symbolIndex.Value = m_sequenceSelector.GetNextSymbol(index);
symbolObj.SetParentAndPosition_Server(m_symbolContainerInstance.NetworkObject, pendingPos);
symbolObj.SetSymbolIndex_Server(m_sequenceSelector.GetNextSymbol(index));
m_remainingSymbolCount++;
}
}

if (selectedSymbol.Clicked)
return;
if (m_sequenceSelector.ConfirmSymbolCorrect(playerId, selectedSymbol.symbolIndex.Value))
if (m_sequenceSelector.ConfirmSymbolCorrect(playerId, selectedSymbol.SymbolIndex))
{
selectedSymbol.ClickedSequence_ServerRpc(playerId);
m_scorer.ScoreSuccess(playerId);

m_scorer.ScoreFailure(playerId);
}
public void OnSymbolDeactivated()
void OnSymbolDeactivated()
{
if (--m_remainingSymbolCount <= 0)
WaitForEndingSequence_ClientRpc();

private void SendLocalEndGameSignal()
{
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.
public void OnReProvided(IInGameInputHandler previousProvider)
{
/*No-op*/
}
}
}

10
Assets/Scripts/GameLobby/NGO/IntroOutroRunner.cs


/// </summary>
public class IntroOutroRunner : MonoBehaviour
{
[SerializeField]
InGameRunner m_inGameRunner;
Action m_onOutroComplete;
Action m_onIntroComplete, m_onOutroComplete;
public void DoIntro()
public void DoIntro(Action onIntroComplete)
m_onIntroComplete = onIntroComplete;
m_animator.SetTrigger("DoIntro");
}

/// </summary>
public void OnIntroComplete()
{
Locator.Get.Messenger.OnReceiveMessage(MessageType.InstructionsShown, null);
m_onIntroComplete?.Invoke();
}
/// <summary>
/// Called via an AnimationEvent.

52
Assets/Scripts/GameLobby/NGO/PlayerCursor.cs


/// The host will use this object's movement for detecting collision with symbol objects.
/// </summary>
[RequireComponent(typeof(Collider))]
public class PlayerCursor : NetworkBehaviour, IReceiveMessages
public class PlayerCursor : NetworkBehaviour
[SerializeField] SpriteRenderer m_renderer = default;
[SerializeField] ParticleSystem m_onClickParticles = default;
[SerializeField] private TMPro.TMP_Text m_nameOutput = default;
private Camera m_mainCamera;
private NetworkVariable<Vector3> m_position = new NetworkVariable<Vector3>( Vector3.zero); // (Using a NetworkTransform to sync position would also work.)
private ulong m_localId;
[SerializeField]
SpriteRenderer m_renderer = default;
[SerializeField]
ParticleSystem m_onClickParticles = default;
[SerializeField]
TMPro.TMP_Text m_nameOutput = default;
Camera m_mainCamera;
NetworkVariable<Vector3> m_position = new NetworkVariable<Vector3>(Vector3.zero);
ulong m_localId;
private Action<ulong, Action<PlayerData>> m_retrieveName;
Action<ulong, Action<PlayerData>> m_retrieveName;
private List<SymbolObject> m_currentlyCollidingSymbols;
public void Awake()
{
Locator.Get.Messenger.Subscribe(this);
}
List<SymbolObject> m_currentlyCollidingSymbols;
/// <summary>
/// This cursor is spawned in dynamically but needs references to some scene objects. Pushing full object references over RPC calls

{
m_retrieveName = NetworkedDataStore.Instance.GetPlayerData;
m_mainCamera = GameObject.Find("InGameCamera").GetComponent<Camera>();
InGameRunner.Instance.onGameBeginning += OnGameBegan;
{ m_renderer.transform.localScale *= 0.75f;
{
m_renderer.transform.localScale *= 0.75f;
{ m_renderer.enabled = false; // The local player should see their cursor instead of the simulated cursor object, since the object will appear laggy.
{
m_renderer.enabled =
false; // The local player should see their cursor instead of the simulated cursor object, since the object will appear laggy.
}
}

if (m_mainCamera == null || !IsOwner)
return;
Vector3 targetPos = (Vector2)m_mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -m_mainCamera.transform.position.z));
Vector3 targetPos = (Vector2)m_mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x,
Input.mousePosition.y, -m_mainCamera.transform.position.z));
SetPosition_ServerRpc(targetPos); // Client can't set a network variable value.
if (IsSelectInputHit())
SendInput_ServerRpc(m_localId);

if (m_currentlyCollidingSymbols.Count > 0)
{
SymbolObject symbol = m_currentlyCollidingSymbols[0];
Locator.Get.InGameInputHandler.OnPlayerInput(id, symbol);
InGameRunner.Instance.OnPlayerInput(id, symbol);
OnInputVisuals_ClientRpc();
}

m_currentlyCollidingSymbols.Remove(symbol);
}
public void OnReceiveMessage(MessageType type, object msg)
public void OnGameBegan()
if (type == MessageType.MinigameBeginning)
{
m_retrieveName.Invoke(OwnerClientId, SetName_ClientRpc);
Locator.Get.Messenger.Unsubscribe(this);
}
m_retrieveName.Invoke(OwnerClientId, SetName_ClientRpc);
InGameRunner.Instance.onGameBeginning -= OnGameBegan;
}
}

135
Assets/Scripts/GameLobby/NGO/SequenceSelector.cs


/// 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.
/// </summary>
public class SequenceSelector : NetworkBehaviour, IReceiveMessages
public class SequenceSelector : NetworkBehaviour
[SerializeField] private SymbolData m_symbolData = default;
[SerializeField] private Image[] m_targetSequenceOutput = default;
public const int k_symbolCount = 140;
private bool m_hasReceivedTargetSequence = false;
private ulong m_localId;
private bool m_canAnimateTargets = false;
[SerializeField]
SymbolData m_SymbolData = default;
[SerializeField]
Image[] m_TargetSequenceOutput = default;
public const int symbolCount = 140;
bool m_HasReceivedTargetSequence = false;
ulong m_LocalId;
bool m_CanAnimateTargets = true;
private List<int> m_fullSequence = new List<int>(); // This is owned by the host, and each index is assigned as a NetworkVariable to each SymbolObject.
private NetworkList<int> m_targetSequence; // This is owned by the host but needs to be available to all clients, so it's a NetworkedList here.
private Dictionary<ulong, int> m_targetSequenceIndexPerPlayer = new Dictionary<ulong, int>(); // Each player's current target. Also owned by the host, indexed by client ID.
// This is owned by the host, and each index is assigned as a NetworkVariable to each SymbolObject.
List<int> m_FullSequenceServer = new List<int>();
// This is owned by the host but needs to be available to all clients, so it's a NetworkedList here.
NetworkList<int> m_targetSequence;
// Each player's current target. Also owned by the host, indexed by client ID.
Dictionary<ulong, int> m_targetSequenceIndexPerPlayer = new Dictionary<ulong, int>();
Locator.Get.Messenger.Subscribe(this);
}
public override void OnDestroy()
{
base.OnDestroy();
Locator.Get.Messenger.Unsubscribe(this);
}
public override void OnNetworkSpawn()

m_localId = NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_localId);
m_targetSequence.ResetDirty();
m_LocalId = NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_LocalId);
}
private void ChooseSymbols()

List<int> symbolsForThisGame = SelectSymbols(m_symbolData.m_availableSymbols.Count, numSymbolTypes);
List<int> symbolsForThisGame = SelectSymbols(m_SymbolData.m_availableSymbols.Count, numSymbolTypes);
int numTargetSequences = (int)(k_symbolCount * 2 / 3f) / 3; // About 2/3 of the symbols will be definitely part of the target sequence.
int numTargetSequences =
(int)(symbolCount * 2 / 3f) /
3; // About 2/3 of the symbols will be definitely part of the 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]);
m_FullSequenceServer.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_FullSequenceServer.Add(m_targetSequence[1]);
m_FullSequenceServer.Add(m_targetSequence[0]);
// Then, fill in with a good mix of the remaining symbols.
for (int n = 3; n < numSymbolTypes - 1; n++)
AddHalfRemaining(n, 2);

{
int remaining = k_symbolCount - m_fullSequence.Count;
int remaining = symbolCount - m_FullSequenceServer.Count;
int randomIndex = UnityEngine.Random.Range(0, m_fullSequence.Count);
m_fullSequence.Insert(randomIndex, symbolsForThisGame[symbolIndex]);
int randomIndex = UnityEngine.Random.Range(0, m_FullSequenceServer.Count);
m_FullSequenceServer.Insert(randomIndex, symbolsForThisGame[symbolIndex]);
}
}
}

public void Update()
{
// A client can't guarantee timing with the host's selection of the target sequence, so retrieve it once it's available.
if (!m_hasReceivedTargetSequence && m_targetSequence.Count > 0)
if (!m_HasReceivedTargetSequence && m_targetSequence.Count > 0)
m_targetSequenceOutput[n].sprite = m_symbolData.GetSymbolForIndex(m_targetSequence[n]);
m_hasReceivedTargetSequence = true;
ScaleTargetUi(m_localId, 0);
m_TargetSequenceOutput[n].sprite = m_SymbolData.GetSymbolForIndex(m_targetSequence[n]);
m_HasReceivedTargetSequence = true;
ScaleTargetUi(m_LocalId, 0);
}
}

{
ScaleTargetUi(id, sequenceIndex);
}
for (int i = 0; i < m_targetSequenceOutput.Length; i++)
m_targetSequenceOutput[i].transform.localScale = Vector3.one * (sequenceIndex == i || !m_canAnimateTargets ? 1 : 0.7f);
for (int i = 0; i < m_TargetSequenceOutput.Length; i++)
m_TargetSequenceOutput[i].transform.localScale =
Vector3.one * (sequenceIndex == i || !m_CanAnimateTargets ? 1 : 0.7f);
return m_fullSequence[symbolObjectIndex];
return m_FullSequenceServer[symbolObjectIndex];
public void OnReceiveMessage(MessageType type, object msg)
public void SetTargetsAnimatable()
if (type == MessageType.InstructionsShown)
{
m_canAnimateTargets = true;
ScaleTargetUi(m_localId, 0);
}
m_CanAnimateTargets = true;
ScaleTargetUi(m_LocalId, 0);
}
/// <summary>

private struct RectCut
{
public Rect rect;
public bool isVertCut { get { return cutIndex % 3 == 2; } }
public bool isVertCut
{
get { return cutIndex % 3 == 2; }
}
public RectCut(Rect rect, int cutIndex)
{
this.rect = rect;
this.cutIndex = cutIndex;
}
public RectCut(Rect rect, int cutIndex) { this.rect = rect; this.cutIndex = cutIndex; }
public RectCut(float xMin, float xMax, float yMin, float yMax, int cutIndex)
{
this.rect = new Rect(xMin, yMin, xMax - xMin, yMax - yMin);

/// <param name="extent">The minimum space between points, to ensure that spawned symbol objects won't overlap.</param>
/// <param name="count">How many positions to choose.</param>
/// <returns>Position list in arbitrary order.</returns>
public List<Vector2> GenerateRandomSpawnPoints(Rect bounds, float extent, int count = k_symbolCount)
public List<Vector2> GenerateRandomSpawnPoints(Rect bounds, float extent, int count = symbolCount)
{
int numTries = 3;
List<Vector2> points = new List<Vector2>();

points.Clear();
rects.Enqueue(new RectCut(bounds, -1)); // Start with an extra horizontal cut since the space is so tall.
rects.Enqueue(new RectCut(bounds,
-1)); // Start with an extra horizontal cut since the space is so tall.
bool isLargeEnough = (currRect.isVertCut && currRect.rect.width > extent * 2) || (!currRect.isVertCut && currRect.rect.height > extent * 2);
bool isLargeEnough = (currRect.isVertCut && currRect.rect.width > extent * 2) ||
(!currRect.isVertCut && currRect.rect.height > extent * 2);
{ points.Add(currRect.rect.center);
{
points.Add(currRect.rect.center);
float xMin = currRect.rect.xMin, xMax = currRect.rect.xMax, yMin = currRect.rect.yMin, yMax = currRect.rect.yMax;
float xMin = currRect.rect.xMin,
xMax = currRect.rect.xMax,
yMin = currRect.rect.yMin,
yMax = currRect.rect.yMax;
{ float cutPosX = Random.Range(xMin + extent, xMax - extent);
rects.Enqueue( new RectCut(xMin, cutPosX, yMin, yMax, currRect.cutIndex + 1) );
rects.Enqueue( new RectCut(cutPosX, xMax, yMin, yMax, currRect.cutIndex + 1) );
}
{
float cutPosX = Random.Range(xMin + extent, xMax - extent);
rects.Enqueue(new RectCut(xMin, cutPosX, yMin, yMax, currRect.cutIndex + 1));
rects.Enqueue(new RectCut(cutPosX, xMax, yMin, yMax, currRect.cutIndex + 1));
}
{ float cutPosY = Random.Range(yMin + extent, yMax - extent);
rects.Enqueue( new RectCut(xMin, xMax, yMin, cutPosY, currRect.cutIndex + 1) );
rects.Enqueue( new RectCut(xMin, xMax, cutPosY, yMax, currRect.cutIndex + 1) );
{
float cutPosY = Random.Range(yMin + extent, yMax - extent);
rects.Enqueue(new RectCut(xMin, xMax, yMin, cutPosY, currRect.cutIndex + 1));
rects.Enqueue(new RectCut(xMin, xMax, cutPosY, yMax, currRect.cutIndex + 1));
}
}

points.Clear();
int numPerLine = Mathf.CeilToInt(bounds.width / (extent * 1.5f));
for (int n = 0; n < count; n++)
points.Add(new Vector2(Mathf.Lerp(bounds.xMin, bounds.xMax, (n % numPerLine) / (numPerLine - 1f)), n / numPerLine * extent * 1.5f));
points.Add(new Vector2(Mathf.Lerp(bounds.xMin, bounds.xMax, (n % numPerLine) / (numPerLine - 1f)),
n / numPerLine * extent * 1.5f));
}
}

8
Assets/Scripts/GameLobby/NGO/SetupInGame.cs


{
m_lobby = localLobby;
m_inGameRunner = Instantiate(m_IngameRunnerPrefab).GetComponentInChildren<InGameRunner>();
m_inGameRunner.Initialize(OnConnectionVerified, m_lobby.PlayerCount, OnGameEnd, localPlayer);
m_inGameRunner.Initialize(OnConnectionVerified, m_lobby.PlayerCount, OnGameBegin, OnGameEnd,
localPlayer);
if (localPlayer.IsHost.Value)
{
await SetRelayHostData();

#pragma warning restore 4014
}
public void MiniGameBeginning()
public void OnGameBegin()
{
if (!m_hasConnectedViaNGO)
{

{
if (m_doesNeedCleanup)
{
NetworkManager.Singleton.Shutdown();
NetworkManager.Singleton.Shutdown(true);
.transform.parent
.gameObject); // Since this destroys the NetworkManager, that will kick off cleaning up networked objects.
SetMenuVisibility(true);
m_lobby.RelayCode.Value = "";

23
Assets/Scripts/GameLobby/NGO/SymbolContainer.cs


/// 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.
/// </summary>
[RequireComponent(typeof(NetworkTransform))]
public class SymbolContainer : NetworkBehaviour, IReceiveMessages
public class SymbolContainer : NetworkBehaviour
{
[SerializeField]
float m_speed = 1;

/// <summary>
/// Verify both that the game has started and that the network connection is working before moving the symbols.
/// </summary>
void OnGameStarted()
public void StartMovingSymbols()
public void Awake() // If there's just one player, Start would occur after the GameBeginning message is sent, so use Awake/OnEnable instead.
{
Locator.Get.Messenger.Subscribe(this);
}
public override void OnNetworkSpawn()
{

void BeginMotion()
{
transform.position += Time.deltaTime * m_speed*Vector3.down;
}
public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.InstructionsShown)
{
Locator.Get.Messenger.Unsubscribe(this);
OnGameStarted();
}
transform.position += Time.deltaTime * m_speed * Vector3.down;
}
}

2
Assets/Scripts/GameLobby/NGO/SymbolData.cs


public Sprite GetSymbolForIndex(int index)
{
if (index < 0 || index >= m_availableSymbols.Count)
if (index < 0 || index >= SymbolCount)
index = 0;
return m_availableSymbols[index];
}

31
Assets/Scripts/GameLobby/NGO/SymbolObject.cs


namespace LobbyRelaySample.ngo
{
/// <summary>
/// This holds the logic and data for an individual symbol, which can be "clicked" if the server detects the collision with a player who sends a click input.
/// This holds the logic and data for an individual symbolIndex, which can be "clicked" if the server detects the collision with a player who sends a click input.
/// </summary>
public class SymbolObject : NetworkBehaviour
{

private Animator m_animator;
public bool Clicked { get; private set; }
[HideInInspector]
public NetworkVariable<int> symbolIndex; // The index into SymbolData, not the index of this object.
public int SymbolIndex { get; private set; }
public override void OnNetworkSpawn()
public void SetSymbolIndex_Server(int symbolIndex)
symbolIndex.OnValueChanged += OnSymbolIndexSet;
SetSymbolSprite(symbolIndex);
SetSymbolIndex_ClientRpc(symbolIndex);
/// <summary>
/// Because of the need to distinguish host vs. client calls, we use the symbolIndex NetworkVariable to learn what symbol to display.
/// </summary>
private void OnSymbolIndexSet(int prevValue, int newValue)
[ClientRpc]
public void SetSymbolIndex_ClientRpc(int symbolIndex)
m_renderer.sprite = m_symbolData.GetSymbolForIndex(symbolIndex.Value);
symbolIndex.OnValueChanged -= OnSymbolIndexSet;
SetSymbolSprite(symbolIndex);
public void SetPosition_Server(Vector3 newPosition)
void SetSymbolSprite(int symbolIndex)
SymbolIndex = symbolIndex;
m_renderer.sprite = m_symbolData.GetSymbolForIndex(SymbolIndex);
}
public void SetParentAndPosition_Server(NetworkObject parentObject, Vector3 newPosition)
{
NetworkObject.TrySetParent(parentObject, false);
SetPosition_ClientRpc(newPosition);
}

[ServerRpc]
public void HideSymbol_ServerRpc()
{
// Actually destroying the symbol objects can cause garbage collection and other delays that might lead to desyncs.
// Actually destroying the symbolIndex 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 we used object pooling, this is where we would instead return it to the pool.)
//The animation calls RemoveSymbol(only for server

//It's easier to have the post-animation symbol "deletion" happen entirely in server world rather than depend on client-side animation triggers.
//It's easier to have the post-animation symbolIndex "deletion" happen entirely in server world rather than depend on client-side animation triggers.
IEnumerator HideSymbolAnimDelay()
{
yield return new WaitForSeconds(0.3f);

1
Assets/Scripts/GameLobby/UI/BackButtonUI.cs


/// </summary>
public class BackButtonUI : UIPanelBase
{
public void ToJoinMenu()
{
Manager.ChangeMenuState(GameState.JoinMenu);

1
Assets/Scripts/GameLobby/UI/JoinMenuUI.cs


using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
namespace LobbyRelaySample.UI
{

15
ProjectSettings/RiderScriptEditorPersistedState.asset


%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &1
MonoBehaviour:
m_ObjectHideFlags: 61
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 0}
m_Name:
m_EditorClassIdentifier: Unity.Rider.Editor:Packages.Rider.Editor:RiderScriptEditorPersistedState
lastWriteTicks: -8585383256260152102
正在加载...
取消
保存