This commit is contained in:
2026-04-28 19:04:09 +07:00
parent 9484f1f1d4
commit 6a5baedced
7 changed files with 321 additions and 330 deletions

View File

@@ -4,14 +4,21 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="f9183c68-daf0-43b8-be4c-fad79983f91b" name="Changes" comment="" />
<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/Scripts/Duy/LobbyManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Duy/LobbyManager.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Duy/_BasicSpawner.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Duy/_BasicSpawner.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/Scripts/UI/UIManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/UIManager.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" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="EmbeddingIndexingInfo">
<option name="cachedIndexableFilesCount" value="39" />
<option name="cachedIndexableFilesCount" value="11" />
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
</component>
<component name="Git.Settings">
@@ -133,6 +140,7 @@
<workItem from="1777181837663" duration="1519000" />
<workItem from="1777269364664" duration="40284000" />
<workItem from="1777373072815" duration="1852000" />
<workItem from="1777376778745" duration="1032000" />
</task>
<servers />
</component>

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
#if false
using System.Collections.Generic;
using Fusion;
using TMPro;
using UnityEngine;
@@ -96,4 +97,5 @@
// tạo phòng mới với tên đã nhập
await spawner.StartHost(roomName, SceneRef.FromIndex(1));
}
}
}
#endif

View File

@@ -6,296 +6,150 @@ 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
namespace Hallucinate.UI
{
private NetworkRunner _runner { get; set; }
public class _BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
private NetworkRunner _runner { get; set; }
public _LobbyManager LobbyManager;
public event Action<List<SessionInfo>> OnSessionListUpdatedEvent;
public event Action<string> OnShutdownEvent;
public event Action OnJoinStartedEvent;
public event Action OnJoinFailedEvent;
private void Awake()
{
if (_runner == null) _runner = gameObject.AddComponent<NetworkRunner>();
DontDestroyOnLoad(gameObject);
}
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)
public _PlayerProfile LocalPlayerProfile { get; private set; }
public void SetLocalPlayerProfile(_PlayerProfile _profile)
{
Debug.Log("Joined lobby successfully!");
LocalPlayerProfile = _profile;
}
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 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.LogError($"Failed to join lobby: {result.ShutdownReason}");
}
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player)
{
}
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public async Task StartHost(string sessionName, string password = null)
{
OnJoinStartedEvent?.Invoke();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
// nếu là host
if (runner.IsServer)
// Sử dụng SessionProperties để lưu mật khẩu
var customProps = new Dictionary<string, SessionProperty>();
if (!string.IsNullOrEmpty(password))
{
Vector2 spawnPosition;
customProps.Add("pw", password);
}
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()
var result = await _runner.StartGame(new StartGameArgs()
{
Name = LocalPlayerProfile.Name,
Role = LocalPlayerProfile.Role,
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
}
GameMode = GameMode.Host,
SessionName = sessionName,
SessionProperties = customProps,
PlayerCount = 2,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
if (runner.IsServer)
if (!result.Ok)
{
runner.Shutdown();
Debug.LogError($"Failed to start host: {result.ShutdownReason}");
OnJoinFailedEvent?.Invoke();
}
}
}
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)
public async Task StartClient(string sessionName, string password = null)
{
_runner.Despawn(networkObject);
}
}
OnJoinStartedEvent?.Invoke();
public NetworkObject SpawnGO(NetworkPrefabRef prefabRef, Vector2 position,Quaternion rotation, PlayerRef owner = default)
{
if (_runner != null)
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Client,
SessionName = sessionName,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
if (!result.Ok)
{
Debug.LogError($"Failed to join client: {result.ShutdownReason}");
OnJoinFailedEvent?.Invoke();
}
}
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
return _runner.Spawn(prefabRef, position, rotation, owner);
if (runner.IsServer)
{
Vector2 spawnPosition = (player == runner.LocalPlayer) ? new Vector2(-8, 0) : new Vector2(8, 0);
var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
_spawnedCharacters.Add(player, networkPlayerObject);
}
if (player == runner.LocalPlayer)
{
var pdm = FindFirstObjectByType<_PlayerDataManager>();
if (pdm != null)
{
var metaData = new _PlayerMetaData()
{
Name = LocalPlayerProfile.Name,
Role = LocalPlayerProfile.Role,
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
}
}
return null;
}
void AssignRoles(NetworkRunner runner)
{
var players = runner.ActivePlayers.ToList();
if (players.Count < 2) return;
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();
}
}
var p1 = players[0];
var p2 = players[1];
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason)
{
OnShutdownEvent?.Invoke(shutdownReason.ToString());
}
bool random = UnityEngine.Random.value > 0.5f;
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
{
OnSessionListUpdatedEvent?.Invoke(sessionList);
}
SetRole(runner, p1, random ? _Role.Seeker : _Role.Trapper);
SetRole(runner, p2, random ? _Role.Trapper : _Role.Seeker);
}
void SetRole(NetworkRunner runner, PlayerRef player, _Role role)
{
var obj = runner.GetPlayerObject(player);
var data = obj.GetComponent<_PlayerData>();
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new _PlayerInputData();
data.direction = Input.GetAxis("Vertical");
input.Set(data);
}
data.PlayerRole = role;
Debug.Log($"Player {player} assigned role: {role}");
public void OnConnectedToServer(NetworkRunner runner) { }
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 OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
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) { }
}
}

