게임 엔진/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);
	}
}