벌목 상호작용

목차

1. 요구 사항

2. 설계 목표

3. 흐름도

4. 구현

        4.1. Raycast 기반 상호작용 대상 감지

       4.2. 상호작용 UI 표시

       4.3. 입력 처리 및 도구 장착 가드

       4.4. 도끼 장착 피드백

5. 개발 의도

1. 시스템 요구 사항

벌목 시스템은 플레이어가 월드에서 벌목 가능한 나무를 바라보고 가까이 있을 때 상호작용을 시작할 수 있어야 한다.

이를 위해 플레이어가 바라보는 방향에 상호작용 가능한 오브젝트가 존재하는지 확인해야 하며, 특정 거리 안에서만 상호작용이 가능하도록 제한해야 한다.

또한 상호작용은 단순히 거리 기반으로 동작해서는 안 되며, 플레이어의 시선 방향과 실제 충돌 판정을 기반으로 정확한 대상을 감지해야 한다.

감지된 대상이 벌목 가능한 나무일 경우에는 상호작용 UI를 화면에 표시하고, 플레이어가 입력 키를 눌렀을 때 벌목 행동이 시작되어야 한다.

벌목은 도구 기반 행동이기 때문에 도끼가 장착되어 있는 경우에만 실행되어야 하며, 도끼가 장착되지 않았을 경우에는 행동을 실행하지 않고 안내 메시지를 출력하여 플레이어에게 도구 교체를 유도해야 한다.

또한 상점 UI나 강화 UI와 같은 다른 인터페이스가 열려 있는 경우에는 상호작용이 동시에 발생하지 않도록 상호작용 입력을 차단해야 한다.

2. 설계  목표

- 전방 Raycast로 상호작용 대상 감지

- LayerMask로 상호작용 가능한 오브젝트만 필터링

- Tree 태그에만 벌목 진입 로직 실행

- F 키 입력 기반 상호작용 시작

- 도끼 장착 여부 확인

3. 흐름도

Start(플레이)

Update()

Raycast(전방, 거리 3m, Interact 레이어) ∧ !isUIOpen ?

├─ 아니오 → 상호작용 UI 숨김(상태 정리)

└─ 예

Tree 태그 확인

├─ 아니오 → (게시글1 범위 밖: 다른 상호작용)

└─ 예

상호작용 UI 표시

F 키 입력 감지

├─ 아니오 → 대기(계속 감지 유지)

└─ 예

도끼 장착(axe.activeSelf) ?

├─ 예 → 벌목 진행 시작(게시글2에서 다룸)

└─ 아니오 → 안내 메시지 출력

이 구조는 플레이어의 시선 방향을 기준으로 상호작용 가능한 오브젝트를 감지하고, 해당 오브젝트가 벌목 대상일 경우에만 상호작용 UI를 활성화하는 방식으로 동작한다.

이후 플레이어 입력이 발생하면 도구 장착 여부를 확인한 뒤 실제 벌목 시스템으로 진입한다.

4. 구현

4.1. Raycast 기반 상호작용 대상 감지
void Update()
{
    RaycastHit hit;

    if (Physics.Raycast(transform.position + new Vector3(0f, 0.5f, 0f),
                        transform.forward,
                        out hit,
                        3f,
                        LayerMask.GetMask("Interact")) && !isUIOpen)
    {
        if (hit.collider != null)
        {
            if (hit.collider.tag == "Tree")
            {
                interactText.text = "벌목하기(F)";
                ShowInteractKey(hit.collider.transform.position + Vector3.up);

                // ... 입력 처리 및 도구 장착 → 4.3 설명
            }
        }
        if(isKeyPress && hit.collider.tag == "Tree")
        {
            GSPBarTimeTree(hit.collider.gameObject);
        }
    }
    else
    {
        HideInteractKey();
        // ... 진행 UI 정리/리셋 → 게시글2에서 설명
    }
}

InteractData 클래스의 Update는 여러 상호작용을 처리한다.

Update에서 상호작용을 처리하는 이유는 플레이어의 시선 방향 변화와 거리 변화가 프레임 단위로 계속 바뀌기 때문이다.

