프로그래밍 공부
작성일
2023. 11. 13. 20:43
작성자
WDmil
728x90

함수포인터를 활용한 Button을 사용해 인벤토리를 구축해보자.

 

먼저. Button을 설계해보자.


Button

#pragma once


// Quad 클래스를 상속하는 Button 클래스를 정의합니다.
class Button : public Quad
{
protected:
	enum State
	{
		// 버튼의 상태를 정의한 열거형입니다.
		// NONE: 기본 상태
		// DOWN: 버튼이 눌렸을 때의 상태
		// OVER: 마우스 포인터가 버튼 위에 있을 때의 상태
		NONE, DOWN, OVER
	};

	// 각 버튼 상태에 대한 색상을 정의한 상수 멤버 변수입니다.
	const Float4 NONE_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; // 기본 상태의 색상 (흰색)
	const Float4 DOWN_COLOR = { 0.5f, 0.5f, 0.5f, 0.5f }; // 눌린 상태의 색상 (회색)
	const Float4 OVER_COLOR = { 0.9f, 0.9f, 0.9f, 0.9f }; // 마우스 포인터가 위에 있을 때의 색상 (연한 회색)

public:
	// 버튼 생성자 함수입니다.
	Button(wstring textureFile);

	// 버튼 생성자 함수 (크기 지정)입니다.
	Button(Float2 size = { 32, 32 });

	// 버튼 소멸자 함수입니다.
	~Button() = default;

	// 버튼의 상태를 업데이트하는 함수입니다.
	void Update();

	// 버튼의 클릭 이벤트를 설정하는 함수입니다.
	void SetEvent(Event event) { this->event = event; }

	// 버튼의 클릭 이벤트 및 이벤트 핸들러 객체를 설정하는 함수입니다.
	void SetParamEvent(ParamEvent paramevent, void* object)
	{
		this->paramevent = paramevent;
		this->object = object;
	}

	// 버튼의 놓아짐 이벤트를 설정하는 함수입니다.
	void SetUpEvent(Event event) { this->Upevent = event; }

	// 버튼의 놓아짐 이벤트 및 이벤트 핸들러 객체를 설정하는 함수입니다.
	void SetUpParamEvent(ParamEvent paramevent, void* object)
	{
		this->Upparamevent = paramevent;
		this->Upobject = object;
	}

	// 현재 버튼의 상태를 반환하는 함수입니다.
	State GetState() { return state; }

private:
	// 버튼의 상태를 확인하고 이벤트를 실행하는 내부 함수입니다.
	void ChackStateEvent();

	// 지정된 이벤트 및 이벤트 핸들러 객체를 실행하는 내부 함수입니다.
	void RunEvent(Event ev, ParamEvent pev, void* input);

protected:
	State state = NONE; // 현재 버튼의 상태

	Event event = nullptr; // 클릭 이벤트 핸들러 함수 포인터
	ParamEvent paramevent = nullptr; // 클릭 이벤트 핸들러 객체 포인터
	void* object = nullptr; // 클릭 이벤트 핸들러 객체

	Event Upevent = nullptr; // 놓아짐 이벤트 핸들러 함수 포인터
	ParamEvent Upparamevent = nullptr; // 놓아짐 이벤트 핸들러 객체 포인터
	void* Upobject = nullptr; // 놓아짐 이벤트 핸들러 객체

	bool isDownCheck = false; // 버튼이 눌렸는지 여부를 나타내는 플래그
};

button이다.

 

위와같은 방식으로 이루어져 있으며, 현재 state에 따라. 함수포인터에 지정된 함수를 실행해주는 방식을 가진다.

 

함수포인터 방식을 사용하면. 현재 지정되지 않은 함수를 미리 지정하여. 현재 지정된 포인터의 값을 기입하고. 그 값을 활용하여. 데이터를 주고받을 수 있다.

 

C++에서 일반적으로 절차지향형으로 코드를 진행하나. 절차를 거의 무시한것처럼 활용할 수 있다.

 

