나무 쓰러짐 물리 시스템과 드랍 및 재생성 구조 설계

목차

1. 요구 사항

2. 설계 목표

3. 흐름도

4. 구현

        4.1. 나무 물리 상태 초기화

       4.2. 벌목 완료 시 물리 활성화

       4.3. 지면 충돌 시 아이템 드랍

       4.4. 나무 재생 시스템

       4.5. 나무 재생성 완료 처리

       4.6. 재생성 취소

5. 개발 의도

1. 시스템 요구 사항

벌목 진행이 완료되면 나무는 단순히 사라지는 것이 아니라 실제로 쓰러지는 연출을 수행해야 한다.

플레이어가 도끼로 나무를 베었을 때 즉시 오브젝트가 제거되는 방식은 현실감이 떨어지고, 행동 결과에 대한 피드백이 약해질 수 있기 때문이다.

따라서 벌목이 완료된 나무는 물리 시스템을 이용하여 중력의 영향을 받아 쓰러지도록 해야 하며, 지면에 충돌했을 때 아이템을 드랍하고 오브젝트를 비활성화하는 흐름을 가져야 한다.

또한 월드 자원 시스템 관점에서 나무는 한 번 베어지고 끝나는 오브젝트가 아니라 일정 시간이 지나면 다시 생성되어야 한다.

그렇지 않으면 플레이어가 채집 가능한 자원이 점점 사라지고 게임 진행이 막히는 문제가 발생한다.

따라서 나무는 벌목 이후 일정 시간이 지나면 처음 위치로 돌아와 다시 채집 가능한 상태로 복구되어야 한다.

이 과정에서 나무는 다음과 같은 생명주기 흐름을 가진다.

벌목 완료 이벤트가 발생하면 물리 상태가 활성화되어 나무가 쓰러지고, 지면과 충돌하면 드랍 아이템을 생성하며 오브젝트를 비활성화한다.

이후 일정 시간이 지나면 처음 위치와 회전값으로 복원되어 다시 상호작용 가능한 상태로 돌아온다.

2. 설계  목표

- 벌목 완료 시 물리 기반 나무 쓰러짐 연출 구현

- Rigidbody를 이용한 중력 기반 낙하 처리

- 지면 충돌 시 아이템 드랍 처리

- 오브젝트 비활성화 후 일정 시간 뒤 재생성

- 초기 위치 기준으로 정확한 리스폰 위치 유지

3. 흐름도

벌목 완료

  ↓

Tree.Felled()

  ↓

물리 활성화 (Rigidbody)

  ↓

나무 쓰러짐

  ↓

Ground 충돌 감지

  ↓

아이템 드랍

  ↓

오브젝트 비활성화

  ↓

재생성 타이머 시작

  ↓

위치 / 회전 초기화

  ↓

나무 재생성

벌목 진행 시스템이 완료되면 Tree 클래스의 Felled 함수가 호출된다.

이 함수는 나무의 물리 상태를 활성화하여 중력에 의해 쓰러지도록 만든다.

이후 나무가 지면과 충돌하면 드랍 아이템을 생성하고 오브젝트를 비활성화하며, 재생성 시스템이 일정 시간이 지난 뒤 나무를 다시 생성한다.

4. 구현

4.1. 나무 물리 상태 초기화
private void Awake()
{
    rb = GetComponent<Rigidbody>();
    cd = GetComponent<MeshCollider>();

    startPos = transform.position;
}

Tree 오브젝트는 Rigidbody와 Collider 컴포넌트를 이용해 물리 기반 동작을 수행한다.

Awake 단계에서 GetComponent를 통해 Rigidbody와 MeshCollider를 캐싱하는 구조를 사용하였다.

GetComponent는 런타임에서 컴포넌트를 탐색하는 함수이며, 반복적으로 호출할 경우 성능 비용이 발생할 수 있다.

따라서 Awake에서 한 번만 호출하여 필요한 컴포넌트를 변수에 저장하는 방식으로 구현하였다.

또한 startPos 변수에 나무의 최초 위치를 저장하였다.

이는 나무가 재생성될 때 원래 위치로 정확하게 돌아가기 위해 필요한 값이다.

