浏览代码

Cleaning up some comments for clarity. I've also removed NetworkTransform from the cursor since we're managing the positions with a NetworkVariable, and renamed some methods.

/main/staging/ngo_more_cleanup
nathaniel.buck@unity3d.com 3 年前
当前提交
80a98484
共有 12 个文件被更改,包括 92 次插入99 次删除
  1. 28
      Assets/Prefabs/NGO/PlayerCursor.prefab
  2. 3
      Assets/Scripts/NGO/IInGameInputHandler.cs
  3. 18
      Assets/Scripts/NGO/InGameRunner.cs
  4. 8
      Assets/Scripts/NGO/NetworkedDataStore.cs
  5. 20
      Assets/Scripts/NGO/PlayerCursor.cs
  6. 6
      Assets/Scripts/NGO/RelayNGOUtpSetup.cs
  7. 8
      Assets/Scripts/NGO/Scorer.cs
  8. 77
      Assets/Scripts/NGO/SequenceSelector.cs
  9. 10
      Assets/Scripts/NGO/SetupInGame.cs
  10. 8
      Assets/Scripts/NGO/SymbolContainer.cs
  11. 3
      Assets/Scripts/NGO/SymbolData.cs
  12. 2
      Assets/Scripts/NGO/SymbolObject.cs

28
Assets/Prefabs/NGO/PlayerCursor.prefab


m_Component:
- component: {fileID: 3227847727972158004}
- component: {fileID: -1321688216342888635}
- component: {fileID: 1724769857655924793}
- component: {fileID: 4403165428829500588}
- component: {fileID: -1476701697690489341}
m_Layer: 0

AlwaysReplicateAsRoot: 0
DontDestroyWithOwner: 0
AutoObjectParentSync: 1
--- !u!114 &1724769857655924793
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3227847727972158006}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3}
m_Name:
m_EditorClassIdentifier:
SyncPositionX: 1
SyncPositionY: 1
SyncPositionZ: 1
SyncRotAngleX: 0
SyncRotAngleY: 0
SyncRotAngleZ: 0
SyncScaleX: 0
SyncScaleY: 0
SyncScaleZ: 0
PositionThreshold: 0.001
RotAngleThreshold: 0.01
ScaleThreshold: 0.01
InLocalSpace: 0
Interpolate: 0
CanCommitToTransform: 0
--- !u!114 &4403165428829500588
MonoBehaviour:
m_ObjectHideFlags: 0

3
Assets/Scripts/NGO/IInGameInputHandler.cs