SetEvent와 SetParamEvent를 사용하여 함수포인터를 지정해줄 수 있다.

#include "Framework.h"

// 버튼의 생성자 함수 정의
Button::Button(wstring textureFile) : Quad(textureFile)
{
	// Quad 클래스의 생성자를 호출하고 텍스처 파일을 전달합니다.
}

// 버튼의 생성자 함수 (크기 지정) 정의
Button::Button(Float2 size) : Quad(size)
{
	// Quad 클래스의 생성자를 호출하고 크기를 전달합니다.
}

// 버튼의 업데이트 함수 정의
void Button::Update()
{
	// 버튼이 활성화되지 않았을 경우 아무 동작도 수행하지 않습니다.
	if (IsActive()) return;

	// 마우스 커서와의 충돌을 체크합니다.
	if (CollisionChack(Mouse::Get()->GetPosition()))
	{
		// 마우스 왼쪽 버튼이 눌렸을 때, 눌린 상태 플래그를 설정합니다.
		if (Mouse::Get()->Down(0))
			isDownCheck = true;

		// 마우스 왼쪽 버튼이 눌린 상태인 경우 DOWN 상태로 설정하고,
		// 그렇지 않은 경우 OVER 상태로 설정합니다.
		if (Mouse::Get()->Press(0))
			state = DOWN;
		else
			state = OVER;

		// 버튼이 눌린 상태에서 마우스 왼쪽 버튼이 놓아질 때, 클릭 이벤트를 실행합니다.
		if (isDownCheck && Mouse::Get()->Up(0))
		{
			// 이벤트가 설정되어 있으면 실행합니다.
			if (event)
				event();

			// 놓아짐 이벤트 및 이벤트 핸들러 객체를 실행합니다.
			RunEvent(Upevent, Upparamevent, Upobject);

			// 눌린 상태 플래그를 해제합니다.
			isDownCheck = false;
		}
	}
	else
	{
		// 마우스 커서가 버튼 위에 없는 경우 NONE 상태로 설정합니다.
		state = NONE;

		// 마우스 왼쪽 버튼이 놓아질 때, 눌린 상태 플래그를 해제합니다.
		if (Mouse::Get()->Up(0)) {
			isDownCheck = false;
		}
	}

	// 버튼의 상태에 따라 적절한 색상을 설정합니다.
	switch (state)
	{
	case Button::NONE:
		material->GetBuffer()->diffuse = NONE_COLOR; // 기본 상태의 색상
		break;
	case Button::DOWN:
		material->GetBuffer()->diffuse = DOWN_COLOR; // 눌린 상태의 색상
		break;
	case Button::OVER:
		material->GetBuffer()->diffuse = OVER_COLOR; // 마우스 포인터가 위에 있을 때의 색상
		break;
	}

	// 버튼의 상태에 따라 이벤트를 체크하고 실행하는 함수를 호출합니다.
	ChackStateEvent();

	// 버튼의 월드 변환을 업데이트합니다.
	UpdateWorld();
}

// 버튼의 상태에 따라 이벤트를 체크하고 실행하는 함수 정의
void Button::ChackStateEvent()
{
	switch (state)
	{
	case Button::NONE:
		break;
	case Button::DOWN:
		// DOWN 상태에서는 클릭 이벤트를 실행합니다.
		RunEvent(event, paramevent, object);
		break;
	case Button::OVER:
		break;
	default:
		break;
	}
}

// 지정된 이벤트 및 이벤트 핸들러 객체를 실행하는 함수 정의
void Button::RunEvent(Event ev, ParamEvent pev, void* input)
{
	// 이벤트 함수가 설정되어 있으면 실행합니다.
	if (ev != nullptr) ev();
	// 이벤트 핸들러 함수와 객체가 설정되어 있으면 실행합니다.
	if (pev != nullptr && input != nullptr) pev(input);
}

현재 버튼의 State를 확인하고, update된 State에 따라 데이터를 처리하는 방식이다.

 

