Elo system update

This commit is contained in:
2026-04-29 13:10:00 +07:00
parent ed86fface3
commit 1222d39fdf
25 changed files with 462 additions and 293 deletions

View File

@@ -1,6 +1,12 @@
using Fusion;
using UnityEngine;
public partial struct _PlayerInputData : INetworkInput
namespace OnlyScove.Scripts
{
public float direction;
}
public struct PlayerInputData : INetworkInput
{
public Vector2 Direction;
public NetworkBool sprint;
public Quaternion rot;
}
}

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Fusion;
using Fusion.Sockets;
using UnityEngine;
using OnlyScove.Scripts;
namespace Hallucinate.UI
{
@@ -42,7 +43,6 @@ namespace Hallucinate.UI
{
OnJoinStartedEvent?.Invoke();
// Sử dụng SessionProperties để lưu mật khẩu
var customProps = new Dictionary<string, SessionProperty>();
if (!string.IsNullOrEmpty(password))
{
@@ -88,13 +88,6 @@ namespace Hallucinate.UI
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
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>();
@@ -104,12 +97,21 @@ namespace Hallucinate.UI
{
Name = LocalPlayerProfile.Name,
Role = LocalPlayerProfile.Role,
IsReady = false
};
pdm.RPC_UpdatePlayerMetaData(player, metaData);
}
}
}
public void StartGame()
{
if (_runner.IsServer)
{
_runner.LoadScene("Main Scene");
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
@@ -132,8 +134,27 @@ namespace Hallucinate.UI
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new _PlayerInputData();
data.direction = Input.GetAxis("Vertical");
var data = new PlayerInputData();
// Try to get input from the local player's InputReader
if (PlayerStateMachine.Local != null && PlayerStateMachine.Local.Input != null)
{
var inputReader = PlayerStateMachine.Local.Input;
data.Direction = inputReader.MoveInput;
data.sprint = inputReader.IsSprintHeld;
if (PlayerStateMachine.Local.Cam != null)
{
data.rot = PlayerStateMachine.Local.Cam.PlanarRotation;
}
}
else
{
// Fallback to basic input if player not spawned or InputReader missing
data.Direction = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
data.sprint = Input.GetKey(KeyCode.LeftShift);
}
input.Set(data);
}
@@ -149,7 +170,31 @@ namespace Hallucinate.UI
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 OnSceneLoadDone(NetworkRunner runner)
{
string currentSceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
if (runner.IsServer && currentSceneName == "Main Scene")
{
foreach (var player in runner.ActivePlayers)
{
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 (currentSceneName == "Main Scene")
{
if (UIManager.Instance != null) UIManager.Instance.OnGameStarted();
}
else if (currentSceneName == "Lobby" || currentSceneName == "Menu")
{
if (UIManager.Instance != null) UIManager.Instance.OnBackToMenu();
}
}
public void OnSceneLoadStart(NetworkRunner runner) { }
}
}

View File

@@ -6,23 +6,29 @@ public struct _PlayerMetaData : INetworkStruct
{
public NetworkString<_16> Name;
public _Role Role;
public NetworkBool IsReady;
}
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);
}
[Rpc(RpcSources.All, RpcTargets.StateAuthority)]
public void RPC_SetReady(PlayerRef playerRef, bool ready)
{
if (Players.TryGet(playerRef, out var data))
{
data.IsReady = ready;
Players.Set(playerRef, data);
}
}
public bool TryGetPlayerMetaData(PlayerRef playerRef, out _PlayerMetaData metaData)
{

View File

@@ -1,187 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Fusion;
using Fusion.Sockets;
using UnityEngine;
using UnityEngine.SceneManagement;
using Random = UnityEngine.Random;
// Struct input đồng bộ giữa Spawner, Movement và StateMachine
public struct PlayerInputData : INetworkInput
{
public Vector2 Direction;
public Quaternion rot;
public bool sprint;
}
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
{
private NetworkRunner _runner;
private string _roomName = "Room1";
public LobbyManager LobbyManager;
[SerializeField] private NetworkPrefabRef _playerPrefab;
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
// Thông tin profile local
public PlayerProfile LocalPlayerProfile { get; private set; }
public void SetLocalPlayerProfile(PlayerProfile profile)
{
LocalPlayerProfile = profile;
}
private void Awake()
{
if (_runner == null) _runner = gameObject.AddComponent<NetworkRunner>();
DontDestroyOnLoad(gameObject);
}
// Khởi tạo Game (Host/Client)
public async Task StartGame(GameMode mode, string sessionName = "TestRoom")
{
Debug.Log($"<color=yellow>Fusion:</color> Đang khởi tạo kết nối với Mode: {mode} | Phòng: {sessionName}");
if (_runner == null) _runner = gameObject.AddComponent<NetworkRunner>();
_runner.ProvideInput = true;
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = mode,
SessionName = sessionName,
Scene = SceneRef.FromIndex(1),
SceneManager = sceneManager
});
if (result.Ok)
{
Debug.Log($"<color=green>Fusion thành công:</color> Đã vào phòng {sessionName}");
}
else
{
Debug.LogError($"<color=red>Fusion thất bại:</color> Lý do: {result.ShutdownReason}");
if (_runner != null)
{
Destroy(_runner);
_runner = null;
}
}
}
private void OnGUI()
{
if (_runner == null || !_runner.IsRunning)
{
float width = 400;
float height = 300;
float x = (Screen.width - width) / 2f;
float y = (Screen.height - height) / 2f;
GUI.Box(new Rect(x, y, width, height), "FUSION MULTIPLAYER");
float innerX = x + 20;
float innerY = y + 40;
float contentWidth = width - 40;
GUI.Label(new Rect(innerX, innerY, 100, 30), "Tên phòng:");
_roomName = GUI.TextField(new Rect(innerX + 100, innerY, contentWidth - 100, 30), _roomName);
if (GUI.Button(new Rect(innerX, innerY + 50, contentWidth, 60), "VÀO PHÒNG\n(Tự động Host/Client)"))
{
_ = StartGame(GameMode.AutoHostOrClient, _roomName);
}
if (GUI.Button(new Rect(innerX, innerY + 120, (contentWidth / 2) - 5, 50), "Tạo phòng\n(Host)"))
{
_ = StartGame(GameMode.Host, _roomName);
}
if (GUI.Button(new Rect(innerX + (contentWidth / 2) + 5, innerY + 120, (contentWidth / 2) - 5, 50), "Tham gia\n(Client)"))
{
_ = StartGame(GameMode.Client, _roomName);
}
GUI.Label(new Rect(innerX, innerY + 180, contentWidth, 50), "Gợi ý: Nhập cùng tên phòng để chơi chung.\nNếu phòng chưa có, máy sẽ tự tạo mới.");
}
else
{
string region = (_runner.SessionInfo != null && _runner.SessionInfo.IsValid) ? _runner.SessionInfo.Region : "Connecting...";
int playerCount = 0;
foreach (var p in _runner.ActivePlayers) playerCount++;
string info = $"Mode: {_runner.GameMode} | Region: {region} | Players: {playerCount}";
GUI.Box(new Rect(10, 10, 400, 30), info);
if (GUI.Button(new Rect(10, 50, 100, 30), "Thoát"))
{
_runner.Shutdown();
}
}
}
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (runner.IsServer)
{
Vector3 spawnPosition = new Vector3(Random.Range(-10f, 10f), 2f, Random.Range(-10f, 10f));
spawnPosition += new Vector3(player.RawEncoded % 3, 0, player.RawEncoded % 3);
var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
runner.SetPlayerObject(player, networkPlayerObject);
_spawnedCharacters.Add(player, networkPlayerObject);
}
}
public void OnPlayerLeft(NetworkRunner runner, PlayerRef player)
{
if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject))
{
if (networkObject != null) runner.Despawn(networkObject);
_spawnedCharacters.Remove(player);
}
}
public void OnInput(NetworkRunner runner, NetworkInput input)
{
var data = new PlayerInputData();
// ĐỌC TRỰC TIẾP: Không dùng Buffer để tránh bị trôi phím
data.Direction = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
data.sprint = Input.GetKey(KeyCode.LeftShift);
if (OnlyScove.Scripts.PlayerStateMachine.Local != null)
{
var sm = OnlyScove.Scripts.PlayerStateMachine.Local;
if (sm.Cam != null) data.rot = sm.Cam.PlanarRotation;
else data.rot = sm.NetworkedCameraRotation;
}
input.Set(data);
}
public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
{
if (LobbyManager != null) LobbyManager.DisplayRoomList(sessionList);
// UI.UIEventBus.TriggerRoomListUpdate();
}
public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { }
public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { }
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 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 OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { }
public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment<byte> data) { }
public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { }
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 44bfaa339c82069418e72a14479a0212

