浏览代码

Adding a fix for players sometimes getting booted from the minigame even when successfully connected (I was mismanaging the ClientRPC call that starts the game so that the last client to connect would sometimes receive the begin game message for another player (and then not initialize since it hadn't gotten its connection confirmation yet) and then try to start the game without being in the connected state, even though it was actually connected.)

Fixing an issue I introduced a few changes ago that made the back button from in-game go to the lobby UI even though it quits the lobby.
There are some minor cleanup changes here as well: icon_bg prefab is consolidated into SymbolObject since that's its only usage. Clearing out some TODOs which have been investigated and addressed or obsolesced.
/main/staging/ngo_minigame_cleanup
nathaniel.buck@unity3d.com 2 年前
当前提交
d2ab33ea
共有 13 个文件被更改,包括 1057 次插入1072 次删除
  1. 999
      Assets/Prefabs/NGO/SymbolObject.prefab
  2. 27
      Assets/Scripts/Netcode/InGameRunner.cs
  3. 20
      Assets/Scripts/Netcode/NetworkedDataStore.cs
  4. 6
      Assets/Scripts/Netcode/PlayerCursor.cs
  5. 3
      Assets/Scripts/Netcode/ResultsUserUI.cs
  6. 2
      Assets/Scripts/Netcode/Scorer.cs
  7. 2
      Assets/Scripts/Netcode/SetupInGame.cs
  8. 14
      Assets/Scripts/Netcode/SymbolObject.cs
  9. 24
      Assets/Scripts/Netcode/PlayerData.cs
  10. 1001
      Assets/Prefabs/Runes/icon_bg.prefab
  11. 7
      Assets/Prefabs/Runes/icon_bg.prefab.meta
  12. 24
      Assets/Scripts/Netcode/LobbyUserData.cs
  13. 0
      /Assets/Scripts/Netcode/PlayerData.cs.meta

999
Assets/Prefabs/NGO/SymbolObject.prefab
文件差异内容过多而无法显示
查看文件

27
Assets/Scripts/Netcode/InGameRunner.cs


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 NetworkObject m_playerCursorPrefab = default;
[SerializeField] private NetworkObject m_symbolContainerPrefab = default;

[SerializeField] private NetworkedDataStore m_dataStore = default;
private Transform m_symbolContainerInstance;
private LobbyUserData m_localUserData; // This has an ID that's not necessarily the OwnerClientId, since all clients will see all spawned objects regardless of ownership.
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, LobbyUser localUser)
{

m_canSpawnInGameObjects = null;
m_localUserData = new LobbyUserData(localUser.DisplayName, 0);
m_localUserData = new PlayerData(localUser.DisplayName, 0);
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).
}

FinishInitialize();
m_localUserData = new LobbyUserData(m_localUserData.name, NetworkManager.Singleton.LocalClientId);
m_localUserData = new PlayerData(m_localUserData.name, NetworkManager.Singleton.LocalClientId);
VerifyConnection_ServerRpc(m_localUserData.id);
}

