Compare commits
3 Commits
2c0ec5ab1c
...
4709fa0c55
| Author | SHA1 | Date | |
|---|---|---|---|
| 4709fa0c55 | |||
| 0542f5656f | |||
| c2b0e96570 |
1
.gemini-workspace-history/active-context.md
Normal file
1
.gemini-workspace-history/active-context.md
Normal file
@@ -0,0 +1 @@
|
||||
No previous session history found for this workspace.
|
||||
21
.gemini-workspace-history/summary-2026-04-30.md
Normal file
21
.gemini-workspace-history/summary-2026-04-30.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Technical Summary - HALLUCINATE Project
|
||||
|
||||
## Overview
|
||||
Project HALLUCINATE is a Unity-based multiplayer game utilizing **Photon Fusion** for networking and **UI Toolkit** for front-end management. Recent development has concentrated on the core networking infrastructure and the lobby system.
|
||||
|
||||
## Modified Files
|
||||
- `Assets/Scripts/Network/BasicSpawner.cs`: Centralized `NetworkRunner` management, handling session creation, joining, and player profile initialization.
|
||||
- `Assets/Scripts/UI/LobbyController.cs`: UI logic for room management, password protection, and lounge interactions.
|
||||
- `Assets/Scripts/Player Controller/PlayerStateMachine.cs`: Core player logic (referenced in memory).
|
||||
|
||||
## Key Logic & Decisions
|
||||
- **Network Architecture**: Singleton `BasicSpawner` manages the Fusion runner lifecycle. Session joining uses `SessionLobby.ClientServer`.
|
||||
- **UI Architecture**: `LobbyController` inherits from a base UI class and uses direct `VisualElement` queries for dynamic UI updates (UXML/USS).
|
||||
- **Security**: Implementation of session passwords via custom properties in Photon sessions.
|
||||
- **State Sync**: Player status (Ready/Start) and lounge info are synchronized between clients, likely via `PlayerDataManager`.
|
||||
|
||||
## Next Steps
|
||||
- [ ] Verify RPC/SyncVar logic in `LobbyController.cs`.
|
||||
- [ ] Connect `PlayerStateMachine` to the spawning flow in `BasicSpawner`.
|
||||
- [ ] Implement/Finalize the chat system within the lobby lounge.
|
||||
- [ ] Ensure all scene transitions (Main Scene) are robust.
|
||||
7
.idea/.idea.HALLUCINATE/.idea/workspace.xml
generated
7
.idea/.idea.HALLUCINATE/.idea/workspace.xml
generated
@@ -6,8 +6,9 @@
|
||||
<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/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/Player Controller/PlayerStateMachine.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Player Controller/PlayerStateMachine.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" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -147,7 +148,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="21208000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
||||
68
Assets/Prefabs/PlayerDataManager.prefab
Normal file
68
Assets/Prefabs/PlayerDataManager.prefab
Normal 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
|
||||
9
Assets/Prefabs/PlayerDataManager.prefab.meta
Normal file
9
Assets/Prefabs/PlayerDataManager.prefab.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33a4edcf030b02446bd8e4bb9a0fb9f3
|
||||
labels:
|
||||
- FusionPrefab
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
@@ -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,18 +66,19 @@ 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++)
|
||||
{
|
||||
@@ -90,7 +95,6 @@ namespace Hallucinate.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Khởi tạo Runner mới sạch sẽ
|
||||
await EnsureRunnerExists();
|
||||
|
||||
var customProps = new Dictionary<string, SessionProperty>();
|
||||
@@ -98,6 +102,7 @@ namespace Hallucinate.UI
|
||||
{
|
||||
customProps.Add("pw", password);
|
||||
}
|
||||
customProps.Add("rn", displayName);
|
||||
|
||||
var result = await _runner.StartGame(new StartGameArgs()
|
||||
{
|
||||
@@ -105,13 +110,18 @@ 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
|
||||
@@ -146,33 +156,48 @@ namespace Hallucinate.UI
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +215,11 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +227,11 @@ namespace Hallucinate.UI
|
||||
{
|
||||
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)
|
||||
@@ -247,12 +277,20 @@ namespace Hallucinate.UI
|
||||
_spawnedCharacters.Add(player, networkPlayerObject);
|
||||
}
|
||||
}
|
||||
// Removed incorrect UI transition for Lobby/Menu scenes to allow LobbyController to manage its state.
|
||||
// The original logic incorrectly called UIManager.OnBackToMenu() when entering the Lobby scene,
|
||||
// causing the redirect to the Main Menu after creating a room.
|
||||
// This block ensures that only the Main Scene triggers a specific UI transition (OnGameStarted).
|
||||
// If other scenes like "Lobby" or "Menu" are loaded, no automatic transition is forced from here,
|
||||
// letting scene-specific controllers (like LobbyController) manage their UI.
|
||||
if (currentSceneName == "Main Scene")
|
||||
{
|
||||
UIManager.Instance?.OnGameStarted();
|
||||
else if (currentSceneName == "Lobby" || currentSceneName == "Menu")
|
||||
UIManager.Instance?.OnBackToMenu();
|
||||
}
|
||||
// Removed the problematic else-if block that would incorrectly call OnBackToMenu for "Lobby" or "Menu" scenes.
|
||||
}
|
||||
|
||||
|
||||
public void OnSceneLoadStart(NetworkRunner runner) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -29,9 +29,15 @@ namespace OnlyScove.Scripts
|
||||
// Pass-through properties for State Compatibility
|
||||
public Vector2 MoveInput { get; private set; }
|
||||
public bool IsSprintHeld { get; private set; }
|
||||
public float VelocityY { get => Movement.VelocityY; set => Movement.VelocityY = value; }
|
||||
public bool IsGrounded => Movement.IsGrounded;
|
||||
public bool WasGrounded => Movement.WasGrounded;
|
||||
|
||||
public float VelocityY
|
||||
{
|
||||
get => (Object != null && Object.IsValid && Movement != null) ? Movement.VelocityY : 0f;
|
||||
set { if (Object != null && Object.IsValid && Movement != null) Movement.VelocityY = value; }
|
||||
}
|
||||
|
||||
public bool IsGrounded => (Object != null && Object.IsValid && Movement != null) ? Movement.IsGrounded : true;
|
||||
public bool WasGrounded => (Object != null && Object.IsValid && Movement != null) ? Movement.WasGrounded : true;
|
||||
|
||||
public float WalkSpeed => Movement.WalkSpeed;
|
||||
public float RunSpeed => Movement.RunSpeed;
|
||||
@@ -48,11 +54,11 @@ namespace OnlyScove.Scripts
|
||||
public static PlayerStateMachine Local { get; private set; }
|
||||
public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None";
|
||||
|
||||
public Quaternion CameraRotation
|
||||
public Quaternion CameraRotation
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (Runner != null && Runner.IsRunning && Object != null) return NetworkedCameraRotation;
|
||||
if (Runner != null && Runner.IsRunning && Object != null && Object.IsValid) return NetworkedCameraRotation;
|
||||
return Cam != null ? Cam.PlanarRotation : transform.rotation;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +73,7 @@ namespace OnlyScove.Scripts
|
||||
Input = GetComponent<InputReader>();
|
||||
Anim = GetComponentInChildren<Animator>();
|
||||
Scanner = GetComponent<EnvironmentScanner>();
|
||||
|
||||
|
||||
Stats = GetComponent<PlayerStats>();
|
||||
Interaction = GetComponent<PlayerInteraction>();
|
||||
Movement = GetComponent<PlayerMovement>();
|
||||
@@ -107,12 +113,28 @@ namespace OnlyScove.Scripts
|
||||
Cam.followTarget = transform;
|
||||
Cam.inputReader = Input;
|
||||
}
|
||||
Input.OnNextInteractEvent += Interaction.NextInteract;
|
||||
Input.OnPreviousInteractEvent += Interaction.PreviousInteract;
|
||||
|
||||
if (Input != null)
|
||||
{
|
||||
Input.OnNextInteractEvent -= Interaction.NextInteract;
|
||||
Input.OnNextInteractEvent += Interaction.NextInteract;
|
||||
Input.OnPreviousInteractEvent -= Interaction.PreviousInteract;
|
||||
Input.OnPreviousInteractEvent += Interaction.PreviousInteract;
|
||||
}
|
||||
|
||||
if (Controller != null) Controller.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Input != null && Interaction != null)
|
||||
{
|
||||
Input.OnNextInteractEvent -= Interaction.NextInteract;
|
||||
Input.OnPreviousInteractEvent -= Interaction.PreviousInteract;
|
||||
}
|
||||
}
|
||||
|
||||
public void Rotate(Vector3 moveDirection, float deltaTime)
|
||||
{
|
||||
Movement.Rotate(transform, moveDirection, deltaTime);
|
||||
@@ -120,13 +142,13 @@ namespace OnlyScove.Scripts
|
||||
|
||||
public void Move(Vector3 velocity, float animatorSpeed, float deltaTime)
|
||||
{
|
||||
bool canMove = (Runner == null || !Runner.IsRunning) || Object.HasInputAuthority || Runner.IsServer;
|
||||
bool canMove = (Runner == null || !Runner.IsRunning) || (Object != null && Object.IsValid && (Object.HasInputAuthority || Runner.IsServer));
|
||||
if (!canMove) return;
|
||||
|
||||
Movement.Move(Controller, velocity, deltaTime);
|
||||
|
||||
|
||||
localAnimatorSpeed = animatorSpeed;
|
||||
if (Object != null && Object.HasStateAuthority)
|
||||
if (Object != null && Object.IsValid && Object.HasStateAuthority)
|
||||
{
|
||||
NetworkedSpeed = animatorSpeed;
|
||||
NetworkedMoveInput = MoveInput;
|
||||
@@ -136,20 +158,21 @@ namespace OnlyScove.Scripts
|
||||
|
||||
private void UpdateAnimator(float deltaTime)
|
||||
{
|
||||
float speedValue = (Runner == null || !Runner.IsRunning || Object.HasInputAuthority) ? localAnimatorSpeed : NetworkedSpeed;
|
||||
Vector2 inputVector = (Runner == null || !Runner.IsRunning || Object.HasInputAuthority) ? MoveInput : NetworkedMoveInput;
|
||||
bool isNetworked = Runner != null && Runner.IsRunning && Object != null && Object.IsValid;
|
||||
float speedValue = (!isNetworked || Object.HasInputAuthority) ? localAnimatorSpeed : NetworkedSpeed;
|
||||
Vector2 inputVector = (!isNetworked || Object.HasInputAuthority) ? MoveInput : NetworkedMoveInput;
|
||||
AnimationHandler.UpdateAnimator(speedValue, inputVector, deltaTime);
|
||||
}
|
||||
|
||||
public override void FixedUpdateNetwork()
|
||||
{
|
||||
bool isRunning = Runner != null && Runner.IsRunning;
|
||||
if (Object == null && isRunning) return;
|
||||
if (isRunning && (Object == null || !Object.IsValid)) return;
|
||||
|
||||
if (GetInput(out PlayerInputData data))
|
||||
{
|
||||
MoveInput = data.Direction;
|
||||
IsSprintHeld = data.sprint;
|
||||
IsSprintHeld = (bool)data.sprint;
|
||||
if (isRunning) NetworkedCameraRotation = data.rot;
|
||||
}
|
||||
else if (!isRunning)
|
||||
@@ -158,7 +181,7 @@ namespace OnlyScove.Scripts
|
||||
IsSprintHeld = UnityEngine.Input.GetKey(KeyCode.LeftShift);
|
||||
}
|
||||
|
||||
if (!isRunning || Object.HasInputAuthority || Runner.IsServer)
|
||||
if (!isRunning || (Object != null && Object.IsValid && (Object.HasInputAuthority || Runner.IsServer)))
|
||||
{
|
||||
if (hasControl)
|
||||
{
|
||||
@@ -172,7 +195,7 @@ namespace OnlyScove.Scripts
|
||||
public override void Render()
|
||||
{
|
||||
bool isRunning = Runner != null && Runner.IsRunning;
|
||||
if (isRunning && !Object.HasInputAuthority)
|
||||
if (isRunning && Object != null && Object.IsValid && !Object.HasInputAuthority)
|
||||
{
|
||||
// Smooth interpolation for proxies
|
||||
if (Movement.NetworkedPosition != Vector3.zero)
|
||||
|
||||
@@ -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,8 +235,14 @@ namespace Hallucinate.UI
|
||||
? _roomPassInput.value
|
||||
: null;
|
||||
|
||||
bool success = await spawner.StartHost(id, pass);
|
||||
if (success) ShowLounge(name);
|
||||
bool success = await spawner.StartHost(id, name, pass);
|
||||
if (success)
|
||||
{
|
||||
ShowLounge(name);
|
||||
// Explicitly push the LobbyController to ensure it's the active UI screen.
|
||||
// This helps prevent unintended navigation away from the lounge.
|
||||
await uiManager.Push<LobbyController>();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRoomList(List<SessionInfo> sessions)
|
||||
@@ -173,7 +253,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 +310,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);
|
||||
}
|
||||
}
|
||||
@@ -238,10 +325,17 @@ namespace Hallucinate.UI
|
||||
BasicSpawner.Instance?.StartGame();
|
||||
}
|
||||
|
||||
private void OnLeaveLoungeClicked()
|
||||
private async void OnLeaveLoungeClicked()
|
||||
{
|
||||
var runner = Object.FindFirstObjectByType<NetworkRunner>();
|
||||
runner?.Shutdown();
|
||||
if (runner != null)
|
||||
{
|
||||
await runner.Shutdown();
|
||||
}
|
||||
if (_playerDataManager != null)
|
||||
{
|
||||
_playerDataManager.OnChatMessageReceived -= OnChatMessageReceived;
|
||||
}
|
||||
ShowJoin();
|
||||
}
|
||||
|
||||
@@ -255,49 +349,107 @@ 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 == 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);
|
||||
// Trong Host Mode, chủ phòng luôn là người có PlayerId = 1
|
||||
var sortedPlayers = runner.ActivePlayers.OrderBy(p => p.PlayerId).ToList();
|
||||
if (sortedPlayers.Count > 0) hostRef = sortedPlayers[0];
|
||||
if (sortedPlayers.Count > 1) guestRef = sortedPlayers[1];
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ namespace Hallucinate.UI
|
||||
|
||||
private const string UI_SCALE_KEY = "UIScale";
|
||||
|
||||
[Header("Development Settings")]
|
||||
[SerializeField] private bool allowMultipleInstances = true;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
@@ -73,6 +76,19 @@ namespace Hallucinate.UI
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
// Single instance guard
|
||||
if (!Application.isEditor && !allowMultipleInstances)
|
||||
{
|
||||
var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
|
||||
var processes = System.Diagnostics.Process.GetProcessesByName(currentProcess.ProcessName);
|
||||
if (processes.Length > 1)
|
||||
{
|
||||
Debug.LogError("[UIManager] Another instance is already running. Quitting to prevent save conflict.");
|
||||
Application.Quit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_uiDocument = GetComponent<UIDocument>();
|
||||
UnityEngine.Cursor.visible = false;
|
||||
|
||||
@@ -92,10 +108,10 @@ namespace Hallucinate.UI
|
||||
public void SetUIScale(float scale)
|
||||
{
|
||||
if (_uiDocument == null || _uiDocument.panelSettings == null) return;
|
||||
|
||||
|
||||
// Unity UI Toolkit dùng panelSettings để điều khiển tỉ lệ
|
||||
// Chúng ta thay đổi scale multiplier
|
||||
_uiDocument.panelSettings.scale = scale;
|
||||
// Chúng ta thay đổi scale multiplier. Mặc định 1.0 sẽ tương ứng với visual scale là 1.3
|
||||
_uiDocument.panelSettings.scale = scale * 1.3f;
|
||||
PlayerPrefs.SetFloat(UI_SCALE_KEY, scale);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
@@ -105,7 +121,6 @@ namespace Hallucinate.UI
|
||||
float savedScale = PlayerPrefs.GetFloat(UI_SCALE_KEY, 1.0f);
|
||||
SetUIScale(savedScale);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (_uiDocument == null) _uiDocument = GetComponent<UIDocument>();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -21,7 +21,7 @@ MonoBehaviour:
|
||||
m_ScaleMode: 1
|
||||
m_ReferenceSpritePixelsPerUnit: 100
|
||||
m_PixelsPerUnit: 100
|
||||
m_Scale: 1
|
||||
m_Scale: 1.3
|
||||
m_ReferenceDpi: 96
|
||||
m_FallbackDpi: 96
|
||||
m_ReferenceResolution: {x: 1200, y: 800}
|
||||
|
||||
Reference in New Issue
Block a user