View File

@@ -1,73 +1,129 @@
using UnityEngine;
using UnityEngine.UIElements;
using System.Collections.Generic;
using System.Threading.Tasks;
using Fusion;
namespace Hallucinate.UI
{
public class LobbyController : BaseUIController
{
private VisualElement _joinContainer;
private VisualElement _createContainer;
private VisualElement _loungeContainer;
private VisualTreeAsset _roomItemTemplate;
private _BasicSpawner _spawner;
private Button _backBtn;
private Button _createFinalBtn;
private Button _startGameBtn;
private Toggle _hostReady;
private Toggle _guestReady;
// Containers
private VisualElement _joinContainer, _createContainer, _loungeContainer, _passOverlay;
// Create Room Fields
private TextField _roomIDInput, _roomNameInput, _roomPassInput;
private Toggle _passToggle;
// Join Room Fields
private ScrollView _roomList;
private TextField _joinPassInput;
private Label _joinPassError;
private SessionInfo _selectedSession;
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
{
base.Initialize(uxmlRoot, manager);
_spawner = Object.FindFirstObjectByType<_BasicSpawner>();
// Query Elements
_joinContainer = root.Q<VisualElement>("JoinContainer");
_createContainer = root.Q<VisualElement>("CreateContainer");
_loungeContainer = root.Q<VisualElement>("LoungeContainer");
_passOverlay = root.Q<VisualElement>("PasswordOverlay");
_backBtn = root.Q<Button>("BackToMenuBtn");
_createFinalBtn = root.Q<Button>("CreateRoomFinalBtn");
_startGameBtn = root.Q<Button>("StartGameBtn");
_hostReady = root.Q<Toggle>("HostReady");
_guestReady = root.Q<Toggle>("GuestReady");
_backBtn.clicked += () => uiManager.Pop();
_createFinalBtn.clicked += () => ShowLounge();
_roomIDInput = root.Q<TextField>("RoomIDInput");
_roomNameInput = root.Q<TextField>("RoomNameInput");
_roomPassInput = root.Q<TextField>("RoomPassInput");
_passToggle = root.Q<Toggle>("PassToggle");
_roomList = root.Q<ScrollView>("RoomList");
_hostReady.RegisterValueChangedCallback(evt => UpdateStartButton());
_guestReady.RegisterValueChangedCallback(evt => UpdateStartButton());
_joinPassInput = root.Q<TextField>("JoinPassInput");
_joinPassError = root.Q<Label>("JoinPassError");
UpdateStartButton();
// Event Bindings
root.Q<Button>("GoToCreateBtn").clicked += ShowCreate;
root.Q<Button>("CancelCreateBtn").clicked += ShowJoin;
root.Q<Button>("BackToMenuBtn").clicked += () => uiManager.Pop();
root.Q<Button>("ConfirmCreateBtn").clicked += OnCreateRoomClicked;
root.Q<Button>("ConfirmJoinBtn").clicked += OnConfirmPasswordClicked;
root.Q<Button>("ClosePassBtn").clicked += () => _passOverlay.style.display = DisplayStyle.None;
_passToggle.RegisterValueChangedCallback(evt =>
_roomPassInput.style.display = evt.newValue ? DisplayStyle.Flex : DisplayStyle.None);
// Đăng ký sự kiện từ Spawner
if (_spawner != null)
{
_spawner.OnSessionListUpdatedEvent += UpdateRoomList;
_spawner.OnJoinFailedEvent += () => _joinPassError.style.display = DisplayStyle.Flex;
}
}
public void SetRoomTemplate(VisualTreeAsset template) => _roomItemTemplate = template;
public void ShowJoin()
{
_joinContainer.style.display = DisplayStyle.Flex;
_createContainer.style.display = DisplayStyle.None;
_loungeContainer.style.display = DisplayStyle.None;
_spawner?.StartLobby();
}
public void ShowCreate()
{
_joinContainer.style.display = DisplayStyle.None;
_createContainer.style.display = DisplayStyle.Flex;
_loungeContainer.style.display = DisplayStyle.None;
}
public void ShowLounge()
private async void OnCreateRoomClicked()
{
_joinContainer.style.display = DisplayStyle.None;
_createContainer.style.display = DisplayStyle.None;
_loungeContainer.style.display = DisplayStyle.Flex;
string id = _roomIDInput.value.Trim();
string name = string.IsNullOrEmpty(_roomNameInput.value) ? id : _roomNameInput.value;
string pass = _passToggle.value ? _roomPassInput.value : null;
if (string.IsNullOrEmpty(id)) return;
await _spawner.StartHost(id, pass);
// Logic chuyển sang Lounge sẽ được xử lý qua sự kiện OnPlayerJoined trong Spawner
}
private void UpdateStartButton()
private void UpdateRoomList(List<SessionInfo> sessions)
{
if (_startGameBtn != null)
_roomList.Clear();
foreach (var session in sessions)
{
_startGameBtn.SetEnabled(_hostReady.value && _guestReady.value);
var item = _roomItemTemplate.Instantiate();
item.Q<Label>("RoomName").text = session.Name;
item.Q<Label>("PlayerCount").text = $"{session.PlayerCount}/{session.MaxPlayers}";
bool needsPass = session.Properties.ContainsKey("pw"); // Giả sử dùng metadata
item.Q<Label>("LockIcon").style.display = needsPass ? DisplayStyle.Flex : DisplayStyle.None;
var joinBtn = item.Q<Button>("JoinBtn");
joinBtn.clicked += () => OnRoomItemClicked(session);
_roomList.Add(item);
}
}
private void OnRoomItemClicked(SessionInfo session)
{
_selectedSession = session;
// Fusion dùng Password trong StartGameArgs, nên ta hiện popup nhập trước
_passOverlay.style.display = DisplayStyle.Flex;
_joinPassError.style.display = DisplayStyle.None;
_joinPassInput.value = "";
}
private async void OnConfirmPasswordClicked()
{
if (_selectedSession == null) return;
string pass = _joinPassInput.value;
await _spawner.StartClient(_selectedSession.Name, pass);
}
}
}