View File

@@ -1 +0,0 @@
// File này hiện tại không còn nội dung, có thể xóa đi hoặc để trống.

View File

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

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
using Fusion;
using UnityEngine;
public class LobbyManager : MonoBehaviour
{
[Header("UI References")]
public Transform roomListContent; // Ô chứa danh sách phòng (nếu có)
public void DisplayRoomList(List<SessionInfo> sessionList)
{
Debug.Log($"<color=green>Lobby Update:</color> Đang tìm thấy {sessionList.Count} phòng.");
// Xóa danh sách cũ (nếu bạn làm UI)
/*
foreach (Transform child in roomListContent) {
Destroy(child.gameObject);
}
*/
// Hiển thị danh sách mới
foreach (var session in sessionList)
{
Debug.Log($"- Phòng: {session.Name} | Người chơi: {session.PlayerCount}/{session.MaxPlayers}");
// Ở đây bạn sẽ Instantiate các Button đại diện cho mỗi phòng
}
}
}

View File

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

View File

@@ -1,17 +0,0 @@
using UnityEngine;
// Enum các loại nhân vật
public enum CharacterClass
{
Warrior,
Mage,
Archer
}
// Lớp quản lý thông tin nhân vật local
[System.Serializable]
public class PlayerProfile
{
public string Name = "Player";
public CharacterClass Class = CharacterClass.Warrior;
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 8a1452ae101af8e43b94c2c778a70fe0

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6aa851b2c7ee553439ee0065f77665cb
guid: 1a38893b1d8574b45bce269c39824bd6
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,64 @@
using UnityEngine;
using Fusion;
namespace Hallucinate.Game
{
public static class EloSystem
{
public static EloResult Calculate(
int ratingA, int ratingB,
int gamesPlayedA, int gamesPlayedB,
float resultA) // 1=win, 0=lose, 0.5=draw
{
float eA = 1f / (1f + Mathf.Pow(10f, (ratingB - ratingA) / 400f));
int kA = GetK(ratingA, gamesPlayedA);
int kB = GetK(ratingB, gamesPlayedB);
int nA = Mathf.Max(100, Mathf.RoundToInt(ratingA + kA * (resultA - eA)));
int nB = Mathf.Max(100, Mathf.RoundToInt(ratingB + kB * ((1 - resultA) - (1 - eA))));
return new EloResult(nA, nB, nA - ratingA, nB - ratingB);
}
private static int GetK(int r, int g) =>
g < 30 ? 40 : r < 1200 ? 32 : r < 2000 ? 24 : 16;
public static string GetRank(int rating)
{
if (rating < 800) return "Iron";
if (rating < 1000) return "Bronze";
if (rating < 1200) return "Silver";
if (rating < 1500) return "Gold";
if (rating < 1800) return "Platinum";
if (rating < 2100) return "Diamond";
return "Master";
}
public static string GetRankColor(int rating)
{
if (rating < 800) return "#8A8A8A";
if (rating < 1000) return "#CD7F32";
if (rating < 1200) return "#C0C0C0";
if (rating < 1500) return "#FFD700";
if (rating < 1800) return "#4DC8A0";
if (rating < 2100) return "#7B6EE8";
return "#E84D8A";
}
}
public struct EloResult : INetworkStruct
{
public int NewRatingA;
public int NewRatingB;
public int DeltaA;
public int DeltaB;
public EloResult(int nA, int nB, int dA, int dB)
{
NewRatingA = nA;
NewRatingB = nB;
DeltaA = dA;
DeltaB = dB;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 366843378ad652a41aeb5972186ebe18

View File

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

View File

@@ -0,0 +1,77 @@
using Fusion;
using UnityEngine;
using Hallucinate.Game;
using Hallucinate.UI;
using System.Linq;
namespace Hallucinate.Network
{
public class MatchResultManager : NetworkBehaviour
{
public static MatchResultManager Instance { get; private set; }
public override void Spawned()
{
if (Object.HasStateAuthority) Instance = this;
}
[Rpc(RpcSources.StateAuthority, RpcTargets.All)]
public void RPC_BroadcastResult(PlayerRef winner, EloResult eloResult)
{
Debug.Log($"Game Over! Winner: {winner}. Elo updated.");
// Update local Elo display and show Result UI
if (Runner.LocalPlayer == winner)
{
ShowResultUI(true, eloResult.DeltaA, eloResult.NewRatingA);
}
else
{
ShowResultUI(false, eloResult.DeltaB, eloResult.NewRatingB);
}
}
private void ShowResultUI(bool isWin, int delta, int newRating)
{
var hud = FindFirstObjectByType<HUDController>();
if (hud != null)
{
// In a real scenario, we might push a new Result screen
// For now, let's assume HUD has a result panel
Debug.Log($"RESULT: {(isWin ? "WIN" : "LOSS")} | Delta: {delta} | New Rating: {newRating}");
// Save to PlayerPrefs as a dummy "Server" persistence
PlayerPrefs.SetInt("EloRating", newRating);
int gamesPlayed = PlayerPrefs.GetInt("GamesPlayed", 0);
PlayerPrefs.SetInt("GamesPlayed", gamesPlayed + 1);
PlayerPrefs.Save();
}
}
public void ProcessMatchEnd(PlayerRef winner)
{
if (!Object.HasStateAuthority) return;
// Get ratings for both players
// In a real game, these would come from the server/metadata
int ratingA = PlayerPrefs.GetInt("EloRating", 1000);
int ratingB = 1000; // Placeholder for opponent
int gamesA = PlayerPrefs.GetInt("GamesPlayed", 0);
int gamesB = 0; // Placeholder
float resultA = (Runner.LocalPlayer == winner) ? 1.0f : 0.0f;
EloResult elo = EloSystem.Calculate(ratingA, ratingB, gamesA, gamesB, resultA);
RPC_BroadcastResult(winner, elo);
// Shut down runner after some delay
Invoke(nameof(ShutdownRunner), 5.0f);
}
private void ShutdownRunner()
{
Runner.Shutdown();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9eac0255d30a2bb40a43ff12cdcdf960

View File

@@ -146,14 +146,6 @@ namespace OnlyScove.Scripts
bool isRunning = Runner != null && Runner.IsRunning;
if (Object == null && isRunning) return;
// Proxy Sync
if (isRunning && Movement.NetworkedPosition != Vector3.zero && !Object.HasInputAuthority)
{
Controller.enabled = false;
transform.position = Movement.NetworkedPosition;
Controller.enabled = true;
}
if (GetInput(out PlayerInputData data))
{
MoveInput = data.Direction;
@@ -175,10 +167,24 @@ namespace OnlyScove.Scripts
currentState?.Tick(isRunning ? Runner.DeltaTime : Time.fixedDeltaTime);
}
}
else
}
public override void Render()
{
bool isRunning = Runner != null && Runner.IsRunning;
if (isRunning && !Object.HasInputAuthority)
{
// Smooth interpolation for proxies
if (Movement.NetworkedPosition != Vector3.zero)
{
transform.position = Vector3.Lerp(transform.position, Movement.NetworkedPosition, Runner.DeltaTime * 15f);
}
UpdateAnimator(Runner.DeltaTime);
}
else if (!isRunning)
{
UpdateAnimator(Time.deltaTime);
}
}
private void Update()

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using Fusion;
using System;
using Hallucinate.Network;
namespace OnlyScove.Scripts
{
@@ -24,10 +25,23 @@ namespace OnlyScove.Scripts
void OnHealthChangedRender()
{
OnHealthChanged?.Invoke(Health);
if (Health <= 0 && Object.HasStateAuthority)
{
// Find the other player as winner
foreach (var player in Runner.ActivePlayers)
{
if (player != Object.InputAuthority)
{
MatchResultManager.Instance?.ProcessMatchEnd(player);
break;
}
}
}
if (Object.HasInputAuthority)
{
// UI Placeholder: Trigger Health UI Change
// Example: UI.UIEventBus.TriggerHealthChange(Health / 100f);
}
}
@@ -37,7 +51,6 @@ namespace OnlyScove.Scripts
if (Object.HasInputAuthority)
{
// UI Placeholder: Trigger Stamina UI Change
// Example: UI.UIEventBus.TriggerStaminaChange(Stamina / 100f);
}
}

View File

@@ -45,6 +45,8 @@ namespace Hallucinate.UI
return key;
}
public virtual void Update() { }
public virtual async Task PlayTransitionIn()
{
if (root == null) return;

View File

@@ -53,7 +53,7 @@ namespace Hallucinate.UI
}
}
public void Update()
public override void Update()
{
if (!_isFaded && Time.time - _lastActionTime > FADE_TIMEOUT)
{

View File

@@ -3,6 +3,7 @@ using UnityEngine.UIElements;
using System.Collections.Generic;
using System.Threading.Tasks;
using Fusion;
using System.Linq;
namespace Hallucinate.UI
{
@@ -10,6 +11,7 @@ namespace Hallucinate.UI
{
private VisualTreeAsset _roomItemTemplate;
private _BasicSpawner _spawner;
private _PlayerDataManager _playerDataManager;
// Containers
private VisualElement _joinContainer, _createContainer, _loungeContainer, _passOverlay;
@@ -24,6 +26,11 @@ namespace Hallucinate.UI
private Label _joinPassError;
private SessionInfo _selectedSession;
// Lounge Elements
private VisualElement _playerListContainer;
private Button _readyBtn, _startBtn;
private Label _loungeRoomName;
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
{
base.Initialize(uxmlRoot, manager);
@@ -44,27 +51,63 @@ namespace Hallucinate.UI
_joinPassInput = root.Q<TextField>("JoinPassInput");
_joinPassError = root.Q<Label>("JoinPassError");
// Event Bindings
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 += () => _passOverlay.style.display = DisplayStyle.None;
// Lounge Elements
_playerListContainer = root.Q<VisualElement>("PlayerList");
_readyBtn = root.Q<Button>("ReadyBtn");
_startBtn = root.Q<Button>("StartBtn");
_loungeRoomName = root.Q<Label>("LoungeRoomName");
_passToggle.RegisterValueChangedCallback(evt =>
_roomPassInput.style.display = evt.newValue ? DisplayStyle.Flex : DisplayStyle.None);
// 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;
if (_readyBtn != null) _readyBtn.clicked += OnReadyClicked;
if (_startBtn != null) _startBtn.clicked += OnStartClicked;
if (_passToggle != null)
{
_passToggle.RegisterValueChangedCallback(evt =>
{
if (_roomPassInput != null)
_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;
_spawner.OnJoinStartedEvent += () => { /* Show loading if needed */ };
}
}
public void SetRoomTemplate(VisualTreeAsset template) => _roomItemTemplate = template;
public override async Task PlayTransitionIn()
{
await base.PlayTransitionIn();
ShowJoin();
}
public void ShowJoin()
{
_joinContainer.style.display = DisplayStyle.Flex;
@@ -79,6 +122,16 @@ namespace Hallucinate.UI
_createContainer.style.display = DisplayStyle.Flex;
}
private void ShowLounge(string roomName)
{
_joinContainer.style.display = DisplayStyle.None;
_createContainer.style.display = DisplayStyle.None;
_loungeContainer.style.display = DisplayStyle.Flex;
_loungeRoomName.text = $"Room: {roomName}";
_playerDataManager = Object.FindFirstObjectByType<_PlayerDataManager>();
}
private async void OnCreateRoomClicked()
{
string id = _roomIDInput.value.Trim();
@@ -88,10 +141,12 @@ namespace Hallucinate.UI
if (string.IsNullOrEmpty(id)) return;
await _spawner.StartHost(id, pass);
ShowLounge(name);
}
private void UpdateRoomList(List<SessionInfo> sessions)
{
if (_roomList == null) return;
_roomList.Clear();
foreach (var session in sessions)
{
@@ -111,17 +166,111 @@ namespace Hallucinate.UI
private void OnRoomItemClicked(SessionInfo session)
{
_selectedSession = session;
_passOverlay.style.display = DisplayStyle.Flex;
_joinPassError.style.display = DisplayStyle.None;
_joinPassInput.value = "";
bool needsPass = session.Properties.ContainsKey("pw");
if (needsPass)
{
_selectedSession = session;
_passOverlay.style.display = DisplayStyle.Flex;
_joinPassError.style.display = DisplayStyle.None;
_joinPassInput.value = "";
}
else
{
JoinRoom(session.Name, null);
}
}
private async void OnConfirmPasswordClicked()
{
if (_selectedSession == null) return;
string pass = _joinPassInput.value;
await _spawner.StartClient(_selectedSession.Name, pass);
_passOverlay.style.display = DisplayStyle.None;
await JoinRoom(_selectedSession.Name, pass);
}
private async Task JoinRoom(string sessionName, string password)
{
await _spawner.StartClient(sessionName, password);
ShowLounge(sessionName);
}
private void OnReadyClicked()
{
if (_playerDataManager != null)
{
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner != null)
{
_playerDataManager.TryGetPlayerMetaData(runner.LocalPlayer, out var myData);
_playerDataManager.RPC_SetReady(runner.LocalPlayer, !myData.IsReady);
}
}
}
private void OnStartClicked()
{
_spawner.StartGame();
}
private void OnLeaveLoungeClicked()
{
var runner = Object.FindFirstObjectByType<NetworkRunner>();
runner?.Shutdown();
ShowJoin();
}
public override void Update()
{
if (_loungeContainer.style.display == DisplayStyle.Flex)
{
UpdateLoungeUI();
}
}
private void UpdateLoungeUI()
{
if (_playerDataManager == null)
{
_playerDataManager = Object.FindFirstObjectByType<_PlayerDataManager>();
return;
}
var runner = Object.FindFirstObjectByType<NetworkRunner>();
if (runner == null) return;
// Update Player List
_playerListContainer.Clear();
bool allReady = true;
int playerCount = 0;
foreach (var kvp in _playerDataManager.Players)
{
playerCount++;
var playerRef = kvp.Key;
var data = kvp.Value;
var playerItem = new VisualElement();
playerItem.style.flexDirection = FlexDirection.Row;
playerItem.style.justifyContent = Justify.SpaceBetween;
playerItem.style.paddingBottom = 5;
var nameLabel = new Label(data.Name.ToString());
var readyLabel = new Label(data.IsReady ? "READY" : "WAITING...");
readyLabel.style.color = data.IsReady ? Color.green : Color.yellow;
playerItem.Add(nameLabel);
playerItem.Add(readyLabel);
_playerListContainer.Add(playerItem);
if (!data.IsReady) allReady = false;
}
// Update Buttons
_startBtn.style.display = (runner.IsServer && allReady && playerCount >= 2) ? DisplayStyle.Flex : DisplayStyle.None;
_playerDataManager.TryGetPlayerMetaData(runner.LocalPlayer, out var myData);
_readyBtn.text = myData.IsReady ? "UNREADY" : "READY UP";
}
}
}

View File

@@ -230,7 +230,7 @@ namespace Hallucinate.UI
.OnComplete(() => _ribbon.style.display = DisplayStyle.None);
}
public void Update()
public override void Update()
{
if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0 || Input.anyKey)
{

View File

@@ -65,13 +65,30 @@ namespace Hallucinate.UI
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
_uiDocument = GetComponent<UIDocument>();
UnityEngine.Cursor.visible = false;
ApplySavedUIScale();
}
public void OnGameStarted()
{
_ = Push<HUDController>();
}
public void OnBackToMenu()
{
_ = Push<MainMenuController>();
}
public void SetUIScale(float scale)
{
if (_uiDocument == null || _uiDocument.panelSettings == null) return;
@@ -187,7 +204,7 @@ namespace Hallucinate.UI
private void Update()
{
if (_mainMenuController != null) _mainMenuController.Update();
if (_history.Count > 0) _history.Peek().Update();
UpdateCursorAndTrail();
}