/// Once the connection is confirmed, check if all players have connected.
/// </summary>
[ServerRpc(RequireOwnership = false)]
private void VerifyConnectionConfirm_ServerRpc(LobbyUserData clientData)
private void VerifyConnectionConfirm_ServerRpc(PlayerData clientData)
{
NetworkObject playerCursor = NetworkObject.Instantiate(m_playerCursorPrefab);
playerCursor.SpawnWithOwnership(clientData.id);

VerifyConnectionConfirm_ClientRpc(clientData.id, areAllPlayersConnected);
}
[ClientRpc]
private void VerifyConnectionConfirm_ClientRpc(ulong clientId, bool shouldStartImmediately)
private void VerifyConnectionConfirm_ClientRpc(ulong clientId, bool canBeginGame)
{
if (shouldStartImmediately)
m_hasConnected = true;
}
if (canBeginGame && m_hasConnected)
{
m_timeout = -1;
BeginGame();

BeginGame();
}
// TODO: BSP for choosing symbol spawn positions?
// TODO: Remove the timer to test for packet loss.
void CheckIfCanSpawnNewSymbol()
{
if (!m_canSpawnInGameObjects.GetValueOrDefault() || m_remainingSymbolCount >= SequenceSelector.k_symbolCount || !IsHost)

{
if (m_sequenceSelector.ConfirmSymbolCorrect(id, selectedSymbol.symbolIndex.Value))
{
selectedSymbol.OnSelectConfirmed_ClientRpc();
selectedSymbol.Destroy_ServerRpc();
m_scorer.ScoreSuccess(id);
OnSymbolDeactivated();

{
EndGame_ClientRpc();
yield return null;
m_onGameEnd();
SendEndGameSignal();
}
[ClientRpc]

return;
SendEndGameSignal();
}
private void SendEndGameSignal()
{
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.
m_onGameEnd();
}

20
Assets/Scripts/Netcode/NetworkedDataStore.cs


// Using a singleton here since we need spawned PlayerCursors to be able to find it, but we don't need the flexibility offered by the Locator.
public static NetworkedDataStore Instance;
private Dictionary<ulong, LobbyUserData> m_playerData = new Dictionary<ulong, LobbyUserData>();
private Dictionary<ulong, PlayerData> m_playerData = new Dictionary<ulong, PlayerData>();
private Action<LobbyUserData> m_onGetCurrentCallback;
private UnityEvent<LobbyUserData> m_onEachPlayerCallback;
private Action<PlayerData> m_onGetCurrentCallback;
private UnityEvent<PlayerData> m_onEachPlayerCallback;
public void Awake()
{

return;
if (!m_playerData.ContainsKey(id))
m_playerData.Add(id, new LobbyUserData(name, id, 0));
m_playerData.Add(id, new PlayerData(name, id, 0));
m_playerData[id] = new LobbyUserData(name, id, 0);
m_playerData[id] = new PlayerData(name, id, 0);
}
/// <returns>The updated score for the player matching the id after adding the delta, or int.MinValue otherwise.</returns>

/// <summary>
/// Retrieve the data for all players from 1st to last place, calling onEachPlayer for each.
/// </summary>
public void GetAllPlayerData(UnityEvent<LobbyUserData> onEachPlayer)
public void GetAllPlayerData(UnityEvent<PlayerData> onEachPlayer)
{
m_onEachPlayerCallback = onEachPlayer;
GetAllPlayerData_ServerRpc(m_localId);

}
[ClientRpc]
private void GetAllPlayerData_ClientRpc(ulong callerId, LobbyUserData[] sortedData)
private void GetAllPlayerData_ClientRpc(ulong callerId, PlayerData[] sortedData)
{
if (callerId != m_localId)
return;

/// <summary>
/// Retreive the data for one player, passing it to the onGet callback.
/// </summary>
public void GetPlayerData(ulong targetId, Action<LobbyUserData> onGet)
public void GetPlayerData(ulong targetId, Action<PlayerData> onGet)
{
m_onGetCurrentCallback = onGet;
GetPlayerData_ServerRpc(targetId, m_localId);

if (m_playerData.ContainsKey(id))
GetPlayerData_ClientRpc(callerId, m_playerData[id]);
else
GetPlayerData_ClientRpc(callerId, new LobbyUserData(null, 0));
GetPlayerData_ClientRpc(callerId, new PlayerData(null, 0));
public void GetPlayerData_ClientRpc(ulong callerId, LobbyUserData data)
public void GetPlayerData_ClientRpc(ulong callerId, PlayerData data)
{
if (callerId == m_localId)
{ m_onGetCurrentCallback?.Invoke(data);

6
Assets/Scripts/Netcode/PlayerCursor.cs


private Camera m_mainCamera;
private NetworkVariable<Vector3> m_position = new NetworkVariable<Vector3>(NetworkVariableReadPermission.Everyone, Vector3.zero);
private ulong m_localId;
private Action<ulong, Action<LobbyUserData>> m_retrieveName;
private Action<ulong, Action<PlayerData>> m_retrieveName;
// The host is responsible for determining if a player has successfully selected a symbol object, since collisions should be handled serverside.
private List<SymbolObject> m_currentlyCollidingSymbols;

}
[ClientRpc]
private void SetName_ClientRpc(LobbyUserData data)
private void SetName_ClientRpc(PlayerData data)
{
if (!IsOwner)
m_nameOutput.text = data.name;

return Input.GetMouseButtonDown(0);
}
public void Update() // TODO: FixedUpdate?
public void Update()
{
transform.position = m_position.Value;
if (m_mainCamera == null || !IsOwner)

3
Assets/Scripts/Netcode/ResultsUserUI.cs


/// <summary>
/// Displays the results for all players after the NGO minigame.
/// </summary>
[RequireComponent(typeof(NetworkObject))] // TODO: Include elsewhere?
public class ResultsUserUI : NetworkBehaviour
{
[Tooltip("The containers for the player data outputs, in order, to be hidden until the game ends.")]

}
// Assigned to an event in the Inspector.
public void ReceiveScoreInOrder(LobbyUserData data)
public void ReceiveScoreInOrder(PlayerData data)
{
m_containers[m_index].alpha = 1;
m_playerNameOutputs[m_index].text = data.name;

2
Assets/Scripts/Netcode/Scorer.cs


[SerializeField] private TMP_Text m_scoreOutputText = default;
[Tooltip("When the game ends, this will be provided the results in order of rank (1st-place first, and so on).")]
[SerializeField] private UnityEvent<LobbyUserData> m_onGameEnd = default;
[SerializeField] private UnityEvent<PlayerData> m_onGameEnd = default;
public override void OnNetworkSpawn()
{

2
Assets/Scripts/Netcode/SetupInGame.cs


if (!m_hasConnectedViaNGO)
{
// If this player hasn't successfully connected via NGO, get booted.
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Failed to join the game."); // TODO: I do still need the quick pause...
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Failed to join the game.");
OnGameEnd();
}
}

14
Assets/Scripts/Netcode/SymbolObject.cs


[SerializeField] private SymbolData m_symbolData;
[SerializeField] private SpriteRenderer m_renderer;
[HideInInspector] public NetworkVariable<int> symbolIndex; // The index into SymbolData, not the index of this object.
private ulong m_localId;
m_localId = NetworkManager.Singleton.LocalClientId;
}
/// <summary>

symbolIndex.OnValueChanged -= OnSymbolIndexSet;
}
/// <summary>
/// The host has confirmed this symbol as a valid selection (this player's cursor collides with it and it's also next in their target sequence), so handle any visual feedback.
/// </summary>
[ClientRpc]
public void OnSelectConfirmed_ClientRpc()
{
// TODO: Visual effects here.
}
[ServerRpc]
public void Destroy_ServerRpc()
{

this.transform.localPosition = Vector3.down * 500;
// TODO: Visually disappear immediately.
this.transform.localPosition += Vector3.forward * 500;
}
}
}

24
Assets/Scripts/Netcode/PlayerData.cs


using Unity.Netcode;
namespace LobbyRelaySample.ngo
{
/// <summary>
/// An example of a custom type serialized for use in RPC calls. This represents the state of a player as far as NGO is concerned,
/// with relevant fields copied in or modified directly.
/// </summary>
public class PlayerData : INetworkSerializable
{
public string name;
public ulong id;
public int score;
public PlayerData() { } // A default constructor is explicitly required for serialization.
public PlayerData(string name, ulong id, int score = 0) { this.name = name; this.id = id; this.score = score; }
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref name);
serializer.SerializeValue(ref id);
serializer.SerializeValue(ref score);
}
}
}

1001
Assets/Prefabs/Runes/icon_bg.prefab
文件差异内容过多而无法显示
查看文件

7
Assets/Prefabs/Runes/icon_bg.prefab.meta


fileFormatVersion: 2
guid: b3e57d1c65c0fc746a2a46a3d227990b
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

24
Assets/Scripts/Netcode/LobbyUserData.cs


using Unity.Netcode;
namespace LobbyRelaySample.ngo
{
/// <summary>
/// An example of a custom type serialized for use in RPC calls. This represents the state of a player as far as NGO is concerned,
/// with relevant fields copied in or modified directly.
/// </summary>
public class LobbyUserData : INetworkSerializable // TODO: Name isn't clear.
{
public string name;
public ulong id;
public int score;
public LobbyUserData() { } // A default constructor is explicitly required for serialization.
public LobbyUserData(string name, ulong id, int score = 0) { this.name = name; this.id = id; this.score = score; }
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref name);
serializer.SerializeValue(ref id);
serializer.SerializeValue(ref score);
}
}
}

/Assets/Scripts/Netcode/LobbyUserData.cs.meta → /Assets/Scripts/Netcode/PlayerData.cs.meta

正在加载...
取消
保存