浏览代码

Merge from ngo_more_cleanup branch.

/main/staging
nathaniel.buck@unity3d.com 2 年前
当前提交
e1d4bfe8
共有 21 个文件被更改,包括 117 次插入124 次删除
  1. 2
      Assets/Art/Icons/CursorIcon.png.meta
  2. 28
      Assets/Prefabs/NGO/PlayerCursor.prefab
  3. 14
      Assets/Prefabs/NGO/SymbolContainer.prefab
  4. 14
      Assets/Prefabs/NGO/SymbolObject.prefab
  5. 2
      Assets/Scripts/Lobby/LobbyAsyncRequests.cs
  6. 4
      Assets/Scripts/Relay/RelayUtpSetup.cs
  7. 4
      Assets/Scripts/Tests/PlayMode/UtpTests.cs
  8. 2
      Assets/Scripts/NGO/SymbolObject.cs
  9. 3
      Assets/Scripts/NGO/SymbolData.cs
  10. 8
      Assets/Scripts/NGO/SymbolContainer.cs
  11. 10
      Assets/Scripts/NGO/SetupInGame.cs
  12. 79
      Assets/Scripts/NGO/SequenceSelector.cs
  13. 11
      Assets/Scripts/NGO/Scorer.cs
  14. 6
      Assets/Scripts/NGO/RelayNGOUtpSetup.cs
  15. 23
      Assets/Scripts/NGO/PlayerCursor.cs
  16. 8
      Assets/Scripts/NGO/NetworkedDataStore.cs
  17. 18
      Assets/Scripts/NGO/InGameRunner.cs
  18. 3
      Assets/Scripts/NGO/IInGameInputHandler.cs
  19. 2
      ProjectSettings/ProjectSettings.asset
  20. 0
      /Assets/Scripts/NGO
  21. 0
      /Assets/Scripts/NGO.meta

2
Assets/Art/Icons/CursorIcon.png.meta


externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0

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

14
Assets/Prefabs/NGO/SymbolContainer.prefab


SyncPositionX: 1
SyncPositionY: 1
SyncPositionZ: 1
SyncRotAngleX: 1
SyncRotAngleY: 1
SyncRotAngleZ: 1
SyncScaleX: 1
SyncScaleY: 1
SyncScaleZ: 1
SyncRotAngleX: 0
SyncRotAngleY: 0
SyncRotAngleZ: 0
SyncScaleX: 0
SyncScaleY: 0
SyncScaleZ: 0
Interpolate: 1
Interpolate: 0
CanCommitToTransform: 0
--- !u!114 &2302923454152093614
MonoBehaviour:

14
Assets/Prefabs/NGO/SymbolObject.prefab


SyncPositionX: 1
SyncPositionY: 1
SyncPositionZ: 1
SyncRotAngleX: 1
SyncRotAngleY: 1
SyncRotAngleZ: 1
SyncScaleX: 1
SyncScaleY: 1
SyncScaleZ: 1
SyncRotAngleX: 0
SyncRotAngleY: 0
SyncRotAngleZ: 0
SyncScaleX: 0
SyncScaleY: 0
SyncScaleZ: 0
InLocalSpace: 1
InLocalSpace: 0
Interpolate: 1
CanCommitToTransform: 0
--- !u!65 &1363360377255918887

2
Assets/Scripts/Lobby/LobbyAsyncRequests.cs


private RateLimitCooldown m_rateLimitQuickJoin = new RateLimitCooldown(10f);
private RateLimitCooldown m_rateLimitHost = new RateLimitCooldown(3f);
// TODO: Shift to using this to do rate limiting for all API calls? E.g. the lobby data pushing is on its own loop.
#endregion
private static Dictionary<string, PlayerDataObject> CreateInitialPlayerData(LobbyUser player)

4
Assets/Scripts/Relay/RelayUtpSetup.cs


var relayServerData = new RelayServerData(ref serverEndpoint, 0, ref allocationId, ref connectionData, ref hostConnectionData, ref key, isSecure);
relayServerData.ComputeNewNonce(); // For security, the nonce value sent when authenticating the allocation must be increased.
var relayNetworkParameter = new RelayNetworkParameter { ServerData = relayServerData };
var networkSettings = new NetworkSettings();
m_networkDriver = NetworkDriver.Create(new INetworkParameter[] { relayNetworkParameter });
m_networkDriver = NetworkDriver.Create(networkSettings.WithRelayParameters(ref relayServerData));
m_connections = new List<NetworkConnection>(connectionCapacity);
if (m_networkDriver.Bind(NetworkEndPoint.AnyIpv4) != 0)

4
Assets/Scripts/Tests/PlayMode/UtpTests.cs


}
private LobbyRelaySample.Auth.SubIdentity_Authentication m_auth;
GameObject m_dummy;
#pragma warning disable CS0414 // This is the "assigned but its value is never used" warning, which will otherwise appear when DTLS is unavailable.
GameObject m_dummy;
#pragma warning restore CS0414
[OneTimeSetUp]
public void Setup()

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

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;

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;

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

79
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 bool m_hasReceivedTargetSequence = false; // TODO: Perhaps split up members by client vs. host?
private bool m_hasReceivedTargetSequence = false;
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();

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

// 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?
// TODO: Most of the ints could be bytes?
[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);

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

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

var trails = m_onClickParticles.trails;
trails.colorOverLifetime = new ParticleSystem.MinMaxGradient(Color.grey);
}
else
{ m_renderer.enabled = false; // The local player should see their cursor instead of the simulated cursor object, since the object will appear laggy.
}
}
[ClientRpc]

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;

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

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

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

2
ProjectSettings/ProjectSettings.asset


accelerometerFrequency: 60
companyName: Unity
productName: Game-Lobby-Sample
defaultCursor: {fileID: 0}
defaultCursor: {fileID: 2800000, guid: 9367b4ce9024f5b4090fabc2941d2116, type: 3}
cursorHotspot: {x: 0, y: 0}
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
m_ShowUnitySplashScreen: 1

/Assets/Scripts/Netcode → /Assets/Scripts/NGO

/Assets/Scripts/Netcode.meta → /Assets/Scripts/NGO.meta

正在加载...
取消
保存