diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml
index 01194da0..a7d42365 100644
--- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml
+++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml
@@ -6,8 +6,11 @@
+
-
+
+
+
@@ -147,7 +150,7 @@
-
+
diff --git a/Assets/Prefabs/PlayerDataManager.prefab b/Assets/Prefabs/PlayerDataManager.prefab
new file mode 100644
index 00000000..95bde44f
--- /dev/null
+++ b/Assets/Prefabs/PlayerDataManager.prefab
@@ -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
diff --git a/Assets/Prefabs/PlayerDataManager.prefab.meta b/Assets/Prefabs/PlayerDataManager.prefab.meta
new file mode 100644
index 00000000..038530ab
--- /dev/null
+++ b/Assets/Prefabs/PlayerDataManager.prefab.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 33a4edcf030b02446bd8e4bb9a0fb9f3
+labels:
+- FusionPrefab
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scove/UIScaleTest.unity b/Assets/Scove/UIScaleTest.unity
index cbada702..522b593b 100644
--- a/Assets/Scove/UIScaleTest.unity
+++ b/Assets/Scove/UIScaleTest.unity
@@ -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
diff --git a/Assets/Scripts/Network/BasicSpawner.cs b/Assets/Scripts/Network/BasicSpawner.cs
index fe0c658b..ac2f1d21 100644
--- a/Assets/Scripts/Network/BasicSpawner.cs
+++ b/Assets/Scripts/Network/BasicSpawner.cs
@@ -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 StartHost(string sessionName, string password = null)
+ public async Task 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();
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() ?? gameObject.AddComponent()
});
if (result.Ok)
{
+ if (_runner.IsServer && _playerDataManagerPrefab.IsValid)
+ {
+ if (FindFirstObjectByType() == 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 _spawnedCharacters = new Dictionary();
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
if (player == runner.LocalPlayer)
{
- var pdm = FindFirstObjectByType();
- if (pdm != null)
+ SendLocalMetaData(player);
+ }
+ }
+
+ private async void SendLocalMetaData(PlayerRef player)
+ {
+ PlayerDataManager pdm = null;
+ int retries = 0;
+ while (pdm == null && retries < 20)
+ {
+ pdm = FindFirstObjectByType();
+ 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 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();
}
diff --git a/Assets/Scripts/Network/PlayerDataManager.cs b/Assets/Scripts/Network/PlayerDataManager.cs
index eecf67ca..e010f1e0 100644
--- a/Assets/Scripts/Network/PlayerDataManager.cs
+++ b/Assets/Scripts/Network/PlayerDataManager.cs
@@ -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 Players => default;
+ public event Action 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)
{
diff --git a/Assets/Scripts/UI/LobbyController.cs b/Assets/Scripts/UI/LobbyController.cs
index 666c536a..d16027e0 100644
--- a/Assets/Scripts/UI/LobbyController.cs
+++ b/Assets/Scripts/UI/LobbyController.cs
@@ -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("JoinContainer");
_createContainer = root.Q("CreateContainer");
_loungeContainer = root.Q("LoungeContainer");
_passOverlay = root.Q("PasswordOverlay");
+ // Create Room Fields
_roomIDInput = root.Q("RoomIDInput");
_roomNameInput = root.Q("RoomNameInput");
_roomPassInput = root.Q("RoomPassInput");
_passToggle = root.Q("PassToggle");
+
+ // Join Room Fields
_roomList = root.Q("RoomList");
-
_joinPassInput = root.Q("JoinPassInput");
_joinPassError = root.Q