프로그래밍 공부
작성일
2023. 11. 8. 15:32
작성자
WDmil
728x90

우선 Block위에 올라섰을 때. Player의 위치값에 대해 참조받아 position을 바꾸어야 한다.

 

그전에 위에 올라설 Block을 만들어주도록 하자.


 Block

class Block : public BoxCollider
{
public:
	Block(string name);
	~Block();

	void Update();
	void Render();
	void GUIRender();

	Vector3 GetDirection(Vector3 point);

private:

	Model* model;
};
#include "Framework.h"

Block::Block(string name)
{
	SetTag(name + "Collider");
	model = new Model(name);
	model->SetParent(this);
	model->SetLocalScale(Vector3(0.1, 0.1, 0.1));
	model->SetLocalPosition(GetLocalPosition() - Vector3(0, +0.5, 0));

}

Block::~Block()
{
	SAFE_DELETE(model);
}

void Block::Update()
{
	Transform::UpdateWorld();
	model->UpdateWorld();
}

void Block::Render()
{
	model->Render();
	//Collider::Render();
}

void Block::GUIRender()
{
	model->GUIRender();
	Transform::GUIRender();
}

Vector3 Block::GetDirection(Vector3 point)
{
	// 가져온 point와 가장 가까운 육면체의 면 한개를 반환하는 함수이다.
	Vector3 direction = (point - GetGlobalPosition()).GetNormalized();

	Vector3 plancs[6];
	plancs[0] = GetRight();
	plancs[1] = GetLeft();
	plancs[2] = GetUp();
	plancs[3] = GetDown();
	plancs[4] = GetForward();
	plancs[5] = GetBack();

	float minAngle = FLT_MAX;
	Vector3 result;

	for (Vector3 plane : plancs)
	{
		// 모든 위치에서 내적한 값을 가져온다.
		float dot = Vector3::Dot(plane, direction);
		// 내적위치의 각도가 최저각도와 비교해서 작을경우 갱신함.
		float angle = abs(acos(dot));

		if (angle < minAngle)
		{
			minAngle = angle;
			result = plane;
		}
	}

	return result;
}

블럭 자체를 구현한 Block객체 이다.

 

스케일값을 조정하여 생성하였으며. 객체의 사이즈는 기본적으로 10 * 10이 모델의 기본적인 한변 임으로.

 

Collider의 기본사이즈인 1에 맞추어주기 위해 0.1배율로 조정해준다.

 

그리고, Collider에 맞추어주기 위해 vector값을 0.5; 칸 내려준다.

 


BlockManager

 

생성한 블럭을 한곳에 뭉쳐서 관리할 수 있도록 만드는 객체이다.

 

SingletonBase로 구현되어 있어. 객체를 명시적으로 활용할 수 있다.

class BlockManager : public Singleton<BlockManager>
{
private:
	friend class Singleton;

	BlockManager();
	~BlockManager();

public:
	void Update();
	void Redner();
	void GUIRender();

	void InsertBlocks(Ray mouseray);
	void DeleteBlocks(Ray mouseray);

	void CreateBlocks(UINT x, UINT y, UINT z);

	const list<Block*>& GetBlocks() { return blocks; }

	float GetHeight(const Vector3& pos);
	Block* Collision(const Ray& ray);


	void AddBlock(Block* block);

private:
	list<Block*> blocks;
};
void BlockManager::CreateBlocks(UINT x, UINT y, UINT z)
{
	// Vector값을 전부 순환하면서 모든 객체의 데이터를 pos값 위치시키는 함수.
	for (UINT i = 0; i < x; i++) {
		for (UINT j = 0; j < y; j++) {
			for (UINT k = 0; k < z; k++) {
				int random = 100 + MATH->Random(0, BlockDataManager::Get()->GetBlockSize()) + 1;
				BlockData data = BlockDataManager::Get()->GetBlockData(random);

				Block* block = new Block(data.modelname);
				block->SetLocalPosition(Vector3(i, j, k));
				blocks.push_back(block);
			}
		}
	}
}