View File

@@ -40,15 +40,17 @@ namespace Hallucinate.UI
[SerializeField] private Color rippleColor = new Color(1, 1, 1, 0.4f);
[Header("UI Templates")]
[SerializeField] private VisualTreeAsset loginTemplate; // Template mới
[SerializeField] private VisualTreeAsset loginTemplate;
[SerializeField] private VisualTreeAsset mainMenuTemplate;
[SerializeField] private VisualTreeAsset lobbyTemplate;
[SerializeField] private VisualTreeAsset roomItemTemplate; // Template cho dòng phòng
[SerializeField] private VisualTreeAsset profileTemplate;
[SerializeField] private VisualTreeAsset settingsTemplate;
[SerializeField] private VisualTreeAsset hudTemplate;
private LoginController _loginController;
private MainMenuController _mainMenuController;
private LobbyController _lobbyController;
private SettingsController _settingsController;
private List<VisualElement> _trailSegments = new List<VisualElement>();
private List<Vector2> _posHistory = new List<Vector2>();
@@ -95,8 +97,6 @@ namespace Hallucinate.UI
}
#endif
InitializeControllers();
// KIỂM TRA LOGIN
CheckLoginStatus();
}
@@ -116,7 +116,6 @@ namespace Hallucinate.UI
public void OnLoginSuccess()
{
// Sau khi login xong thì hiện MainMenu
_ = Push<MainMenuController>();
}
@@ -286,14 +285,17 @@ namespace Hallucinate.UI
private void InitializeControllers()
{
_loginController = RegisterController<LoginController>(loginTemplate);
_mainMenuController = RegisterController<MainMenuController>(mainMenuTemplate);
if (_mainMenuController != null && gameIcon != null) _mainMenuController.SetGameIcon(gameIcon);
RegisterController<LobbyController>(lobbyTemplate);
_lobbyController = RegisterController<LobbyController>(lobbyTemplate);
if (_lobbyController != null) _lobbyController.SetRoomTemplate(roomItemTemplate);
RegisterController<ProfileController>(profileTemplate);
_settingsController = RegisterController<SettingsController>(settingsTemplate);
RegisterController<HUDController>(hudTemplate);
_loginController = RegisterController<LoginController>(loginTemplate);
}
private T RegisterController<T>(VisualTreeAsset template) where T : BaseUIController, new()

