XR 플밍 - 8. UnityEngine3D 입문 - 프로젝트 GwisinRun 1일차 (4/28)

이형원·2025년 4월 28일
0

XR플밍

목록 보기
57/215

0. 들어가기에 앞서
프로젝트 귀신런의 1일차가 시작되었다. 주말 동안에도 구현할 수 있는 부분을 미리 작업해놨지만, 여전히 구현해야 할 기능이 많았다. 토요일부터 시작된 작업부터 지금까지 구현한 내용까지의 기록을 남기고자 한다.

1. UML 업데이트


왼쪽이 구현 내용, 오른쪽이 미구현 내용

현황은 이와 같으며, 특이사항으로 커서 변경 건은 스크립트를 만들지 않고 변경하는 것으로 결정되었다.

2. 열리고 닫히는 문 - 트리거 구현

앞서 구현한 문에서는 플레이어의 위치에 따라 문이 열리는 방향을 설정하려고 했으나, 이와 같은 문제점이 발생했다.

  • 당장 구현한 코드는 플레이어의 월드스페이스 좌표상의 위치로 플레이어가 문의 앞 / 뒤에 있는지 구분했기 때문에 문의 방향이 바뀌는 순간 문의 작동이 이상해진다.

이와 같은 문제를 해결하기 위해, 문의 트리거 요소를 추가해야 한다고 생각했다.

문의 앞과 뒤 각각의 사각형 공간 안에 들어왔을 때 문과 상호작용할 수 있으며, 트리거를 각각 구별하여 열리는 문을 구현했다.

이와 같이 작성하고 각각의 코드를 아래와 같이 작성하였다.

  • 문 트리거1 코드
using UnityEngine;

public class DoorTrigger1 : MonoBehaviour
{
    private bool m_playerDetected1 = false;

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "Player")
        {
            m_playerDetected1 = true;
        }
    }
    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Player")
        {
            m_playerDetected1 = false;
        }
    }

    public bool PlayerDetected()
    {
        return m_playerDetected1;
    }
}
  • 문 트리거2 코드
using UnityEngine;

public class DoorTrigger2 : MonoBehaviour
{
    private bool m_playerDetected2 = false;

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "Player")
        {
            m_playerDetected2 = true;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Player")
        {
            m_playerDetected2 = false;
        }
    }
    public bool PlayerDetected()
    {
        return m_playerDetected2;
    }
}
  • 문 컨트롤러 코드
using UnityEngine;

public class DoorController : MonoBehaviour, IInteractable
{
    [SerializeField] Animator m_doorAnimator;
    private bool m_isOpen = false;
    private bool m_isOpen2 = false;
    private bool m_close = true;
    [SerializeField] DoorTrigger1 m_doortrigger1;
    [SerializeField] DoorTrigger2 m_doortrigger2;

    public void Interact()
    {
        if (m_close == true && m_doortrigger1.PlayerDetected())
        {
            OpenDoorCounterClockwise();
        }
        else if (m_close == true && m_doortrigger2.PlayerDetected())
        {
            OpenDoorClockwise();
        }
        else if (m_close == false)
        {
            CloseDoor();
        }
        else
        {
            return;
        }
    }

    private void OpenDoorClockwise()
    {
        m_isOpen2 = true;
        m_close = false;
        m_doorAnimator.SetBool("IsOpen2", m_isOpen2);
        m_doorAnimator.SetBool("Close", m_close);
    }

    private void OpenDoorCounterClockwise()
    {
        m_isOpen = true;
        m_close = false;
        m_doorAnimator.SetBool("IsOpen", m_isOpen);
        m_doorAnimator.SetBool("Close", m_close);
    }
    private void CloseDoor()
    {
        m_isOpen = false;
        m_isOpen2 = false;
        m_close = true;
        m_doorAnimator.SetBool("IsOpen", m_isOpen);
        m_doorAnimator.SetBool("IsOpen2", m_isOpen2);
        m_doorAnimator.SetBool("Close", m_close);
    }
}

팀장님의 조언에 따라 문 컨트롤러 코드에서 트리거 영역을 Find로 찾는 방식이 아닌, 컴포넌트를 인스펙터 창에서 직접 넣는 방식으로 구현하였다. 이와 같이 작성하고 각각의 트리거 영역을 참조해주면 아래와 같이 결과가 출력된다.

  • 문이 정방향일 때

  • 문의 방향을 돌려도 트리거 영역이 같이 돌아가기 때문에 문이 어느 방향이든 정상적으로 작동함.

이와 같이 문제를 해결했으나 나중에 리팩토링 및 테스트를 거치는 과정에서 고민해야 할 과제가 있다.

  • 지금은 문 트리거 코드 두개를 추가하는 방식으로 제작했지만, 이 부분을 인터페이스 등으로 활용해서 코드 량을 줄일 수 있는 방법이 있지 않을까?
  • 지금의 작동방식으로는 문 자체는 잘 작동하지만, 플레이어가 문에 부딪혀 밀리거나 하는 움직임 등이 보인다. 이걸 어떻게든 개선할 방법?