float BlockManager::GetHeight(const Vector3& pos)
{
	// 해당 위치에서 밑으로 뻗어나오는 Ray를 가져온다.
	Ray ray(pos, Vector3::Down());
	Contact contact;

	// 최대 높이는 -5로 정한다. 최저높이를 제한함.
	float maxHeight = -5.0f;

	// 블록 전체를 순환하면서. 가장 가까운 블록을 갱신처리한다.
	for (Block* block : blocks)
	{
		if (block->IsRayCollision(ray, &contact));
		{
			if (contact.hitPoint.y > maxHeight)
				maxHeight = contact.hitPoint.y;
		}
	}
	//최대 높이는 객체에서 충돌범위까지 가장 적은 거리를 가진 블록의 높이값을 반환하게 한다.
	return maxHeight;
}

Block* BlockManager::Collision(const Ray& ray)
{
	float minDistance = FLT_MAX;
	Contact contact;

	list<Block*>::iterator iter = blocks.begin();
	list<Block*>::iterator collisionBlock = blocks.end();

	for (; iter != blocks.end(); iter++) {
		if ((*iter)->IsRayCollision(ray, &contact));
		{
			if (contact.distance < minDistance)
			{
				minDistance = contact.distance;
				collisionBlock = iter;
			}
		}
	}
	
	if (collisionBlock == blocks.end())
		return nullptr;

	Block* block = *collisionBlock;
	blocks.erase(collisionBlock);

	return block;
}

void BlockManager::AddBlock(Block* block)
{
	// 카메라의 중앙에서 뻗어나오는 Ray를 가져온다.
	Ray ray = CAM->ScreenPointToRay(Vector3(CENTER_X, CENTER_Y, 0));

	// 가장 최소거리에 float값의 최대값을 넣어서. 다음값이 무조건 최소가 될 수 있도록 한다.
	float minDistance = FLT_MAX;

	// 접촉부
	Contact contact;
	Vector3 hitPoint;

	// 리스트의 iterator를 사용하는 방식. 좀 구식방법
	list<Block*>::iterator iter = blocks.begin();
	list<Block*>::iterator collisionBlock = blocks.end();

	// 이터레이터를 순환하며. 전체 Ray검사를 실시하고. 가장 가까운 block을 찾아 갱신한다.
	for (; iter != blocks.end(); iter++) {
		if ((*iter)->IsRayCollision(ray, &contact));
		{
			if (contact.distance < minDistance)
			{
				minDistance = contact.distance;
				collisionBlock = iter;
				hitPoint = contact.hitPoint;
			}
		}
	}


	// 만약 충돌블록이 마지막이라면 충돌 안했다는 의미임으로. 반환한다.(보통 iter의 마지막값은 마지막요소가 아닌
	// 리스트의 끝을 의미함. 
	if (collisionBlock == blocks.end())
		return;

	// 충돌블록중 가장 가까운 블록에 collisionBlock을 대입.
	Block* hitBlock = *collisionBlock;

	// 충돌거리는 hitBlock의 hitpoint를 대입한다.
	Vector3 direction = hitBlock->GetDirection(hitPoint);
	// 충돌된 위치의 hitpoint는. hitBlock의 GlobalPosition의 direction만큼 위치이동한곳을 지정한다.
	// 블록의 사이즈는 Normalize되었음으로 그냥 위치이동시켜주면 그곳이 생성부가 된다.
	Vector3 pos = hitBlock->GetGlobalPosition() * direction;

	block->SetLocalPosition(pos);
	block->Update();
	blocks.push_back(block);
}

중요한 부분만 몃개 살펴보자.

 

CreateBlocks

x,y,z값을 0,0,0기준으로 월드상에 펼쳐준다.

 

맵을 전체적으로 구현해준다고 생각하면 될것이다.

 

Block은, BlockDataManager를 통해 csv파일을 로드하여 사용하게 된다.

 

물론. 사용하지 않고 그냥 로컬경로를 사용해서 Diff값을 설정해도 무방하다.

 

생성된 block을 pos값 위치해주고. 리스트에 넣어준다.

 

GetHeight

위치된 pos값에서 밑으로 내린 Ray상. 가장 가까운 Block의 높이를 반환하는 함수이다.

 

만약, 충돌이 검출되지 않았다면. -5를 반환하게 된다.

 