ChackStateEvent()를 통해 현재 상태값을 갱신받으면, 상태값에 따라 Event를 실행할지 말지를 결정한다.

위의 Up에 적혀있는건 State로 해결이 안되고, 현재 마우스가 위로 올라갔는지 여부를 따지기 때문에 어쩔 수 없이 저기에 놓았다.

 


해당 Button을 사용해서 각 인벤토리 칸끼리 유동적으로 연결되는 함수를 작성해보자.

 

Button이 어떤 데이터인지 어떤 데이터가 필요한지 등을 확인해보면 쉽게 할 수 있는데,

 

먼저. list이든 map이든 편한대로 데이터Block을 생성하고 관리해주면 된다.

 

각 Block은 Collision을 확인하여 Collision이 True라면, 함수를 실행함으로. 맵을 생성하고 위치를 조정한뒤.

 

Update만 해주면된다.

#pragma once


class InventoryUI : public Quad
{
public:
	InventoryUI(wstring textureFile);
	~InventoryUI();

	void Update();
	void PostRender();

	void insertblock(Block* block);

	InvenBlock* GetUnderinventory(UINT number) { return Under_inventorymap[number]; }

	void SetDrag() { isDrag = !isDrag; }

private:
	void InventoryRender();
	void InventoryposUpdate();

	void Under_inventoryViewUpdate();
	void Under_inventoryViewRender();

	void Update_MouseBag();
	void PostRender_MouseBag();

private:
	Vector3 dragoffset;

	unordered_map<UINT, InvenBlock*> inventorymap;
	Vector3 inventoryBase = { -18 * 4, -18 * 2.5, 0.0f };
	Vector3 inventoryBaseDown = { -18 * 4, -18 * 3.5 - 4, 0.0f };

	unordered_map<UINT, InvenBlock*> Under_inventorymap;
	vector<Quad*> Under_inventoryView;

	Vector3 Under_inventorymapBaseDown = { CENTER_X - 160, 20, 0.0f };

	pair<InvenBlock*, pair<UINT,Block*>> MouseBag;
	Quad* MouseObject;

	bool isDrag = false;
};
#include "Framework.h"
#include "InventoryUI.h"

InventoryUI::InventoryUI(wstring textureFile)
	: Quad(textureFile)
{
	SetActive(false);

	MouseObject = new Quad(L"Textures/UI/Blocks/block0.png");
	MouseObject->IsRender();

	Under_inventoryView.resize(9);
	FOR(9)
	{
		Under_inventorymap[i] = new InvenBlock(inventoryBaseDown + Vector3(i * 18, 0, 0), this);
		Under_inventorymap[i]->SetParent(this);

		ParamEvent def = bind(&InvenBlock::PopMouse, Under_inventorymap[i], placeholders::_1);
		Under_inventorymap[i]->SetPramEvnet(def, &MouseBag);

		def = bind(&InvenBlock::InsertMouse, Under_inventorymap[i], placeholders::_1);
		Under_inventorymap[i]->SetUpPramEvnet(def, &MouseBag);

		Under_inventorymap[i]->IsRender();

		Quad* insertquad = new Quad(L"Textures/UI/Blocks/block0.png");
		insertquad->SetLocalPosition(Under_inventorymapBaseDown + Vector3(i * 40, 0, 0));
		insertquad->UpdateWorld();

		Under_inventoryView[i] = insertquad;
		Under_inventoryView[i]->IsRender();
		Under_inventoryView[i]->SetActive(false);
		
		UIManager::Get()->AddUI(Under_inventoryView[i]);
	}

	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			inventorymap[i * 9 + j] = new InvenBlock(inventoryBase + Vector3(j * 18, i * 18, 0), this);
			inventorymap[i * 9 + j]->SetParent(this);

			ParamEvent def = bind(&InvenBlock::PopMouse, inventorymap[i * 9 + j], placeholders::_1);
			inventorymap[i * 9 + j]->SetPramEvnet(def, &MouseBag);

			def = bind(&InvenBlock::InsertMouse, inventorymap[i * 9 + j], placeholders::_1);
			inventorymap[i * 9 + j]->SetUpPramEvnet(def, &MouseBag);

			inventorymap[i * 9 + j]->IsRender();
			inventorymap[i * 9 + j]->SetActive(false);
			UIManager::Get()->AddUI(inventorymap[i * 9 + j]);
		}
	}

	Load();
}