FixedUpdate는 물리 갱신 주기(고정 틱)에 맞춰 실행되므로, UI 표기나 입력 반응을 FixedUpdate에 두면 프레임 기반 입력과 타이밍이 어긋나 체감 반응성이 떨어질 수 있다.

반면 Update는 입력(Input.GetKeyDown)과 같은 프레임 이벤트를 같은 루프에서 처리하기 적합하고, 상호작용 UI 또한 화면 갱신과 일관된 타이밍으로 업데이트된다.

벌목 상호작용은 Unity의 Physics.Raycast를 사용하여 구현하였다.

Raycast는 특정 위치에서 특정 방향으로 광선을 발사하여 충돌한 오브젝트를 감지하는 물리 쿼리 기능이다.

이를 이용하면 플레이어가 실제로 바라보고 있는 대상만 상호작용 대상으로 인식할 수 있다.

Raycast의 시작 위치는 플레이어 transform.position에 약간의 y 오프셋을 추가한 위치이다.

이는 플레이어 발 위치에서 레이캐스트를 발사하면 지면에 먼저 충돌하는 경우가 발생할 수 있기 때문에, 약간 위에서 시작하도록 보정한 것이다.

Raycast의 방향은 transform.forward를 사용하였다.

이는 플레이어 캐릭터가 바라보는 방향을 의미하며, 결과적으로 플레이어 시선 기준 전방 오브젝트를 감지하게 된다.

Raycast의 최대 거리는 3f로 제한하였다.

이 값은 플레이어가 상호작용할 수 있는 최대 거리이며, 너무 멀리 있는 오브젝트와 상호작용이 발생하지 않도록 하기 위한 안전 장치 역할을 한다.

또한 LayerMask.GetMask("Interact")를 사용하여 Raycast 충돌 대상을 제한하였다.

이는 레이캐스트가 월드의 모든 콜라이더를 대상으로 하지 않도록 필터링하기 위함이다.

상호작용 가능한 오브젝트만 Interact 레이어에 모아두면, 레이캐스트 연산 자체가 줄어들고 의도치 않은 오브젝트에 히트되는 문제도 예방된다.

태그(tag == "Tree")는 레이어보다 더 구체적인 의미 분기를 위해 사용한다.

즉, 레이어는 상호작용 후보군을 만드는 1차 필터, 태그는 무슨 상호작용인가를 결정하는 2차 분기 역할이다.

마지막으로 isUIOpen 조건을 추가하였다.

이 변수는 상점 UI나 강화 UI가 열려 있는 상태를 의미한다.

UI가 열려 있는 동안에 레이캐스트와 입력 처리가 그대로 살아 있으면, UI 조작 도중 벌목이 시작되는 식의 사고가 날 수 있다.

따라서 UI가 열려 있으면 상호작용을 막는다는 정책을 Update 루프의 진입 조건에 묶어 상호작용을 구조적으로 차단한다.

4.2. 상호작용 UI 표시
public void ShowInteractKey(Vector3 position)
{
    Vector3 viewPoint = Camera.main.WorldToScreenPoint(position);
    interactKey.transform.position = viewPoint;
    interactKey.gameObject.SetActive(true);
}

상호작용 UI는 단순히 화면 고정 위치에 표시하지 않고, 실제 오브젝트 위치를 기준으로 화면에 표시되도록 설계하였다.

이를 위해 Unity의 Camera.WorldToScreenPoint 함수를 사용하였다.

이 함수는 월드 좌표를 화면 좌표로 변환하는 기능을 제공한다.

즉, 나무의 월드 위치를 화면 좌표로 변환한 뒤 해당 위치에 UI를 배치하면, 플레이어가 바라보는 오브젝트 위에 상호작용 키 UI가 표시된다.

이 방식의 장점은 플레이어가 어떤 오브젝트와 상호작용 가능한지 직관적으로 이해할 수 있다는 점이다.

특히 월드에 여러 상호작용 오브젝트가 존재하는 경우에도 어떤 대상이 현재 선택되었는지 명확하게 전달할 수 있다.

