인터랙션 시스템 및 터널 기능 생성

This commit is contained in:
2026-01-15 16:45:14 +09:00
parent f70223a4aa
commit b8ecb9a24c
12 changed files with 959 additions and 37 deletions

View File

@@ -0,0 +1,79 @@
using UnityEngine;
using UnityEngine.InputSystem; // New Input System 네임스페이스
public class PlayerInteraction : MonoBehaviour
{
[Header("Detection Settings")]
[SerializeField] private float interactionRadius = 2.5f;
[SerializeField] private LayerMask interactableLayer;
private PlayerInputActions _inputActions;
void Awake()
{
_inputActions = new PlayerInputActions();
}
void OnEnable()
{
// Interact 액션이 수행되었을 때(버튼을 눌렀을 때) 실행될 함수 연결
_inputActions.Player.Interact.performed += OnInteractPerformed;
_inputActions.Enable();
}
void OnDisable()
{
// 이벤트 연결 해제 및 비활성화
_inputActions.Player.Interact.performed -= OnInteractPerformed;
_inputActions.Disable();
}
// Input Action 콜백 함수
private void OnInteractPerformed(InputAction.CallbackContext context)
{
Debug.Log("E 키 눌림!"); // <-- 이게 콘솔에 찍히나요?
// 건설 모드 중일 때는 상호작용을 막고 싶다면 아래 조건 추가
if (BuildManager.Instance.IsBuildMode) return;
CheckAndInteract();
}
private void CheckAndInteract()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, interactionRadius, interactableLayer);
Debug.Log($"주변에서 {colliders.Length}개의 물체 감지됨"); // 0이 나오면 레이어나 콜라이더 문제
IInteractable nearestInteractable = null;
float minDistance = Mathf.Infinity;
foreach (var col in colliders)
{
// 부모까지 포함하여 IInteractable 인터페이스를 찾음
IInteractable interactable = col.GetComponentInParent<IInteractable>();
if (interactable != null)
{
Debug.Log($"{col.name}에서 인터페이스 발견!");
float distance = Vector3.Distance(transform.position, col.transform.position);
if (distance < minDistance)
{
minDistance = distance;
nearestInteractable = interactable;
}
}
}
if (nearestInteractable != null)
{
nearestInteractable.Interact(gameObject);
Debug.Log($"[Interaction] {nearestInteractable.GetInteractionText()} 실행");
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, interactionRadius);
}
}

View File

@@ -0,0 +1,91 @@
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerInteractionController : MonoBehaviour
{
[Header("Detection Settings")]
[SerializeField] private float range = 3f;
[SerializeField] private LayerMask interactableLayer; // Tunnel용 레이어
[SerializeField] private LayerMask constructionLayer; // 건설 토대용 레이어
[Header("Build Settings")]
[SerializeField] private float buildSpeedMultiplier = 2f;
private PlayerInputActions _inputActions;
private bool _isHoldingInteract = false;
void Awake()
{
_inputActions = new PlayerInputActions();
// 탭(짧게 누르기) 시점 체크
_inputActions.Player.Interact.performed += OnInteractTap;
// 홀드(꾹 누르기) 시작/종료 체크
_inputActions.Player.Interact.started += ctx => {
_isHoldingInteract = true;
Debug.Log("인터랙션 버튼 누르기 시작 (건설 가속 준비)");
};
_inputActions.Player.Interact.canceled += ctx => {
_isHoldingInteract = false;
Debug.Log("인터랙션 버튼 뗌");
};
}
void OnEnable() => _inputActions.Enable();
void OnDisable() => _inputActions.Disable();
void Update()
{
if (_isHoldingInteract)
{
PerformConstructionSupport();
}
}
private void OnInteractTap(InputAction.CallbackContext context)
{
Debug.Log("E 키 탭 감지! 주변 탐색 시작...");
// 1. 주변 모든 콜라이더 가져오기 (레이어 마스크 없이 먼저 테스트해보고 싶다면 0 대신 ~0 입력)
Collider[] colliders = Physics.OverlapSphere(transform.position, range, interactableLayer);
Debug.Log($"주변 {interactableLayer.value} 레이어에서 {colliders.Length}개의 오브젝트 감지됨");
foreach (var col in colliders)
{
// 2. 인터페이스 찾기 (본인 또는 부모에게서)
IInteractable interactable = col.GetComponentInParent<IInteractable>();
if (interactable != null)
{
Debug.Log($"[성공] {col.name}에서 IInteractable 발견! 터널 진입합니다.");
interactable.Interact(gameObject);
_isHoldingInteract = false; // 이동 중에는 건설 지원 중단
return;
}
else
{
Debug.Log($"[실패] {col.name} 감지되었으나 IInteractable 스크립트가 없음");
}
}
}
private void PerformConstructionSupport()
{
Collider[] targets = Physics.OverlapSphere(transform.position, range, constructionLayer);
foreach (var col in targets)
{
ConstructionSite site = col.GetComponent<ConstructionSite>();
if (site != null)
{
site.AdvanceConstruction(Time.deltaTime * buildSpeedMultiplier);
}
}
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, range);
}
}

