189 lines
6.5 KiB
C#
189 lines
6.5 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using Unity.Netcode;
|
|
using Unity.Netcode.Components;
|
|
using Unity.Cinemachine;
|
|
|
|
[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;
|
|
|
|
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 초기화
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
if (!IsOwner) return;
|
|
|
|
// 1. 씬에 있는 가상 카메라를 찾습니다.
|
|
// Unity 6에서는 CinemachineVirtualCamera 대신 CinemachineCamera를 주로 사용합니다.
|
|
var vcam = GameObject.FindAnyObjectByType<CinemachineCamera>();
|
|
|
|
if (vcam != null)
|
|
{
|
|
// 2. 카메라의 Follow와 LookAt 대상을 '나'로 설정합니다.
|
|
vcam.Follow = transform;
|
|
vcam.LookAt = transform;
|
|
Debug.Log("<color=green>[Camera] 로컬 플레이어에게 카메라가 연결되었습니다.</color>");
|
|
}
|
|
|
|
_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);
|
|
}
|
|
|
|
// --- 채광 로직 (기존 유지) ---
|
|
[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()
|
|
{
|
|
if (!IsOwner) return;
|
|
_animator.SetTrigger("Interact");
|
|
|
|
// 1. 캐릭터 주변 반경 내의 모든 콜라이더 감지
|
|
Collider[] colliders = Physics.OverlapSphere(transform.position, interactRange, interactableLayer);
|
|
|
|
IInteractable closestTarget = null;
|
|
float minDistance = float.MaxValue;
|
|
|
|
foreach (var col in colliders)
|
|
{
|
|
// 2. 방향 조건 없이 거리만 체크하여 가장 가까운 것 선택
|
|
float dist = Vector3.Distance(transform.position, col.transform.position);
|
|
if (dist < minDistance)
|
|
{
|
|
IInteractable interactable = col.GetComponentInParent<IInteractable>();
|
|
if (interactable != null)
|
|
{
|
|
minDistance = dist;
|
|
closestTarget = interactable;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. 가장 가까운 대상이 있다면 상호작용 실행
|
|
if (closestTarget != null)
|
|
{
|
|
closestTarget.Interact(gameObject);
|
|
}
|
|
}
|
|
|
|
// 건설 지원 로직 (범위 내 지속 작업이므로 OverlapSphere 유지 또는 Raycast로 변경 가능)
|
|
private void PerformConstructionSupport()
|
|
{
|
|
Collider[] targets = Physics.OverlapSphere(transform.position, interactRange, constructionLayer);
|
|
foreach (var col in targets)
|
|
{
|
|
ConstructionSite site = col.GetComponentInParent<ConstructionSite>();
|
|
if (site != null)
|
|
{
|
|
site.AdvanceConstruction(Time.deltaTime * buildSpeedMultiplier);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
if (IsOwner && _inputActions != null) _inputActions.Disable();
|
|
}
|
|
} |