This commit is contained in:
2026-04-30 15:08:19 +07:00
parent 2c0ec5ab1c
commit c2b0e96570
8 changed files with 416 additions and 117 deletions

View File

@@ -6,8 +6,11 @@
<component name="ChangeListManager">
<list default="true" id="f9183c68-daf0-43b8-be4c-fad79983f91b" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/.idea.HALLUCINATE/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HALLUCINATE/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scove/UIScaleTest.unity" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scove/UIScaleTest.unity" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Network/BasicSpawner.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Network/BasicSpawner.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/UI/MainPanelSettings.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/MainPanelSettings.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Network/PlayerDataManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Network/PlayerDataManager.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/LobbyController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/LobbyController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/UI/Lobby.uxml" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/Lobby.uxml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -147,7 +150,7 @@
<workItem from="1777376778745" duration="10727000" />
<workItem from="1777392719306" duration="13382000" />
<workItem from="1777443280908" duration="5223000" />
<workItem from="1777484328779" duration="2736000" />
<workItem from="1777484328779" duration="14811000" />
</task>
<servers />
</component>

View File

@@ -0,0 +1,68 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1290203079495312901
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6224859379081039779}
- component: {fileID: 5687802730781322084}
- component: {fileID: -7009574229380509654}
m_Layer: 0
m_Name: PlayerDataManager
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &6224859379081039779
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1290203079495312901}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &5687802730781322084
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1290203079495312901}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b3d9934ebd60c9c4ea3e464b77fd7ae0, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PlayerDataManager
_Players:
_items: []
--- !u!114 &-7009574229380509654
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1290203079495312901}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -1552182283, guid: e725a070cec140c4caffb81624c8c787, type: 3}
m_Name:
m_EditorClassIdentifier: Fusion.Runtime.dll::Fusion.NetworkObject
SortKey: 1822710516
ObjectInterest: 1
Flags: 262145
NestedObjects: []
NetworkedBehaviours:
- {fileID: 5687802730781322084}
ForceRemoteRenderTimeframe: 0

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 33a4edcf030b02446bd8e4bb9a0fb9f3
labels:
- FusionPrefab
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -150,6 +150,8 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::Hallucinate.UI.BasicSpawner
_playerPrefab:
RawGuidValue: 761bdf2e5c0cff4488527355acb975e5
_playerDataManagerPrefab:
RawGuidValue: 33a4edcf030b02446bd8e4bb9a0fb9f3
--- !u!4 &417583767
Transform:
m_ObjectHideFlags: 0

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -19,6 +19,10 @@ namespace Hallucinate.UI
public event Action OnJoinStartedEvent;
public event Action OnJoinFailedEvent;
[Header(""Prefabs"")]
[SerializeField] private NetworkPrefabRef _playerPrefab;
[SerializeField] private NetworkPrefabRef _playerDataManagerPrefab;
private void Awake()
{
if (Instance != null && Instance != this)
@@ -62,22 +66,23 @@ namespace Hallucinate.UI
{
await EnsureRunnerExists();
if (_runner.SessionInfo.IsValid) return;
var result = await _runner.JoinSessionLobby(SessionLobby.ClientServer);
if (!result.Ok)
{
Debug.LogError($"Failed to join lobby: {result.ShutdownReason}. Check AppID type or Network/Firewall.");
Debug.LogWarning($""Join lobby result: {result.ShutdownReason}. This is often normal on first run if already connecting."");
}
}
public async Task<bool> StartHost(string sessionName, string password = null)
public async Task<bool> StartHost(string sessionName, string displayName, string password = null)
{
OnJoinStartedEvent?.Invoke();
// 1. Kiểm tra Build Settings
bool sceneExists = false;
for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; i++)
{
if (UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(i).Contains("Main Scene"))
if (UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(i).Contains(""Main Scene""))
{
sceneExists = true;
break;
@@ -86,18 +91,18 @@ namespace Hallucinate.UI
if (!sceneExists)
{
Debug.LogError("CRITICAL: 'Main Scene' is NOT in Build Settings!");
Debug.LogError(""CRITICAL: 'Main Scene' is NOT in Build Settings!"");
return false;
}
// 2. Khởi tạo Runner mới sạch sẽ
await EnsureRunnerExists();
var customProps = new Dictionary<string, SessionProperty>();
if (!string.IsNullOrEmpty(password))
{
customProps.Add("pw", password);
customProps.Add(""pw"", password);
}
customProps.Add(""rn"", displayName);
var result = await _runner.StartGame(new StartGameArgs()
{
@@ -105,18 +110,23 @@ namespace Hallucinate.UI
SessionName = sessionName,
SessionProperties = customProps,
PlayerCount = 2,
// Thêm fixed region để tránh timeout khi tìm server
// SceneManager sẽ tự động add nếu thiếu
SceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>() ?? gameObject.AddComponent<NetworkSceneManagerDefault>()
});
if (result.Ok)
{
if (_runner.IsServer && _playerDataManagerPrefab.IsValid)
{
if (FindFirstObjectByType<PlayerDataManager>() == null)
{
_runner.Spawn(_playerDataManagerPrefab, Vector3.zero, Quaternion.identity, null);
}
}
return true;
}
else
{
Debug.LogError($"Fusion StartHost Failed: {result.ShutdownReason}.");
Debug.LogError($""Fusion StartHost Failed: {result.ShutdownReason}."");
OnJoinFailedEvent?.Invoke();
return false;
}
@@ -140,39 +150,54 @@ namespace Hallucinate.UI
}
else
{
Debug.LogError($"Fusion StartClient Failed: {result.ShutdownReason}");
Debug.LogError($""Fusion StartClient Failed: {result.ShutdownReason}"");
OnJoinFailedEvent?.Invoke();
return false;
}
}
// --- Các phương thức Callbacks ---
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (player == runner.LocalPlayer)
{
var pdm = FindFirstObjectByType<PlayerDataManager>();
if (pdm != null)
SendLocalMetaData(player);
}
}
private async void SendLocalMetaData(PlayerRef player)
{
PlayerDataManager pdm = null;
int retries = 0;
while (pdm == null && retries < 20)
{
pdm = FindFirstObjectByType<PlayerDataManager>();
if (pdm != null) break;
await Task.Delay(500);
retries++;
}
if (pdm != null)
{
string playerName = LocalPlayerProfile != null ? LocalPlayerProfile.Name : ""Player "" + player.PlayerId;
// Thêm hậu tố (HOST) nếu là server để dễ phân biệt
if (_runner.IsServer) playerName += "" (HOST)"";
_Role playerRole = _Role.Seeker;
var metaData = new _PlayerMetaData()
{
string playerName = "Player";
_Role playerRole = _Role.Seeker;
if (LocalPlayerProfile != null)
{
playerName = LocalPlayerProfile.Name;
}
var metaData = new _PlayerMetaData()
{
Name = playerName,
Role = playerRole,
IsReady = false
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
Name = playerName,
Role = playerRole,
IsReady = false
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
else
{
Debug.LogError(""[BasicSpawner] Could not find PlayerDataManager after retries. Data will not sync."");
}
}
@@ -180,7 +205,7 @@ namespace Hallucinate.UI
{
if (_runner != null && _runner.IsServer)
{
_runner.LoadScene("Main Scene");
_runner.LoadScene(""Main Scene"");
}
}
@@ -190,18 +215,23 @@ namespace Hallucinate.UI
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
// Chỉ Shutdown nếu người thoát chính là Server (Host)
if (runner.IsServer && player == runner.LocalPlayer)
{
runner.Shutdown();
}
}
if (runner.IsServer && player == runner.LocalPlayer)
{
runner.Shutdown();
}
}
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
{
Debug.LogWarning($"[Fusion] Shutdown occurred. Reason: {shutdownReason}");
Debug.LogWarning($""[Fusion] Shutdown occurred. Reason: {shutdownReason}"");
OnShutdownEvent?.Invoke(shutdownReason.ToString());
if (UIManager.Instance != null)
{
UIManager.Instance.OnBackToMenu();
}
}
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
@@ -238,7 +268,7 @@ namespace Hallucinate.UI
public void OnSceneLoadDone(NetworkRunner runner)
{
string currentSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
if (runner.IsServer && currentSceneName == "Main Scene")
if (runner.IsServer && currentSceneName == ""Main Scene"")
{
foreach (var player in runner.ActivePlayers)
{
@@ -247,9 +277,9 @@ namespace Hallucinate.UI
_spawnedCharacters.Add(player, networkPlayerObject);
}
}
if (currentSceneName == "Main Scene")
if (currentSceneName == ""Main Scene"")
UIManager.Instance?.OnGameStarted();
else if (currentSceneName == "Lobby" || currentSceneName == "Menu")
else if (currentSceneName == ""Lobby"" || currentSceneName == ""Menu"")
UIManager.Instance?.OnBackToMenu();
}

View File

@@ -1,3 +1,4 @@
using System;
using Fusion;
using UnityEngine;
@@ -11,9 +12,18 @@ public struct _PlayerMetaData : INetworkStruct
public class PlayerDataManager : NetworkBehaviour
{
public static PlayerDataManager Instance { get; private set; }
[Networked]
public NetworkDictionary<PlayerRef, _PlayerMetaData> Players => default;
public event Action<PlayerRef, string> OnChatMessageReceived;
public override void Spawned()
{
Instance = this;
}
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void RPC_UpdatePlayerMetaData(PlayerRef playerRef, _PlayerMetaData metaData)
{
@@ -29,6 +39,12 @@ public class PlayerDataManager : NetworkBehaviour
Players.Set(playerRef, data);
}
}
[Rpc(RpcSources.All, RpcTargets.All)]
public void RPC_SendChatMessage(PlayerRef sender, string message)
{
OnChatMessageReceived?.Invoke(sender, message);
}
public bool TryGetPlayerMetaData(PlayerRef playerRef, out _PlayerMetaData metaData)
{

View File

@@ -1,4 +1,4 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.UIElements;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -26,56 +26,71 @@ namespace Hallucinate.UI
private SessionInfo _selectedSession;
// Lounge Elements
private VisualElement _playerListContainer;
private Button _readyBtn, _startBtn;
private Label _loungeRoomName;
private Button _readyBtn, _startBtn;
// Host Slot
private Label _hostNameLabel, _hostStatusLabel;
private VisualElement _hostChatBox;
private Label _hostChatMessage;
// Guest Slot
private Label _guestNameLabel, _guestStatusLabel;
private VisualElement _guestChatBox;
private Label _guestChatMessage;
// Chat Input
private TextField _chatInput;
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
{
base.Initialize(uxmlRoot, manager);
// Query Elements
// Query Containers
_joinContainer = root.Q<VisualElement>("JoinContainer");
_createContainer = root.Q<VisualElement>("CreateContainer");
_loungeContainer = root.Q<VisualElement>("LoungeContainer");
_passOverlay = root.Q<VisualElement>("PasswordOverlay");
// Create Room Fields
_roomIDInput = root.Q<TextField>("RoomIDInput");
_roomNameInput = root.Q<TextField>("RoomNameInput");
_roomPassInput = root.Q<TextField>("RoomPassInput");
_passToggle = root.Q<Toggle>("PassToggle");
// Join Room Fields
_roomList = root.Q<ScrollView>("RoomList");
_joinPassInput = root.Q<TextField>("JoinPassInput");
_joinPassError = root.Q<Label>("JoinPassError");
// Lounge Elements
_playerListContainer = root.Q<VisualElement>("PlayerList");
_loungeRoomName = root.Q<Label>("LoungeRoomName");
_readyBtn = root.Q<Button>("ReadyBtn");
_startBtn = root.Q<Button>("StartBtn");
_loungeRoomName = root.Q<Label>("LoungeRoomName");
// Host Slot
_hostNameLabel = root.Q<Label>("HostName");
_hostStatusLabel = root.Q<Label>("HostReadyStatus");
_hostChatBox = root.Q<VisualElement>("HostChatBox");
_hostChatMessage = root.Q<Label>("HostChatMessage");
// Guest Slot
_guestNameLabel = root.Q<Label>("GuestName");
_guestStatusLabel = root.Q<Label>("GuestReadyStatus");
_guestChatBox = root.Q<VisualElement>("GuestChatBox");
_guestChatMessage = root.Q<Label>("GuestChatMessage");
// Chat Input
_chatInput = root.Q<TextField>("ChatInput");
// Event Bindings
var goToCreateBtn = root.Q<Button>("GoToCreateBtn");
if (goToCreateBtn != null) goToCreateBtn.clicked += ShowCreate;
var cancelCreateBtn = root.Q<Button>("CancelCreateBtn");
if (cancelCreateBtn != null) cancelCreateBtn.clicked += ShowJoin;
var backToMenuBtn = root.Q<Button>("BackToMenuBtn");
if (backToMenuBtn != null) backToMenuBtn.clicked += async () => await uiManager.Pop();
var confirmCreateBtn = root.Q<Button>("ConfirmCreateBtn");
if (confirmCreateBtn != null) confirmCreateBtn.clicked += OnCreateRoomClicked;
var confirmJoinBtn = root.Q<Button>("ConfirmJoinBtn");
if (confirmJoinBtn != null) confirmJoinBtn.clicked += OnConfirmPasswordClicked;
var closePassBtn = root.Q<Button>("ClosePassBtn");
if (closePassBtn != null) closePassBtn.clicked += () => { if(_passOverlay != null) _passOverlay.style.display = DisplayStyle.None; };
var leaveLoungeBtn = root.Q<Button>("LeaveLoungeBtn");
if (leaveLoungeBtn != null) leaveLoungeBtn.clicked += OnLeaveLoungeClicked;
root.Q<Button>("GoToCreateBtn").clicked += ShowCreate;
root.Q<Button>("CancelCreateBtn").clicked += ShowJoin;
root.Q<Button>("BackToMenuBtn").clicked += async () => await uiManager.Pop();
root.Q<Button>("ConfirmCreateBtn").clicked += OnCreateRoomClicked;
root.Q<Button>("ConfirmJoinBtn").clicked += OnConfirmPasswordClicked;
root.Q<Button>("ClosePassBtn").clicked += () => { if(_passOverlay != null) _passOverlay.style.display = DisplayStyle.None; };
root.Q<Button>("LeaveLoungeBtn").clicked += OnLeaveLoungeClicked;
if (_readyBtn != null) _readyBtn.clicked += OnReadyClicked;
if (_startBtn != null) _startBtn.clicked += OnStartClicked;
@@ -89,6 +104,11 @@ namespace Hallucinate.UI
});
}
if (_chatInput != null)
{
_chatInput.RegisterCallback<KeyDownEvent>(OnChatKeyDown, TrickleDown.TrickleDown);
}
// Đăng ký sự kiện từ Spawner
if (BasicSpawner.Instance != null)
{
@@ -106,10 +126,60 @@ namespace Hallucinate.UI
if (BasicSpawner.Instance == null) return;
BasicSpawner.Instance.OnSessionListUpdatedEvent += UpdateRoomList;
BasicSpawner.Instance.OnJoinFailedEvent += () => { if(_joinPassError != null) _joinPassError.style.display = DisplayStyle.Flex; };
BasicSpawner.Instance.OnJoinStartedEvent += () => { /* Show loading if needed */ };
BasicSpawner.Instance.OnJoinStartedEvent += () => { };
_ = BasicSpawner.Instance.StartLobby();
}
private void OnChatKeyDown(KeyDownEvent evt)
{
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
{
evt.StopImmediatePropagation();
evt.PreventDefault();
string msg = _chatInput.value.Trim();
if (!string.IsNullOrEmpty(msg) && PlayerDataManager.Instance != null)
{
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner != null)
{
PlayerDataManager.Instance.RPC_SendChatMessage(runner.LocalPlayer, msg);
_chatInput.value = "";
// Re-focus after clearing
_chatInput.Focus();
}
}
}
}
private void OnChatMessageReceived(PlayerRef sender, string message)
{
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner == null) return;
// Kiểm tra sender là Host hay Guest
bool isHost = sender.PlayerId == 1; // Trong Host Mode, người tạo phòng luôn có ID 1
if (isHost)
{
ShowChatBubble(_hostChatBox, _hostChatMessage, message);
}
else
{
ShowChatBubble(_guestChatBox, _guestChatMessage, message);
}
}
private async void ShowChatBubble(VisualElement box, Label label, string msg)
{
if (box == null || label == null) return;
label.text = msg;
box.style.display = DisplayStyle.Flex;
await Task.Delay(4000);
if (label.text == msg) // Chỉ ẩn nếu chưa có tin nhắn mới đè lên
box.style.display = DisplayStyle.None;
}
public void SetRoomTemplate(VisualTreeAsset template) => _roomItemTemplate = template;
public override async Task PlayTransitionIn()
@@ -137,9 +207,13 @@ namespace Hallucinate.UI
if (_joinContainer != null) _joinContainer.style.display = DisplayStyle.None;
if (_createContainer != null) _createContainer.style.display = DisplayStyle.None;
if (_loungeContainer != null) _loungeContainer.style.display = DisplayStyle.Flex;
if (_loungeRoomName != null) _loungeRoomName.text = $"Room: {roomName}";
if (_loungeRoomName != null) _loungeRoomName.text = roomName.ToUpper();
_playerDataManager = Object.FindFirstObjectByType<PlayerDataManager>();
if (_playerDataManager != null)
{
_playerDataManager.OnChatMessageReceived += OnChatMessageReceived;
}
}
private async void OnCreateRoomClicked()
@@ -161,7 +235,7 @@ namespace Hallucinate.UI
? _roomPassInput.value
: null;
bool success = await spawner.StartHost(id, pass);
bool success = await spawner.StartHost(id, name, pass);
if (success) ShowLounge(name);
}
@@ -173,7 +247,15 @@ namespace Hallucinate.UI
{
if (_roomItemTemplate == null) continue;
var item = _roomItemTemplate.Instantiate();
item.Q<Label>("RoomName").text = session.Name;
// Hiển thị tên phòng thân thiện nếu có
string displayName = session.Name;
if (session.Properties.TryGetValue("rn", out var rnProp))
{
displayName = rnProp;
}
item.Q<Label>("RoomName").text = displayName;
item.Q<Label>("PlayerCount").text = $"{session.PlayerCount}/{session.MaxPlayers}";
bool needsPass = session.Properties.ContainsKey("pw");
@@ -222,12 +304,11 @@ namespace Hallucinate.UI
private void OnReadyClicked()
{
if (_playerDataManager != null)
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner != null && _playerDataManager != null)
{
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner != null)
if (_playerDataManager.TryGetPlayerMetaData(runner.LocalPlayer, out var myData))
{
_playerDataManager.TryGetPlayerMetaData(runner.LocalPlayer, out var myData);
_playerDataManager.RPC_SetReady(runner.LocalPlayer, !myData.IsReady);
}
}
@@ -241,7 +322,14 @@ namespace Hallucinate.UI
private void OnLeaveLoungeClicked()
{
var runner = Object.FindFirstObjectByType<NetworkRunner>();
runner?.Shutdown();
if (runner != null)
{
runner.Shutdown();
}
if (_playerDataManager != null)
{
_playerDataManager.OnChatMessageReceived -= OnChatMessageReceived;
}
ShowJoin();
}
@@ -255,49 +343,114 @@ namespace Hallucinate.UI
private void UpdateLoungeUI()
{
if (_playerDataManager == null)
{
_playerDataManager = Object.FindFirstObjectByType<PlayerDataManager>();
return;
}
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner == null) return;
if (_playerListContainer != null)
if (_playerDataManager == null)
{
_playerListContainer.Clear();
bool allReady = true;
int playerCount = 0;
foreach (var kvp in _playerDataManager.Players)
_playerDataManager = Object.FindFirstObjectByType<PlayerDataManager>();
if (_playerDataManager != null)
{
playerCount++;
var data = kvp.Value;
_playerDataManager.OnChatMessageReceived += OnChatMessageReceived;
}
}
var playerItem = new VisualElement();
playerItem.style.flexDirection = FlexDirection.Row;
playerItem.style.justifyContent = Justify.SpaceBetween;
playerItem.style.paddingBottom = 5;
if (_playerDataManager == null || !_playerDataManager.Object.IsValid) return;
var nameLabel = new Label(data.Name.ToString());
var readyLabel = new Label(data.IsReady ? "READY" : "WAITING...");
readyLabel.style.color = data.IsReady ? Color.green : Color.yellow;
PlayerRef hostRef = PlayerRef.None;
PlayerRef guestRef = PlayerRef.None;
playerItem.Add(nameLabel);
playerItem.Add(readyLabel);
_playerListContainer.Add(playerItem);
foreach (var p in runner.ActivePlayers)
{
if (runner.IsPlayerServer(p))
{
hostRef = p;
}
else
{
guestRef = p;
}
}
// Update Room Name for Guest
if (runner.SessionInfo != null && runner.SessionInfo.Properties.TryGetValue("rn", out var rnProp))
{
_loungeRoomName.text = rnProp.ToString().ToUpper();
}
// Update Host UI
if (hostRef != PlayerRef.None && _playerDataManager.TryGetPlayerMetaData(hostRef, out var hostData))
{
_hostNameLabel.text = hostData.Name.ToString().ToUpper();
_hostStatusLabel.text = hostData.IsReady ? "READY" : "NOT READY";
_hostStatusLabel.style.color = hostData.IsReady ? Color.green : Color.red;
}
else if (hostRef != PlayerRef.None)
{
_hostNameLabel.text = "SYNCING...";
_hostStatusLabel.text = "-";
}
// Update Guest UI
if (guestRef != PlayerRef.None && _playerDataManager.TryGetPlayerMetaData(guestRef, out var guestData))
{
_guestNameLabel.text = guestData.Name.ToString().ToUpper();
_guestStatusLabel.text = guestData.IsReady ? "READY" : "NOT READY";
_guestStatusLabel.style.color = guestData.IsReady ? Color.green : Color.red;
}
else if (runner.ActivePlayers.Count() >= 2)
{
_guestNameLabel.text = "SYNCING...";
_guestStatusLabel.text = "-";
}
else
{
_guestNameLabel.text = "WAITING...";
_guestStatusLabel.text = "-";
_guestStatusLabel.style.color = Color.gray;
}
// Start Button visibility logic
bool allReady = true;
int playerCount = 0;
foreach (var p in runner.ActivePlayers)
{
playerCount++;
if (_playerDataManager.TryGetPlayerMetaData(p, out var data))
{
if (!data.IsReady) allReady = false;
}
if (_startBtn != null)
_startBtn.style.display = (runner.IsServer && allReady && playerCount >= 2) ? DisplayStyle.Flex : DisplayStyle.None;
if (_readyBtn != null)
else
{
_playerDataManager.TryGetPlayerMetaData(runner.LocalPlayer, out var myData);
_readyBtn.text = myData.IsReady ? "UNREADY" : "READY UP";
allReady = false;
}
}
bool isHost = runner.LocalPlayer == hostRef;
if (_startBtn != null)
{
_startBtn.style.display = isHost ? DisplayStyle.Flex : DisplayStyle.None;
_startBtn.SetEnabled(allReady && playerCount >= 2);
}
if (_readyBtn != null)
{
if (_playerDataManager.TryGetPlayerMetaData(runner.LocalPlayer, out var myData))
{
// Style for Ready Button
if (myData.IsReady)
{
_readyBtn.text = "UNREADY";
_readyBtn.style.backgroundColor = new StyleColor(Color.green);
_readyBtn.style.color = new StyleColor(Color.black);
}
else
{
_readyBtn.text = "READY UP";
_readyBtn.style.backgroundColor = new StyleColor(new Color(0.2f, 0.2f, 0.2f, 0.8f));
_readyBtn.style.color = new StyleColor(Color.white);
}
}
}
}

