浏览代码

A couple things:

- Fixing the bug with clients having trouble connecting to the host even when getting the necessary data. I again hit that issue with the local lobby pulling before pushing and overwriting data.
- Adding a game end UI that shows the player scores before returning to the lobby. This required modifying data storage on the host to accommodate providing the scores at the end.
/main/staging/ngo_minigame_cleanup
nathaniel.buck@unity3d.com 2 年前
当前提交
ca5164bb
共有 14 个文件被更改,包括 1144 次插入53 次删除
  1. 968
      Assets/Prefabs/InGame/InGameLogic.prefab
  2. 8
      Assets/Scripts/Game/LocalLobby.cs
  3. 2
      Assets/Scripts/Infrastructure/Messenger.cs
  4. 1
      Assets/Scripts/Lobby/LobbyContentHeartbeat.cs
  5. 5
      Assets/Scripts/Lobby/ToLocalLobby.cs
  6. 7
      Assets/Scripts/Netcode/InGameRunner.cs
  7. 10
      Assets/Scripts/Netcode/LobbyUserData.cs
  8. 86
      Assets/Scripts/Netcode/NetworkedDataStore.cs
  9. 10
      Assets/Scripts/Netcode/PlayerCursor.cs
  10. 40
      Assets/Scripts/Netcode/Scorer.cs
  11. 6
      Assets/Scripts/Netcode/SetupInGame.cs
  12. 8
      ProjectSettings/Packages/com.unity.services.vivox/Settings.json
  13. 35
      Assets/Scripts/Netcode/ResultsUserUI.cs
  14. 11
      Assets/Scripts/Netcode/ResultsUserUI.cs.meta

968
Assets/Prefabs/InGame/InGameLogic.prefab
文件差异内容过多而无法显示
查看文件

8
Assets/Scripts/Game/LocalLobby.cs


public LobbyColor Color { get; set; }
public long State_LastEdit { get; set; }
public long Color_LastEdit { get; set; }
public long RelayNGOCode_LastEdit { get; set; }
public LobbyData(LobbyData existing)
{

Color = existing.Color;
State_LastEdit = existing.State_LastEdit;
Color_LastEdit = existing.Color_LastEdit;
RelayNGOCode_LastEdit = existing.RelayNGOCode_LastEdit;
}
public LobbyData(string lobbyCode)

Color = LobbyColor.None;
State_LastEdit = 0;
Color_LastEdit = 0;
RelayNGOCode_LastEdit = 0;
}
}

set
{
m_data.RelayNGOCode = value;
m_data.RelayNGOCode_LastEdit = DateTime.Now.Ticks;
OnChanged(this);
}
}

// If that happens, the edit will be lost, so instead we maintain the time of last edit to detect that case.
var pendingState = data.State;
var pendingColor = data.Color;
var pendingNgoCode = data.RelayNGOCode;
if (m_data.RelayNGOCode_LastEdit > data.RelayNGOCode_LastEdit)
pendingNgoCode = m_data.RelayNGOCode;
m_data.RelayNGOCode = pendingNgoCode;
if (currUsers == null)
m_LobbyUsers = new Dictionary<string, LobbyUser>();

2
Assets/Scripts/Infrastructure/Messenger.cs


StartCountdown = 200,
CancelCountdown = 201,
CompleteCountdown = 202,
GameBeginning = 203,
MinigameBeginning = 203,
InstructionsShown = 204,
DisplayErrorPopup = 300,

1
Assets/Scripts/Lobby/LobbyContentHeartbeat.cs


data.Add("Color", ((int)lobby.Color).ToString());
data.Add("State_LastEdit", lobby.Data.State_LastEdit.ToString());
data.Add("Color_LastEdit", lobby.Data.Color_LastEdit.ToString());
data.Add("RelayNGOCode_LastEdit", lobby.Data.RelayNGOCode_LastEdit.ToString());
return data;
}

