diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab index 43488f0e..464d70c3 100644 --- a/Assets/Prefabs/Player.prefab +++ b/Assets/Prefabs/Player.prefab @@ -374,6 +374,9 @@ MonoBehaviour: k__BackingField: {fileID: 5600577104145922999} k__BackingField: {fileID: 9098752589608501196} k__BackingField: {fileID: 5811177247042239962} + speedParamName: Speed + velocityXParamName: Velocity X + velocityZParamName: Velocity Z k__BackingField: 5 k__BackingField: 10 k__BackingField: 9 @@ -393,6 +396,9 @@ MonoBehaviour: k__BackingField: serializedVersion: 2 m_Bits: 512 + _NetworkedCameraRotation: {x: 0, y: 0, z: 0, w: 0} + _NetworkedMoveInput: {x: 0, y: 0} + _NetworkedSpeed: 0 --- !u!114 &3043298118541876184 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Scove/DEMO FUSION.unity b/Assets/Scove/DEMO FUSION.unity index 129740ec..af2a0848 100644 --- a/Assets/Scove/DEMO FUSION.unity +++ b/Assets/Scove/DEMO FUSION.unity @@ -341,6 +341,7 @@ GameObject: m_Component: - component: {fileID: 966137138} - component: {fileID: 966137137} + - component: {fileID: 966137139} m_Layer: 0 m_Name: NetworkManager m_TagString: Untagged @@ -360,6 +361,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 44bfaa339c82069418e72a14479a0212, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::BasicSpawner + LobbyManager: {fileID: 966137139} _playerPrefab: RawGuidValue: 761bdf2e5c0cff4488527355acb975e5 --- !u!4 &966137138 @@ -377,6 +379,19 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &966137139 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 966137136} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b5aeb4670d7bf41499d3aaf409820260, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::LobbyManager + roomListContent: {fileID: 966137138} --- !u!1 &1016072950 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Debug/PlayerDebugProvider.cs b/Assets/Scripts/Debug/PlayerDebugProvider.cs index 547b7941..7238f62f 100644 --- a/Assets/Scripts/Debug/PlayerDebugProvider.cs +++ b/Assets/Scripts/Debug/PlayerDebugProvider.cs @@ -20,12 +20,20 @@ namespace OnlyScove.Scripts public string GroundedStatus => (stateMachine != null && stateMachine.IsGrounded) ? "YES" : "NO"; public float HorizontalSpeed => stateMachine != null ? new Vector3(stateMachine.Controller.velocity.x, 0, stateMachine.Controller.velocity.z).magnitude : 0f; public float VerticalSpeed => stateMachine != null ? stateMachine.VelocityY : 0f; - public Vector2 MoveInput => stateMachine != null ? stateMachine.Input.MoveInput : Vector2.zero; - public bool IsSprinting => stateMachine != null ? stateMachine.Input.IsSprintHeld : false; + + // Sửa lỗi truy cập InputReader từ StateMachine + public Vector2 MoveInput => (stateMachine != null && stateMachine.Input != null) ? stateMachine.Input.MoveInput : Vector2.zero; + public bool IsSprinting => (stateMachine != null && stateMachine.Input != null) ? stateMachine.Input.IsSprintHeld : false; + public string TargetInteractable => stateMachine != null ? (stateMachine.GetInteractable()?.InteractionPrompt ?? "None") : "N/A"; public IInteractable GetActiveInteractable() => stateMachine?.GetInteractable(); - public Vector3 GetInteractionPoint() => stateMachine != null ? stateMachine.Scanner.GetLastInteractionPoint(stateMachine.InteractionRange, stateMachine.InteractionMask) : Vector3.zero; + + public Vector3 GetInteractionPoint() + { + if (stateMachine == null || stateMachine.Scanner == null) return Vector3.zero; + return stateMachine.Scanner.GetLastInteractionPoint(stateMachine.InteractionRange, stateMachine.InteractionMask); + } private Vector3 currentVelocity; private Transform cameraTransform; diff --git a/Assets/Scripts/Fusion/BasicSpawner.cs b/Assets/Scripts/Fusion/BasicSpawner.cs index 9296e8ec..104a32b8 100644 --- a/Assets/Scripts/Fusion/BasicSpawner.cs +++ b/Assets/Scripts/Fusion/BasicSpawner.cs @@ -1,14 +1,17 @@ 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; -// ĐỊNH NGHĨA DỮ LIỆU GỬI QUA MẠNG -public struct NetworkInputData : INetworkInput +// Struct input đồng bộ giữa Spawner, Movement và StateMachine +public struct PlayerInputData : INetworkInput { - public Vector2 move; + public Vector2 Direction; public Quaternion rot; public bool sprint; } @@ -17,30 +20,80 @@ public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks { private NetworkRunner _runner; + public LobbyManager LobbyManager; [SerializeField] private NetworkPrefabRef _playerPrefab; private Dictionary _spawnedCharacters = new Dictionary(); - async void StartGame(GameMode mode) + // Thông tin profile local + public PlayerProfile LocalPlayerProfile { get; private set; } + public void SetLocalPlayerProfile(PlayerProfile profile) { - if (_runner != null) return; - _runner = gameObject.AddComponent(); - _runner.ProvideInput = true; + LocalPlayerProfile = profile; + } + + private void Awake() + { + if (_runner == null) _runner = gameObject.AddComponent(); DontDestroyOnLoad(gameObject); - await _runner.StartGame(new StartGameArgs() + } + + // Khởi tạo Game (Host/Client) + public async Task StartGame(GameMode mode, string sessionName = "TestRoom") + { + Debug.Log($"Fusion: Đang khởi tạo kết nối với Mode: {mode} | Phòng: {sessionName}"); + + if (_runner == null) _runner = gameObject.AddComponent(); + _runner.ProvideInput = true; + + var sceneManager = gameObject.GetComponent(); + if (sceneManager == null) sceneManager = gameObject.AddComponent(); + + var result = await _runner.StartGame(new StartGameArgs() { GameMode = mode, - SessionName = "TestRoom", + SessionName = sessionName, Scene = SceneRef.FromIndex(1), - SceneManager = gameObject.AddComponent() + SceneManager = sceneManager }); + + if (result.Ok) + { + Debug.Log($"Fusion thành công: Đã vào phòng {sessionName}"); + } + else + { + Debug.LogError($"Fusion thất bại: Lý do: {result.ShutdownReason}"); + if (_runner != null) + { + Destroy(_runner); + _runner = null; + } + } } private void OnGUI() { - if (_runner == null) + if (_runner == null || !_runner.IsRunning) { - if (GUI.Button(new Rect(10, 10, 200, 40), "Host (Tạo phòng)")) StartGame(GameMode.AutoHostOrClient); - if (GUI.Button(new Rect(10, 60, 200, 40), "Join (Vào phòng)")) StartGame(GameMode.Client); + if (GUI.Button(new Rect(10, 10, 250, 40), "Bắt đầu (Auto Host/Client)")) + { + _ = StartGame(GameMode.AutoHostOrClient); + } + GUI.Label(new Rect(10, 60, 300, 20), "Gợi ý: Mở bản Build trước, sau đó mở Unity bấm nút trên."); + } + 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(); + } } } @@ -48,34 +101,15 @@ public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks { if (runner.IsServer) { - Vector3 spawnPosition = new Vector3((player.RawEncoded % 10) * 2, 1, 0); - NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player); + 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 OnInput(NetworkRunner runner, NetworkInput input) - { - var data = new NetworkInputData(); - - // Lấy dữ liệu từ nhân vật local của chính mình - if (OnlyScove.Scripts.PlayerStateMachine.Local != null) - { - var sm = OnlyScove.Scripts.PlayerStateMachine.Local; - data.move = sm.Input.MoveInput; - data.sprint = sm.Input.IsSprintHeld; - - // Lấy hướng xoay từ Camera (nếu có) - if (sm.Cam != null) - data.rot = sm.Cam.PlanarRotation; - else - data.rot = sm.NetworkedCameraRotation; // Fallback - } - - input.Set(data); - } - public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject)) @@ -85,6 +119,29 @@ public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks } } + 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 sessionList) + { + if (LobbyManager != null) LobbyManager.DisplayRoomList(sessionList); + } + public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { } public void OnConnectedToServer(NetworkRunner runner) { } @@ -92,7 +149,6 @@ public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks 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 OnSessionListUpdated(NetworkRunner runner, List sessionList) { } public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } public void OnSceneLoadDone(NetworkRunner runner) { } @@ -101,4 +157,4 @@ public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment data) { } public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { } -} +} \ No newline at end of file diff --git a/Assets/Scripts/Fusion/LobbyHelper.cs b/Assets/Scripts/Fusion/LobbyHelper.cs new file mode 100644 index 00000000..e129507a --- /dev/null +++ b/Assets/Scripts/Fusion/LobbyHelper.cs @@ -0,0 +1 @@ +// File này hiện tại không còn nội dung, có thể xóa đi hoặc để trống. diff --git a/Assets/Scripts/Fusion/LobbyHelper.cs.meta b/Assets/Scripts/Fusion/LobbyHelper.cs.meta new file mode 100644 index 00000000..ce238440 --- /dev/null +++ b/Assets/Scripts/Fusion/LobbyHelper.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2bf2c7f159565794b87d6f3ca2eb2976 \ No newline at end of file diff --git a/Assets/Scripts/Fusion/LobbyManager.cs b/Assets/Scripts/Fusion/LobbyManager.cs new file mode 100644 index 00000000..996e94ec --- /dev/null +++ b/Assets/Scripts/Fusion/LobbyManager.cs @@ -0,0 +1,28 @@ +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 sessionList) + { + Debug.Log($"Lobby Update: Đ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 + } + } +} diff --git a/Assets/Scripts/Fusion/LobbyManager.cs.meta b/Assets/Scripts/Fusion/LobbyManager.cs.meta new file mode 100644 index 00000000..a269a8c8 --- /dev/null +++ b/Assets/Scripts/Fusion/LobbyManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b5aeb4670d7bf41499d3aaf409820260 \ No newline at end of file diff --git a/Assets/Scripts/Fusion/PlayerProfile.cs b/Assets/Scripts/Fusion/PlayerProfile.cs new file mode 100644 index 00000000..14fb3da9 --- /dev/null +++ b/Assets/Scripts/Fusion/PlayerProfile.cs @@ -0,0 +1,17 @@ +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; +} diff --git a/Assets/Scripts/Fusion/PlayerProfile.cs.meta b/Assets/Scripts/Fusion/PlayerProfile.cs.meta new file mode 100644 index 00000000..2b9acb62 --- /dev/null +++ b/Assets/Scripts/Fusion/PlayerProfile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8a1452ae101af8e43b94c2c778a70fe0 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/PlayerIdleState.cs b/Assets/Scripts/Player Controller/PlayerIdleState.cs index 3b7e7acf..c43c5757 100644 --- a/Assets/Scripts/Player Controller/PlayerIdleState.cs +++ b/Assets/Scripts/Player Controller/PlayerIdleState.cs @@ -23,26 +23,25 @@ namespace OnlyScove.Scripts public override void Tick(float deltaTime) { - stateMachine.Anim.SetFloat(speedHash, 0f, stateMachine.AnimationDamping, deltaTime); - stateMachine.Anim.SetFloat(speedXHash, 0f, stateMachine.AnimationDamping, deltaTime); - stateMachine.Anim.SetFloat(speedZHash, 0f, stateMachine.AnimationDamping, deltaTime); - - if (stateMachine.Input.MoveInput != Vector2.zero) - { - stateMachine.SwitchState(new PlayerMoveState(stateMachine)); - return; - } - + // Cập nhật trọng lực if (stateMachine.IsGrounded && stateMachine.VelocityY < 0) { stateMachine.VelocityY = -2f; } else { - stateMachine.VelocityY += Physics.gravity.y * deltaTime; + stateMachine.VelocityY += stateMachine.Gravity * deltaTime; + } + + // Sử dụng hàm Move tập trung để đồng bộ Animator và Vị trí + stateMachine.Move(new Vector3(0, stateMachine.VelocityY, 0), 0f, deltaTime); + + // QUAN TRỌNG: Đọc dữ liệu đã đồng bộ từ mạng (stateMachine.MoveInput) + if (stateMachine.MoveInput != Vector2.zero) + { + stateMachine.SwitchState(new PlayerMoveState(stateMachine)); + return; } - - stateMachine.Controller.Move(new Vector3(0, stateMachine.VelocityY, 0) * deltaTime); } public override void PhysicsTick(float fixedDeltaTime) {} diff --git a/Assets/Scripts/Player Controller/PlayerMoveState.cs b/Assets/Scripts/Player Controller/PlayerMoveState.cs index 38024e87..0abac5d7 100644 --- a/Assets/Scripts/Player Controller/PlayerMoveState.cs +++ b/Assets/Scripts/Player Controller/PlayerMoveState.cs @@ -20,7 +20,8 @@ namespace OnlyScove.Scripts public override void Tick(float deltaTime) { - Vector2 input = stateMachine.Input.MoveInput; + // QUAN TRỌNG: Đọc trực tiếp từ stateMachine (Dữ liệu đã đồng bộ mạng) + Vector2 input = stateMachine.MoveInput; float moveAmount = Mathf.Clamp01(Mathf.Abs(input.x) + Mathf.Abs(input.y)); if (moveAmount <= 0.01f) @@ -29,25 +30,17 @@ namespace OnlyScove.Scripts return; } - if (stateMachine.Input.IsSprintHeld) + if (stateMachine.IsSprintHeld) { stateMachine.SwitchState(new PlayerDashState(stateMachine)); return; } Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized; - - // DÙNG HƯỚNG CAMERA TỪ MẠNG ĐỂ ĐỒNG BỘ MÁY CHỦ Vector3 moveDirection = stateMachine.NetworkedCameraRotation * inputDir; - moveDirection.y = 0; // Đảm bảo không bay lên trời + moveDirection.y = 0; moveDirection.Normalize(); - if (stateMachine.Cam != null && stateMachine.Object.HasInputAuthority) - { - // Log chỉ hiện ở máy khách để debug hướng xoay - // Debug.Log($"[Move] Input: {inputDir}, MoveDir: {moveDirection}"); - } - Vector3 velocity = moveDirection * stateMachine.WalkSpeed; if (stateMachine.IsGrounded && stateMachine.VelocityY < 0) @@ -56,11 +49,12 @@ namespace OnlyScove.Scripts } else { - stateMachine.VelocityY += Physics.gravity.y * deltaTime; + stateMachine.VelocityY += stateMachine.Gravity * deltaTime; } velocity.y = stateMachine.VelocityY; - stateMachine.Controller.Move(velocity * deltaTime); + // DÙNG HÀM MOVE TẬP TRUNG ĐỂ ĐỒNG BỘ TỐC ĐỘ VỚI MẠNG + stateMachine.Move(velocity, 0.7f, deltaTime); if (moveDirection != Vector3.zero) { @@ -71,10 +65,6 @@ namespace OnlyScove.Scripts stateMachine.RotationSpeed * deltaTime ); } - - stateMachine.Anim.SetFloat(speedHash, 0.7f, stateMachine.AnimationDamping, deltaTime); - stateMachine.Anim.SetFloat(speedXHash, input.x * 0.5f, stateMachine.AnimationDamping, deltaTime); - stateMachine.Anim.SetFloat(speedZHash, input.y * 0.5f, stateMachine.AnimationDamping, deltaTime); } public override void PhysicsTick(float fixedDeltaTime) {} diff --git a/Assets/Scripts/Player Controller/PlayerStateMachine.cs b/Assets/Scripts/Player Controller/PlayerStateMachine.cs index 0694a900..d1ea438e 100644 --- a/Assets/Scripts/Player Controller/PlayerStateMachine.cs +++ b/Assets/Scripts/Player Controller/PlayerStateMachine.cs @@ -14,10 +14,19 @@ namespace OnlyScove.Scripts [field: SerializeField] public EnvironmentScanner Scanner { get; private set; } public CameraController Cam { get; private set; } + [field: Header("Animator Settings")] + [SerializeField] private string speedParamName = "Speed"; + [SerializeField] private string velocityXParamName = "Velocity X"; + [SerializeField] private string velocityZParamName = "Velocity Z"; + + private int speedHash; + private int velocityXHash; + private int velocityZHash; + [field: Header("Movement Settings")] [field: SerializeField] public float WalkSpeed { get; private set; } = 3f; [field: SerializeField] public float RunSpeed { get; private set; } = 6f; - [field: SerializeField] public float SprintSpeed { get; private set; } = 9f; // 150% of RunSpeed + [field: SerializeField] public float SprintSpeed { get; private set; } = 9f; [field: SerializeField] public float SneakSpeed { get; private set; } = 1.5f; [field: SerializeField] public float DashForce { get; private set; } = 10f; [field: SerializeField] public float RotationSpeed { get; private set; } = 500f; @@ -25,7 +34,7 @@ namespace OnlyScove.Scripts [field: Header("Airborne Settings")] [field: SerializeField] public float JumpHeight { get; private set; } = 2f; - [field: SerializeField] public float Gravity { get; private set; } = -9.81f; + [field: SerializeField] public float Gravity { get; private set; } = -15f; [field: SerializeField] public float ThrustDownwardForce { get; private set; } = -20f; [field: Header("Ground Check")] @@ -38,7 +47,11 @@ namespace OnlyScove.Scripts [field: SerializeField] public LayerMask InteractionMask { get; private set; } [Networked] public Quaternion NetworkedCameraRotation { get; set; } + [Networked] public Vector2 NetworkedMoveInput { get; set; } + [Networked] public float NetworkedSpeed { get; set; } + public Vector2 MoveInput { get; private set; } + public bool IsSprintHeld { get; private set; } public float VelocityY { get; set; } public bool IsGrounded { get; private set; } public bool WasGrounded { get; private set; } @@ -48,7 +61,7 @@ namespace OnlyScove.Scripts public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None"; - public static PlayerStateMachine Local { get; private set; } // THÊM DÒNG NÀY + public static PlayerStateMachine Local { get; private set; } private PlayerBaseState currentState; private bool hasControl = true; @@ -59,13 +72,21 @@ namespace OnlyScove.Scripts Input = GetComponent(); Anim = GetComponentInChildren(); Scanner = GetComponent(); + + speedHash = Animator.StringToHash(speedParamName); + velocityXHash = Animator.StringToHash(velocityXParamName); + velocityZHash = Animator.StringToHash(velocityZParamName); } public override void Spawned() { - // BẮT BUỘC: Mọi máy (Server và Client) đều phải khởi tạo trạng thái ban đầu SwitchState(new PlayerIdleState(this)); + if (Runner.IsClient && !Object.HasInputAuthority) + { + if (Controller != null) Controller.enabled = false; + } + if (Object.HasInputAuthority) { Local = this; @@ -74,8 +95,8 @@ namespace OnlyScove.Scripts if (cameraController != null) { Cam = cameraController; - Cam.followTarget = this.transform; - Cam.inputReader = this.Input; + Cam.followTarget = transform; + Cam.inputReader = Input; } Input.OnNextInteractEvent += OnNextInteract; @@ -83,33 +104,80 @@ namespace OnlyScove.Scripts } } + public void Move(Vector3 motion, float speed, float deltaTime) + { + if (Controller != null && Controller.enabled) + { + Controller.Move(motion * deltaTime); + } + + if (Object.HasStateAuthority) + { + NetworkedSpeed = speed; + NetworkedMoveInput = MoveInput; + } + + UpdateAnimator(deltaTime); + } + + private void UpdateAnimator(float deltaTime) + { + if (Anim == null) return; + + // Nếu là chính mình (Input Authority): Dùng dữ liệu phím bấm trực tiếp (Mượt nhất) + // Nếu là người khác (Proxy/Server): Dùng dữ liệu đã đồng bộ qua mạng + float speedValue; + Vector2 inputVector; + + if (Object.HasInputAuthority) + { + speedValue = (MoveInput.magnitude > 0.01f) ? NetworkedSpeed : 0f; + inputVector = MoveInput; + } + else + { + speedValue = NetworkedSpeed; + inputVector = NetworkedMoveInput; + } + + try { + Anim.SetFloat(speedHash, speedValue, AnimationDamping, deltaTime); + Anim.SetFloat(velocityXHash, inputVector.x * speedValue, AnimationDamping, deltaTime); + Anim.SetFloat(velocityZHash, inputVector.y * speedValue, AnimationDamping, deltaTime); + } catch { } + } + public override void FixedUpdateNetwork() { if (Object == null) return; - // 1. NHẬN DỮ LIỆU TỪ MẠNG - if (GetInput(out NetworkInputData data)) + if (GetInput(out PlayerInputData data)) { - // Gán phím bấm vào InputReader để các State (Move, Jump...) sử dụng - Input.ApplyNetworkInput(data.move, data.sprint); - - // CẬP NHẬT HƯỚNG CAMERA (Cho cả Server và Client) - // Đây là mấu chốt để Server tính toán hướng chạy đúng + MoveInput = data.Direction; + IsSprintHeld = data.sprint; NetworkedCameraRotation = data.rot; } + else + { + MoveInput = Vector2.zero; + IsSprintHeld = false; + } - // 2. CHẶN MÁY KHÁCH KHÁC, NHƯNG CHO PHÉP SERVER VÀ LOCAL PLAYER CHẠY LOGIC - if (!Object.HasInputAuthority && !Runner.IsServer) return; + if (!Object.HasInputAuthority && !Runner.IsServer) + { + UpdateAnimator(Runner.DeltaTime); + return; + } + if (!hasControl) return; WasGrounded = IsGrounded; CheckGround(); UpdateInteractablesList(); + currentState?.Tick(Runner.DeltaTime); } - protected virtual void Update() { } - private void CheckGround() { IsGrounded = Physics.CheckSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius, GroundMask); @@ -158,8 +226,8 @@ namespace OnlyScove.Scripts public void SetControl(bool control) { hasControl = control; - Controller.enabled = control; - if (!control) Anim.SetFloat("Speed", 0f); + if (Controller != null) Controller.enabled = control; + if (!control && Anim != null) Anim.SetFloat(speedHash, 0f); } private void OnDrawGizmosSelected() @@ -168,4 +236,4 @@ namespace OnlyScove.Scripts Gizmos.DrawSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius); } } -} +} \ No newline at end of file