View File

@@ -43,25 +43,43 @@
<!-- LOUNGE VIEW -->
<ui:VisualElement name="LoungeContainer" style="flex-grow: 1; display: none;">
<ui:Label name="LoungeTitle" text="SESSION NAME" class="text-heading" />
<ui:Label name="LoungeRoomName" text="SESSION NAME" class="text-heading" />
<ui:Label name="LoungeID" text="ID: 12345" class="text-label" style="margin-bottom: 30px;" />
<ui:VisualElement style="flex-direction: row; flex-grow: 1;">
<ui:VisualElement name="HostSlot" class="panel-glass border-create" style="flex-grow: 1; margin: 5px; padding: 15px; align-items: center;">
<ui:VisualElement style="flex-direction: row; flex-grow: 1; justify-content: center; align-items: center;">
<!-- HOST SLOT -->
<ui:VisualElement name="HostSlot" class="panel-glass border-create" style="flex-grow: 1; margin: 5px; padding: 15px; align-items: center; position: relative;">
<ui:VisualElement name="HostAvatar" class="border-create" style="width: 80px; height: 80px; border-radius: 40px; background-color: #333; border-width: 2px;" />
<ui:Label name="HostName" text="HOST" class="text-body" style="-unity-font-style: bold; margin-top: 10px;" />
<ui:Label name="HostReadyStatus" text="NOT READY" class="text-label" style="color: #ff4444;" />
<!-- Host Chat Overlay -->
<ui:VisualElement name="HostChatBox" style="position: absolute; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); padding: 5px; display: none;">
<ui:Label name="HostChatMessage" text="..." style="color: white; font-size: 12px; -unity-text-align: middle-center;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="GuestSlot" class="panel-glass" style="flex-grow: 1; margin: 5px; padding: 15px; align-items: center;">
<ui:Label text="VS" style="font-size: 48px; -unity-font-style: bold; color: rgba(255, 255, 255, 0.2); margin: 0 20px;" />
<!-- GUEST SLOT -->
<ui:VisualElement name="GuestSlot" class="panel-glass" style="flex-grow: 1; margin: 5px; padding: 15px; align-items: center; position: relative;">
<ui:VisualElement name="GuestAvatar" style="width: 80px; height: 80px; border-radius: 40px; background-color: #333;" />
<ui:Label name="GuestName" text="WAITING..." class="text-body" style="margin-top: 10px; color: #555;" />
<ui:Label name="GuestReadyStatus" text="-" class="text-label" />
<!-- Guest Chat Overlay -->
<ui:VisualElement name="GuestChatBox" style="position: absolute; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); padding: 5px; display: none;">
<ui:Label name="GuestChatMessage" text="..." style="color: white; font-size: 12px; -unity-text-align: middle-center;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<!-- GLOBAL CHAT INPUT -->
<ui:TextField name="ChatInput" placeholder-text="Press Enter to chat..." style="margin-top: 10px;" />
<ui:Button name="ReadyToggleBtn" text="READY" class="button-spring" style="height: 56px; margin-top: 20px;" />
<ui:Button name="StartGameBtn" text="START GAME" class="button-spring btn-join" style="height: 64px; margin-top: 10px;" />
<ui:Button name="ReadyBtn" text="READY" class="button-spring" style="height: 56px; margin-top: 20px;" />
<ui:Button name="StartBtn" text="START GAME" class="button-spring btn-join" style="height: 64px; margin-top: 10px;" />
<ui:Button name="LeaveLoungeBtn" text="LEAVE ROOM" class="button-spring btn-exit" style="margin-top: 10px;" />
</ui:VisualElement>
</ui:VisualElement>