diff --git a/.gemini-workspace-history/active-context.md b/.gemini-workspace-history/active-context.md new file mode 100644 index 00000000..5b572ad1 --- /dev/null +++ b/.gemini-workspace-history/active-context.md @@ -0,0 +1 @@ +No previous session history found for this workspace. \ No newline at end of file diff --git a/.gemini-workspace-history/summary-2026-04-30.md b/.gemini-workspace-history/summary-2026-04-30.md new file mode 100644 index 00000000..08c203ea --- /dev/null +++ b/.gemini-workspace-history/summary-2026-04-30.md @@ -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. diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml index dfcda12b..5e3db94e 100644 --- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml +++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml @@ -6,9 +6,9 @@ - + - + diff --git a/Assets/Scripts/Player Controller/PlayerStateMachine.cs b/Assets/Scripts/Player Controller/PlayerStateMachine.cs index 8bbed301..8207c95b 100644 --- a/Assets/Scripts/Player Controller/PlayerStateMachine.cs +++ b/Assets/Scripts/Player Controller/PlayerStateMachine.cs @@ -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(); Anim = GetComponentInChildren(); Scanner = GetComponent(); - + Stats = GetComponent(); Interaction = GetComponent(); Movement = GetComponent(); @@ -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) diff --git a/Assets/Scripts/UI/LobbyController.cs b/Assets/Scripts/UI/LobbyController.cs index 4a0d10e8..7d4982f1 100644 --- a/Assets/Scripts/UI/LobbyController.cs +++ b/Assets/Scripts/UI/LobbyController.cs @@ -236,7 +236,13 @@ namespace Hallucinate.UI : null; bool success = await spawner.StartHost(id, name, pass); - if (success) ShowLounge(name); + 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(); + } } private void UpdateRoomList(List sessions) @@ -319,12 +325,12 @@ namespace Hallucinate.UI BasicSpawner.Instance?.StartGame(); } - private void OnLeaveLoungeClicked() + private async void OnLeaveLoungeClicked() { var runner = Object.FindFirstObjectByType(); if (runner != null) { - runner.Shutdown(); + await runner.Shutdown(); } if (_playerDataManager != null) { @@ -355,7 +361,7 @@ namespace Hallucinate.UI } } - if (_playerDataManager == null || !_playerDataManager.Object.IsValid) return; + if (_playerDataManager == null || _playerDataManager.Object == null || !_playerDataManager.Object.IsValid) return; PlayerRef hostRef = PlayerRef.None; PlayerRef guestRef = PlayerRef.None; diff --git a/Assets/Scripts/UI/UIManager.cs b/Assets/Scripts/UI/UIManager.cs index b33dad67..64356c63 100644 --- a/Assets/Scripts/UI/UIManager.cs +++ b/Assets/Scripts/UI/UIManager.cs @@ -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(); 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();