이번에는 만들어준 직교투영 카메라를 활용하여 버튼을 만들고 기능까지 붙여주자. 지금은 레이케스팅을 할 때, 원근 투영된 뷰포트에 대한 값을 찾아주고 있는데

이를 직교투영된 방식으로 해준다고하면 충돌체크는 OBB를 통해 해주면 될 것이고 방향을 잘 맞춰서 레이케스팅을 해주면 충돌이 이루어지면서 클릭이 될 것이다. 

 

Unity에서는 UI를 생성하면 기본적으로 EventSystem이라는 오브젝트가 자동으로 생성된다. 이 EventSystem을 통해 

피격 판정이 이루어졌는지 체크한다. 

우리는 클릭한 스크린 좌표를 가져와서 클릭한 좌표에 이 버튼이 포함되는지 확인해주는 방식으로 구현해보자. 

우선 버튼클래스를 만들어주고 버튼을 눌렀을 때 콜백함수가 호출되도록 만들어주자. 이때 콜백함수는 Std의 Function  변수를 사용해주자.

이 변수를 사용할 때 주의해야할 점이 있는데 만약 연결되는 콜백함수가 static이나 전역이면 문제가 없지만

람다나 멤버함수, 람다캡쳐를 통해 전달해주게 된다면 문제가 생길 수 있다는 것이다.

이때 람다캡처는 외부에 있던 함수를 복사해서 넣어줄 수 있는 것인데 이때 포인터가 들어가면 포인터가 외부에서 해제된 경우 없어진 주소를 참조하는 것이기 때문에 문제가 생길 수 있다.

스마트 포인터를 쓴다고 하면 참조가 계속되어서  릴리즈가 안되는 문제가 생길 수 있다.

 

Button.h

#pragma once
#include "Component.h"
class Button : public Component
{
	using Super = Component;

public:
	Button();
	virtual ~Button();

	bool Picked(POINT screenPos);

	void Create(Vec2 screenPos, Vec2 size, shared_ptr<class Material> material);

	void AddOnClickedEvent(std::function<void(void)> func);
	void InvokeOnClicked();

private:
	std::function<void(void)> _onClicked;
	RECT _rect;
};

Button.cpp

#include "pch.h"
#include "Button.h"
#include "MeshRenderer.h"
#include "Material.h"

Button::Button() : Super(ComponentType::Button)
{

}

Button::~Button()
{
}

bool Button::Picked(POINT screenPos)
{
	//해당영역에 들어가 있는지 확인해줌
	return ::PtInRect(&_rect, screenPos);
}

void Button::Create(Vec2 screenPos, Vec2 size, shared_ptr<class Material> material)
{
	auto go = _gameObject.lock();

	float height = GRAPHICS->GetViewport().GetHeight();
	float width = GRAPHICS->GetViewport().GetWidth();

	//실제 배치할 좌표구하기 
	float x = screenPos.x - width / 2;
	float y = height / 2 - screenPos.y;
	Vec3 position = Vec3(x, y, 0);

	go->GetOrAddTransform()->SetPosition(position);
	go->GetOrAddTransform()->SetScale(Vec3(size.x, size.y, 1));

	go->SetLayerIndex(Layer_UI);

	if (go->GetMeshRenderer() == nullptr)
		go->AddComponent(make_shared<MeshRenderer>());

	go->GetMeshRenderer()->SetMaterial(material);

	auto mesh = RESOURCES->Get<Mesh>(L"Quad");
	go->GetMeshRenderer()->SetMesh(mesh);
	go->GetMeshRenderer()->SetPass(0);

	//Picking
	//사각형 상하좌우 실제 영역구해주기
	_rect.left = screenPos.x - size.x / 2;
	_rect.right = screenPos.x + size.x / 2;
	_rect.top = screenPos.y - size.y / 2;
	_rect.bottom = screenPos.y + size.y / 2;
}