즉, 바닥의 발밑의 Block의 충돌범위에 충돌위치를 반환하는 함수이다.

 

Collision

Ray를 받아와서 모든 Block을 검사하여. 가장 가까운 Block의 위치를 가져온뒤. 해당 Block을 blocks에서 삭제하고.

 

해당 block을 반환해주는 함수이다.

필드 블록을 삭제하고 포인터를 반환해주는 함수라고 생각하면 될것이다.

 

AddBlock

카메라의 정중앙에서 뻗어나오는 Ray를 가져와서 사용한다.

 

해당 Ray에서 가장 가까운 block을 찾은다음.

 

Block의 함수중 GetDirection함수를 사용해서 충돌위치에서 가장 가까운 면을 찾은다음.

 

해당 면에서 블록사이즈만큼 이동한곳에 새 블록을 생성하고 붙여주는 역할을 한다.


Player

플레이어를 만들어주자.

 

마인크래프트 라는 게임의 주인공 이름이 Steve임으로. Steve라는 객체 한개를 만들어주자.

class Steve : public SphereCollider
{
private:
	const float GRAVITY = 98.0f;
	const float JUMP_POWER = 50.0f;
public:
	Steve();
	~Steve();

	void Update();
	void GUIRender();
	void PostRender();
	void Render();
private:
	void Move();
	void Jump();
	void Control();
private:
	float moveSpeed = 5;
	float rotSpeed = 5;

	float velocity = 0.0f;

	Vector3 prevMousePos;
	POINT clientCenterPos = { WIN_WIDTH >> 1, WIN_HEIGHT >> 1 };

	bool isFree = true;

	MineCraftUI* ui;
};
Steve::Steve()
{
	localPosition = { 5, 10, 5 };

	prevMousePos = mousePos;
	ClientToScreen(hWnd, &clientCenterPos);

	ui = new MineCraftUI();
}

Steve::~Steve()
{
}

void Steve::Update()
{
	ui->Update();

	Move();
	Jump();
	Control();

	UpdateWorld();
}

void Steve::GUIRender()
{
}

void Steve::PostRender()
{
	ui->PostRender();
}

void Steve::Render()
{
}

void Steve::Move()
{
	// 이동벡터에서 y값은 의미없음으로 y값은 항상 0으로 초기화해준다.
	if (KEY->Press('W'))
	{
		Vector3 forward = GetForward();
		forward.y = 0;
		Translate(forward.GetNormalized() * moveSpeed * DELTA);
	}
	else if (KEY->Press('S'))
	{
		Vector3 back = GetBack();
		back.y = 0;
		Translate(back.GetNormalized() * moveSpeed * DELTA);
	}

	if (KEY->Press('A'))
	{
		Vector3 left = GetLeft();
		left.y = 0;
		Translate(left.GetNormalized() * moveSpeed * DELTA);
	}
	else if (KEY->Press('D'))
	{
		Vector3 right = GetRight();
		right.y = 0;
		Translate(right.GetNormalized() * moveSpeed * DELTA);
	}
	// esc가 눌렸을 경우에만 이동을 반환한다.
	if (KEY->Down(VK_ESCAPE))
		isFree = !isFree;
	// Free모드일경우에는 그냥 이동벡터 적용안함.
	if (isFree) return;

	// 현재 delta값에 마우스 pos값을 받아옴.
	Vector3 delta = mousePos - Vector3(CENTER_X, CENTER_Y);
	SetCursorPos(clientCenterPos.x, clientCenterPos.y);

	// 회전값 적용
	Rotate(Vector3::Up() * delta.x * rotSpeed * DELTA);
	Rotate(Vector3::Left() * delta.y * rotSpeed * DELTA);

	//localPosition.y = BlockManager::Get()->GetHeight(localPosition) + Radius();

	// 카메라 업데이트
	CAM->SetLocalPosition(localPosition);
	CAM->SetLocalRotation(localRotation);
}

