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(); if (vcam != null) { // 2. 카메라의 Follow와 LookAt 대상을 '나'로 설정합니다. vcam.Follow = transform; vcam.LookAt = transform; Debug.Log("[Camera] 로컬 플레이어에게 카메라가 연결되었습니다."); } _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(); _animator = GetComponent(); _traveler = GetComponent(); } 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(); 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(); 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(); 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(); if (site != null) { site.AdvanceConstruction(Time.deltaTime * buildSpeedMultiplier); } } } public override void OnNetworkDespawn() { if (IsOwner && _inputActions != null) _inputActions.Disable(); } }