이런 내용은 추후에 해결하고, 우선은 다른 기능 개발에 집중하기로 했다.

3. 문 스위치 만들기

이번에는 철창과 같은 상하로 움직이는 문을 만들고, 해당 문을 스위치를 통해서 조작하는 기능을 만드려고 한다.
또한 요청사항으로, 스위치의 거리와 상관 없이 멀리서도 열 수 있는 문을 구현하는 것이 목적이 되었다.

당장에는 심화 과제에 대한 부분은 최대한 배제하고, 기능 자체의 구현만 했으며 해당 기능을 구현하는 건 그리 어렵지 않았다.

3.1 애니메이션 만들기

애니메이션을 만드는 과정은 저번 영상에서 이미 언급했으니 간단하게만 설정을 어떻게 했는지만 하고 넘어가려 한다.

  • 애니메이션 - Idle과 애니메이션 - Open을 각각 만든다

Idle은 초기 상태를 그대로 저장하면 되고, Open은 Position을 Y축으로 상승시키는 방식으로 구현하였다.
애니메이션 - Open은 루프를 해제한다.

3.2 애니메이터 세팅


IsOpen bool 변수를 추가하고, 해당 변수가 true 일때 open 애니메이션이 출력되고 false 일 때 idle 애니메이션으로 돌아가게 한다.
또한 둘 다의 경우에서 Has Exit Time을 해제하고, 애니메이션의 자연스러움을 위해 세팅을 조금 수정했다.

3.3 스위치의 트리거 영역 설정 및 스크립트 작성


스위치의 트리거 영역을 위와 같이 설정하고, 스위치의 트리거 이벤트에 대한 코드를 작성하였다.

using UnityEngine;

public class SwitchController : MonoBehaviour, IInteractable
{
    [SerializeField] Animator m_cageAnimator;
    private bool m_switchOn = false;
    private bool m_interactable = false;

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "Player")
            m_interactable = true;
    }
    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "Player")
            m_interactable = false;
    }

    public void Interact()
    {
        if (m_interactable)
        m_switchOn = !m_switchOn;
        m_cageAnimator.SetBool("IsOpen", m_switchOn);
    }
}

이와 같이 작성하고, 테스트용으로 PlayerController에서 F키를 누르면 해당 Interact를 가져오도록 하여 테스트를 해 보았다.
애니메이터 인스펙터 참조 변수로 만들어놨기 때문에 원하는 오브젝트는 얼마든지 바꿀 수 있게 세팅되어 있다.

결과는 아래와 같이 나온다.

해당 영역 밖에서는 스위치를 누를 수 없고 스위치를 누르면 애니메이션이 정상작동되는 것을 확인할 수 있었다.
또한 오브젝트의 애니메이터를 직접 이용하는 방식이기 때문에 스위치 거리와는 관계 없이 애니메이션을 작동시킬 수 있었다.

  • 유의해야 할 점

테스트 진행 과정에서 확인된 문제지만, 처음엔 OnTriggerEnter 부분에 if (other.gameObject.tag == "Player") && (Input.GetKeyDown(KeyCode.F))로 구현하려 했지만, 이와 같이 작성했을 때 간헐적으로 입력이 먹히지 않는 등의 문제가 생겼다. 그래서 판정을 확실히 하기 위해서 위와 같이 bool 변수 판정에 따라 Interact를 작동하는 방식으로 수정하였다.

이재 생각할 수 있는 추가 과제는 아래와 같은 게 있다.

  • 스위치 하나로 여러 개의 문을 열 수 있게 만드는 방법? - 해당 기믹을 이용해 퍼즐 요소를 만들 수 있을 것으로 기대됨

4. 커서 아이콘의 구현

4.1 커서의 구현 1 - 코드로 구현하기

커서 아이콘을 구현하는 방법에 관해 찾아보았고, 생각보다 간단하게 구현할 수 있었다.
먼저 테스트용으로 커서 아이콘을 직접 만들었다.

아래는 커서 아이콘을 구현한 코드이다.

using UnityEngine;

public class CursorUI : MonoBehaviour
{
    [SerializeField] Transform m_cursor;

    void Update()
    {
        CursorMoving();
    }

