using UnityEngine; using System.Collections; /// /// CharacterController 기반 플레이어 이동 (완전판) /// - 벽 뚫림 방지 /// - 물리 오브젝트 밀기 /// - 중력 처리 /// - 대시 충돌 감지 /// [RequireComponent(typeof(CharacterController))] public class PlayerMovement : MonoBehaviour { [Header("=== 참조 ===")] [SerializeField] private Stats stats; [SerializeField] private PlayerHealth health; [SerializeField] private PlayerAnimator pAnim; [SerializeField] private PlayerAttack attackScript; [Header("=== CharacterController ===")] private CharacterController _controller; [Header("=== 대시 설정 ===")] [SerializeField] private float dashDistance = 3f; [SerializeField] private float dashDuration = 0.08f; [SerializeField] private float dashCooldown = 1.5f; [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; private void Awake() { _controller = GetComponent(); if (_controller == null) { Debug.LogError("[PlayerMovement] CharacterController가 필요합니다!"); } } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 입력 처리 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ public void SetMoveInput(Vector3 dir, bool sprint) { _moveDir = dir; _isSprinting = sprint; } public void AttemptDash() { if (CanDash()) StartCoroutine(DashRoutine()); } private bool CanDash() { bool isCooldownOver = Time.time >= _lastDashTime + dashCooldown; bool isAlive = health != null && !health.IsDead; return isCooldownOver && !_isDashing && isAlive; } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 메인 이동 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ private void Update() { // 1. 사망/대시/피격 중 이동 차단 if (health != null && (health.IsDead || health.isHit || _isDashing)) { if (pAnim != null && !health.IsDead) pAnim.UpdateMove(0f); ApplyGravityOnly(); return; } // 2. 공격 중 이동 차단 if (attackScript != null && attackScript.IsAttacking) { if (pAnim != null) pAnim.UpdateMove(0f); ApplyGravityOnly(); return; } // 3. 이동 속도 계산 float speed = _isSprinting ? stats.CurrentRunSpeed : stats.CurrentMoveSpeed; if (attackScript != null && attackScript.IsCharging) { float speedReduction = Mathf.Lerp(1.0f, minSpeedMultiplier, attackScript.ChargeProgress); speed *= speedReduction; } // 4. ✅ CharacterController로 이동 (벽 충돌 처리!) Vector3 motion = _moveDir * speed * Time.deltaTime; ApplyGravity(); motion.y = _verticalVelocity * Time.deltaTime; _controller.Move(motion); // 5. 애니메이션 UpdateAnimation(); } private void ApplyGravity() { if (_controller.isGrounded) { _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; 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; }