Button 실습

Jaemyeong Lee·2025년 4월 2일
0

1. 직교 투영 기반 UI 버튼 클릭 구현 개요

일반적으로 UI를 구성할 때는 게임의 3D 씬과는 별도의 2D 공간에서 버튼과 같은 인터페이스 요소를 렌더링하고, 이 위에 클릭 이벤트를 처리할 수 있어야 한다. Unity에서 EventSystem과 InputModule이 이런 역할을 해주는데, 여기서는 직접 구현해본다.

핵심 구현 요소는 다음과 같다:

  • 직교 투영 카메라를 이용한 UI 전용 뷰 구성
  • 버튼 영역 정의 및 클릭 좌표 포함 여부 판단
  • 콜백 함수 연결 및 실행 구조
  • Component 기반 확장성 고려

2. Button 클래스 구조 설계

버튼 기능은 UI이므로 Component를 상속하는 방식으로 설계한다. 기본 구조는 다음과 같다.

class Button : public Component
{
public:
    Button(); 
    virtual ~Button(); 

    bool Picked(POINT screenPos);   // 마우스 좌표가 버튼 안에 있는지 판별
    void Create(Vec2 screenPos, Vec2 size, shared_ptr<Material> material); // 버튼 생성

    void AddOnClickedEvent(std::function<void(void)> func);  // 클릭 이벤트 등록
    void InvokeOnClicked(); // 클릭 이벤트 발생 시 호출

private:
    std::function<void(void)> _onClicked;  // 등록된 콜백
    RECT _rect;  // 버튼의 실제 클릭 가능한 영역
};

🧠 핵심 로직 설명

  • Create: 버튼 생성 시 화면 좌표를 월드 좌표로 변환해서 버튼 위치 지정 + 크기 설정 + 렌더러 구성
  • _rect: 화면 좌표 기준으로 실제 마우스 클릭 시 피킹 여부를 판단하는 영역
  • Picked: WinAPI의 PtInRect()를 사용해 클릭 여부 판별
  • AddOnClickedEvent: 클릭 시 호출될 사용자 정의 콜백 함수 등록
  • InvokeOnClicked: 클릭된 경우 콜백 호출

3. Scene에 UI 피킹 시스템 추가

버튼이 클릭되었는지를 판단하고 해당 버튼의 콜백을 실행시킬 위치가 필요하다. 이 로직은 Scene::PickUI()로 분리한다.

void Scene::PickUI()
{
    if (INPUT->GetButtonDown(KEY_TYPE::LBUTTON) == false)
        return;

    if (GetUICamera() == nullptr)
        return;

    POINT screenPt = INPUT->GetMousePos();

    for (auto& gameObject : GetObjects())
    {
        if (gameObject->GetButton() == nullptr)
            continue;

        if (gameObject->GetButton()->Picked(screenPt))
            gameObject->GetButton()->InvokeOnClicked();
    }
}

Scene::Update() 내부에서 매 프레임마다 호출해주는 방식으로 항상 체크되게 한다.


4. 버튼 클릭 이벤트 연결 방법

버튼이 클릭되었을 때 수행할 작업은 다음처럼 람다 함수AddOnClickedEvent()에 등록하면 된다.

obj->GetButton()->AddOnClickedEvent([obj]() {
    CUR_SCENE->Remove(obj);  // 버튼 클릭 시 자기 자신 제거
});

람다 사용 시 주의사항

  • 캡처한 포인터가 해제되면 위험하므로 스마트 포인터 사용 필수
  • 참조 카운트 문제로 해제되지 않는 상황 발생 가능
  • 캡처가 내부적으로 Functor처럼 동작하는 점 인지할 것

5. 직교 투영 기반 UI 카메라 세팅

UI는 3D 씬과는 별도로 항상 고정된 크기로 보여야 하므로, Orthographic 카메라를 별도로 세팅한다.

auto camera = make_shared<GameObject>();
camera->AddComponent(make_shared<Camera>());
camera->GetCamera()->SetProjectionType(ProjectionType::Orthographic);
camera->GetCamera()->SetCullingMaskAll();  // 모든 걸 안 보이게 한 뒤
camera->GetCamera()->SetCullingMaskLayerOnOff(Layer_UI, false); // UI만 보이게

6. ButtonDemo 예시로 테스트

버튼을 추가하고 클릭했을 때 해당 오브젝트가 사라지는 예제를 구현한다.

auto obj = make_shared<GameObject>();
obj->AddComponent(make_shared<Button>());

obj->GetButton()->Create(Vec2(100, 100), Vec2(100, 100), RESOURCES->Get<Material>(L"UnityLogo"));

obj->GetButton()->AddOnClickedEvent([obj]() {
    CUR_SCENE->Remove(obj);
});

CUR_SCENE->Add(obj);

✅ 클릭 시 등록된 콜백이 호출되어 오브젝트가 삭제됨을 확인 가능.


profile
李家네_공부방

0개의 댓글