InventoryUI::~InventoryUI()
{
	Save();
}

void InventoryUI::Update()
{
	Under_inventoryViewUpdate();

	if (MouseBag.second.second == nullptr) {
		if (!isDrag && Mouse::Get()->Down(0) && CollisionChack(Mouse::Get()->GetPosition()))
		{
			isDrag = true;
			dragoffset = GetGlobalPosition() - Mouse::Get()->GetPosition();
		}

		if (isDrag && Mouse::Get()->Press(0) && MouseBag.second.second == nullptr)
		{
			SetLocalPosition(Mouse::Get()->GetPosition() + dragoffset);
		}
	}
	if (Mouse::Get()->Up(0))
		isDrag = false;

	UpdateWorld();
	InventoryposUpdate();
	Update_MouseBag();
}

void InventoryUI::PostRender()
{
	Quad::Render();
	InventoryRender();
	Under_inventoryViewRender();
	PostRender_MouseBag();
}

void InventoryUI::insertblock(Block* block)
{
	FOR(9)
	{
		if (Under_inventorymap[i]->InsertBlock(block))
			return;
	}

	FOR(27)
	{
		if (inventorymap[i]->InsertBlock(block))
			return;
	}
}

void InventoryUI::InventoryRender()
{
	if (GetRender()) {
		FOR(9)
			Under_inventorymap[i]->PostRender();

		FOR(27)
			inventorymap[i]->PostRender();
	}

}

void InventoryUI::InventoryposUpdate()
{
	FOR(9) {
		Under_inventorymap[i]->Update();
	}

	FOR(27) {
		inventorymap[i]->Update();
	}
}

void InventoryUI::Under_inventoryViewUpdate()
{
	FOR(9)
	{
		if (Under_inventorymap[i]->GetBlock() != nullptr)
		{
			if (Under_inventoryView[i]->GetRender() == false)
				Under_inventoryView[i]->IsRender();

			wstring def = ToWString(Under_inventorymap[i]->GetBlock()->GetBlockData().modelname);
			Under_inventoryView[i]->GetMaterial()->SetDiffuseMap(L"Textures/UI/Blocks/" + def + L".png");
		}
		else
		{
			if (Under_inventoryView[i]->GetRender() == true)
				Under_inventoryView[i]->IsRender();
		}
	}
}

void InventoryUI::Under_inventoryViewRender()
{
	FOR(9)
	{
		if (Under_inventorymap[i]->GetBlock() != nullptr)
		{
			Under_inventoryView[i]->Render();
			string co = to_string(Under_inventorymap[i]->GetCount());
			Float2 pos = Float2(Under_inventoryView[i]->GetLocalPosition().x + 18, Under_inventoryView[i]->GetLocalPosition().y - 8);
			Font::Get()->RenderText(co, pos);
		}
	}
}

void InventoryUI::Update_MouseBag()
{
	if (MouseBag.second.second != nullptr)
	{
		MouseObject->SetLocalPosition(Mouse::Get()->GetPosition());
		MouseObject->UpdateWorld();
	}
}

void InventoryUI::PostRender_MouseBag()
{
	if (MouseBag.second.second != nullptr)
	{
		if (MouseObject->GetRender() == false)
			MouseObject->IsRender();

		wstring def = ToWString(MouseBag.second.second->GetBlockData().modelname);
		MouseObject->GetMaterial()->SetDiffuseMap(L"Textures/UI/Blocks/" + def + L".png");

		string co = to_string(MouseBag.second.first);
		Float2 pos = MouseObject->GetLocalPosition() + Vector3(18, -8, 0);
		Font::Get()->RenderText(co, pos);
		MouseObject->Render();
	}
	else
	{
		if (MouseObject->GetRender() == true)
			MouseObject->IsRender();
	}
}

