Files
ProjectMD/Assets/Scripts/Player/PlayerNetworkController.cs
Dal4segno d6292b6879 멀티플레이어 지원
이동, 건설, 인터랙션, 공격 등
2026-01-16 19:30:26 +09:00

173 lines
5.9 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
using Unity.Netcode; // NGO 필수 네임스페이스
using Unity.Netcode.Components;
[RequireComponent(typeof(NetworkObject))]
public class PlayerNetworkController : NetworkBehaviour
{
[Header("Movement Settings")]
public float moveSpeed = 5f;
public float rotationSpeed = 10f;
public float jumpHeight = 1.5f;
public float gravity = -19.62f;
[Header("Interaction Settings")]
[SerializeField] private float interactRange = 3f;
[SerializeField] private LayerMask interactableLayer;
[SerializeField] private LayerMask constructionLayer;
[SerializeField] private float buildSpeedMultiplier = 2f;
[Header("Mining Settings")]
[SerializeField] private float attackRange = 1.5f;
[SerializeField] private int miningDamage = 25;
[SerializeField] private LayerMask mineableLayer; // 'Mineable' 레이어 설정 필요
private CharacterController _controller;
private PlayerInputActions _inputActions;
private Animator _animator;
private TunnelTraveler _traveler;
private Vector2 _moveInput;
private Vector3 _velocity;
private Vector3 _currentMoveDir;
private bool _isGrounded;
private bool _isHoldingInteract = false;
// NGO에서 Start 대신 사용하는 네트워크 초기화 메서드
public override void OnNetworkSpawn()
{
// 내 캐릭터가 아니라면 입력을 활성화하지 않습니다.
if (!IsOwner) return;
_inputActions = new PlayerInputActions();
_inputActions.Player.Jump.performed += ctx => OnJump();
_inputActions.Player.Attack.performed += ctx => OnAttackServerRpc(); // 서버에 공격 요청
_inputActions.Player.Interact.performed += ctx => OnInteractTap();
_inputActions.Player.Interact.started += ctx => _isHoldingInteract = true;
_inputActions.Player.Interact.canceled += ctx => _isHoldingInteract = false;
_inputActions.Enable();
}
void Awake()
{
_controller = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
_traveler = GetComponent<TunnelTraveler>();
}
void Update()
{
// [중요] '나'의 캐릭터가 아니면 조종 로직을 아예 실행하지 않습니다.
if (!IsOwner) return;
if (_traveler != null && _traveler.IsTraveling) return;
HandleGravity();
HandleMovement();
// 건설 가속 로직
if (_isHoldingInteract) PerformConstructionSupport();
}
private void HandleMovement()
{
_isGrounded = _controller.isGrounded;
_animator.SetBool("isGrounded", _isGrounded);
bool isAttacking = _animator.GetCurrentAnimatorStateInfo(0).IsTag("Attack");
_moveInput = _inputActions.Player.Move.ReadValue<Vector2>();
Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y).normalized;
// 지상/공중 및 공격 중 관성 처리
if (isAttacking) move = _isGrounded ? Vector3.zero : _currentMoveDir;
else if (move.magnitude > 0.1f) _currentMoveDir = move;
if (move.magnitude >= 0.1f)
{
if (!isAttacking)
{
Quaternion targetRotation = Quaternion.LookRotation(move);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
}
_controller.Move(move * moveSpeed * Time.deltaTime);
}
_animator.SetFloat("MoveSpeed", isAttacking && _isGrounded ? 0 : move.magnitude);
}
private void HandleGravity()
{
if (_isGrounded && _velocity.y < 0) _velocity.y = -2f;
_velocity.y += gravity * Time.deltaTime;
_controller.Move(_velocity * Time.deltaTime);
}
private void OnJump()
{
if (_isGrounded) _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
// 기존 OnAttackServerRpc를 수정하거나 호출되는 시점에 아래 로직 포함
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
private void OnAttackServerRpc()
{
// 모든 유저에게 애니메이션 재생 신호 전송
OnAttackClientRpc();
// [채광 판정] 서버에서 물리 연산을 통해 전방의 블록 탐색
Collider[] hitBlocks = Physics.OverlapSphere(transform.position + transform.forward, attackRange, mineableLayer);
foreach (var col in hitBlocks)
{
MineableBlock block = col.GetComponentInParent<MineableBlock>();
if (block != null)
{
// 서버가 직접 블록의 대미지 함수 호출
block.TakeDamageRpc(miningDamage);
}
}
}
[ClientRpc]
private void OnAttackClientRpc()
{
_animator.SetTrigger("Attack");
}
private void OnInteractTap()
{
_animator.SetTrigger("Interact");
Collider[] colliders = Physics.OverlapSphere(transform.position, interactRange, interactableLayer);
foreach (var col in colliders)
{
IInteractable interactable = col.GetComponentInParent<IInteractable>();
if (interactable != null)
{
interactable.Interact(gameObject);
break;
}
}
}
private void PerformConstructionSupport()
{
Collider[] targets = Physics.OverlapSphere(transform.position, interactRange, constructionLayer);
foreach (var col in targets)
{
ConstructionSite site = col.GetComponentInParent<ConstructionSite>();
if (site != null)
{
// 건설 진행도는 서버에서 관리하는 것이 안전하므로 나중에 RPC로 전환 필요
site.AdvanceConstruction(Time.deltaTime * buildSpeedMultiplier);
}
}
}
public override void OnNetworkDespawn()
{
if (IsOwner) _inputActions.Disable();
}
}