게임 엔진/Window API
[Window API] UI
Henzee
2025. 3. 5. 18:26
게임 프로그래머 입문 올인원 강의 수강 후 복습용으로 작성
1. UI 만들기
1) UI 클래스 추가
// UI.h
#pragma once
class UI
{
public:
UI();
virtual ~UI();
virtual void BeginPlay();
virtual void Tick();
virtual void Render(HDC hdc);
void SetPos(Vec2 pos) { _pos = pos; }
Vec2 GetPos() { return _pos; }
RECT GetRect();
bool IsMouseInRect();
protected:
Vec2 _pos = { 400, 300 };
Vec2Int _size = { 150, 150 };
};
// UI.cpp
// ...
RECT UI::GetRect()
{
RECT rect =
{
_pos.x - _size.x / 2,
_pos.y - _size.y / 2,
_pos.x + _size.x / 2,
_pos.y + _size.y / 2,
};
return rect;
}
bool UI::IsMouseInRect()
{
RECT rect = GetRect();
POINT mousePos = GET_SINGLE(InputManager)->GetMousePos();
// 1) 메서드 이용해서 반환
//return ::PtInRect(&rect, mousePos);
// 2) 직접 구해주기
if (mousePos.x < rect.left) return false;
if (rect.right < mousePos.x) return false;
if (mousePos.y < rect.top) return false;
if (rect.bottom < mousePos.y) return false;
return true;
return false;
}
2) Button 클래스 추가
// Button.h
#pragma once
#include "UI.h"
class Sprite;
enum ButtonState
{
BS_Default,
BS_Hovered,
BS_Pressed,
BS_Clicked,
BS_MaxCount,
};
class Button : public UI
{
using Super = UI;
public:
Button();
virtual ~Button() override;
virtual void BeginPlay() override;
virtual void Tick() override;
virtual void Render(HDC hdc) override;
void SetSize(Vec2Int size) { _size = size; }
Sprite* GetSprite(ButtonState state) { return _sprites[state]; }
void SetCurrentSprite(Sprite* sprite) { _currentSprite = sprite; }
void SetSprite(Sprite* sprite, ButtonState state) { _sprites[state] = sprite; }
void SetButtonState(ButtonState state);
void OnClickButton() {}
protected:
Sprite* _currentSprite = nullptr;
Sprite* _sprites[BS_MaxCount] = {};
ButtonState _state = BS_Default;
float _sumTime = 0.f;
public:
template<typename T>
void AddOnClickDelegate(T* owner, void(T::* func)())
{
_onClick = [owner, func]()
{
(owner->*func)();
};
}
// 함수 포인터 + 함수 객체
std::function<void(void)> _onClick = nullptr;
};
// Button.cpp
// ...
void Button::BeginPlay()
{
Super::BeginPlay();
SetButtonState(BS_Default);
}
void Button::Tick()
{
POINT mousePos = GET_SINGLE(InputManager)->GetMousePos();
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
if (_state == BS_Clicked)
{
_sumTime += deltaTime;
if (_sumTime >= 0.2f)
{
_sumTime = 0.f;
SetButtonState(BS_Default);
}
}
if (IsMouseInRect())
{
if (GET_SINGLE(InputManager)->GetButton(KeyType::LeftMouse))
{
SetButtonState(BS_Pressed);
// OnPressed
}
else
{
if (_state == BS_Pressed)
{
SetButtonState(BS_Clicked);
// OnClicked
}
}
}
else
{
SetButtonState(BS_Default);
}
}
void Button::Render(HDC hdc)
{
if (_currentSprite)
{
::TransparentBlt(hdc,
(int32)_pos.x - _size.x / 2,
(int32)_pos.y - _size.y / 2,
_size.x,
_size.y,
_currentSprite->GetDC(),
_currentSprite->GetPos().x,
_currentSprite->GetPos().y,
_currentSprite->GetSize().x,
_currentSprite->GetSize().x,
_currentSprite->GetTransparent());
}
else
{
Utils::DrawRect(hdc, _pos, _size.x, _size.y);
}
}
void Button::SetButtonState(ButtonState state)
{
_state = state;
if (_sprites[state])
SetCurrentSprite(_sprites[state]);
}
3) DevScene 수정
- Button 추가
// DevScene.h
class UI;
class DevScene : public Scene
{
public:
vector<UI*> _uis;
};
// DevScene.cpp
void DevScene::Init()
{
{
Button* ui = new Button();
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Start_Off"), BS_Default);
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Start_On"), BS_Clicked);
ui->SetPos({200, 200});
_uis.push_back(ui);
}
for (UI* ui : _uis)
ui->BeginPlay();
}
void DevScene::Update()
{
for (UI* ui : _uis)
ui->Tick();
}
void DevScene::Render(HDC hdc)
{
for (UI* ui : _uis)
ui->Render(hdc);
}
2. Button 클릭 상태에 따라 콜백 함수 호출 : Delegate 연결
1) Button 클래스 수정
- Button이 클릭되면 OnClickButton() 호출
// Button.h
class Button : public UI
{
void OnClickButton() {}
public:
template<typename T>
void AddOnClickDelegate(T* owner, void(T::* func)())
{
_onClick = [owner, func]()
{
(owner->*func)();
};
}
// 함수 포인터 + 함수 객체
std::function<void(void)> _onClick = nullptr;
};
// Button.cpp
void Button::BeginPlay()
{
// ...
AddOnClickDelegate(this, &Button::OnClickButton);
}
void Button::Tick()
{
// ...
if (IsMouseInRect())
{
if (GET_SINGLE(InputManager)->GetButton(KeyType::LeftMouse))
{
SetButtonState(BS_Pressed);
// OnPressed
}
else
{
if (_state == BS_Pressed)
{
SetButtonState(BS_Clicked);
// OnClicked
if (_onClick)
_onClick();
}
}
}
else
{
SetButtonState(BS_Default);
}
}
// pch.h
#include <functional>
3. UI를 계층 관계로 만들기
1) Panel 클래스 추가
- Panel : UI를 묶어서 관리하는 UI Sheet
// Panel.h
#pragma once
#include "UI.h"
class Panel : public UI
{
using Super = UI;
public:
Panel();
virtual ~Panel() override;
virtual void BeginPlay() override;
virtual void Tick() override;
virtual void Render(HDC hdc) override;
void AddChild(UI* ui);
bool RemoveChild(UI* ui);
private:
vector<UI*> _children;
};
// Panel.cpp
#include "pch.h"
#include "Panel.h"
Panel::Panel()
{
}
Panel::~Panel()
{
for (UI* child : _children)
SAFE_DELETE(child);
_children.clear();
}
void Panel::BeginPlay()
{
Super::BeginPlay();
for (UI* child : _children)
child->BeginPlay();
}
void Panel::Tick()
{
Super::Tick();
for (UI* child : _children)
child->Tick();
}
void Panel::Render(HDC hdc)
{
Super::Render(hdc);
for (UI* child : _children)
child->Render(hdc);
}
void Panel::AddChild(UI* ui)
{
if (ui == nullptr)
return;
_children.push_back(ui);
}
bool Panel::RemoveChild(UI* ui)
{
auto findIt = std::find(_children.begin(), _children.end(), ui);
if (findIt == _children.end())
return false;
_children.erase(findIt);
return true;
return false;
}
2) TestPanel 클래스 추가
- Start, Edit, Exit 버튼 생성하기
// TestPanel.h
#pragma once
#include "Panel.h"
class TestPanel : public Panel
{
using Super = Panel;
public:
TestPanel();
virtual ~TestPanel() override;
virtual void BeginPlay() override;
virtual void Tick() override;
virtual void Render(HDC hdc) override;
void OnClickStartButton();
void OnClickEditButton();
void OnClickExitButton();
int32 _count = 0;
};
// TestPanel.cpp
#include "pch.h"
#include "TestPanel.h"
#include "Utils.h"
#include "Button.h"
#include "ResourceManager.h"
TestPanel::TestPanel()
{
{
Button* ui = new Button();
ui->SetPos({ 400, 200 });
ui->SetSize({ 530, 300 });
AddChild(ui);
}
{
Button* ui = new Button();
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Start_Off"), BS_Default);
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Start_On"), BS_Clicked);
ui->SetPos({ 200, 200 });
ui->AddOnClickDelegate(this, &TestPanel::OnClickStartButton);
AddChild(ui);
}
{
Button* ui = new Button();
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Edit_Off"), BS_Default);
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Edit_On"), BS_Clicked);
ui->SetPos({ 400, 200 });
ui->AddOnClickDelegate(this, &TestPanel::OnClickEditButton);
AddChild(ui);
}
{
Button* ui = new Button();
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Exit_Off"), BS_Default);
ui->SetSprite(GET_SINGLE(ResourceManager)->GetSprite(L"Exit_On"), BS_Clicked);
ui->SetPos({ 600, 200 });
ui->AddOnClickDelegate(this, &TestPanel::OnClickExitButton);
AddChild(ui);
}
}
TestPanel::~TestPanel()
{
}
void TestPanel::BeginPlay()
{
Super::BeginPlay();
}
void TestPanel::Tick()
{
Super::Tick();
}
void TestPanel::Render(HDC hdc)
{
Super::Render(hdc);
wstring str = std::format(L"Count : {0}", _count);
Utils::DrawTextW(hdc, { 100, 0 }, str);
}
void TestPanel::OnClickStartButton()
{
_count++;
}
void TestPanel::OnClickEditButton()
{
_count--;
}
void TestPanel::OnClickExitButton()
{
_count = 0;
}
3) DevScene 수정
- TestPanel 사용하기
// DevScene.cpp
void DevScene::Init()
{
{
TestPanel* ui = new TestPanel();
_uis.push_back(ui);
}
}