위와같이. Inventory에 대하여. 생성작업과 소멸작업. 그리고 위치값만 잘 잡아준 후, 현재 InventoryUI에 Parent(부모 기준점) 만 잡아주면

 

알아서 유동적으로 위치가 이동하는걸 확인할 수 있다.

 

각 블록은 스스로 개수와, 어떤 블록을 가지고 있는지, 자기자신이 어떤 Pos에 위치하는지(이건 없어도 무방하긴 하다) 를 가지고 있어야 한다.

 

각 인벤토리 칸을 구성해보자.

#pragma once

// Button 클래스를 상속하는 InvenBlock 클래스를 정의합니다.
class InvenBlock : public Button
{
public:
	// InvenBlock의 생성자 함수 정의
	InvenBlock(Vector3 pos, Transform* Parent = nullptr);

	// InvenBlock의 소멸자 함수 정의
	~InvenBlock() = default;

	// InvenBlock을 초기화하는 함수 정의
	void clear();

	// InvenBlock을 업데이트하는 함수 정의
	void Update();

	// InvenBlock을 화면에 렌더링하는 함수 정의
	void PostRender();

	// 블록을 InvenBlock에 삽입하는 함수 정의
	bool InsertBlock(Block* block, UINT count = 1);

	// InvenBlock에 다른 InvenBlock을 삽입하는 함수 정의
	void InsertBlock(InvenBlock* block);

	// InvenBlock에서 블록을 꺼내는 함수 정의
	Block* PopBlock();

	// 다른 InvenBlock과 블록을 비교하는 함수 정의
	bool CheckBlock(InvenBlock* block);

	// 마우스에서 블록을 꺼내는 함수 정의
	void PopMouse(void* mouseBag);

	// 마우스에 블록을 삽입하는 함수 정의
	void InsertMouse(void* mouseBag);

	// 블록의 개수를 반환하는 함수 정의
	const UINT& GetBlockCount() { return count; }

	// 현재 InvenBlock에 들어있는 블록을 반환하는 함수 정의
	Block* GetBlock() { return block; }

	// InvenBlock에 들어있는 블록 개수를 반환하는 함수 정의
	UINT GetCount() { return count; }

	// InvenBlock에 블록을 설정하는 함수 정의
	void SetBlock(Block* input) { block = input; }

	// InvenBlock에 들어있는 블록 개수를 설정하는 함수 정의
	void SetCount(UINT input) { count = input; }

private:
	UINT count = 0; // InvenBlock에 들어있는 블록의 개수
	Vector3 mainPos; // InvenBlock의 위치
	Block* block = nullptr; // InvenBlock에 들어있는 블록의 포인터
};
#include "Framework.h"
#include "InvenBlock.h"

// InvenBlock 클래스의 생성자 함수 정의
InvenBlock::InvenBlock(Vector3 pos, Transform* Parent)
	: Button(Float2(16, 16)), mainPos(pos)
{
	// Button 클래스의 생성자를 호출하고 버튼 크기와 위치를 설정합니다.
	GetMaterial()->SetDiffuseMap(L"Textures/Colors/Blue.png"); // 버튼의 디퓨즈 맵 설정
	SetLocalPosition(pos); // 버튼의 로컬 위치 설정

	if (Parent != nullptr)
		SetParent(Parent); // 부모 Transform 설정
}

// InvenBlock를 초기화하는 함수 정의
void InvenBlock::clear()
{
	count = 0;
	block = nullptr;

	if (GetRender() == true)
		IsRender();
}