namespace LobbyRelaySample.ngo
{
/// <summary>
/// Something that will handle player input while in the game.
/// </summary>
public interface IInGameInputHandler : IProvidable<IInGameInputHandler>
{
void OnPlayerInput(ulong id, SymbolObject selectedSymbol);

18
Assets/Scripts/NGO/InGameRunner.cs


}
/// <summary>
/// To verify the connection, invoke a server RPC call that then invokes a client RPC call.
/// 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)

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

}
/// <summary>
/// Once the connection is confirmed, check if all players have connected.
/// Once the connection is confirmed, spawn a player cursor and check if all players have connected.
NetworkObject playerCursor = NetworkObject.Instantiate(m_playerCursorPrefab);
NetworkObject playerCursor = NetworkObject.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);
playerCursor.name += clientData.name;
m_dataStore.AddPlayer(clientData.id, clientData.name);

}
}
/// <summary>
/// The game will begin either when all players have connected successfully or after a timeout.
/// </summary>
private void BeginGame()
{
m_canSpawnInGameObjects = true;

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

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

8
Assets/Scripts/NGO/NetworkedDataStore.cs


namespace LobbyRelaySample.ngo
{
/// <summary>
/// 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.
/// </summary>
public class NetworkedDataStore : NetworkBehaviour
{

private Dictionary<ulong, PlayerData> m_playerData = new Dictionary<ulong, PlayerData>();
private ulong m_localId;
// Clients will need to retrieve the host's player data since it isn't synchronized. During that process, they will supply these callbacks
// Clients will need to retrieve the host's player data since it isn't synchronized. During that process, they will supply these callbacks.
// Since we use RPC calls to retrieve data, these callbacks need to be retained (since the scope of the method that the client calls to request
// data will be left in order to make the server RPC call).
private Action<PlayerData> m_onGetCurrentCallback;
private UnityEvent<PlayerData> m_onEachPlayerCallback;

}
/// <summary>
/// Retrieve the data for all players from 1st to last place, calling onEachPlayer for each.
/// Retrieve the data for all players in order from 1st to last place, calling onEachPlayer for each.
/// </summary>
public void GetAllPlayerData(UnityEvent<PlayerData> onEachPlayer)
{

20
Assets/Scripts/NGO/PlayerCursor.cs


namespace LobbyRelaySample.ngo
{
/// <summary>
/// Each player's cursor needs to be controlled by them and visible to the other players.
/// This cursor object will follow the owning player's mouse cursor and be visible to the other players.
/// The host will use this object's movement for detecting collision with symbol objects.
/// </summary>
[RequireComponent(typeof(Collider))]
public class PlayerCursor : NetworkBehaviour, IReceiveMessages

[SerializeField] private TMPro.TMP_Text m_nameOutput = default;
private Camera m_mainCamera;
private NetworkVariable<Vector3> m_position = new NetworkVariable<Vector3>(NetworkVariableReadPermission.Everyone, Vector3.zero);
private NetworkVariable<Vector3> m_position = new NetworkVariable<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.
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.

Locator.Get.Messenger.Subscribe(this);
}
// We can't pass object references as RPC calls by default, and we don't have a different convenient way to, I think,
// get the object spawned on the server to assign some member on the client, so instead let's retrieve dynamically what we need.
// I guess I'd just have a "singleton" to hold the references?
/// <summary>
/// This cursor is spawned in dynamically but needs references to some scene objects. Pushing full object references over RPC calls
/// is an option if we create custom data for each and ensure they're all spawned on the host correctly, but it's simpler to do
/// some one-time retrieval here instead.
/// This also sets up the visuals to make remote player cursors appear distinct from the local player's cursor.
/// </summary>
public override void OnNetworkSpawn()
{
m_retrieveName = NetworkedDataStore.Instance.GetPlayerData;

m_nameOutput.text = data.name;
}
// Don't love having the input here, but it doesn't need to be anywhere else.
// It'd be better to have a separate input handler, but we don't need the mouse input anywhere else, so keep it simple.
private bool IsSelectInputHit()
{
return Input.GetMouseButtonDown(0);

SendInput_ServerRpc(m_localId);
}
[ServerRpc] // Leave RequireOwnership = true for these so that only the player whose cursor this is can make updates.
[ServerRpc] // Leave (RequireOwnership = true) for these so that only the player whose cursor this is can make updates.
private void SetPosition_ServerRpc(Vector3 position)
{
m_position.Value = position;

6
Assets/Scripts/NGO/RelayNGOUtpSetup.cs


namespace LobbyRelaySample.ngo
{
/*
* When using the Relay adapter for UTP to connect the NetworkManager for Netcode for GameObjects (NGO), we need to provide the Allocation info without manually binding to it.
* To use Netcode for GameObjects (NGO), we use the Relay adapter for UTP, attached to a NetworkManager. This needs to be provided the Allocation info before we bind to it.
* In actual use, if you are using NGO for your game's networking, you would not also use the RelayUtpSetupHost/RelayUtpSetupClient at all, since their direct data transmission would be unnecessary.
* We keep both versions for this sample to demonstrate how each is set up, whether you want to just use Lobby + Relay or use NGO as well.
*/

/// </summary>
public class RelayUtpNGOSetupHost : MonoBehaviour // If this is a MonoBehaviour, it can be added to the InGameRunner object for easier cleanup on game end.
public class RelayUtpNGOSetupHost : MonoBehaviour // This is a MonoBehaviour so that it can be added to the InGameRunner object for easier cleanup on game end.
{
private SetupInGame m_setupInGame;
private LocalLobby m_localLobby;

private void OnJoin(JoinAllocation joinAllocation)
{
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.
return;
bool isSecure = false;
var endpoint = RelayUtpSetup.GetEndpointForAllocation(joinAllocation.ServerEndpoints, joinAllocation.RelayServer.IpV4, joinAllocation.RelayServer.Port, out isSecure);

8
Assets/Scripts/NGO/Scorer.cs


using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using TMPro;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;

private ulong m_localId;
[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).")]
[Tooltip("When the game ends, this will be called once for each player in order of rank (1st-place first, and so on).")]
[SerializeField] private UnityEvent<PlayerData> m_onGameEnd = default;
public override void OnNetworkSpawn()

int newScore = m_dataStore.UpdateScore(id, 1);
UpdateScoreOutput_ClientRpc(id, newScore);
}
public void ScoreFailure(ulong id)
{
int newScore = m_dataStore.UpdateScore(id, -1);

77
Assets/Scripts/NGO/SequenceSelector.cs


using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;

{
/// <summary>
/// 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.
/// </summary>
public class SequenceSelector : NetworkBehaviour, IReceiveMessages
{

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>(); // Also owned by the host, indexed by client ID.
private Dictionary<ulong, int> m_targetSequenceIndexPerPlayer = new Dictionary<ulong, int>(); // Each player's current target. Also owned by the host, indexed by client ID.
public void Awake()
{

public override void OnNetworkSpawn()
{
if (IsHost)
ChooseSymbols();
m_localId = NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_localId);
}
private void ChooseSymbols()
{
// Choose some subset of the list of symbols to be present in this game, along with a target sequence.
int numSymbolTypes = 8;
List<int> symbolsForThisGame = SelectSymbols(m_symbolData.m_availableSymbols.Count, numSymbolTypes);
m_targetSequence.Add(symbolsForThisGame[0]);
m_targetSequence.Add(symbolsForThisGame[1]);
m_targetSequence.Add(symbolsForThisGame[2]);
// Then, ensure that the target sequence is present in order throughout most of the full set of symbols to spawn.
int numTargetSequences = (int)(k_symbolCount * 2 / 3f) / 3; // About 2/3 of the symbols will be definitely part of the target sequence.
for (; numTargetSequences >= 0; numTargetSequences--)
// Choose some subset of the list of symbols to be present in this game, along with a target sequence.
List<int> symbolsForThisGame = SelectSymbols(m_symbolData.m_availableSymbols.Count, 8);
m_targetSequence.Add(symbolsForThisGame[0]);
m_targetSequence.Add(symbolsForThisGame[1]);
m_targetSequence.Add(symbolsForThisGame[2]);
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 (int n = 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.
int numTargetSequences = (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.
AddHalfRemaining(3, 2);
AddHalfRemaining(4, 2);
AddHalfRemaining(5, 2);
AddHalfRemaining(6, 2);
AddHalfRemaining(7, 1);
void AddHalfRemaining(int symbolIndex, int divider)
void AddHalfRemaining(int symbolIndex, int divider)
{
int remaining = k_symbolCount - m_fullSequence.Count;
for (int n = 0; n < remaining / divider; n++)
int remaining = k_symbolCount - m_fullSequence.Count;
for (int n = 0; n < remaining / divider; n++)
{
int randomIndex = UnityEngine.Random.Range(0, m_fullSequence.Count);
m_fullSequence.Insert(randomIndex, symbolsForThisGame[symbolIndex]);
}
int randomIndex = UnityEngine.Random.Range(0, m_fullSequence.Count);
m_fullSequence.Insert(randomIndex, symbolsForThisGame[symbolIndex]);
m_localId = NetworkManager.Singleton.LocalClientId;
AddClient_ServerRpc(m_localId);
}
[ServerRpc(RequireOwnership = false)]

public void Update()
{
// We can't guarantee timing with the host's selection of the target sequence, so retrieve it once it's available.
// 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)
{
for (int n = 0; n < m_targetSequence.Count; n++)

/// <summary>
/// If the index is correct, this will advance the current sequence index.
/// </summary>
/// <returns>True if the correct symbol index was chosen, false otherwise.</returns>
public bool ConfirmSymbolCorrect(ulong id, int symbolIndex)
{
int index = m_targetSequenceIndexPerPlayer[id];

}
/// <summary>
/// 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>
private struct RectCut
{

points.Clear();
rects.Enqueue(new RectCut(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.
while (rects.Count + points.Count < count && rects.Count > 0)
{
RectCut currRect = rects.Dequeue();

10
Assets/Scripts/NGO/SetupInGame.cs


go.SetActive(areVisible);
}
/// <summary>
/// The prefab with the NetworkManager contains all of the assets and logic needed to set up the NGO minigame.
/// The UnityTransport needs to also be set up with a new Allocation from Relay.
/// </summary>
private void CreateNetworkManager()
{
m_inGameManagerObj = GameObject.Instantiate(m_prefabNetworkManager);

else
m_inGameManagerObj.AddComponent<RelayUtpNGOSetupClient>().Initialize(this, m_lobby, () => { m_initializeTransport(transport); m_networkManager.StartClient(); });
}
private void OnConnectionVerified()
{ m_hasConnectedViaNGO = true;

{ m_localUser = user; // Same, regarding redundancy.
}
/// <summary>
/// Once the Relay Allocation is created, this passes its data to the UnityTransport.
/// </summary>
public void SetRelayServerData(string address, int port, byte[] allocationBytes, byte[] key, byte[] connectionData, byte[] hostConnectionData, bool isSecure)
{
m_initializeTransport = (transport) => { transport.SetRelayServerData(address, (ushort)port, allocationBytes, key, connectionData, hostConnectionData, isSecure); };

{
if (!m_hasConnectedViaNGO)
{
// If this player hasn't successfully connected via NGO, get booted.
// If this player hasn't successfully connected via NGO, forcibly exit the minigame.
Locator.Get.Messenger.OnReceiveMessage(MessageType.DisplayErrorPopup, "Failed to join the game.");
OnGameEnd();
}

8
Assets/Scripts/NGO/SymbolContainer.cs


namespace LobbyRelaySample.ngo
{
// 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] private float m_speed = 1;
private bool m_isConnected = false;
private bool m_hasGameStarted = false;
/// <summary>
/// Verify both that the game has started and that the network connection is working before moving the symbols.
/// </summary>
private void OnGameStarted()
{
m_hasGameStarted = true;

3
Assets/Scripts/NGO/SymbolData.cs


namespace LobbyRelaySample.ngo
{
/// <summary>
/// Associates a symbol index with the sprite to display for symbol objects matching the index.
/// </summary>
public class SymbolData : ScriptableObject
{
[SerializeField] public List<Sprite> m_availableSymbols;

2
Assets/Scripts/NGO/SymbolObject.cs


{
// 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.)
this.transform.localPosition += Vector3.forward * 500;
}
}
正在加载...
取消
保存