...
This commit is contained in:
@@ -7,8 +7,8 @@ namespace OnlyScove.Scripts
|
||||
{
|
||||
public enum CameraViewMode { ThirdPerson, FirstPerson }
|
||||
|
||||
[SerializeField] InputReader inputReader;
|
||||
[SerializeField] Transform followTarget; // Player's root for TPV
|
||||
public InputReader inputReader; // Đổi từ [SerializeField] thành public
|
||||
public Transform followTarget; // Player's root for TPV
|
||||
[SerializeField] float positionSmoothTime = 0.12f;
|
||||
[SerializeField] float rotationSmoothTime = 5f;
|
||||
[SerializeField] Vector2 framingOffset;
|
||||
@@ -72,6 +72,7 @@ namespace OnlyScove.Scripts
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (followTarget == null) return;
|
||||
HandleViewTransition();
|
||||
|
||||
if (inputReader != null)
|
||||
|
||||
8
Assets/Scripts/Fusion.meta
Normal file
8
Assets/Scripts/Fusion.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6aa851b2c7ee553439ee0065f77665cb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
104
Assets/Scripts/Fusion/BasicSpawner.cs
Normal file
104
Assets/Scripts/Fusion/BasicSpawner.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Fusion;
|
||||
using Fusion.Sockets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
// ĐỊNH NGHĨA DỮ LIỆU GỬI QUA MẠNG
|
||||
public struct NetworkInputData : INetworkInput
|
||||
{
|
||||
public Vector2 move;
|
||||
public Quaternion rot;
|
||||
public bool sprint;
|
||||
}
|
||||
|
||||
public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks
|
||||
{
|
||||
private NetworkRunner _runner;
|
||||
|
||||
[SerializeField] private NetworkPrefabRef _playerPrefab;
|
||||
private Dictionary<PlayerRef, NetworkObject> _spawnedCharacters = new Dictionary<PlayerRef, NetworkObject>();
|
||||
|
||||
async void StartGame(GameMode mode)
|
||||
{
|
||||
if (_runner != null) return;
|
||||
_runner = gameObject.AddComponent<NetworkRunner>();
|
||||
_runner.ProvideInput = true;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
await _runner.StartGame(new StartGameArgs()
|
||||
{
|
||||
GameMode = mode,
|
||||
SessionName = "TestRoom",
|
||||
Scene = SceneRef.FromIndex(1),
|
||||
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (_runner == null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
|
||||
{
|
||||
if (runner.IsServer)
|
||||
{
|
||||
Vector3 spawnPosition = new Vector3((player.RawEncoded % 10) * 2, 1, 0);
|
||||
NetworkObject 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))
|
||||
{
|
||||
if (networkObject != null) runner.Despawn(networkObject);
|
||||
_spawnedCharacters.Remove(player);
|
||||
}
|
||||
}
|
||||
|
||||
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 OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) { }
|
||||
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) { }
|
||||
}
|
||||
2
Assets/Scripts/Fusion/BasicSpawner.cs.meta
Normal file
2
Assets/Scripts/Fusion/BasicSpawner.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44bfaa339c82069418e72a14479a0212
|
||||
@@ -12,6 +12,13 @@ namespace OnlyScove.Scripts
|
||||
public virtual Vector2 ScrollInput { get; protected set; }
|
||||
public virtual bool IsSprintHeld { get; protected set; } // Left Shift
|
||||
public virtual bool IsAttackHeld { get; protected set; } // Left Mouse Button
|
||||
|
||||
// THÊM HÀM NÀY ĐỂ ĐỒNG BỘ MẠNG
|
||||
public void ApplyNetworkInput(Vector2 move, bool isSprint)
|
||||
{
|
||||
MoveInput = move;
|
||||
IsSprintHeld = isSprint;
|
||||
}
|
||||
|
||||
// One-shot Events
|
||||
public event Action OnJumpEvent; // Space
|
||||
|
||||
@@ -36,11 +36,16 @@ namespace OnlyScove.Scripts
|
||||
}
|
||||
|
||||
Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized;
|
||||
Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir;
|
||||
|
||||
// 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.Normalize();
|
||||
|
||||
if (stateMachine.Cam != null)
|
||||
if (stateMachine.Cam != null && stateMachine.Object.HasInputAuthority)
|
||||
{
|
||||
Debug.Log($"[PlayerMoveState] View: {(stateMachine.Cam.PlanarRotation.eulerAngles.y)}, InputDir: {inputDir}, MoveDir: {moveDirection}, PlayerRot: {stateMachine.transform.rotation.eulerAngles.y}");
|
||||
// Log chỉ hiện ở máy khách để debug hướng xoay
|
||||
// Debug.Log($"[Move] Input: {inputDir}, MoveDir: {moveDirection}");
|
||||
}
|
||||
|
||||
Vector3 velocity = moveDirection * stateMachine.WalkSpeed;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Fusion;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
[RequireComponent(typeof(CharacterController), typeof(InputReader), typeof(Animator))]
|
||||
public class PlayerStateMachine : MonoBehaviour
|
||||
public class PlayerStateMachine : NetworkBehaviour
|
||||
{
|
||||
[field: Header("References")]
|
||||
[field: SerializeField] public CharacterController Controller { get; private set; }
|
||||
@@ -36,16 +37,19 @@ namespace OnlyScove.Scripts
|
||||
[field: SerializeField] public float InteractionRange { get; private set; } = 2f;
|
||||
[field: SerializeField] public LayerMask InteractionMask { get; private set; }
|
||||
|
||||
[Networked] public Quaternion NetworkedCameraRotation { get; set; }
|
||||
|
||||
public float VelocityY { get; set; }
|
||||
public bool IsGrounded { get; private set; }
|
||||
public bool WasGrounded { get; private set; }
|
||||
|
||||
// Interaction system variables
|
||||
private List<IInteractable> interactablesNearby = new List<IInteractable>();
|
||||
private int currentInteractableIndex = 0;
|
||||
|
||||
public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None";
|
||||
|
||||
public static PlayerStateMachine Local { get; private set; } // THÊM DÒNG NÀY
|
||||
|
||||
private PlayerBaseState currentState;
|
||||
private bool hasControl = true;
|
||||
|
||||
@@ -55,43 +59,56 @@ namespace OnlyScove.Scripts
|
||||
Input = GetComponent<InputReader>();
|
||||
Anim = GetComponentInChildren<Animator>();
|
||||
Scanner = GetComponent<EnvironmentScanner>();
|
||||
Cam = Camera.main?.GetComponent<CameraController>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
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));
|
||||
|
||||
// Subscribe to cycle events
|
||||
Input.OnNextInteractEvent += OnNextInteract;
|
||||
Input.OnPreviousInteractEvent += OnPreviousInteract;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Input != null)
|
||||
if (Object.HasInputAuthority)
|
||||
{
|
||||
Input.OnNextInteractEvent -= OnNextInteract;
|
||||
Input.OnPreviousInteractEvent -= OnPreviousInteract;
|
||||
Local = this;
|
||||
|
||||
CameraController cameraController = GameObject.FindAnyObjectByType<CameraController>();
|
||||
if (cameraController != null)
|
||||
{
|
||||
Cam = cameraController;
|
||||
Cam.followTarget = this.transform;
|
||||
Cam.inputReader = this.Input;
|
||||
}
|
||||
|
||||
Input.OnNextInteractEvent += OnNextInteract;
|
||||
Input.OnPreviousInteractEvent += OnPreviousInteract;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
public override void FixedUpdateNetwork()
|
||||
{
|
||||
if (Object == null) return;
|
||||
|
||||
// 1. NHẬN DỮ LIỆU TỪ MẠNG
|
||||
if (GetInput(out NetworkInputData 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
|
||||
NetworkedCameraRotation = data.rot;
|
||||
}
|
||||
|
||||
// 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 (!hasControl) return;
|
||||
|
||||
WasGrounded = IsGrounded;
|
||||
CheckGround();
|
||||
UpdateInteractablesList();
|
||||
|
||||
currentState?.Tick(Time.deltaTime);
|
||||
currentState?.Tick(Runner.DeltaTime);
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!hasControl) return;
|
||||
currentState?.PhysicsTick(Time.fixedDeltaTime);
|
||||
}
|
||||
protected virtual void Update() { }
|
||||
|
||||
private void CheckGround()
|
||||
{
|
||||
@@ -101,16 +118,8 @@ namespace OnlyScove.Scripts
|
||||
private void UpdateInteractablesList()
|
||||
{
|
||||
interactablesNearby.Clear();
|
||||
|
||||
// Sử dụng Scanner để tìm vật thể người chơi đang nhìn vào
|
||||
IInteractable target = Scanner.ScanForInteractable(InteractionRange, InteractionMask);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
interactablesNearby.Add(target);
|
||||
}
|
||||
|
||||
// Reset index vì hiện tại Scanner trả về 1 kết quả chính xác nhất
|
||||
if (target != null) interactablesNearby.Add(target);
|
||||
currentInteractableIndex = 0;
|
||||
}
|
||||
|
||||
@@ -118,7 +127,6 @@ namespace OnlyScove.Scripts
|
||||
{
|
||||
if (interactablesNearby.Count <= 1) return;
|
||||
currentInteractableIndex = (currentInteractableIndex + 1) % interactablesNearby.Count;
|
||||
Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}");
|
||||
}
|
||||
|
||||
private void OnPreviousInteract()
|
||||
@@ -126,7 +134,6 @@ namespace OnlyScove.Scripts
|
||||
if (interactablesNearby.Count <= 1) return;
|
||||
currentInteractableIndex--;
|
||||
if (currentInteractableIndex < 0) currentInteractableIndex = interactablesNearby.Count - 1;
|
||||
Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}");
|
||||
}
|
||||
|
||||
public IInteractable GetInteractable()
|
||||
@@ -152,19 +159,13 @@ namespace OnlyScove.Scripts
|
||||
{
|
||||
hasControl = control;
|
||||
Controller.enabled = control;
|
||||
if (!control)
|
||||
{
|
||||
Anim.SetFloat("Speed", 0f);
|
||||
}
|
||||
if (!control) Anim.SetFloat("Speed", 0f);
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = new Color(0, 1, 0, 0.5f);
|
||||
Gizmos.DrawSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius);
|
||||
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user