본문 바로가기
Unity 궁둥이 쪼물쪼물

007. 넉백(Knockback) 구현

by daco2020 2026. 4. 6.

총알이 적에 맞았을 때 뒤로 밀려나는 넉백 효과를 구현한 과정을 기록한다.
삽질이 많았던 파트!

 

 

1. 넉백이란?

적이 공격을 받았을 때 뒤로 밀려나는 효과. 게임에서 타격감을 주는 핵심 요소다.

총알 → → → 💥 적
                적 → → (뒤로 밀려남)

구현에 필요한 것

  1. Enemy에 Rigidbody - 물리 힘을 받을 수 있어야 함
  2. AddForce() - Rigidbody에 힘을 가하는 함수
  3. NavMeshAgent 비활성화 - Agent가 위치를 잡으면 밀리지 않음

 

2. Enemy에 Rigidbody 추가

 

설정은 다음과 같다.

  • Is Kinematic: 체크 ✅ - 평소에는 물리 영향 안 받음 (NavMeshAgent가 이동 담당)
  • Use Gravity: 해제 - NavMeshAgent가 바닥에 붙여주니까 불필요

 

Is Kinematic이란? 체크하면 물리 엔진이 이 오브젝트를 직접 움직이지 않는다. 코드에서 isKinematic = false로 풀어줄 때만 물리가 적용된다.

 

 

 

3. 넉백 코드

private void OnCollisionEnter(Collision collision)
{
    Enemy enemy = collision.gameObject.GetComponentInParent<Enemy>();
    if (enemy != null)
    {
        // 1. NavMeshAgent 끄기 (안 끄면 Agent가 위치를 잡아서 안 밀림)
        NavMeshAgent agent = enemy.GetComponent<NavMeshAgent>();
        if (agent != null)
            agent.enabled = false;

        // 2. Rigidbody 물리 활성화
        Rigidbody enemyRb = enemy.GetComponent<Rigidbody>();
        if (enemyRb != null)
        {
            enemyRb.isKinematic = false;
            enemyRb.constraints = FreezePositionY | FreezeRotation;
            enemyRb.linearDamping = 5f;

            // 3. 밀려나는 방향 계산 (총알→적 방향, 수평만)
            Vector3 knockbackDirection = (enemy.transform.position - transform.position);
            knockbackDirection.y = 0f;
            knockbackDirection.Normalize();

            // 4. 힘 가하기
            enemyRb.AddForce(knockbackDirection * knockbackForce, ForceMode.Impulse);
        }

        enemy.Die();
    }
}

흐름 순서

  1. NavMeshAgent 끄기 → Agent가 위치를 고정하지 않게
  2. isKinematic = false → 물리 엔진이 움직일 수 있게
  3. 방향 계산 → 총알 위치에서 적 위치 방향 (수평만)
  4. AddForce(Impulse) → 한 번에 강하게 밀기
  5. Die() → 죽기 상태로 전환

 

4. 이슈 기록 (꽤 많이 헤맸다!)

이슈 1: 넉백이 적용 안 됨

원인: NavMeshAgent가 켜져 있으면 매 프레임 위치를 제어해서 AddForce가 무시됨.

해결: 넉백 전에 agent.enabled = false로 먼저 끄기.

 

 

이슈 2: "ResetPath" can only be called on an active agent

 

원인: Bullet.cs에서 NavMeshAgent를 먼저 껐는데, Enemy.cs의 Die 상태 초기화에서 _agent.ResetPath()를 호출 → 이미 꺼진 Agent에 명령을 보내서 에러.

해결: if (_agent.enabled) _agent.ResetPath(); 로 활성 상태일 때만 호출.

 

 

이슈 3: NullReferenceException (Play 멈출 때)

 

원인: Play를 멈추면 OnDisable()이 호출되는데, _inputActions가 이미 null인 상태에서 .Disable() 호출.

해결: if (_inputActions != null) _inputActions.Disable(); null 체크 추가.

 

 

이슈 4: 적이 앞으로 밀림 (뒤가 아니라!???)

원인: transform.forward (총알의 앞 방향)를 밀어내는 방향으로 썼는데, 총알이 적을 향해 날아가니까 forward가 적 쪽을 가리킴 → 적이 앞으로 밀림.

해결: 방향 계산을 적 위치 - 총알 위치로 변경. 총알에서 적 쪽으로 향하는 벡터 = 적이 밀려나는 방향.

 

 

이슈 5: 적이 위로 떠오름

원인: knockbackDirection에 Y값이 있어서 위쪽으로도 힘이 가해짐.

해결:

  • knockbackDirection.y = 0f — 수평으로만 밀기
  • FreezePositionY — Y축 이동 잠금

 

이슈 6: 적이 맵 끝까지 미끄러짐

원인: 처음에 미는 힘을 100으로 설정하여 너무 강했다. FreezeRotation 때문에 마찰이 안 먹어서 멈추질 않음.

해결: linearDamping = 5f 마찰(Drag)을 넣어서 자연스럽게 감속.

 

 

5. 새로 배운 개념들

AddForce와 ForceMode

enemyRb.AddForce(방향 * 힘, ForceMode.Impulse);

 

AddForce는 오브젝트에 힘을 가하는 함수이고, ForceMode는 그 힘을 한 번에 줄지(Impulse), 계속 줄지(Force), 아니면 속도를 바로 바꿀지(VelocityChange)를 결정하는 옵션이다.

 

RigidbodyConstraints - 축 잠금

enemyRb.constraints = RigidbodyConstraints.FreezePositionY
                    | RigidbodyConstraints.FreezeRotation;
  • FreezePositionY: Y축(위아래) 이동 잠금 → 위로 안 뜸
  • FreezeRotation: 회전 잠금 → 굴러가지 않음
  • | (OR 연산자): 여러 제약을 동시에 적용

 

linearDamping - 마찰/공기저항

linearDamping = 0  → 힘 받으면 영원히 미끄러짐 (우주 공간)
linearDamping = 5  → 힘 받고 자연스럽게 감속 후 멈춤
linearDamping = 50 → 힘 받아도 거의 안 움직임 (진흙탕)

 

 

GetComponentInParent - 부모까지 검색

Enemy enemy = collision.gameObject.GetComponentInParent<Enemy>();
  • GetComponent: 해당 오브젝트에서만 찾음
  • GetComponentInParent: 해당 오브젝트 + 모든 부모에서 찾음

총알이 오리의 자식 오브젝트(Body, Leg 등)에 맞을 수 있으니 부모까지 검색해야 Enemy를 찾을 수 있다.

 

 

6. Apply Root Motion

 

Animator의 Apply Root Motion이 켜져있으면 애니메이션이 오브젝트 위치를 제어한다. 넉백 중에 stun 애니메이션이 위치를 덮어쓸 수 있어서 해제했다.

 

 

7. 이번 작업에서 배운 것 요약

  1. 넉백 = NavMeshAgent 끄기 → isKinematic 끄기 → AddForce
  2. 이 순서가 중요! Agent가 켜져있으면 물리가 무시됨
  3. AddForce(Impulse) = 한 번에 강하게 밀기
  4. FreezePositionY = 위로 안 뜨게(상황에 따라)
  5. linearDamping = 자연스럽게 감속
  6. 넉백 방향 = 적 위치 - 총알 위치 (transform.forward 아님!)
  7. GetComponentInParent = 자식에서 충돌해도 부모의 컴포넌트를 찾음
  8. 비활성화된 컴포넌트에 메서드 호출하면 에러 → null/enabled 체크!