View File

@@ -1,38 +1,86 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False"> <Style src="project:/Assets/UI/Global.uss" />
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
<Style src="project:/Assets/UI/Global.uss" />
<ui:VisualElement name="LobbyRoot" class="screen-root" style="flex-direction: row;">
<!-- Left Pane: Placeholder for 3D Character -->
<ui:VisualElement name="LeftPane" style="width: 60%; height: 100%; background-color: transparent;" />
<!-- Left Pane: 3D Character View -->
<ui:VisualElement name="LeftPane" style="width: 60%; background-color: transparent;" />
<!-- Right Pane: Glass UI -->
<ui:VisualElement name="RightPane" style="width: 40%; height: 100%; background-color: rgba(255, 255, 255, 0.1); border-left-width: 2px; border-left-color: rgba(255, 255, 255, 0.2);">
<ui:VisualElement name="JoinContainer" style="flex-grow: 1; padding: 20px;">
<ui:Label text="JOIN ROOM" style="font-size: 30px; -unity-font-style: bold;" />
<ui:TextField label="Search" name="SearchRoom" />
<ui:ScrollView name="RoomList" style="flex-grow: 1; margin-top: 10px;" />
<ui:Button name="BackToMenuBtn" text="Back" class="button-spring" />
<!-- Right Pane: UI Content -->
<ui:VisualElement name="RightPane" style="width: 40%; background-color: rgba(0, 0, 0, 0.85); padding: 30px; border-left-width: 1px; border-left-color: #333;">
<!-- JOIN VIEW -->
<ui:VisualElement name="JoinContainer" style="flex-grow: 1;">
<ui:Label text="FIND SESSIONS" style="font-size: 28px; -unity-font-style: bold; color: #00ffcc; margin-bottom: 20px;" />
<ui:TextField name="SearchInput" placeholder-text="Search room by name..." />
<ui:ScrollView name="RoomList" style="flex-grow: 1; margin-top: 15px; margin-bottom: 15px;" />
<ui:VisualElement style="flex-direction: row; justify-content: space-between;">
<ui:Button name="BackToMenuBtn" text="BACK" class="button-spring" style="width: 48%;" />
<ui:Button name="GoToCreateBtn" text="CREATE NEW" class="button-spring" style="width: 48%; background-color: #333; color: white;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="CreateContainer" style="flex-grow: 1; padding: 20px; display: none;">
<ui:Label text="CREATE ROOM" style="font-size: 30px; -unity-font-style: bold;" />
<ui:TextField label="Room Name" name="RoomNameInput" />
<ui:Toggle label="Password Protected" name="PasswordToggle" />
<ui:Button name="CreateRoomFinalBtn" text="CREATE" class="button-spring" style="height: 60px; margin-top: auto;" />
<!-- CREATE VIEW -->
<ui:VisualElement name="CreateContainer" style="flex-grow: 1; display: none;">
<ui:Label text="CREATE SESSION" style="font-size: 28px; -unity-font-style: bold; color: #00ffcc; margin-bottom: 20px;" />
<ui:Label text="ROOM ID (Required)" style="font-size: 12px; color: #888; margin-top: 10px;" />
<ui:TextField name="RoomIDInput" placeholder-text="e.g. ROOM_123" />
<ui:Label text="ROOM NAME (Optional)" style="font-size: 12px; color: #888; margin-top: 10px;" />
<ui:TextField name="RoomNameInput" placeholder-text="e.g. Pro Match Only" />
<ui:Toggle name="PassToggle" label="REQUIRE PASSWORD" style="margin-top: 20px;" />
<ui:TextField name="RoomPassInput" password="true" placeholder-text="Password..." style="display: none;" />
<ui:VisualElement style="flex-grow: 1;" />
<ui:VisualElement style="flex-direction: row; justify-content: space-between; margin-top: 20px;">
<ui:Button name="CancelCreateBtn" text="CANCEL" class="button-spring" style="width: 48%;" />
<ui:Button name="ConfirmCreateBtn" text="CREATE" class="button-spring" style="width: 48%; background-color: #00ffcc; color: black; -unity-font-style: bold;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="LoungeContainer" style="flex-grow: 1; padding: 20px; display: none;">
<ui:Label text="LOUNGE" style="font-size: 30px; -unity-font-style: bold;" />
<ui:VisualElement style="flex-direction: row; flex-grow: 1;">
<ui:VisualElement name="HostSlot" style="flex-grow: 1; background-color: rgba(0, 0, 255, 0.2); margin: 5px; padding: 10px;">
<ui:Label text="HOST" />
<ui:Toggle label="Ready" name="HostReady" />
<!-- LOUNGE VIEW -->
<ui:VisualElement name="LoungeContainer" style="flex-grow: 1; display: none;">
<ui:Label name="LoungeTitle" text="SESSION NAME" style="font-size: 24px; -unity-font-style: bold; color: #00ffcc;" />
<ui:Label name="LoungeID" text="ID: 12345" style="font-size: 12px; color: #888; margin-bottom: 30px;" />
<ui:VisualElement style="flex-direction: row; flex-grow: 1;">
<ui:VisualElement name="HostSlot" style="flex-grow: 1; background-color: rgba(255, 255, 255, 0.05); margin: 5px; padding: 15px; border-radius: 10px; align-items: center;">
<ui:VisualElement name="HostAvatar" style="width: 80px; height: 80px; border-radius: 40px; background-color: #333;" />
<ui:Label name="HostName" text="HOST" style="margin-top: 10px; -unity-font-style: bold;" />
<ui:Label name="HostReadyStatus" text="NOT READY" style="color: #ff4444; font-size: 10px;" />
</ui:VisualElement>
<ui:VisualElement name="GuestSlot" style="flex-grow: 1; background-color: rgba(255, 165, 0, 0.2); margin: 5px; padding: 10px;">
<ui:Label text="GUEST" />
<ui:Toggle label="Ready" name="GuestReady" />
<ui:VisualElement name="GuestSlot" style="flex-grow: 1; background-color: rgba(255, 255, 255, 0.05); margin: 5px; padding: 15px; border-radius: 10px; align-items: center;">
<ui:VisualElement name="GuestAvatar" style="width: 80px; height: 80px; border-radius: 40px; background-color: #333;" />
<ui:Label name="GuestName" text="WAITING..." style="margin-top: 10px; color: #555;" />
<ui:Label name="GuestReadyStatus" text="-" style="color: #555; font-size: 10px;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:Button name="StartGameBtn" text="START GAME" class="button-spring" style="height: 60px;" />
</ui:VisualElement>
<ui:Button name="ReadyToggleBtn" text="READY" class="button-spring" style="height: 50px; margin-top: 20px;" />
<ui:Button name="StartGameBtn" text="START GAME" class="button-spring" style="height: 60px; background-color: #00ffcc; color: black; -unity-font-style: bold; margin-top: 10px;" />
</ui:VisualElement>
</ui:VisualElement>
<!-- PASSWORD OVERLAY -->
<ui:VisualElement name="PasswordOverlay" style="position: absolute; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.9); justify-content: center; align-items: center; display: none;">
<ui:VisualElement style="width: 350px; padding: 25px; background-color: #1a1a1a; border-radius: 15px; border-width: 2px; border-color: #333;">
<ui:Label text="PROTECTED SESSION" style="font-size: 20px; -unity-font-style: bold; color: white; align-self: center;" />
<ui:Label text="This room requires a password" style="font-size: 12px; color: #888; align-self: center; margin-bottom: 20px;" />
<ui:TextField name="JoinPassInput" password="true" placeholder-text="Enter password..." />
<ui:Label name="JoinPassError" text="Incorrect password!" style="color: #ff4444; font-size: 10px; margin-bottom: 10px; display: none;" />
<ui:VisualElement style="flex-direction: row; justify-content: space-between; margin-top: 10px;">
<ui:Button name="ClosePassBtn" text="CANCEL" class="button-spring" style="width: 48%;" />
<ui:Button name="ConfirmJoinBtn" text="JOIN" class="button-spring" style="width: 48%; background-color: #00ffcc; color: black;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>

