2026-02-12 15:23:25 +00:00
|
|
|
|
using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을
|
2026-02-01 15:49:12 +00:00
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
|
public class PlayerInput : MonoBehaviour // 클래스를 선언할거에요 -> MonoBehaviour를 상속받는 PlayerInput을
|
2026-01-29 06:58:38 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 다른 스크립트에게 명령을 내려야 하니, 미리 참조 시킴
|
2026-02-12 15:23:25 +00:00
|
|
|
|
[SerializeField] private PlayerHealth health; // 변수를 선언할거에요 -> 죽었는지 확인할 health를
|
|
|
|
|
|
[SerializeField] private PlayerMovement movement; // 변수를 선언할거에요 -> 이동 명령을 내릴 movement를
|
|
|
|
|
|
[SerializeField] private PlayerAim aim; // 변수를 선언할거에요 -> 회전 명령을 내릴 aim을
|
|
|
|
|
|
[SerializeField] private PlayerInteraction interaction; // 변수를 선언할거에요 -> 상호작용 명령을 내릴 interaction을
|
|
|
|
|
|
[SerializeField] private PlayerAttack attack; // 변수를 선언할거에요 -> 공격 명령을 내릴 attack을
|
|
|
|
|
|
[SerializeField] private PlayerStatsUI statsUI; // 변수를 선언할거에요 -> UI 토글을 위한 statsUI를
|
2026-01-30 03:24:13 +00:00
|
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
|
// ── 입력 잠금 (보스 연출, 컷씬 등에서 사용) ──────────────────
|
|
|
|
|
|
private bool _isInputLocked = false; // 변수를 선언할거에요 -> 입력 잠금 여부를 (true면 모든 입력 차단)
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 플레이어 입력을 잠가요. 보스 연출, 컷씬 등에서 호출하세요.
|
|
|
|
|
|
/// 잠금 중에는 이동, 공격, 상호작용 등 모든 입력이 무시돼요.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void LockInput() // 함수를 선언할거에요 -> 입력을 잠그는 (외부에서 호출 가능)
|
|
|
|
|
|
{
|
|
|
|
|
|
_isInputLocked = true; // 설정할거에요 -> 입력 잠금 켜기
|
|
|
|
|
|
// 잠금 즉시 이동 입력을 0으로 초기화 (관성으로 미끄러지는 것 방지)
|
|
|
|
|
|
if (movement != null) movement.SetMoveInput(Vector3.zero, false); // 실행할거에요 -> 이동 정지를
|
|
|
|
|
|
Debug.Log("[PlayerInput] 입력 잠금 ON"); // 로그를 찍을거에요
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 플레이어 입력 잠금을 해제해요. 연출 종료 후 호출하세요.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void UnlockInput() // 함수를 선언할거에요 -> 입력 잠금을 해제하는 (외부에서 호출 가능)
|
|
|
|
|
|
{
|
|
|
|
|
|
_isInputLocked = false; // 설정할거에요 -> 입력 잠금 끄기
|
|
|
|
|
|
Debug.Log("[PlayerInput] 입력 잠금 OFF"); // 로그를 찍을거에요
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 현재 입력이 잠겨있는지 확인 (읽기 전용) </summary>
|
|
|
|
|
|
public bool IsInputLocked => _isInputLocked; // 프로퍼티를 선언할거에요 -> 잠금 상태 조회용
|
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
|
private void Update() // 함수를 실행할거에요 -> 매 프레임마다 Update를
|
2026-01-29 06:58:38 +00:00
|
|
|
|
{
|
2026-03-22 03:31:16 +00:00
|
|
|
|
// 0. 입력 잠금 체크
|
|
|
|
|
|
// 보스 연출, 컷씬 등에서 입력이 잠겨있으면 모든 입력을 차단해요
|
|
|
|
|
|
if (_isInputLocked) return; // 조건이 맞으면 중단할거에요 -> 입력이 잠겨있다면
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 사망 체크
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 죽었는데 키보드 눌린다고 시체가 움직이면 안 되니까 아예 입력을 차단(return)함
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (health != null && health.IsDead) return; // 조건이 맞으면 중단할거에요 -> 플레이어가 죽었다면
|
2026-01-30 03:24:13 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 2. UI 토글 (C키)
|
|
|
|
|
|
// 스탯 창을 껐다 켰다 함
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (Input.GetKeyDown(KeyCode.C) && statsUI != null) statsUI.ToggleWindow(); // 조건이 맞으면 실행할거에요 -> C키를 눌렀다면 스탯창 토글을
|
2026-01-30 03:24:13 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 3. 이동 입력 감지
|
|
|
|
|
|
// GetAxisRaw를 쓴 이유: 0에서 1로 부드럽게 변하는 게 아니라,
|
|
|
|
|
|
// 키를 누르면 즉시 1, 떼면 0이 되어서 빠릿빠릿한 조작감을 줌
|
2026-02-12 15:23:25 +00:00
|
|
|
|
float h = Input.GetAxisRaw("Horizontal"); // 값을 가져올거에요 -> 좌우 입력값(-1, 0, 1)을 h에
|
|
|
|
|
|
float v = Input.GetAxisRaw("Vertical"); // 값을 가져올거에요 -> 상하 입력값(-1, 0, 1)을 v에
|
|
|
|
|
|
bool sprint = Input.GetKey(KeyCode.LeftShift); // 값을 가져올거에요 -> 쉬프트 키 입력 여부를 sprint에
|
2026-02-01 15:49:12 +00:00
|
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
|
// 4. 이동 명령 하달 (★ 카메라 방향 기준 이동)
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (movement != null) // 조건이 맞으면 실행할거에요 -> 이동 스크립트가 있다면
|
2026-02-01 15:49:12 +00:00
|
|
|
|
{
|
2026-03-22 03:31:16 +00:00
|
|
|
|
// 카메라의 앞/오른쪽 방향을 기준으로 이동 방향을 계산해요
|
|
|
|
|
|
// 이렇게 하면 W키가 항상 "화면 기준 앞"으로 이동합니다
|
|
|
|
|
|
Vector3 inputDir = new Vector3(h, 0, v); // 만들거에요 -> 원시 입력 벡터를
|
|
|
|
|
|
Vector3 cameraRelativeDir = ConvertToCameraRelative(inputDir); // 변환할거에요 -> 카메라 기준 방향으로
|
|
|
|
|
|
movement.SetMoveInput(cameraRelativeDir, sprint); // 실행할거에요 -> 카메라 기준 이동 입력 전달을
|
2026-02-01 15:49:12 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// ⭐ [추가] 스페이스바 누르면 대시 명령!
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (Input.GetKeyDown(KeyCode.Space)) movement.AttemptDash(); // 조건이 맞으면 실행할거에요 -> 스페이스바 누르면 대시 시도를
|
2026-02-01 15:49:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 5. 마우스 회전 명령
|
|
|
|
|
|
// 캐릭터가 마우스 커서를 바라보게 합니다.
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (aim != null) aim.RotateTowardsMouse(); // 조건이 맞으면 실행할거에요 -> 마우스 방향 회전 함수를
|
2026-02-03 14:41:49 +00:00
|
|
|
|
|
|
|
|
|
|
// 6. 상호작용 (F키)
|
|
|
|
|
|
// 바닥에 떨어진 무기를 줍기
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (Input.GetKeyDown(KeyCode.F) && interaction != null) interaction.TryInteract(); // 조건이 맞으면 실행할거에요 -> F키 누르면 상호작용 시도를
|
2026-01-30 03:24:13 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 7. 공격 입력 (좌클릭/우클릭)
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (attack != null) // 조건이 맞으면 실행할거에요 -> 공격 스크립트가 있다면
|
2026-01-29 06:58:38 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 우클릭 꾹: 차징(모으기) 시작
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (Input.GetMouseButtonDown(1)) attack.StartCharging(); // 조건이 맞으면 실행할거에요 -> 우클릭 누르면 차징 시작을
|
2026-02-03 14:41:49 +00:00
|
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
|
// 우클릭 뗌: 차징된 단계로 부채꼴 발사! (기존: 취소 → 변경: 발사)
|
|
|
|
|
|
if (Input.GetMouseButtonUp(1)) attack.ReleaseAttack(); // 조건이 맞으면 실행할거에요 -> 우클릭 떼면 차징 발사를
|
2026-02-03 14:41:49 +00:00
|
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
|
// 좌클릭: 차징 중이 아닐 때만 일반 공격
|
2026-02-12 15:23:25 +00:00
|
|
|
|
if (Input.GetMouseButtonDown(0)) // 조건이 맞으면 실행할거에요 -> 좌클릭을 눌렀다면
|
2026-01-29 06:58:38 +00:00
|
|
|
|
{
|
2026-03-22 03:31:16 +00:00
|
|
|
|
if (!attack.IsCharging) // 조건이 맞으면 실행할거에요 -> 차징 중이 아니라면
|
|
|
|
|
|
attack.PerformNormalAttack(); // 실행할거에요 -> 일반 공격을
|
|
|
|
|
|
// 차징 중 좌클릭은 무시 (우클릭 떼기로만 발사)
|
2026-01-29 06:58:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-22 03:31:16 +00:00
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
// 카메라 기준 이동 방향 변환
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// WASD 입력을 카메라가 바라보는 방향 기준으로 변환합니다.
|
|
|
|
|
|
/// W = 카메라 앞, A = 카메라 왼쪽, S = 카메라 뒤, D = 카메라 오른쪽
|
|
|
|
|
|
/// Y축(높이)은 무시하고 수평 방향만 사용합니다.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Vector3 ConvertToCameraRelative(Vector3 rawInput) // 함수를 정의할거에요 -> 카메라 기준 방향 변환을
|
|
|
|
|
|
{
|
|
|
|
|
|
if (rawInput.sqrMagnitude < 0.001f) return Vector3.zero; // 방어할거에요 -> 입력이 없으면 제로 벡터 반환
|
|
|
|
|
|
|
|
|
|
|
|
Camera cam = Camera.main; // 가져올거에요 -> 메인 카메라를
|
|
|
|
|
|
if (cam == null) return rawInput.normalized; // 방어할거에요 -> 카메라 없으면 월드 좌표 그대로 사용
|
|
|
|
|
|
|
|
|
|
|
|
// 카메라의 앞/오른쪽 방향에서 Y 성분을 제거 (수평면만 사용)
|
|
|
|
|
|
Vector3 camForward = cam.transform.forward; // 가져올거에요 -> 카메라의 앞 방향을
|
|
|
|
|
|
camForward.y = 0f; // 제거할거에요 -> 높이 성분을 (수평면에 투영)
|
|
|
|
|
|
camForward.Normalize(); // 정규화할거에요 -> 단위 벡터로
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 camRight = cam.transform.right; // 가져올거에요 -> 카메라의 오른쪽 방향을
|
|
|
|
|
|
camRight.y = 0f; // 제거할거에요 -> 높이 성분을 (수평면에 투영)
|
|
|
|
|
|
camRight.Normalize(); // 정규화할거에요 -> 단위 벡터로
|
|
|
|
|
|
|
|
|
|
|
|
// 입력 × 카메라 방향 = 최종 이동 방향
|
|
|
|
|
|
// W(v=1) → 카메라 앞, D(h=1) → 카메라 오른쪽
|
|
|
|
|
|
Vector3 result = camForward * rawInput.z + camRight * rawInput.x; // 계산할거에요 -> 카메라 기준 최종 방향을
|
|
|
|
|
|
return result.normalized; // 반환할거에요 -> 정규화된 방향을 (대각선 속도 보정)
|
|
|
|
|
|
}
|
2026-01-29 06:58:38 +00:00
|
|
|
|
}
|