// 써있는거는 점프라고 써져있지만, 사실 점프와 높이값설정을 같이사용한다.
void Steve::Jump()
{
	// 스페이스바가 눌리면 윗벡터를 Jump_Power로 설정
	if (KEY->Down(VK_SPACE))
		velocity = JUMP_POWER;

	// 윗벡터에 중력값 만큼 시간마다 빼줌
	velocity -= GRAVITY * DELTA;

	// 현재 위치값에 위로향하는 힘을 곱해준다.
	Translate(Vector3::Up() * velocity * DELTA);

	// 현재 위치값에서 Block에 대한 높이값을 검사한다.
	float height = BlockManager::Get()->GetHeight(localPosition);

	// 현재 y높이가. 원래높이보다 작을경우. 블록 위에 올라가있다는 의미 임으로. velocity를 0으로.
	// 현재 높이값을 블록의 높이값 + 구체의 반지름으로 설정한다.
	if (height > localPosition.y - Radius())
	{
		velocity = 0.0f;
		localPosition.y = height + Radius();
	}
}

void Steve::Control()
{
	// 왼쪽 버튼을 누르면 블럭을 캐는걸로 정의
	if (KEY->Down(VK_LBUTTON))
		ui->Mining();

	// 오른쪽 버튼을 누르면 블럭을 쌓는걸로 정의.
	if (KEY->Down(VK_RBUTTON))
		ui->Build();
}

 

Stave는 Ui라는 객체를 가지고 행동한다.

Ui는 Stave의 위치에서 블럭을 삭제하는것 과 쌓는것 두가지를 수행하는 객체이다.

 

Move

이동벡터이다.

이전과 같은 방식으로 이동을 구현해주면 된다.

 

그러나,  객체가 필드에서 2차원적으로 움직여야 함으로 항상 y값은 0으로 맞추어주도록 하자

 

Jump는 사실 중력객체와 같이 작동한다.

 

만약, Space가 눌렸다면. JumpPower로 velocity값을 초기화해주고.

시간마다 중력값을 빼주면 된다.

그리고, Up벡터에 현재 중력값으로 빼버리면 된다.

 

현재 위치값에서 Block에 대한 높이값을 확인하고.

 

충돌의 높이가 현재 높이보다 클경우. 블록 내부에 들어갔다는 의미임으로.

 

velocity를 0으로 초기화해주고. 현재 높이값을 충돌높이에서 반지름만큼 더해주어 위에 올라설 수 있게 해준다.


UI

Player의 UI를 담당하는 객체를 생성해준다. 분리하는 이유는. 인벤토리 구현을 위해서 분리하였다.

#pragma once

class MineCraftUI
{
public:
	MineCraftUI();
	~MineCraftUI();

	void Update();
	void PostRender();

	void Mining();
	void Build();

private:
	Quad* cursor;

	list<Block*> blocks;
};

UI에서는 보관중인 blocks를 가지고있다.

#include "Framework.h"

MineCraftUI::MineCraftUI()
{
	cursor = new Quad(L"Textures/UI/Cursor.png");
	cursor->SetLocalPosition({ CENTER_X, CENTER_Y, 0.0f });
	cursor->UpdateWorld();
}

MineCraftUI::~MineCraftUI()
{
	delete cursor;
	
	for (Block* block : blocks)
		delete block;
}

void MineCraftUI::Update()
{
	cursor->UpdateWorld();
}

void MineCraftUI::PostRender()
{
	cursor->Render();
}

void MineCraftUI::Mining()
{
	Ray ray = CAM->ScreenPointToRay(Vector3(CENTER_X, CENTER_Y, 0));

	Block* block = BlockManager::Get()->Collision(ray);

	if (block == nullptr)
		return;

	blocks.push_back(block);
}

void MineCraftUI::Build()
{
	if (blocks.size() == 0) return;

	Block* block = blocks.back();
	blocks.pop_back();

	BlockManager::Get()->AddBlock(block);

}

 

이미 전에 구현해놓았던 데이터를 가져다가 조립만 하면된다.

 

Mining은 현재 블록이 Ray와 충돌했는지. 검사하고 가장 가까운 충돌객체를 빼서. 인벤토리에 넣어준다.

 

Build는 인벤토리 리스트가 0이 아닐경우에만 동작하며.

blocks의 가장 뒤에있는 객체를 빼서. 필드에 배치해준다.

 

 

 

728x90