SceneLobby(main):Add script and setup

This commit is contained in:
manhduyhoang90
2026-04-11 18:13:40 +07:00
parent e51f5cb82d
commit 77eb16bb37
14 changed files with 3886 additions and 3 deletions

3185
Assets/Scenes/Lobby.unity Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e1bb3a5dcccfeee4f948b63b991fd8e6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Scripts/Duy.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 42e6f48f26d671a42a9b398d02557c1f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,113 @@
using System.Collections.Generic;
using Fusion;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class _LobbyManager : MonoBehaviour
{
public GameObject lobbyPanel;
public GameObject characterSelectionPanel;
public _BasicSpawner spawner;
[Header("Character Selection")] public TMP_InputField playerNameInput;
public Image[] characterPreviewImages;
public int selectedCharacterIndex = 0;
[Header("Room List")] public GameObject roomListParent;
public GameObject roomListItemPrefab;
public TMP_InputField roomNameInput;
// Start is called once before the first execution of Update after the MonoBehaviour is created
async void Start()
{
lobbyPanel.SetActive(false);
characterSelectionPanel.SetActive(true);
OnSelectCharacter(selectedCharacterIndex);
spawner = FindFirstObjectByType<_BasicSpawner>();
await spawner.StartLobby();
}
public void OnSelectCharacter(int index)
{
selectedCharacterIndex = index;
// update preview images
for (var i = 0; i < characterPreviewImages.Length; i++)
{
characterPreviewImages[i].color = (i == index)
? Color.green
: Color.white;
}
}
public void OnNextButton()
{
var playerName = playerNameInput.text;
if (string.IsNullOrEmpty(playerName))
{
Debug.LogWarning("Player name cannot be empty!");
return;
}
// tạo 1 player _profile tạm thời, sau này sẽ gửi lên server để tạo player object
var _profile = new _PlayerProfile()
{
Name = playerName,
Class = (_CharacterClass)selectedCharacterIndex
};
spawner.SetLocalPlayerProfile(_profile);
// đưa lên host để tạo player object, ở đây tạm thời chỉ log ra console
Debug.Log($"Player Name: {_profile.Name}, Class: {_profile.Class}");
// chuyển sang lobby panel
characterSelectionPanel.SetActive(false);
lobbyPanel.SetActive(true);
}
// hiển thị danh sách phòng
public void DisplayRoomList(List<SessionInfo> sessions)
{
Debug.Log($"Received {sessions.Count} sessions from lobby");
// clear danh sách cũ
foreach (Transform child in roomListParent.transform)
{
Destroy(child.gameObject);
}
if (sessions.Count == 0) return;
// tạo item mới cho mỗi phòng
foreach (var session in sessions)
{
var item = Instantiate(roomListItemPrefab, roomListParent.transform);
var text = item.GetComponentInChildren<TextMeshProUGUI>();
text.text = $"{session.Name} ({session.PlayerCount}/{session.MaxPlayers})";
var button = item.GetComponentInChildren<Button>();
button.onClick.AddListener(() => OnJoinRoom(session.Name));
item.SetActive(true);
}
}
async void OnJoinRoom(string sessionName)
{
await spawner.StartClient(sessionName);
}
public async void OnCreateRoomButton()
{
var roomName = roomNameInput.text;
if (string.IsNullOrEmpty(roomName))
{
Debug.LogWarning("Room name cannot be empty!");
return;
}
// tạo phòng mới với tên đã nhập
await spawner.StartHost(roomName, SceneRef.FromIndex(1));
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 258164a5e282e34489a3c62c443c22f0

View File

@@ -0,0 +1,31 @@
using Fusion;
using UnityEngine;
// struct quản lý thông tin
public struct _PlayerMetaData : INetworkStruct
{
public NetworkString<_16> Name;
public _CharacterClass Class;
}
public class _PlayerDataManager : NetworkBehaviour
{
// biến này của Fusion sẽ tự động đồng bộ giữa các client và host,
// khi có thay đổi sẽ tự động cập nhật ở tất cả các bên
[Networked]
public NetworkDictionary<PlayerRef, _PlayerMetaData> Players => default;
// RPC: phương thức này sẽ được gọi từ client hoặc
// host để cập nhật thông tin player, sau đó sẽ được gửi
// đến state authority (host) để xử lý và đồng bộ lại cho tất cả các client
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void RPC_UpdatePlayerMetaData(PlayerRef playerRef, _PlayerMetaData metaData)
{
Players.Set(playerRef, metaData);
}
public bool TryGetPlayerMetaData(PlayerRef playerRef, out _PlayerMetaData metaData)
{
return Players.TryGet(playerRef, out metaData);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b3d9934ebd60c9c4ea3e464b77fd7ae0

View File

@@ -0,0 +1,50 @@
using Fusion;
using TMPro;
using UnityEngine;
public enum _CharacterClass
{
Blue,
Red
}
public struct _PlayerProfile
{
public string Name;
public _CharacterClass Class;
}
public class PlayerInfo : NetworkBehaviour
{
[Networked] public string playerName { get; set; }
public _PlayerDataManager playerDataManager;
public TextMeshProUGUI nameText;
public GameObject[] characterIcons; // mảng chứa icon tương ứng với từng class, có thể gán trong inspector
// sau khi game object được tạo ra trên mạng,
// sẽ gọi phương thức này để khởi tạo thông tin player
public override void Spawned()
{
playerDataManager = FindFirstObjectByType<_PlayerDataManager>(); // tìm PlayerDataManager trong scene
}
// phương thức này sẽ được gọi mỗi frame để cập nhật thông tin hiển thị của player
public override void Render()
{
if (playerDataManager == null) return;
if (playerDataManager.TryGetPlayerMetaData(Object.InputAuthority, out var metadata))
{
var name = metadata.Name;
var charClass = metadata.Class;
nameText.text = $"{name} ({charClass})";
for (var i = 0; i < characterIcons.Length; i++)
{
characterIcons[i].SetActive(i == (int)charClass); // hiển thị icon tương ứng với class của player
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 70abb536cf50f2948882e913634daedf

View File

@@ -0,0 +1,6 @@
using Fusion;
public partial struct _PlayerInputData : INetworkInput
{
public float direction;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 731ed4a4b6e0ae64c8194463a76646c7

View File

@@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Fusion;
using Fusion.Sockets;
using UnityEngine;
// ghi nhận các input của người chơi, ở đây chỉ có hướng di chuyển
public class _BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
private NetworkRunner _runner { get; set; }
public LobbyManager LobbyManager;
private void Awake()
{
// dont destroy this object when loading new scenes
if(_runner == null) _runner = gameObject.AddComponent<NetworkRunner>();
DontDestroyOnLoad(gameObject);
}
// thông tin profile của player local, sẽ được tạo ra từ lobby và gửi lên host để tạo player object, ở đây tạm thời chỉ tạo 1 profile mặc định
public _PlayerProfile LocalPlayerProfile { get; private set; }
public void SetLocalPlayerProfile(_PlayerProfile _profile)
{
LocalPlayerProfile = _profile;
}
// start lobby
public async Task StartLobby()
{
if(_runner == null) _runner = gameObject.AddComponent<NetworkRunner>();
_runner.ProvideInput = true;
_runner.AddCallbacks(this);
var result = await _runner.JoinSessionLobby(SessionLobby.ClientServer);
if (result.Ok)
{
Debug.Log("Joined lobby successfully!");
}
else
{
Debug.LogError($"Failed to join lobby: {result.ShutdownReason}");
}
}
// tạo phòng
public async Task StartHost(string sessionName, SceneRef scene)
{
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Host,
SessionName = sessionName,
Scene = scene,
PlayerCount = 2,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
if (result.Ok)
{
Debug.Log("Started host successfully!");
}
else
{
Debug.LogError($"Failed to start host: {result.ShutdownReason}");
}
}
// tham gia phòng
public async Task StartClient(string sessionName)
{
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Client,
SessionName = sessionName,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
if (result.Ok) {
Debug.Log("Started client successfully!");
}
else {
Debug.LogError($"Failed to start client: {result.ShutdownReason}");
}
}
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player)
{
}
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player)
{
}
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
// nếu là host
if (runner.IsServer)
{
Vector2 spawnPosition;
if (player == runner.LocalPlayer)
{
spawnPosition = new Vector2(-8, 0);
}
else
{
spawnPosition = new Vector2(8, 0);
}
// Create a unique position for the player
// var spawnPosition = new Vector2(Random.Range(0, 5), Random.Range(0, 5));
var networkPlayerObject = runner.Spawn(
_playerPrefab,
spawnPosition,
Quaternion.identity,
player);
// Keep track of the player avatars for easy access
_spawnedCharacters.Add(player, networkPlayerObject);
// if (runner.ActivePlayers.Count() == 2 && !_ballStarted)
// {
// var ball = FindFirstObjectByType<Ball>();
// ball.Launch();
// _ballStarted = true;
// }
}
Debug.Log("Player joined: " + player + ", is local player: " + (player == runner.LocalPlayer));
if (player == runner.LocalPlayer)
{
var pdm = FindFirstObjectByType<_PlayerDataManager>();
if (pdm == null) return;
var metaData = new _PlayerMetaData()
{
Name = LocalPlayerProfile.Name,
Class = LocalPlayerProfile.Class,
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
if (runner.IsServer)
{
runner.Shutdown();
}
}
}
private bool IsGameReady(NetworkRunner runner)
{
return runner.ActivePlayers.Count() >= 2;
}
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
{
Debug.Log("Game shutdown: " + shutdownReason);
UnityEngine.SceneManagement.SceneManager.LoadScene("Lobby");
}
public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason)
{
}
public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token)
{
}
public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason)
{
}
public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message)
{
}
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data)
{
}
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress)
{
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
// collect the input and send it to the runner, which will then be sent to the server and other clients
var data = new _PlayerInputData();
data.direction = Input.GetAxis("Vertical");
input.Set(data);
}
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input)
{
}
public void OnConnectedToServer(NetworkRunner runner)
{
}
// khi có update về danh sách phòng,
// ví dụ khi có phòng mới được tạo hoặc phòng bị xóa,
// host sẽ gửi update này cho tất cả client trong lobby
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
{
Debug.Log("Session list updated, total sessions: " + sessionList.Count);
LobbyManager.DisplayRoomList(sessionList);
}
public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary<string, object> data)
{
}
public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken)
{
}
public void OnSceneLoadDone(NetworkRunner runner)
{
}
public void OnSceneLoadStart(NetworkRunner runner)
{
}
public void DespawnGO(NetworkObject networkObject)
{
if (_runner != null && networkObject != null)
{
_runner.Despawn(networkObject);
}
}
public NetworkObject SpawnGO(NetworkPrefabRef prefabRef, Vector2 position,Quaternion rotation, PlayerRef owner = default)
{
if (_runner != null)
{
return _runner.Spawn(prefabRef, position, rotation, owner);
}
return null;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ca752d01bdc2c5e42938776307031da3

View File

@@ -112,9 +112,9 @@ Material:
- _Parallax: 0.02
- _PerspectiveFilter: 0.875
- _Reflectivity: 10
- _ScaleRatioA: 0.90909094
- _ScaleRatioA: 0.9
- _ScaleRatioB: 0.73125
- _ScaleRatioC: 0.7386364
- _ScaleRatioC: 0.73125
- _ScaleX: 1
- _ScaleY: 1
- _ShaderFlags: 0
@@ -236,7 +236,202 @@ MonoBehaviour:
m_FontFeatureTable:
m_MultipleSubstitutionRecords: []
m_LigatureSubstitutionRecords: []
m_GlyphPairAdjustmentRecords: []
m_GlyphPairAdjustmentRecords:
- m_FirstAdjustmentRecord:
m_GlyphIndex: 85
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 15
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 85
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 17
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 85
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 3.1914062
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 2020
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 36
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -1.5537109
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 55
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -1.5537109
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 60
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 827
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 836
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 839
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -4.745117
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 846
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -1.5537109
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 854
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -1.5537109
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 855
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
- m_FirstAdjustmentRecord:
m_GlyphIndex: 3
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: -1.5537109
m_YAdvance: 0
m_SecondAdjustmentRecord:
m_GlyphIndex: 861
m_GlyphValueRecord:
m_XPlacement: 0
m_YPlacement: 0
m_XAdvance: 0
m_YAdvance: 0
m_FeatureLookupFlags: 0
m_MarkToBaseAdjustmentRecords: []
m_MarkToMarkAdjustmentRecords: []
m_ShouldReimportFontFeatures: 0