5
Assets/Scripts/Lobby/ToLocalLobby.cs


RelayNGOCode = lobby.Data?.ContainsKey("RelayNGOCode") == true ? lobby.Data["RelayNGOCode"].Value : null,
State = lobby.Data?.ContainsKey("State") == true ? (LobbyState) int.Parse(lobby.Data["State"].Value) : LobbyState.Lobby,
Color = lobby.Data?.ContainsKey("Color") == true ? (LobbyColor) int.Parse(lobby.Data["Color"].Value) : LobbyColor.None,
State_LastEdit = lobby.Data?.ContainsKey("State_LastEdit") == true ? long.Parse(lobby.Data["State_LastEdit"].Value) : 0,
Color_LastEdit = lobby.Data?.ContainsKey("Color_LastEdit") == true ? long.Parse(lobby.Data["Color_LastEdit"].Value) : 0
State_LastEdit = lobby.Data?.ContainsKey("State_LastEdit") == true ? long.Parse(lobby.Data["State_LastEdit"].Value) : 0,
Color_LastEdit = lobby.Data?.ContainsKey("Color_LastEdit") == true ? long.Parse(lobby.Data["Color_LastEdit"].Value) : 0,
RelayNGOCode_LastEdit = lobby.Data?.ContainsKey("RelayNGOCode_LastEdit") == true ? long.Parse(lobby.Data["RelayNGOCode_LastEdit"].Value) : 0,
};
Dictionary<string, LobbyUser> lobbyUsers = new Dictionary<string, LobbyUser>();

7
Assets/Scripts/Netcode/InGameRunner.cs


private void BeginGame()
{
m_canSpawnInGameObjects = true;
Locator.Get.Messenger.OnReceiveMessage(MessageType.GameBeginning, null);
Locator.Get.Messenger.OnReceiveMessage(MessageType.MinigameBeginning, null);
m_introOutroRunner.DoIntro();
}

[ServerRpc]
private void EndGame_ServerRpc()
{
// TODO: Display results
m_scorer.OnGameEnd();
// TODO: Something better.
yield return new WaitForSeconds(6);
EndGame_ClientRpc();
yield return null;
m_onGameEnd();

10
Assets/Scripts/Netcode/LobbyUserData.cs


namespace LobbyRelaySample.ngo
{
/// <summary>
/// An example of a custom type serialized for use in RPC calls.
/// 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.
public struct LobbyUserData : INetworkSerializable
public class LobbyUserData : INetworkSerializable // TODO: Name isn't clear.
public LobbyUserData(string name, ulong id) { this.name = name; this.id = 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; }
serializer.SerializeValue(ref score);
}
}
}