// InvenBlock을 업데이트하는 함수 정의
void InvenBlock::Update()
{
	__super::Update(); // 상위 클래스(Button)의 Update 함수 호출

	if (block != nullptr)
		block->Update(); // InvenBlock에 들어있는 블록을 업데이트

	UpdateWorld(); // 월드 변환 업데이트
}

// 블록을 InvenBlock에 삽입하는 함수 정의
bool InvenBlock::InsertBlock(Block* block, UINT count)
{
	if (block != nullptr && this->block == nullptr) {
		wstring name = ToWString(block->GetBlockData().modelname) + L".png";
		GetMaterial()->SetDiffuseMap(L"Textures/UI/Blocks/" + name); // 버튼의 디퓨즈 맵을 블록 텍스처로 설정

		if (GetRender() == false)
			IsRender();

		this->block = block;
		this->count = count;
		return true;
	}

	if (block != nullptr && (block->GetBlockData().name == this->block->GetBlockData().name)) {
		delete block;
		this->count += count;
		return true;
	}
	return false;
}

// InvenBlock에 다른 InvenBlock을 삽입하는 함수 정의
void InvenBlock::InsertBlock(InvenBlock* block)
{
	if (block != nullptr && CheckBlock(block)) {
		count += block->GetCount();
		delete block;
		return;
	}

	if (block != nullptr)
		block->SetLocalPosition(mainPos);
	return;
}

// InvenBlock에서 블록을 꺼내는 함수 정의
Block* InvenBlock::PopBlock()
{
	if (count == 1) {
		Block* result = block;
		clear();
		return result;
	}
	else
		count--;

	Block* result = nullptr;
	if (block != nullptr)
		result = new Block(block->GetBlockData());

	return result;
}

// 다른 InvenBlock과 블록을 비교하는 함수 정의
bool InvenBlock::CheckBlock(InvenBlock* block)
{
	if (block->GetBlock()->GetBlockData().name == GetBlock()->GetBlockData().name)
		return true;
	return false;
}

// InvenBlock를 화면에 렌더링하는 함수 정의
void InvenBlock::PostRender()
{
	if (block != nullptr)
	{
		string c = to_string(count);
		Vector3 pos = GetGlobalPosition();

		Font::Get()->RenderText(c, Float2(pos.x + 18, pos.y - 8)); // 블록 개수를 화면에 표시
		Render();
	}
}

// 마우스에서 블록을 꺼내는 함수 정의
void InvenBlock::PopMouse(void* mouseBag)
{
	pair<InvenBlock*, pair<UINT, Block*>>* input = static_cast<pair<InvenBlock*, pair<UINT, Block*>>*>(mouseBag);

	if (input->second.second == nullptr && input->second.first <= 0)
	{
		input->first = this;

		input->second.first = count;
		count = 0;

		input->second.second = block;
		block = nullptr;
	}
}

// 마우스에 블록을 삽입하는 함수 정의
void InvenBlock::InsertMouse(void* mouseBag)
{
	pair<InvenBlock*, pair<UINT, Block*>>* input = static_cast<pair<InvenBlock*, pair<UINT, Block*>>*>(mouseBag);

	if (input->second.second == nullptr) return;

	if (block == nullptr || block->GetBlockData().modelname == input->second.second->GetBlockData().modelname) {
		if (InsertBlock(input->second.second, input->second.first))
		{
			input->first = nullptr;
			input->second.first = 0;
			input->second.second = nullptr;
			return;
		}
	}

	input->first->SetBlock(input->second.second);
	input->first->SetCount(input->second.first);

	input->second.first = 0;
	input->second.second = nullptr;
}

 

각 마우스 객체를 정의하는 부분이다.

 

여기서 Void*를 사용하는 부분은 Button이 사용하게될 함수포인터의 지정부이다.

 

시연영상.

 

사실 함수포인터는 주관적으로 생각했을 때, 객체구조를 박살내는 방식인것 같기는 하다.

 

그런데, 제귀함수마냥 잘쓰면 깔끔하게 나타나기는 한다.

728x90