단점은 카메라가 여러 개 존재하거나 UI Canvas 설정이 다를 경우 좌표 변환 방식이 달라질 수 있다는 점이다.

그러나 현재 프로젝트에서는 메인 카메라 기준 UI 표시 방식이 가장 단순하고 직관적이기 때문에 이 방식을 선택하였다.

4.3. 입력 처리 및 도구 장착 가드
if (Input.GetKeyDown(KeyCode.F))
{
    if (WeaponChange.instance.axe.activeSelf)
    {
        isKeyPress = true;

        GSPTxt.text = "도끼를 휘두르는 중...";

        InteractGSP(hit.collider.transform.position + Vector3.up);
    }
    else
    {
        EquipTxt.text = "[1]번을 눌러 도끼로 교체해주세요";

        StartCoroutine(EquipChangeText());
    }
}

벌목 상호작용은 Unity의 Input.GetKeyDown을 사용하여 구현하였다.

GetKeyDown은 키가 눌린 그 프레임에만 true를 반환하는 입력 함수이다.

벌목 행동은 지속 입력이 아니라 한 번의 입력으로 시작되는 행동이기 때문에 GetKeyDown이 적합하다.

만약 GetKey를 사용할 경우 키를 누르고 있는 동안 계속 입력이 발생하여 벌목이 반복 실행되는 문제가 발생할 수 있다.

입력이 감지되면 먼저 도구 장착 여부를 검사한다.

WeaponChange.instance.axe.activeSelf를 통해 현재 도끼 오브젝트가 활성화되어 있는지 확인한다.

이 방식은 장비 상태를 GameObject 활성 상태로 판단하는 구조이다.

장점은 구현이 단순하고 상태 확인이 빠르다는 것이다.

단점은 장비 시스템 규모가 커질 경우 오브젝트 활성 상태와 장비 상태가 강하게 결합될 수 있다는 점이다.

그러나 현재 시스템에서는 벌목 조건이 도끼 장착 여부 하나이기 때문에 가장 단순하고 직관적인 방식이다.

도끼가 장착된 경우에는 벌목 진행 UI를 시작한다.

반대로 장착되어 있지 않을 경우에는 EquipTxt에 안내 메시지를 출력하고 EquipChangeText 코루틴을 실행하여 일정 시간 후 메시지를 자동으로 숨긴다.

4.4. 도끼 장착 피드백
IEnumerator EquipChangeText()
{
    EquipTxt.enabled = true;

    yield return new WaitForSeconds(disPlayTime);

    EquipTxt.enabled = false;
}

코루틴은 Unity에서 시간 기반 흐름을 간단하게 작성할 수 있는 기능이다.

WaitForSeconds를 사용하면 일정 시간이 지난 뒤다음 코드가 실행되도록 만들 수 있다.

이 방식은 Update에서 시간을 누적하는 방식보다 코드 구조가 간결하며, 단발성 UI 메시지 처리에 매우 적합하다.

5. 개발 의도

벌목 시스템의 가장 중요한 부분은 어떻게 벌목을 시작하는가이다.

단순히 나무 근처에 있으면 벌목이 시작되는 방식은 게임 플레이 경험을 떨어뜨릴 수 있기 때문에, 플레이어의 시선 방향과 실제 충돌 판정을 기반으로 상호작용을 시작하도록 설계하였다.

Raycast를 이용한 전방 감지 구조를 사용하면 플레이어가 실제로 바라보는 대상만 상호작용 대상으로 인식할 수 있기 때문에 직관적인 인터랙션을 만들 수 있다.

또한 상호작용 UI를 월드 위치 기반으로 표시하여 플레이어가 어떤 대상과 상호작용 가능한지 명확하게 전달하도록 하였다.

도구 장착 조건을 벌목 시작 단계에 배치한 이유는 행동 가능 조건을 명확히 하기 위함이다.

플레이어가 행동을 시도한 뒤 실패하는 것이 아니라, 시도 순간 즉시 안내 메시지를 제공함으로써 UX 흐름을 자연스럽게 만들 수 있다.