만약 최초 위치를 저장하지 않는다면 나무가 쓰러진 위치 기준으로 재생성될 수 있기 때문에 월드 자원 위치가 점점 어긋나는 문제가 발생할 수 있다.

   

4.2. 벌목 완료 시 물리 활성화
public void Felled()
{
    RegenElapsedTime = 0;

    cd.isTrigger = false;
    rb.useGravity = true;
    rb.isKinematic = false;
}

벌목이 완료되면 Interact 클래스에서 Felled 함수가 호출된다.

이 함수는 나무의 물리 상태를 변경하여 실제로 쓰러지도록 만든다.

MeshCollider의 isTrigger 값을 false로 변경하면 해당 콜라이더가 실제 물리 충돌을 처리하도록 전환된다.

Trigger 상태에서는 물리 충돌이 발생하지 않기 때문에 나무가 바닥에 부딪히는 물리 이벤트를 감지할 수 없다.

Rigidbody.useGravity를 true로 설정하면 Unity 물리 시스템에서 해당 오브젝트에 중력이 적용된다.

이로 인해 나무는 자연스럽게 아래 방향으로 떨어지게 된다.

또한 Rigidbody.isKinematic을 false로 설정하여 물리 시뮬레이션이 적용되도록 한다.

Kinematic 상태에서는 Rigidbody가 물리 엔진의 영향을 받지 않기 때문에, 나무가 중력에 의해 쓰러지도록 만들기 위해서는 이 값을 반드시 false로 변경해야 한다.

이 세 가지 설정을 통해 나무는 단순한 정적 오브젝트에서 물리 오브젝트로 전환되어 자연스럽게 쓰러지는 연출을 수행하게 된다.

4.3. 지면 충돌 시 아이템 드랍 및 재생성 시작
void OnCollisionEnter(Collision collision)
{
    if (collision.gameObject.CompareTag("Ground"))
    {
        gameObject.SetActive(false);

        UiManager_.instance.DropItem(woodItem, 5);

        StartRegeneration();
    }
}

나무가 쓰러진 뒤 지면과 충돌하면 OnCollisionEnter 이벤트가 발생한다.

OnCollisionEnter는 Unity 물리 시스템에서 두 콜라이더가 실제 충돌했을 때 호출되는 이벤트 함수이다.

충돌한 오브젝트가 Ground 태그를 가지고 있는지 확인하여 지면과의 충돌인지 판별한다.

나무가 다른 오브젝트와 부딪힐 수 있기 때문에, 모든 충돌을 드랍 이벤트로 처리하지 않도록 조건을 추가하였다.

지면 충돌이 확인되면 먼저 gameObject.SetActive(false)를 호출하여 나무 오브젝트를 비활성화한다.

Destroy는 오브젝트를 완전히 제거하고 메모리를 해제하지만, SetActive(false)는 오브젝트를 비활성화 상태로 유지하기 때문에 이후 재사용이 가능하다.

아이템 드랍은 UiManager를 통해 처리된다. DropItem 함수는 지정한 아이템과 개수를 월드에 생성하는 역할을 수행한다.

벌목이 완료되면 woodItem을 최대 5개, 최소 0개 랜덤으로 인벤토리에 추가하도록 설정하였다.

마지막으로 StartRegeneration 함수를 호출하여 나무 재생성 타이머를 시작한다.

4.4. 나무 재생성 시스템
bool regenerate = false;

void StartRegeneration()
{
    if (!regenerate)
    {
        regenerate = true;
        InvokeRepeating("Regenerate", 0f, 3.0f);
    }
}

나무 재생성은 InvokeRepeating 함수를 이용하여 일정 시간(3초) 간격으로 실행되도록 설계하였다.

InvokeRepeating은 Unity에서 특정 함수를 일정 주기로 반복 실행하는 기능이다.

일반적으로 이러한 타이머 시스템은 Coroutine을 이용해 구현할 수도 있지만, 이 프로젝트에서는 Coroutine을 사용할 경우 재생성 로직이 정상적으로 동작하지 않는 문제가 발생하였다.

