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

008. 넉백 코드 리팩토링

by daco2020 2026. 4. 13.

멘토 피드백을 반영하여 Bullet이 Enemy 내부를 직접 조작하던 구조를
Enemy.OnHit()으로 역할을 분리한 과정을 기록한다.

 

 

1. 멘토 피드백 요약

Bullet이 넉백을 직접 처리하기보다 Enemy에 넉백을 요청하는 등 역할을 분리하는 것이
이후에 상태 추가나 유지보수 측면에서 더 유연한 구조가 될 수 있습니다.

 

 

 

2. 문제: 높은 결합도

기존 Bullet.cs가 Enemy의 내부를 너무 많이 알고 있었다:

// Bullet.cs가 Enemy의 내부를 직접 조작 (나쁜 예)
agent.enabled = false;           // NavMeshAgent 끄기
enemyRb.isKinematic = false;     // Rigidbody 설정
enemyRb.constraints = ...;      // 축 잠금
enemyRb.linearDamping = 5f;     // 마찰 설정
enemyRb.AddForce(...);          // 힘 가하기
enemy.Die();                     // 죽이기

 

비유하자면

손님이 주방에 직접 들어가서 요리함
→ 메뉴가 바뀌면 손님도 바꿔야 함
→ 요리사가 여러 명이면 혼란

 

왜 안좋은가?

  • 보스 몹(죽지 않고 넉백만 받는 적)을 만들려면 Bullet.cs를 수정해야 함
  • Enemy 내부 구조가 바뀌면 Bullet.cs도 수정해야 함
  • Bullet이 Enemy의 컴포넌트(NavMeshAgent, Rigidbody)를 다 알아야 함

 

 

 

3. 해결: 역할 분리

"자기 일은 자기가 한다"

Bullet의 역할: "맞았다"는 사실과 힘의 방향/크기를 알려주기
Enemy의 역할: 맞았을 때 어떻게 반응할지 스스로 결정하기

 

비유하자면

손님이 주문만 하고, 요리사가 알아서 만듦
→ 메뉴가 바뀌어도 손님은 몰라도 됨
→ 요리사만 바꾸면 끝

 

 

 

4. 변경 내용

Bullet.cs - 간결해짐

// 변경 전
agent.enabled = false;
enemyRb.isKinematic = false;
enemyRb.constraints = ...;
enemyRb.linearDamping = 5f;
enemyRb.AddForce(...);
enemy.Die();

// 변경 후
Vector3 knockbackDirection = (enemy.transform.position - transform.position);
knockbackDirection.y = 0f;
knockbackDirection.Normalize();

enemy.OnHit(knockbackDirection * knockbackForce);

 

Bullet은 이제 방향 계산 + OnHit 호출만 한다.

 

 

Enemy.cs — OnHit() 메서드 추가

public void OnHit(Vector3 knockbackForce)
{
    if (state == State.Die) return;

    if (_agent.enabled)
        _agent.enabled = false;

    Rigidbody rb = GetComponent<Rigidbody>();
    if (rb != null)
    {
        rb.isKinematic = false;
        rb.constraints = RigidbodyConstraints.FreezePositionY
                       | RigidbodyConstraints.FreezeRotation;
        rb.linearDamping = 5f;
        rb.AddForce(knockbackForce, ForceMode.Impulse);
    }

    nextState = State.Die;
}

 

Enemy가 자기 자신의 넉백을 스스로 처리한다.

 

 

 

5. 이 구조의 장점

확장성 예시) 보스 몹

// 일반 Enemy
public void OnHit(Vector3 knockbackForce)
{
    // 넉백 + 죽기
    ApplyKnockback(knockbackForce);
    nextState = State.Die;
}

// 보스 Enemy (죽지 않고 넉백만)
public void OnHit(Vector3 knockbackForce)
{
    _hp -= 10;
    ApplyKnockback(knockbackForce * 0.3f);  // 약하게 밀림
    if (_hp <= 0)
        nextState = State.Die;
    else
        nextState = State.Chase;  // 다시 추적!
}

 

Bullet.cs는 한 줄도 안 바꿔도 된다! enemy.OnHit(힘)만 호출하면 되니까.

 

 

 

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

  1. 결합도를 낮추자. 다른 객체의 내부를 직접 조작하지 말고, 메서드를 통해 요청
  2. 자기 일은 자기가 한다. Enemy의 반응은 Enemy가 결정
  3. 단일 책임 원칙. 하나의 스크립트는 하나의 역할만
  4. 확장성. 역할이 분리되면 새로운 타입(보스 등)을 추가할 때 기존 코드를 안 건드려도 됨