void Button::AddOnClickedEvent(std::function<void(void)> func)
{
	_onClicked = func;
}

void Button::InvokeOnClicked()
{
	if (_onClicked)
		_onClicked();
}

 

UI를 피킹하는 코드를 Scene클래스에 추가해주자.

Scene.cpp

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

	if (GetUICamera() == nullptr)
		return;

	//누른 좌표가져오기
	POINT screenPt = INPUT->GetMousePos();

	shared_ptr<Camera> camera = GetUICamera()->GetCamera();

	const auto gameObjects = GetObjects();

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

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

 

이렇게 해주고 실제로 테스트해보기 위해 메인코드를 만들어주자. 아까 위에서 봤던 문제에서 핵심은 람다캡처에서 

메모리 문제가 생길 수 있기 때문에 주의해서 사용하거나 전역이나 static함수를 사용해주자.

람다에서 캡처는 fuctor에서 주고자하는 값을 멤버변수로 들고있는 것이라고 보면 된다.

ButtonDemo.cpp

#include "pch.h"
#include "ButtonDemo.h"
#include "RawBuffer.h"
#include "TextureBuffer.h"
#include "Material.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
#include "Light.h"
#include "Graphics.h"
#include "SphereCollider.h"
#include "Scene.h"
#include "AABBBoxCollider.h"
#include "OBBBoxCollider.h"
#include "Terrain.h"
#include "Camera.h"
#include "Button.h"

void ButtonDemo::Init()
{
	_shader = make_shared<Shader>(L"23. RenderDemo.fx");

	// Camera
	{
		auto camera = make_shared<GameObject>();
		camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
		camera->AddComponent(make_shared<Camera>());
		camera->AddComponent(make_shared<CameraScript>());
		camera->GetCamera()->SetCullingMaskLayerOnOff(Layer_UI, true);
		CUR_SCENE->Add(camera);
	}

	//UI_Camera
	{
		auto camera = make_shared<GameObject>();
		camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
		camera->AddComponent(make_shared<Camera>());
		camera->GetCamera()->SetProjectionType(ProjectionType::Orthographic);
		camera->GetCamera()->SetNear(1.f);
		camera->GetCamera()->SetFar(100.f);

		//UI만 그려주게
		camera->GetCamera()->SetCullingMaskAll();
		camera->GetCamera()->SetCullingMaskLayerOnOff(Layer_UI, false);
		CUR_SCENE->Add(camera); 
	}

	// Light
	{
		auto light = make_shared<GameObject>();
		light->AddComponent(make_shared<Light>());
		LightDesc lightDesc;
		lightDesc.ambient = Vec4(0.4f);
		lightDesc.diffuse = Vec4(1.f);
		lightDesc.specular = Vec4(0.1f);
		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
		light->GetLight()->SetLightDesc(lightDesc);
		CUR_SCENE->Add(light);
	}

	// Material
	{
		shared_ptr<Material> material = make_shared<Material>();
		material->SetShader(_shader);
		auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
		material->SetDiffuseMap(texture);
		MaterialDesc& desc = material->GetMaterialDesc();
		desc.ambient = Vec4(1.f);
		desc.diffuse = Vec4(1.f);
		desc.specular = Vec4(1.f);
		RESOURCES->Add(L"Veigar", material);
	}
	
	// Mesh - 버튼 역활
	{
		auto obj = make_shared<GameObject>();
		obj->AddComponent(make_shared<Button>());

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

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

		CUR_SCENE->Add(obj);
	}

	// Mesh
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
		obj->GetOrAddTransform()->SetScale(Vec3(2.f));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}

		CUR_SCENE->Add(obj);
	}
}

void ButtonDemo::Update()
{

}

void ButtonDemo::Render()
{

}

 

이렇게 람다 캡처를 사용해서 콜백함수를 만들어준 다음 실행시켜보면 버튼이 잘 없어지는 것을 볼 수 있다.

+ Recent posts