86
Assets/Scripts/Netcode/NetworkedDataStore.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Events;
namespace LobbyRelaySample.ngo
{

// 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, string> m_playerNames = new Dictionary<ulong, string>();
private Dictionary<ulong, LobbyUserData> m_playerData = new Dictionary<ulong, LobbyUserData>();
// Clients will need to retrieve the host's player data since it isn't synchronized. During that process, they will supply these callbacks
private Action<LobbyUserData> m_onGetCurrentCallback;
private UnityEvent<LobbyUserData> m_onEachPlayerCallback;
public void Awake()
{

if (!IsServer)
return;
if (!m_playerNames.ContainsKey(id))
m_playerNames.Add(id, name);
if (!m_playerData.ContainsKey(id))
m_playerData.Add(id, new LobbyUserData(name, id, 0));
m_playerNames[id] = name;
m_playerData[id] = new LobbyUserData(name, id, 0);
}
/// <returns>The updated score for the player matching the id after adding the delta, or int.MinValue otherwise.</returns>
public int UpdateScore(ulong id, int delta)
{
if (!IsServer)
return int.MinValue;
if (m_playerData.ContainsKey(id))
{
m_playerData[id].score += delta;
return m_playerData[id].score;
}
return int.MinValue;
// NetworkList and NetworkDictionary are not considered suitable for production, so instead we use RPC calls to retrieve names from the host.
private Action<string> m_onGetCurrent;
public void GetPlayerName(ulong ownerId, Action<string> onGet)
/// <summary>
/// Retrieve the data for all players from 1st to last place, calling onEachPlayer for each.
/// </summary>
public void GetAllPlayerData(UnityEvent<LobbyUserData> onEachPlayer)
m_onGetCurrent = onGet;
GetPlayerName_ServerRpc(ownerId, m_localId);
m_onEachPlayerCallback = onEachPlayer;
GetAllPlayerData_ServerRpc(m_localId);
private void GetPlayerName_ServerRpc(ulong id, ulong callerId)
private void GetAllPlayerData_ServerRpc(ulong callerId)
string name = string.Empty;
if (m_playerNames.ContainsKey(id))
name = m_playerNames[id];
GetPlayerName_ClientRpc(callerId, name);
var sortedData = m_playerData.Select(kvp => kvp.Value).OrderByDescending(data => data.score);
GetAllPlayerData_ClientRpc(callerId, sortedData.ToArray());
}
[ClientRpc]
private void GetAllPlayerData_ClientRpc(ulong callerId, LobbyUserData[] sortedData)
{
if (callerId != m_localId)
return;
int rank = 1;
foreach (var data in sortedData)
{
m_onEachPlayerCallback.Invoke(data);
rank++;
}
m_onEachPlayerCallback = null;
}
/// <summary>
/// Retreive the data for one player, passing it to the onGet callback.
/// </summary>
public void GetPlayerData(ulong targetId, Action<LobbyUserData> onGet)
{
m_onGetCurrentCallback = onGet;
GetPlayerData_ServerRpc(targetId, m_localId);
}
[ServerRpc(RequireOwnership = false)]
private void GetPlayerData_ServerRpc(ulong id, ulong callerId)
{
if (m_playerData.ContainsKey(id))
GetPlayerData_ClientRpc(callerId, m_playerData[id]);
else
GetPlayerData_ClientRpc(callerId, new LobbyUserData(null, 0));
public void GetPlayerName_ClientRpc(ulong callerId, string name)
public void GetPlayerData_ClientRpc(ulong callerId, LobbyUserData data)
{ m_onGetCurrent?.Invoke(name);
m_onGetCurrent = null;
{ m_onGetCurrentCallback?.Invoke(data);
m_onGetCurrentCallback = null;
}
}
}

10
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<string>> m_retrieveName;
private Action<ulong, Action<LobbyUserData>> 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;

// I guess I'd just have a "singleton" to hold the references?
public override void OnNetworkSpawn()
{
m_retrieveName = NetworkedDataStore.Instance.GetPlayerName;
m_retrieveName = NetworkedDataStore.Instance.GetPlayerData;
m_mainCamera = GameObject.Find("InGameCamera").GetComponent<Camera>();
if (IsHost)
m_currentlyCollidingSymbols = new List<SymbolObject>();

}
[ClientRpc]
private void SetName_ClientRpc(string name)
private void SetName_ClientRpc(LobbyUserData data)
m_nameOutput.text = name;
m_nameOutput.text = data.name;
}
// Don't love having the input here, but it doesn't need to be anywhere else.