WaitForSecondsRealtime을 사용한 방식으로도 해결되지 않았기 때문에, 보다 안정적인 반복 호출 방식인 InvokeRepeating을 사용하였다.

InvokeRepeating은 첫 번째 매개변수로 함수 이름을 문자열로 전달하며, 두 번째 매개변수는 첫 실행 시간, 세 번째 매개변수는 반복 주기를 의미한다.

4.5. 나무 재생성 완료 처리
void Regenerate()
{
    RegenElapsedTime += 1;

    if (RegenElapsedTime >= RegenTime)
    {
        RegenElapsedTime = RegenTime;

        cd.isTrigger = true;
        rb.useGravity = false;
        rb.isKinematic = true;

        StopRegeneration();

        transform.position = startPos;
        transform.rotation = Quaternion.Euler(0f, 0f, 0f);

        gameObject.SetActive(true);
    }
}

Regenerate는 일정 주기로 호출되어 체력을 점진적으로 회복한다.

즉시 최대 체력으로 복구하지 않고 5초마다 10씩 증가시키는 이유는 월드에 자원이 다시 자라난다는 시간 흐름을 표현하기 위함이다.

curHp가 maxHp 이상이 되면 회복을 종료하고 상태 복구를 수행한다.

먼저 Collider를 다시 Trigger 상태로 되돌린다.

이는 플레이어와 충돌 시 물리 반응이 발생하지 않도록 하기 위함이다.

rb.useGravity를 false로, rb.isKinematic을 true로 설정하면 Rigidbody는 물리 엔진의 영향을 받지 않는 정적 상태로 돌아간다.

이렇게 하면 나무는 다시 고정된 월드 오브젝트가 된다.

transform.position을 startPos로 되돌리는 이유는 쓰러지면서 이동한 위치를 원래 자리로 복구하기 위함이다.

transform.rotation을 초기화하는 것은 물리 시뮬레이션 중 발생한 회전값을 제거하기 위한 조치다.

여기서 중요한 점은 NavMeshAgent와 달리 Rigidbody가 붙은 오브젝트는 transform을 직접 변경해도 큰 문제가 없지만, 물리 계산 직후에 위치를 강제 이동시키는 경우 충돌 상태가 남아 있을 수 있다.

이 구조에서는 재생성 시점에 이미 비활성화 상태였다가 활성화되므로 안전하게 적용된다.

gameObject.SetActive(true)는 오브젝트를 다시 활성화하여 월드에 복귀시키는 단계다.

Destroy 대신 SetActive를 사용했기 때문에, 오브젝트 재사용이 가능하다.

이는 생성/파괴 비용을 줄이고, 메모리 할당을 최소화하는 장점이 있다.

4.6. 재생성 취소
void StopRegeneration()
{
    if (regenerate)
    {   
        regenerate = false;       
        CancelInvoke("Regenerate"); 
    }
}

5. 개발 의도

벌목 시스템의 핵심은 자원을 획득하는 것 뿐만 아니라, 행동의 결과가 자연스럽게 표현되는 것이다.

이를 위해 나무를 단순히 삭제하는 방식이 아니라 실제 물리 기반으로 쓰러지는 연출을 구현하였다.

Rigidbody를 활용하여 중력 기반 낙하를 적용하면 별도의 애니메이션 없이도 자연스러운 쓰러짐 효과를 얻을 수 있다.

또한 나무가 지면에 충돌했을 때 아이템을 드랍하도록 설계하여 벌목 행동의 결과가 플레이어에게 즉각적으로 전달되도록 하였다.

월드 자원 시스템 측면에서는 나무가 영구적으로 사라지지 않도록 재생성 구조를 추가하였다.

이는 플레이어가 지속적으로 자원을 채집할 수 있도록 하기 위한 설계이다.

오브젝트 삭제 대신 비활성화를 사용하는 방식은 성능과 재사용성 측면에서 유리하며, 동일한 오브젝트를 반복적으로 생성하는 비용을 줄일 수 있다.

결과적으로 이 시스템은 '벌목 행동 → 물리 연출 → 아이템 드랍 → 자원 재생성' 이라는 자연스러운 자원 생명주기를 형성하도록 설계된 구조이다.