21
Assets/UI/RoomItem.uxml Normal file
View File

@@ -0,0 +1,21 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
<ui:VisualElement name="RoomItemRoot" style="flex-direction: row; height: 80px; background-color: rgba(255, 255, 255, 0.05); margin-bottom: 8px; border-radius: 8px; padding: 10px; align-items: center; border-width: 1px; border-color: rgba(255, 255, 255, 0.1);">
<!-- Avatar chủ phòng -->
<ui:VisualElement name="HostAvatar" style="width: 60px; height: 60px; border-radius: 30px; background-color: #333; margin-right: 15px; border-width: 2px; border-color: #00ffcc;" />
<!-- Thông tin phòng -->
<ui:VisualElement style="flex-grow: 1;">
<ui:Label name="RoomName" text="ROOM NAME" style="font-size: 18px; -unity-font-style: bold; color: white;" />
<ui:VisualElement style="flex-direction: row; align-items: center;">
<ui:Label name="PlayerCount" text="1/2" style="color: #888; font-size: 12px; margin-right: 10px;" />
<ui:Label name="StatusBadge" text="WAITING" style="background-color: #0088ff; color: white; font-size: 10px; padding: 2px 6px; border-radius: 4px;" />
</ui:VisualElement>
</ui:VisualElement>
<!-- Nút Join / Trạng thái -->
<ui:VisualElement name="ActionArea" style="width: 80px; align-items: flex-end;">
<ui:Label name="LockIcon" text="🔒" style="display: none; font-size: 16px; margin-bottom: 5px;" />
<ui:Button name="JoinBtn" text="JOIN" class="button-spring" style="width: 70px; height: 35px; background-color: #00ffcc; color: black; -unity-font-style: bold;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>