diff --git a/Assets/Scripts/Core.cs b/Assets/Scripts/Core.cs index 988abe1..e5bc493 100644 --- a/Assets/Scripts/Core.cs +++ b/Assets/Scripts/Core.cs @@ -194,15 +194,16 @@ namespace Northbound return false; // 플레이어가 자원을 가지고 있는지 확인 - if (NetworkManager.Singleton != null && - NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client)) + // NetworkPlayerController로 찾기 (서버 소유권으로 스폰한 경우 PlayerObject가 null일 수 있음) + var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects; + foreach (var kvp in spawnedObjects) { - if (client.PlayerObject != null) + var controller = kvp.Value.GetComponent(); + if (controller != null && controller.OwnerPlayerId == playerId) { - var playerInventory = client.PlayerObject.GetComponent(); + var playerInventory = kvp.Value.GetComponent(); if (playerInventory != null) { - // 플레이어가 자원을 가지고 있어야 함 return playerInventory.CurrentResourceAmount > 0; } } @@ -297,13 +298,23 @@ namespace Northbound [Rpc(SendTo.ClientsAndHost)] private void UpdatePlayerResourcesClientRpc(ulong playerId) { - var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject; - if (playerObject != null) + // 해당 플레이어만 업데이트 + if (NetworkManager.Singleton.LocalClientId != playerId) + return; + + // 로컬 플레이어의 PlayerResourceInventory 찾아서 업데이트 요청 + var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects; + foreach (var kvp in spawnedObjects) { - var playerInventory = playerObject.GetComponent(); - if (playerInventory != null) + var controller = kvp.Value.GetComponent(); + if (controller != null && controller.IsLocalPlayer) { - playerInventory.RequestResourceUpdateServerRpc(); + var inventory = kvp.Value.GetComponent(); + if (inventory != null) + { + inventory.RequestResourceUpdateServerRpc(playerId); + return; + } } } } diff --git a/Assets/Scripts/GameResourceUI.cs b/Assets/Scripts/GameResourceUI.cs index 2d8d985..7768b3b 100644 --- a/Assets/Scripts/GameResourceUI.cs +++ b/Assets/Scripts/GameResourceUI.cs @@ -171,9 +171,26 @@ namespace Northbound ulong localClientId = NetworkManager.Singleton.LocalClientId; + // PlayerObject가 있으면 사용 if (NetworkManager.Singleton.ConnectedClients.TryGetValue(localClientId, out var client)) { - return client.PlayerObject?.gameObject; + if (client.PlayerObject != null) + { + return client.PlayerObject.gameObject; + } + } + + // PlayerObject가 없으면 NetworkPlayerController로 찾기 + // (서버 소유권으로 스폰한 경우 PlayerObject가 null일 수 있음) + var allNetworkObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects; + foreach (var kvp in allNetworkObjects) + { + var networkObj = kvp.Value; + var playerController = networkObj.GetComponent(); + if (playerController != null && playerController.IsLocalPlayer) + { + return networkObj.gameObject; + } } return null; diff --git a/Assets/Scripts/InteractableModalManager.cs b/Assets/Scripts/InteractableModalManager.cs index bff2be8..1896c93 100644 --- a/Assets/Scripts/InteractableModalManager.cs +++ b/Assets/Scripts/InteractableModalManager.cs @@ -104,10 +104,10 @@ namespace Northbound if (playerInteraction == null) return false; - var networkObject = playerInteraction.GetComponent(); - if (networkObject != null) + var networkPlayerController = playerInteraction.GetComponent(); + if (networkPlayerController != null) { - return networkObject.IsOwner; + return networkPlayerController.IsLocalPlayer; } return false; } diff --git a/Assets/Scripts/NetworkConnectionHandler.cs b/Assets/Scripts/NetworkConnectionHandler.cs index 0e7bdeb..221d188 100644 --- a/Assets/Scripts/NetworkConnectionHandler.cs +++ b/Assets/Scripts/NetworkConnectionHandler.cs @@ -110,8 +110,16 @@ namespace Northbound { return; } - - networkObject.SpawnAsPlayerObject(clientId); + + // 서버 소유권으로 스폰 + networkObject.SpawnWithOwnership(NetworkManager.ServerClientId); + + // 플레이어 컨트롤러 초기화 + var playerController = playerObject.GetComponent(); + if (playerController != null) + { + playerController.Initialize(clientId); + } } private void ApprovalCheck( diff --git a/Assets/Scripts/NetworkPlayerController.cs b/Assets/Scripts/NetworkPlayerController.cs index 4488571..5e3fe7d 100644 --- a/Assets/Scripts/NetworkPlayerController.cs +++ b/Assets/Scripts/NetworkPlayerController.cs @@ -1,4 +1,6 @@ +using System; using Unity.Netcode; +using Unity.Netcode.Components; using UnityEngine; using UnityEngine.InputSystem; using Unity.Cinemachine; @@ -20,6 +22,13 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl [SerializeField] private GameObject damageEffectPrefab; [SerializeField] private GameObject deathEffectPrefab; + // 이 플레이어를 제어하는 클라이언트 ID (서버 소유권이지만 논리적 소유자) + private NetworkVariable _ownerPlayerId = new NetworkVariable( + ulong.MaxValue, + NetworkVariableReadPermission.Everyone, + NetworkVariableWritePermission.Server + ); + private NetworkVariable _team = new NetworkVariable( TeamType.Player, NetworkVariableReadPermission.Everyone, @@ -36,11 +45,21 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl private CharacterController _controller; private PlayerInputActions _inputActions; private Animator _animator; + private NetworkAnimator _networkAnimator; + + // 이 플레이어가 로컬 플레이어인지 확인 + public bool IsLocalPlayer => _ownerPlayerId.Value == NetworkManager.Singleton.LocalClientId; + + public ulong OwnerPlayerId => _ownerPlayerId.Value; + + // 소유자 변경 이벤트 + public event Action OnOwnerChanged; void Awake() { _controller = GetComponent(); _animator = GetComponent(); + _networkAnimator = GetComponent(); } public override void OnNetworkSpawn() @@ -61,28 +80,52 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl } } - // 체력 변경 이벤트 구독 + // 이벤트 구독 _currentHealth.OnValueChanged += OnHealthChanged; + _ownerPlayerId.OnValueChanged += OnOwnerPlayerIdChanged; - if (!IsOwner) return; + // 이미 로컬 플레이어로 설정되어 있으면 입력 초기화 + TryInitializeLocalPlayer(); + } - var vcam = GameObject.FindFirstObjectByType(); + private void OnOwnerPlayerIdChanged(ulong previousValue, ulong newValue) + { + OnOwnerChanged?.Invoke(newValue); + TryInitializeLocalPlayer(); + } - if (vcam != null) - { - vcam.Follow = transform; - vcam.LookAt = transform; - } + private void TryInitializeLocalPlayer() + { + if (!IsLocalPlayer) return; + if (_inputActions != null) return; // 이미 초기화됨 + + var vcam = GameObject.FindFirstObjectByType(); + + if (vcam != null) + { + vcam.Follow = transform; + vcam.LookAt = transform; + } _inputActions = new PlayerInputActions(); _inputActions.Enable(); } + /// + /// 플레이어 초기화 (서버에서 호출) + /// + public void Initialize(ulong ownerPlayerId) + { + if (!IsServer) return; + _ownerPlayerId.Value = ownerPlayerId; + } + public override void OnNetworkDespawn() { _currentHealth.OnValueChanged -= OnHealthChanged; + _ownerPlayerId.OnValueChanged -= OnOwnerPlayerIdChanged; - if (IsOwner && _inputActions != null) + if (IsLocalPlayer && _inputActions != null) { _inputActions.Disable(); } @@ -92,7 +135,11 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl void Update() { - if (!IsOwner) return; + // 로컬 플레이어만 입력 처리 + if (!IsLocalPlayer) return; + + // 입력 시스템이 초기화되지 않았으면 스킵 + if (_inputActions == null) return; // 죽었으면 이동 불가 if (_currentHealth.Value <= 0) return; @@ -106,16 +153,30 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl if (isActionBlocked) { - // 이동 불가 시 애니메이션 속도를 0으로 - if (_animator != null) - { - _animator.SetFloat("MoveSpeed", 0f); - } + // 서버에 이동 중지 알림 (애니메이션 포함) + MoveServerRpc(Vector2.zero); return; } _moveInput = _inputActions.Player.Move.ReadValue(); - Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y).normalized; + + // 서버에 이동 요청 전송 (애니메이션 포함) + MoveServerRpc(_moveInput); + } + + [Rpc(SendTo.Server)] + private void MoveServerRpc(Vector2 moveInput) + { + // 죽었으면 이동 불가 + if (_currentHealth.Value <= 0) return; + + Vector3 move = new Vector3(moveInput.x, 0, moveInput.y).normalized; + + // NetworkAnimator로 애니메이션 동기화 + if (_networkAnimator != null) + { + _networkAnimator.Animator.SetFloat("MoveSpeed", move.magnitude); + } if (move.magnitude >= 0.1f) { @@ -127,11 +188,6 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl _controller.Move(move * moveSpeed * Time.deltaTime); } } - - if (_animator != null) - { - _animator.SetFloat("MoveSpeed", move.magnitude); - } } #region ITeamMember Implementation @@ -149,7 +205,6 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl #endregion #region IDamageable Implementation - public void TakeDamage(int damage, ulong attackerId) { if (!IsServer) return; @@ -184,17 +239,17 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl } } - private void Die(ulong killerId) - { - if (!IsServer) return; + private void Die(ulong killerId) + { + if (!IsServer) return; - // 사망 이펙트 + // 사망 이펙트 ShowDeathEffectClientRpc(); - // 애니메이션 (있는 경우) - if (_animator != null) + // 애니메이션 - NetworkAnimator로 동기화 + if (_networkAnimator != null) { - _animator.SetTrigger("Die"); + _networkAnimator.SetTrigger("Die"); } // 일정 시간 후 리스폰 또는 디스폰 @@ -205,8 +260,6 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl { if (!IsServer) return; - // 여기서 리스폰 로직을 추가하거나 게임 오버 처리 - // 예: 리스폰 위치로 이동 및 체력 회복 Respawn(); } @@ -217,16 +270,20 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl // 체력 회복 _currentHealth.Value = maxHealth; - // 스폰 포인트로 이동 (PlayerSpawnPoint 활용) + // 스폰 포인트로 이동 var spawnPoints = FindObjectsByType(FindObjectsSortMode.None); if (spawnPoints.Length > 0) { - var spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Length)]; + var spawnPoint = spawnPoints[UnityEngine.Random.Range(0, spawnPoints.Length)]; transform.position = spawnPoint.transform.position; transform.rotation = spawnPoint.transform.rotation; } - _animator.SetTrigger("Revive"); + // NetworkAnimator로 애니메이션 동기화 + if (_networkAnimator != null) + { + _networkAnimator.SetTrigger("Revive"); + } } [ClientRpc] @@ -253,32 +310,17 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl #region Health Management - /// - /// 현재 체력 - /// public int GetCurrentHealth() => _currentHealth.Value; - /// - /// 최대 체력 - /// public int GetMaxHealth() => maxHealth; - /// - /// 체력 비율 (0.0 ~ 1.0) - /// public float GetHealthPercentage() { return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f; } - /// - /// 죽었는지 여부 - /// public bool IsDead() => _currentHealth.Value <= 0; - /// - /// 체력 회복 - /// public void Heal(int amount) { if (!IsServer) return; @@ -289,10 +331,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl private void OnHealthChanged(int previousValue, int newValue) { - // 체력바 UI 업데이트 또는 체력 변경 시각 효과 - - // 클라이언트에서도 체력 변경 인지 가능 - if (IsOwner) + if (IsLocalPlayer) { // UI 업데이트 등 } diff --git a/Assets/Scripts/PlayerActionSystem.cs b/Assets/Scripts/PlayerActionSystem.cs index 4759507..77a8064 100644 --- a/Assets/Scripts/PlayerActionSystem.cs +++ b/Assets/Scripts/PlayerActionSystem.cs @@ -1,4 +1,5 @@ using Unity.Netcode; +using Unity.Netcode.Components; using UnityEngine; using UnityEngine.InputSystem; using System.Collections.Generic; @@ -19,17 +20,23 @@ namespace Northbound private PlayerInputActions _inputActions; private Dictionary _actions = new Dictionary(); private Animator _animator; + private NetworkAnimator _networkAnimator; + private NetworkPlayerController _networkPlayerController; + + // 로컬 플레이어인지 확인 + private bool IsLocalPlayer => _networkPlayerController != null && _networkPlayerController.IsLocalPlayer; + private ulong LocalPlayerId => _networkPlayerController != null ? _networkPlayerController.OwnerPlayerId : OwnerClientId; private void Awake() { _animator = GetComponent(); + _networkAnimator = GetComponent(); + _networkPlayerController = GetComponent(); } public override void OnNetworkSpawn() { - if (!IsOwner) return; - - // 액션 컴포넌트들을 딕셔너리에 등록 + // 액션 컴포넌트들을 딕셔너리에 등록 (모든 클라이언트에서) foreach (var component in actionComponents) { if (component is IAction action) @@ -38,15 +45,39 @@ namespace Northbound } } + // _ownerPlayerId 변경 이벤트 구독 + if (_networkPlayerController != null) + { + _networkPlayerController.OnOwnerChanged += OnOwnerPlayerIdChanged; + } + + // 이미 로컬 플레이어면 입력 초기화 + TryInitializeInput(); + } + + private void OnOwnerPlayerIdChanged(ulong newOwnerId) + { + TryInitializeInput(); + } + + private void TryInitializeInput() + { + if (!IsLocalPlayer) return; + if (_inputActions != null) return; + _inputActions = new PlayerInputActions(); _inputActions.Player.Attack.performed += OnAttack; - // 다른 액션들도 여기에 바인딩 _inputActions.Enable(); } public override void OnNetworkDespawn() { - if (IsOwner && _inputActions != null) + if (_networkPlayerController != null) + { + _networkPlayerController.OnOwnerChanged -= OnOwnerPlayerIdChanged; + } + + if (_inputActions != null) { _inputActions.Player.Attack.performed -= OnAttack; _inputActions.Disable(); @@ -63,23 +94,30 @@ namespace Northbound { if (_actions.TryGetValue(actionName, out IAction action)) { - if (action.CanExecute(OwnerClientId)) + if (action.CanExecute(LocalPlayerId)) { - // 애니메이션 재생 (액션 실행 전) - if (playAnimations && _animator != null) + string animTrigger = action.GetActionAnimation(); + + // 서버에서 애니메이션 실행 (동기화를 위해) + if (playAnimations && !string.IsNullOrEmpty(animTrigger)) { - string animTrigger = action.GetActionAnimation(); - if (!string.IsNullOrEmpty(animTrigger)) - { - _animator.SetTrigger(animTrigger); - } + PlayAnimationServerRpc(animTrigger); } - action.Execute(OwnerClientId); + action.Execute(LocalPlayerId); } } } + [Rpc(SendTo.Server)] + private void PlayAnimationServerRpc(string animTrigger) + { + if (_networkAnimator != null && !string.IsNullOrEmpty(animTrigger)) + { + _networkAnimator.SetTrigger(animTrigger); + } + } + override public void OnDestroy() { if (_inputActions != null) diff --git a/Assets/Scripts/PlayerInteraction.cs b/Assets/Scripts/PlayerInteraction.cs index 26a64aa..fcca994 100644 --- a/Assets/Scripts/PlayerInteraction.cs +++ b/Assets/Scripts/PlayerInteraction.cs @@ -1,4 +1,5 @@ using Unity.Netcode; +using Unity.Netcode.Components; using UnityEngine; using UnityEngine.InputSystem; @@ -37,6 +38,7 @@ namespace Northbound private IInteractable _unavailableInteractable; private Camera _mainCamera; private Animator _animator; + private NetworkAnimator _networkAnimator; private EquipmentSocket _equipmentSocket; private EquipmentData _pendingEquipmentData; @@ -44,21 +46,51 @@ namespace Northbound private bool _isInteracting = false; private Coroutine _interactionTimeoutCoroutine; + private NetworkPlayerController _networkPlayerController; + public bool IsInteracting => _isInteracting; public float WorkPower => workPower; public IInteractable CurrentUnavailableInteractable => _unavailableInteractable; + // 로컬 플레이어인지 확인 + private bool IsLocalPlayer => _networkPlayerController != null && _networkPlayerController.IsLocalPlayer; + private ulong LocalPlayerId => _networkPlayerController != null ? _networkPlayerController.OwnerPlayerId : OwnerClientId; + + private void Awake() + { + _networkPlayerController = GetComponent(); + _networkAnimator = GetComponent(); + } + public override void OnNetworkSpawn() { - if (!IsOwner) return; - - _mainCamera = Camera.main; _animator = GetComponent(); _equipmentSocket = GetComponent(); if (rayOrigin == null) rayOrigin = transform; + // _ownerPlayerId 변경 이벤트 구독 + if (_networkPlayerController != null) + { + _networkPlayerController.OnOwnerChanged += OnOwnerPlayerIdChanged; + } + + // 이미 로컬 플레이어면 입력 초기화 + TryInitializeInput(); + } + + private void OnOwnerPlayerIdChanged(ulong newOwnerId) + { + TryInitializeInput(); + } + + private void TryInitializeInput() + { + if (!IsLocalPlayer) return; + if (_inputActions != null) return; + + _mainCamera = Camera.main; _inputActions = new PlayerInputActions(); _inputActions.Player.Interact.performed += OnInteract; _inputActions.Enable(); @@ -66,7 +98,12 @@ namespace Northbound public override void OnNetworkDespawn() { - if (IsOwner && _inputActions != null) + if (_networkPlayerController != null) + { + _networkPlayerController.OnOwnerChanged -= OnOwnerPlayerIdChanged; + } + + if (_inputActions != null) { _inputActions.Player.Interact.performed -= OnInteract; _inputActions.Disable(); @@ -76,12 +113,11 @@ namespace Northbound private void Update() { - if (!IsOwner) return; + if (!IsLocalPlayer) return; - // Check if current interactable is no longer valid (e.g., building completed) if (_isInteracting && _currentInteractable != null) { - if (!_currentInteractable.CanInteract(OwnerClientId)) + if (!_currentInteractable.CanInteract(LocalPlayerId)) { EndInteraction(); } @@ -92,6 +128,13 @@ namespace Northbound private void DetectInteractable() { + // 카메라가 없으면 다시 시도 + if (_mainCamera == null) + { + _mainCamera = Camera.main; + if (_mainCamera == null) return; + } + Vector3 origin = rayOrigin.position; Vector3 direction = useForwardDirection ? transform.forward : _mainCamera.transform.forward; @@ -112,7 +155,7 @@ namespace Northbound if (interactable != null) { - if (interactable.CanInteract(OwnerClientId)) + if (interactable.CanInteract(LocalPlayerId)) { _currentInteractable = interactable; _unavailableInteractable = null; @@ -120,7 +163,6 @@ namespace Northbound } else { - // CanInteract가 false인 경우 추적 _currentInteractable = null; _unavailableInteractable = interactable; return; @@ -139,8 +181,14 @@ namespace Northbound if (_currentInteractable != null) { + // 상호작용 가능 여부 다시 확인 (NetworkVariable 동기화 지연 대응) + if (!_currentInteractable.CanInteract(LocalPlayerId)) + { + return; + } + bool isResource = _currentInteractable is Resource; - bool hasWorker = assignedWorker != null && assignedWorker.OwnerPlayerId == OwnerClientId; + bool hasWorker = assignedWorker != null && assignedWorker.OwnerPlayerId == LocalPlayerId; bool isWorkerFollowing = hasWorker && (int)assignedWorker.CurrentState == 1; bool shouldPlayAnimation = !isResource || !hasWorker || !isWorkerFollowing; @@ -150,9 +198,10 @@ namespace Northbound string animTrigger = _currentInteractable.GetInteractionAnimation(); bool hasAnimation = !string.IsNullOrEmpty(animTrigger); - if (playAnimations && _animator != null && hasAnimation && shouldPlayAnimation) + if (playAnimations && hasAnimation && shouldPlayAnimation) { - _animator.SetTrigger(animTrigger); + // 서버에서 애니메이션 실행 (동기화를 위해) + PlayAnimationServerRpc(animTrigger); _interactionTimeoutCoroutine = StartCoroutine(InteractionTimeout(3f)); } else @@ -173,13 +222,18 @@ namespace Northbound } } - _currentInteractable.Interact(OwnerClientId); + _currentInteractable.Interact(LocalPlayerId); } } - // ======================================== - // Animation Event 함수들 - // ======================================== + [Rpc(SendTo.Server)] + private void PlayAnimationServerRpc(string animTrigger) + { + if (_networkAnimator != null && !string.IsNullOrEmpty(animTrigger)) + { + _networkAnimator.SetTrigger(animTrigger); + } + } public void OnEquipTool() { @@ -207,7 +261,7 @@ namespace Northbound public void OnInteractionComplete() { - if (!IsOwner) return; + if (!IsLocalPlayer) return; if (_interactionTimeoutCoroutine != null) { @@ -217,10 +271,6 @@ namespace Northbound _isInteracting = false; } - // ======================================== - // 내부 헬퍼 함수들 - // ======================================== - private void AttachEquipment(string socketName = null) { if (_equipmentSocket == null || _pendingEquipmentData == null) @@ -283,7 +333,13 @@ namespace Northbound private void OnGUI() { - if (!IsOwner) return; + if (!IsLocalPlayer) return; + + // 디버그: _mainCamera 상태 확인 + if (_mainCamera == null) + { + _mainCamera = Camera.main; + } GUIStyle style = new GUIStyle(GUI.skin.label) { @@ -299,7 +355,7 @@ namespace Northbound prompt = _currentInteractable.GetInteractionPrompt(); bool isResource = _currentInteractable is Resource; - if (isResource && assignedWorker != null && assignedWorker.OwnerPlayerId == OwnerClientId) + if (isResource && assignedWorker != null && assignedWorker.OwnerPlayerId == LocalPlayerId) { prompt += " (워커 할당 가능)"; } @@ -311,7 +367,7 @@ namespace Northbound } } - if (assignedWorker != null && assignedWorker.OwnerPlayerId == OwnerClientId) + if (assignedWorker != null && assignedWorker.OwnerPlayerId == LocalPlayerId) { string workerStatus = assignedWorker.CurrentState switch { diff --git a/Assets/Scripts/PlayerResourceInventory.cs b/Assets/Scripts/PlayerResourceInventory.cs index 3bde825..bfef347 100644 --- a/Assets/Scripts/PlayerResourceInventory.cs +++ b/Assets/Scripts/PlayerResourceInventory.cs @@ -11,32 +11,65 @@ namespace Northbound public int CurrentResourceAmount => _displayAmount; public int MaxResourceCapacity => maxResourceCapacity; + private NetworkPlayerController _networkPlayerController; + + private bool IsLocalPlayer => _networkPlayerController != null && _networkPlayerController.IsLocalPlayer; + private ulong LocalPlayerId => _networkPlayerController != null ? _networkPlayerController.OwnerPlayerId : OwnerClientId; + + private void Awake() + { + _networkPlayerController = GetComponent(); + } + [Rpc(SendTo.Server)] - private void SetMaxCapacityServerRpc(int maxCapacity) + private void SetMaxCapacityServerRpc(int maxCapacity, ulong playerId) { var resourceManager = ServerResourceManager.Instance; if (resourceManager != null) { - resourceManager.SetPlayerMaxCapacity(OwnerClientId, maxCapacity); + resourceManager.SetPlayerMaxCapacity(playerId, maxCapacity); } } public override void OnNetworkSpawn() { - if (IsClient && IsOwner) + // _ownerPlayerId 변경 이벤트 구독 + if (_networkPlayerController != null) { - SetMaxCapacityServerRpc(maxResourceCapacity); - RequestResourceUpdateServerRpc(); + _networkPlayerController.OnOwnerChanged += OnOwnerPlayerIdChanged; + } + + TryInitialize(); + } + + private void OnOwnerPlayerIdChanged(ulong newOwnerId) + { + TryInitialize(); + } + + private void TryInitialize() + { + if (!IsLocalPlayer) return; + + SetMaxCapacityServerRpc(maxResourceCapacity, LocalPlayerId); + RequestResourceUpdateServerRpc(LocalPlayerId); + } + + public override void OnNetworkDespawn() + { + if (_networkPlayerController != null) + { + _networkPlayerController.OnOwnerChanged -= OnOwnerPlayerIdChanged; } } [Rpc(SendTo.Server)] - public void RequestResourceUpdateServerRpc() + public void RequestResourceUpdateServerRpc(ulong playerId) { var resourceManager = ServerResourceManager.Instance; if (resourceManager != null) { - int amount = resourceManager.GetPlayerResourceAmount(OwnerClientId); + int amount = resourceManager.GetPlayerResourceAmount(playerId); UpdateResourceAmountClientRpc(amount); } } @@ -57,7 +90,7 @@ namespace Northbound var resourceManager = ServerResourceManager.Instance; if (resourceManager != null) { - return resourceManager.CanAddResource(OwnerClientId, amount); + return resourceManager.CanAddResource(LocalPlayerId, amount); } return false; } @@ -67,7 +100,7 @@ namespace Northbound var resourceManager = ServerResourceManager.Instance; if (resourceManager != null) { - return resourceManager.GetAvailableSpace(OwnerClientId); + return resourceManager.GetAvailableSpace(LocalPlayerId); } return 0; } diff --git a/Assets/Scripts/Resource.cs b/Assets/Scripts/Resource.cs index 5966b7c..733139d 100644 --- a/Assets/Scripts/Resource.cs +++ b/Assets/Scripts/Resource.cs @@ -235,14 +235,45 @@ namespace Northbound if (Time.time - _lastGatheringTime < gatheringCooldown) return false; + // 플레이어의 자원 공간 확인 + int availableSpace = GetPlayerAvailableSpace(playerId); + if (availableSpace <= 0) + return false; + + return true; + } + + /// + /// 플레이어의 자원 공간 확인 (서버와 클라이언트 모두 지원) + /// + private int GetPlayerAvailableSpace(ulong playerId) + { + // 서버에서는 ServerResourceManager 사용 var resourceManager = ServerResourceManager.Instance; if (resourceManager != null) { - if (resourceManager.GetAvailableSpace(playerId) <= 0) - return false; + return resourceManager.GetAvailableSpace(playerId); } - return true; + // 클라이언트에서는 PlayerResourceInventory 사용 + if (NetworkManager.Singleton != null) + { + var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects; + foreach (var kvp in spawnedObjects) + { + var controller = kvp.Value.GetComponent(); + if (controller != null && controller.OwnerPlayerId == playerId) + { + var inventory = kvp.Value.GetComponent(); + if (inventory != null) + { + return inventory.MaxResourceCapacity - inventory.CurrentResourceAmount; + } + } + } + } + + return 0; } public void Interact(ulong playerId) @@ -324,15 +355,28 @@ namespace Northbound ShowGatheringEffectClientRpc(); } - [Rpc(SendTo.Owner)] + [Rpc(SendTo.ClientsAndHost)] private void UpdatePlayerResourcesClientRpc(ulong playerId) { - var playerObject = NetworkManager.Singleton.ConnectedClients[playerId]?.PlayerObject; - if (playerObject == null) + // 해당 플레이어만 업데이트 + if (NetworkManager.Singleton.LocalClientId != playerId) return; - var playerInventory = playerObject.GetComponent(); - playerInventory?.RequestResourceUpdateServerRpc(); + // 로컬 플레이어의 PlayerResourceInventory 찾아서 업데이트 요청 + var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects; + foreach (var kvp in spawnedObjects) + { + var controller = kvp.Value.GetComponent(); + if (controller != null && controller.IsLocalPlayer) + { + var inventory = kvp.Value.GetComponent(); + if (inventory != null) + { + inventory.RequestResourceUpdateServerRpc(playerId); + return; + } + } + } } [Rpc(SendTo.NotServer)] diff --git a/Assets/Scripts/ResourcePickup.cs b/Assets/Scripts/ResourcePickup.cs index 15bede0..91f907c 100644 --- a/Assets/Scripts/ResourcePickup.cs +++ b/Assets/Scripts/ResourcePickup.cs @@ -88,15 +88,28 @@ namespace Northbound Invoke(nameof(DestroyPickup), 0.1f); } - [Rpc(SendTo.Owner)] + [Rpc(SendTo.ClientsAndHost)] private void UpdatePlayerResourcesClientRpc(ulong playerId) { - var playerObject = NetworkManager.Singleton.ConnectedClients[playerId]?.PlayerObject; - if (playerObject == null) + // 해당 플레이어만 업데이트 + if (NetworkManager.Singleton.LocalClientId != playerId) return; - var playerInventory = playerObject.GetComponent(); - playerInventory?.RequestResourceUpdateServerRpc(); + // 로컬 플레이어의 PlayerResourceInventory 찾아서 업데이트 요청 + var spawnedObjects = NetworkManager.Singleton.SpawnManager.SpawnedObjects; + foreach (var kvp in spawnedObjects) + { + var controller = kvp.Value.GetComponent(); + if (controller != null && controller.IsLocalPlayer) + { + var inventory = kvp.Value.GetComponent(); + if (inventory != null) + { + inventory.RequestResourceUpdateServerRpc(playerId); + return; + } + } + } } [Rpc(SendTo.ClientsAndHost)]