public void OnReceiveMessage(MessageType type, object msg)
{
if (type == MessageType.GameBeginning)
if (type == MessageType.MinigameBeginning)
{
m_retrieveName.Invoke(OwnerClientId, SetName_ClientRpc);
Locator.Get.Messenger.Unsubscribe(this);

40
Assets/Scripts/Netcode/Scorer.cs


using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Events;
// TODO: I'm using host and server interchangeably...
// 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?
/// <summary>
/// Used by the host to actually track scores for all players, and by each client to monitor for updates to their own score.

// TODO: Most of the ints could be bytes?
private Dictionary<ulong, int> m_scoresByClientId = new Dictionary<ulong, int>();
[SerializeField] private NetworkedDataStore m_dataStore = 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;
AddClient_ServerRpc(m_localId);
}
[ServerRpc(RequireOwnership = false)]
private void AddClient_ServerRpc(ulong id)
{
if (!m_scoresByClientId.ContainsKey(id))
m_scoresByClientId.Add(id, 0);
// Called on the host.
m_scoresByClientId[id] += 2;
UpdateScoreOutput_ClientRpc(id, m_scoresByClientId[id]);
int newScore = m_dataStore.UpdateScore(id, 2);
UpdateScoreOutput_ClientRpc(id, newScore);
m_scoresByClientId[id] -= 1;
UpdateScoreOutput_ClientRpc(id, m_scoresByClientId[id]);
int newScore = m_dataStore.UpdateScore(id, -1);
UpdateScoreOutput_ClientRpc(id, newScore);
}
[ClientRpc]

m_scoreOutputText.text = score.ToString("00");
}
public void OnGameEnd()
{
OnGameEnd_ClientRpc();
}
[ClientRpc]
public void OnGameEnd_ClientRpc()
{
m_dataStore.GetAllPlayerData(m_onGameEnd);
}
}
}

6
Assets/Scripts/Netcode/SetupInGame.cs


CreateNetworkManager();
}
else if (type == MessageType.GameBeginning)
else if (type == MessageType.MinigameBeginning)
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Failed to join the game.");
// TODO: Need to handle both failing to connect and connecting but failing to initialize.
// I.e. cleaning up networked objects *might* be necessary.
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Failed to join the game."); // TODO: I do still need the quick pause...
OnGameEnd();
}
}

8
ProjectSettings/Packages/com.unity.services.vivox/Settings.json


{
"type": "System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"key": "isEnvironmentCustom",
"value": "{\"m_Value\":false}"
"value": "{\"m_Value\":true}"
},
{
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",

{
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"key": "server",
"value": "{\"m_Value\":\"https://unity.vivox.com/appconfig/0bf04-com_u-76576-test\"}"
"value": "{\"m_Value\":\"https://unity.vivox.com/appconfig/13746-com_u-21128-test\"}"
"value": "{\"m_Value\":\"0bf04-com_u-76576-test\"}"
"value": "{\"m_Value\":\"13746-com_u-21128-test\"}"
"value": "{\"m_Value\":\"jQr72GjGtoB2lpdtK1GmV1BgtOsFUzK6\"}"
"value": "{\"m_Value\":\"FDIlna7382W91CMCoztXRWK12KcxJRZu\"}"
},
{
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",

35
Assets/Scripts/Netcode/ResultsUserUI.cs


using UnityEngine;
using Unity.Netcode;
namespace LobbyRelaySample.ngo
{
/// <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.")]
[SerializeField] private CanvasGroup[] m_containers;
[Tooltip("These should be in order of appearance, i.e. the 0th entry is the 1st-place player, and so on.")]
[SerializeField] private TMPro.TMP_Text[] m_playerNameOutputs;
[Tooltip("These should also be in order of appearance.")]
[SerializeField] private TMPro.TMP_Text[] m_playerScoreOutputs;
private int m_index = 0;
public void Start()
{
foreach (var container in m_containers)
container.alpha = 0;
}
// Assigned to an event in the Inspector.
public void ReceiveScoreInOrder(LobbyUserData data)
{
m_containers[m_index].alpha = 1;
m_playerNameOutputs[m_index].text = data.name;
m_playerScoreOutputs[m_index].text = data.score.ToString("00");
m_index++;
}
}
}

11
Assets/Scripts/Netcode/ResultsUserUI.cs.meta


fileFormatVersion: 2
guid: 3a4ddeb182a27644480e6c47c065924e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存