diff --git a/Assets/Scripts/Player/Controller/PlayerMovement.cs b/Assets/Scripts/Player/Controller/PlayerMovement.cs
index 462fd8e4..63d681d8 100644
--- a/Assets/Scripts/Player/Controller/PlayerMovement.cs
+++ b/Assets/Scripts/Player/Controller/PlayerMovement.cs
@@ -1,33 +1,74 @@
using UnityEngine;
using System.Collections;
+///
+/// CharacterController 기반 플레이어 이동 (완전판)
+/// - 벽 뚫림 방지
+/// - 물리 오브젝트 밀기
+/// - 중력 처리
+/// - 대시 충돌 감지
+///
+[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : MonoBehaviour
{
- [Header("--- 참조 ---")]
+ [Header("=== 참조 ===")]
[SerializeField] private Stats stats;
[SerializeField] private PlayerHealth health;
[SerializeField] private PlayerAnimator pAnim;
[SerializeField] private PlayerAttack attackScript;
- [Header("--- 대시 설정 ---")]
- // ⭐ 유저님 이미지(image_42ccbd.png) 수치를 기본값으로 적용했습니다.
+ [Header("=== CharacterController ===")]
+ private CharacterController _controller;
+
+ [Header("=== 대시 설정 ===")]
[SerializeField] private float dashDistance = 3f;
[SerializeField] private float dashDuration = 0.08f;
[SerializeField] private float dashCooldown = 1.5f;
- [Header("--- 차징 감속 설정 ---")]
+ [Header("=== 차징 감속 설정 ===")]
[Range(0.1f, 1f)]
[SerializeField] private float minSpeedMultiplier = 0.3f;
+ [Header("=== 중력 설정 ===")]
+ [SerializeField] private float gravity = -20f;
+
+ [Header("=== 물리 상호작용 설정 ===")]
+ [Tooltip("일반 이동 시 물체 밀기 힘")]
+ [SerializeField] private float pushPower = 2f;
+
+ [Tooltip("대시 중 물체 밀기 힘 (더 강함)")]
+ [SerializeField] private float dashPushPower = 5f;
+
+ [Tooltip("밀 수 있는 최대 질량 (kg)")]
+ [SerializeField] private float maxPushMass = 10f;
+
+ // 이동 상태
private Vector3 _moveDir;
private bool _isSprinting;
private bool _isDashing;
private float _lastDashTime;
+ private float _verticalVelocity;
- // ⭐ PlayerInput에서 호출하는 입력 세팅
- public void SetMoveInput(Vector3 dir, bool sprint) { _moveDir = dir; _isSprinting = sprint; }
+ private void Awake()
+ {
+ _controller = GetComponent();
+
+ if (_controller == null)
+ {
+ Debug.LogError("[PlayerMovement] CharacterController가 필요합니다!");
+ }
+ }
+
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ // 입력 처리
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ public void SetMoveInput(Vector3 dir, bool sprint)
+ {
+ _moveDir = dir;
+ _isSprinting = sprint;
+ }
- // ⭐ PlayerInput에서 Space를 누를 때 호출하는 대시 시도 함수
public void AttemptDash()
{
if (CanDash()) StartCoroutine(DashRoutine());
@@ -35,29 +76,34 @@ public class PlayerMovement : MonoBehaviour
private bool CanDash()
{
- // 쿨타임 체크 + 대시 중 아님 + 사망 아님
bool isCooldownOver = Time.time >= _lastDashTime + dashCooldown;
bool isAlive = health != null && !health.IsDead;
return isCooldownOver && !_isDashing && isAlive;
}
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ // 메인 이동
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
private void Update()
{
- // 1. 💀 사망, ⚡ 대시 중, 🤕 혹은 피격(isHit) 중일 때 이동 차단
+ // 1. 사망/대시/피격 중 이동 차단
if (health != null && (health.IsDead || health.isHit || _isDashing))
{
if (pAnim != null && !health.IsDead) pAnim.UpdateMove(0f);
+ ApplyGravityOnly();
return;
}
- // 2. ⚔️ 공격 중(콤보 및 후딜레이)일 때 이동 차단
+ // 2. 공격 중 이동 차단
if (attackScript != null && attackScript.IsAttacking)
{
if (pAnim != null) pAnim.UpdateMove(0f);
+ ApplyGravityOnly();
return;
}
- // 3. 이동 속도 계산 및 차징 감속 적용
+ // 3. 이동 속도 계산
float speed = _isSprinting ? stats.CurrentRunSpeed : stats.CurrentMoveSpeed;
if (attackScript != null && attackScript.IsCharging)
@@ -66,41 +112,115 @@ public class PlayerMovement : MonoBehaviour
speed *= speedReduction;
}
- // 4. 실제 이동 처리
- transform.Translate(_moveDir * speed * Time.deltaTime, Space.World);
+ // 4. ✅ CharacterController로 이동 (벽 충돌 처리!)
+ Vector3 motion = _moveDir * speed * Time.deltaTime;
- // 5. 애니메이션 연동
- if (pAnim != null)
+ ApplyGravity();
+ motion.y = _verticalVelocity * Time.deltaTime;
+
+ _controller.Move(motion);
+
+ // 5. 애니메이션
+ UpdateAnimation();
+ }
+
+ private void ApplyGravity()
+ {
+ if (_controller.isGrounded)
{
- float animVal = _moveDir.magnitude > 0.1f ? (_isSprinting ? 1.0f : 0.5f) : 0f;
- if (attackScript != null && attackScript.IsCharging) animVal *= 0.5f;
- pAnim.UpdateMove(animVal);
+ _verticalVelocity = -2f;
+ }
+ else
+ {
+ _verticalVelocity += gravity * Time.deltaTime;
}
}
- // ⭐ [핵심] 방향성 대시 및 무적 처리 로직
+ private void ApplyGravityOnly()
+ {
+ ApplyGravity();
+ _controller.Move(new Vector3(0, _verticalVelocity * Time.deltaTime, 0));
+ }
+
+ private void UpdateAnimation()
+ {
+ if (pAnim == null) return;
+
+ float animVal = _moveDir.magnitude > 0.1f ? (_isSprinting ? 1.0f : 0.5f) : 0f;
+ if (attackScript != null && attackScript.IsCharging) animVal *= 0.5f;
+
+ pAnim.UpdateMove(animVal);
+ }
+
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ // 대시
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
private IEnumerator DashRoutine()
{
_isDashing = true;
_lastDashTime = Time.time;
- // 입력 방향이 있으면 그쪽으로, 없으면 캐릭터의 정면이 아닌 후방(회피)으로 대시
Vector3 dashDir = _moveDir.sqrMagnitude > 0.001f ? _moveDir : -transform.forward;
- // 🛡️ [무적] 대시 시작 시 무적 상태 활성화
if (health != null) health.isInvincible = true;
float startTime = Time.time;
while (Time.time < startTime + dashDuration)
{
- // 속도 = 거리 / 시간
float speed = dashDistance / dashDuration;
- transform.Translate(dashDir * speed * Time.deltaTime, Space.World);
+ Vector3 dashMotion = dashDir * speed * Time.deltaTime;
+
+ // ✅ 벽 충돌 감지
+ CollisionFlags flags = _controller.Move(dashMotion);
+
+ // 벽에 부딪히면 대시 조기 종료 (선택사항)
+ // if ((flags & CollisionFlags.Sides) != 0) break;
+
yield return null;
}
- // 🛡️ [무적] 대시 종료 시 무적 상태 해제
if (health != null) health.isInvincible = false;
_isDashing = false;
}
+
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ // ⭐ 물리 상호작용 (핵심!)
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ ///
+ /// CharacterController가 물체와 충돌할 때 호출됨
+ ///
+ private void OnControllerColliderHit(ControllerColliderHit hit)
+ {
+ // Rigidbody 확인
+ Rigidbody body = hit.collider.attachedRigidbody;
+
+ // Rigidbody 없거나 Kinematic이면 밀 수 없음
+ if (body == null || body.isKinematic) return;
+
+ // 너무 무거운 물체는 밀 수 없음
+ if (body.mass > maxPushMass) return;
+
+ // 바닥/천장 충돌은 무시
+ if (hit.moveDirection.y < -0.3f || hit.moveDirection.y > 0.3f) return;
+
+ // 💪 밀기 방향 계산
+ Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
+ pushDir.Normalize();
+
+ // 대시 중이면 더 강한 힘
+ float power = _isDashing ? dashPushPower : pushPower;
+
+ // ✅ 힘 적용!
+ body.AddForce(pushDir * power, ForceMode.Impulse);
+ }
+
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ // 유틸리티
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ public bool IsGrounded() => _controller.isGrounded;
+ public bool IsDashing() => _isDashing;
+ public float GetCurrentSpeed() => _contzroller.velocity.magnitude;
}
\ No newline at end of file