모든 네트워크 오브젝트의 소유권을 서버가 갖도록 함
Distributable -> None 관련 사이드 이펙트로 인한 버그 수정
This commit is contained in:
@@ -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<ulong> _ownerPlayerId = new NetworkVariable<ulong>(
|
||||
ulong.MaxValue,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
|
||||
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<ulong> OnOwnerChanged;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_controller = GetComponent<CharacterController>();
|
||||
_animator = GetComponent<Animator>();
|
||||
_networkAnimator = GetComponent<NetworkAnimator>();
|
||||
}
|
||||
|
||||
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<CinemachineCamera>();
|
||||
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<CinemachineCamera>();
|
||||
|
||||
if (vcam != null)
|
||||
{
|
||||
vcam.Follow = transform;
|
||||
vcam.LookAt = transform;
|
||||
}
|
||||
|
||||
_inputActions = new PlayerInputActions();
|
||||
_inputActions.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 플레이어 초기화 (서버에서 호출)
|
||||
/// </summary>
|
||||
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<Vector2>();
|
||||
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<PlayerSpawnPoint>(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
|
||||
|
||||
/// <summary>
|
||||
/// 현재 체력
|
||||
/// </summary>
|
||||
public int GetCurrentHealth() => _currentHealth.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 최대 체력
|
||||
/// </summary>
|
||||
public int GetMaxHealth() => maxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// 체력 비율 (0.0 ~ 1.0)
|
||||
/// </summary>
|
||||
public float GetHealthPercentage()
|
||||
{
|
||||
return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 죽었는지 여부
|
||||
/// </summary>
|
||||
public bool IsDead() => _currentHealth.Value <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// 체력 회복
|
||||
/// </summary>
|
||||
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 업데이트 등
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user