View File

@@ -3,22 +3,25 @@ using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
private Vector2 _moveInput;
[Header("Movement Settings")]
public float moveSpeed = 5f;
public float jumpHeight = 1.5f; // 점프 높이
public float gravity = -19.62f; // 기본 중력보다 약간 무거운 값 추천
private CharacterController _controller;
private PlayerInputActions _inputActions;
private Vector3 _velocity;
public float gravity = -19.62f; // 조금 더 묵직하게 설정
private Vector2 _moveInput;
private Vector3 _velocity; // 수직 속도 (중력용)
private bool _isGrounded;
void Awake()
{
_controller = GetComponent<CharacterController>();
_inputActions = new PlayerInputActions();
// Move 액션 연결 (Vector2 타입으로 설정되어 있어야 함)
_inputActions.Player.Move.performed += ctx => _moveInput = ctx.ReadValue<Vector2>();
_inputActions.Player.Move.canceled += ctx => _moveInput = Vector2.zero;
// 점프 액션 연결
_inputActions.Player.Jump.performed += ctx => OnJump();
}
void OnEnable() => _inputActions.Enable();
@@ -26,29 +29,46 @@ public class PlayerMovement : MonoBehaviour
void Update()
{
// 1. 수직 속도(중력) 계산
if (_controller.isGrounded && _velocity.y < 0)
// [해결책] 터널 이동 중이라면 일반 이동/중력 로직을 모두 중단합니다.
if (GetComponent<TunnelTraveler>().IsTraveling)
{
// 바닥에 닿아있을 때 속도를 아주 작게 유지 (경사로 등에서 안정적)
return;
}
// 1. 바닥 체크 (CharacterController의 기능 활용)
_isGrounded = _controller.isGrounded;
if (_isGrounded && _velocity.y < 0)
{
// 바닥에 닿아있을 때는 아주 작은 하방 힘만 유지 (안정성)
_velocity.y = -2f;
}
// 2. 입력받은 방향으로 평면 이동 계산
Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y);
// 2. 이동 로직
_moveInput = _inputActions.Player.Move.ReadValue<Vector2>();
Vector3 move = transform.right * _moveInput.x + transform.forward * _moveInput.y;
_controller.Move(move * moveSpeed * Time.deltaTime);
// 수평 이동 실행
_controller.Move(move * Time.deltaTime * moveSpeed);
// 3. 중력 가속도 적용 (v = v + g * t)
// 3. 중력 적용
_velocity.y += gravity * Time.deltaTime;
// 4. 수직 이동(중력) 실행 (s = v * t)
// 4. 최종 수직 이동 적용 (중력/점프 속도)
_controller.Move(_velocity * Time.deltaTime);
}
// 이동 중일 때 바라보는 방향 전환
if (move != Vector3.zero)
private void OnJump()
{
// TunnelTraveler가 이동 중인지 체크 (상태 변수 활용)
if (GetComponent<TunnelTraveler>().IsTraveling) return;
if (_isGrounded)
{
transform.forward = move;
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
}
// 터널 이동 등 외부에서 중력을 초기화해야 할 때 사용
public void ResetVelocity()
{
_velocity.y = 0;
}
}