    private void CursorMoving()
    { 
        // 마우스 커서 위치 보정값
        float x = Input.mousePosition.x - (Screen.width / 2 ) + 40;
        float y = Input.mousePosition.y - (Screen.height / 2 ) - 55;
        m_cursor.localPosition = new Vector2(x, y);
       

        // 마우스가 화면 밖으로 튀어나가지 않도록 함
        float tempCursorPosX = m_cursor.localPosition.x;
        float tempCursorPosY = m_cursor.localPosition.y;

        float tempCursorMinWidth = -Screen.width / 2;
        float tempCursorMaxWidth = Screen.width / 2;
        float tempCursorMinHeight = -Screen.height / 2;
        float tempCursourMaxHeight = Screen.height / 2;

        // 튀어나가는 정도 보정값
        int padding = 20;

        tempCursorPosX = Mathf.Clamp(tempCursorPosX, tempCursorMinWidth + padding, tempCursorMaxWidth - padding);
        tempCursorPosY = Mathf.Clamp(tempCursorPosY, tempCursorMinHeight + padding, tempCursourMaxHeight - padding);

        m_cursor.localPosition = new Vector2(tempCursorPosX, tempCursorPosY);
    }
}

이와 같이 정상적으로 작동하는 것을 확인할 수 있었다.

  • Mathf.Clamp

기본형

public float Clamp(float value, float min, float max);

매개변수
value : 제한하려는 값
min : 허용 가능한 최소값
max : 허용 가능한 최대값

반환
만약 value가 min보다 작으면 min 값으로, max보다 크다면 max값으로, min과 max의 사이에 있다면 그대로 반환된다.

4.2 커서의 구현 - Project Setting 변경

위와 같은 방식으로 커서를 구현하긴 했으나, 어이 없게도 더 쉬운 방법을 찾아내게 되었다.

바로 Project Setting을 사용하는 방식이다.

Project Setting에 들어가서 플레이어 쪽으로 가 보면, Default Player Cursor를 설정할 수 있는 부분이 있다. 여기에서 단순하게 이미지 세팅만 해주면 아주 자연스러운 마우스 커서 세팅이 가능하다.

실제로 위의 코드로 구현한 것보다 훨씬 자연스러운 커서를 만들 수가 있었다.(위의 방법 같은 경우는 약간의 지연시간이 발생)

결국 기존에 작성했던 커서 스크립트는 폐기하고, Default Player Cursor를 설정하는 방식으로 진행하기로 결정되었다.

5. 이슈 발생 - UI 동작 오류

버그는 아니지만 버그같은 문제가 발생하였다. 여러모로 테스트를 진행하고 있던 중 아래와 같은 문제가 발생했다.

영상으로도 보여주려고 한 부분은, Item을 표시하는 UI가 문 또는 스위치 트리거 영역 안에 들어가면 UI가 출력되지 않는 현상이 발생했기 때문이다. 왜 이런 문제가 발생하는 건지 코드를 살펴 보니, 아무래도 이와 같은 문제가 발생하는 것 같았다.

// 문제가 발생하는 영역의 코드 일부

...

 private void OnMouseOver()
 {
     m_panel.SetActive(true);

     Vector2 localPos = Vector2.zero;

     RectTransform rectTransform = m_canvas.transform as RectTransform;
     Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);

     RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, mousePos, m_canvas.worldCamera, out localPos);
     m_panelpos.anchoredPosition = localPos;
 }

...
  • 오브젝트가 OnMouseOver일 때 Panel이 출력되는 것으로 보인다. 하지만 아이템이 트리거 영역 안에 들어 있으면 아이템을 직접 올릴 수 없고 트리거 영역 위에 마우스가 놓이는 셈이다. 이로 인해 아이템을 인식하지 못하는 현상이 발생하는 것으로 보인다.

하지만 그렇다고 해서 문이나 스위치의 트리거 영역을 없앨 수도 없는 상황이고, 이를 해결할 만한 대책이 필요해 보였다.
단적인 방법으로 문이나 스위치가 있는 쪽에는 아이템 배치를 최대한 안하도록 하는 것이 최선일 수도 있으나, 우선은 팀장님의 요청으로 해당 문제는 이슈로 등록하여 해결 방법을 고민해 보기로 하였다.

이슈로 해당 내용을 등록해 놓고 문제 해결을 위한 방법을 찾아보고자 한다.

6. 결론

1일차에서 진행한 작업은 다음과 같다.

  • Item에 커서를 갖다대면 UI를 출력하는 기능 구현
  • 양 방향으로 움직이면서 근거리에서만 열 수 있는 문 구현
  • 스위치로 열 수 있는 철창 문의 구현
  • 마우스의 커서를 변경

차후에 해결해야 할 문제점은 다음과 같다.

  • 아이템 표시 UI를 출력할 때, 어느 정도 가까운 거리에서만 출력하도록 하는 기능 구현
  • 문의 트리거를 이용한 코드를 좀 더 간결하게 만들 방법
  • 인게임 아이템 UI가 트리거와 겹쳤을 때 출력되지 않는 현상 해결

아직 구현하지 않은, 구현해야 할 과제는 다음과 같다

  • 캐릭터 체력과 스태미나 UI의 구현 - 토요일이 이미 구현해보았으나, 현재 그 방법을 그대로 채택할 지는 보류중
  • 맵 내에서 발생하는 트리거 이벤트 - 현재 합의된 내용이 없고, 맵이 없어 보류중
profile
게임 만들러 코딩 공부중

0개의 댓글