본문으로 바로가기

200724_ImGui_Collision.zip
1.59MB

 

 

 

0. Collider Render On/Off

  a. isDebug 전역변수 선언, Program에서 F1키로 Toggle

 

1. Mouse class

  a. Mouse Button Down, Press, Up 구분을 위한 메시지 처리

더보기
[main.cpp]

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    /* For Mouse(Singleton) */
	MOUSE->SetParam(message, wParam, lParam);
    /* 이하 생략 */
}
/////////////////////////////////////////////////////////////////////////////////
#pragma once

class Mouse
{
private:
	enum {
		NONE,
		DOWN,
		UP,
		PRESS
	};

private:
	UINT msg;
	WPARAM wParam;
	LPARAM lParam;
	//
	byte curState[2];
	byte oldState[2];
	byte mapState[2];
	//
	Vector2 mousePos;

private:
	Mouse();
	~Mouse();

public:
	static Mouse* Get()
	{
		static Mouse instance;
		return &instance;
	}
	Vector2 GetMousePos() { return mousePos; }
	//
	void SetParam(UINT message, WPARAM wParam, LPARAM lParam);
	//
	void Update();
	//
	bool Down(UINT key) { return mapState[key] == DOWN; }
	bool Up(UINT key) { return mapState[key] == UP; }
	bool Press(UINT key) { return mapState[key] == PRESS; }
	//
	void SetMousePos();
	void SetMouseClick();
};
/////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "Mouse.h"

Mouse::Mouse()
	: curState{}, oldState{}, mapState{},
	wParam(0), lParam(0), mousePos(0, 0),
	msg(0)
{
}

Mouse::~Mouse()
{
}

void Mouse::SetParam(UINT message, WPARAM wParam, LPARAM lParam)
{
	msg = message;
	this->wParam = wParam;
	this->lParam = lParam;
}

void Mouse::Update()
{
	SetMousePos();
	SetMouseClick();
}

void Mouse::SetMousePos()
{
	if (msg == WM_MOUSEMOVE)
	{
		mousePos.x = (float)LOWORD(lParam);
		mousePos.y = WinHeight - (float)HIWORD(lParam);
	}
}

void Mouse::SetMouseClick()
{
	memcpy(oldState, curState, sizeof(oldState));

	if (GetAsyncKeyState(VK_LBUTTON))
		curState[0] = 1;
	else
		curState[0] = 0;
	//
	if (GetAsyncKeyState(VK_RBUTTON))
		curState[1] = 1;
	else
		curState[1] = 0;

	for (int i = 0; i < 2; i++)
	{
		int old = oldState[i];
		int cur = curState[i];

		if (old == 0 && cur == 1)
			mapState[i] = DOWN;		
		else if (old == 1 && cur == 0)
			mapState[i] = UP;		
		else if (old == 1 && cur == 1)		
			mapState[i] = PRESS;		
		else
			mapState[i] = NONE;		
	}
}

 

2. Player class

  a. Player <-> Gun <-> Bullet 포함 관계 설정

  b. 하위 객체는 생성시 상위 객체 포인터를 할당받아 위치 초기화시 사용

더보기
#pragma once

class Player
{
public:
	Quad* quad;
	Gun* gun;
	RectCollider* rect;
	//
	float moveSpeed;
	float gravity;
	float jumpPower;
	float forceOfJump;
	//
public:
	Player();
	~Player();
	//
	void Update();
	void Render();
	void PostRender();
	//
	void Control();
	void Move();
	void Jump();
	void GunTarget();
};
////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "Player.h"

Player::Player()
	: moveSpeed(500.0f), gravity(980.0f), jumpPower(0.0f), forceOfJump(500.0f)
{
	quad = new Quad(L"Textures/player.png");
	quad->pos = { WinWidth * 0.5f, WinHeight * 0.5f };
	//
	rect = new RectCollider({ quad->texture->GetSize() }, (Transform*)quad);
	//
	gun = new Gun;
	gun->SetPlayer(this);
}

Player::~Player()
{
	delete quad;
	quad = nullptr;
	//
	delete rect;
	rect = nullptr;
	//
	delete gun;
	gun = nullptr;
}

void Player::Update()
{
	Control();
	//
	quad->Update();
	rect->Update();
	//
	gun->Update();
	//
}

void Player::Render()
{
	quad->Render();
	rect->Render();
	// 플레이어를 먼저 그리고 그 위에 장비를 그린다
	gun->Render();
	//
}

void Player::PostRender()
{
	ImGui::Text("[Player]");
	ImGui::SliderFloat2("Pos", (float*)&(quad->pos), 0, WinWidth);
	gun->PostRender();
}

void Player::Control()
{
	Move();
	Jump();
	//
	GunTarget();
}

void Player::GunTarget()
{
	Vector2 dir = (MOUSE->GetMousePos() - quad->pos).Normal();
	gun->SetDir(dir);
}

void Player::Move()
{
	if (KeyPress('A'))
	{
		quad->pos.x -= moveSpeed * (float)DELTA;
		quad->scale.x = -1;
	}
	else if (KeyPress('D'))
	{
		quad->pos.x += moveSpeed * (float)DELTA;
		quad->scale.x = 1;
	}
}

void Player::Jump()
{
	if (KeyDown(VK_SPACE))
	{
		jumpPower = forceOfJump;
	}
	/* 중력 */
	jumpPower -= gravity * (float)DELTA;
	// DX에서 스크린 좌표는 중앙이 (0,0), 위쪽이 +, 아래가 - 이므로
	// pos.y는 jumpPower를 더한값으로 계산한다(API의 스크린좌표와 반대)
	quad->pos.y += jumpPower * (float)DELTA;

	if (quad->pos.y <= quad->texture->GetSize().y * 0.5)
	{
		// texture는 pos를 중점으로 네 Vertex가 +- size * 0.5 로 계산되어 있다(Texture class 생성자)
		// 따라서 바닥과의 충돌처리를 위한 좌표를 Size()를 이용하여 계산한다
		quad->pos.y = (quad->texture->GetSize().y * 0.5);
		jumpPower = 0;
	}
}

 

3. Gun class

  a. 자유로운 Gun 이미지의 방향 변환을 위해 Player와 귀속관계가 아닌 포인터만 참조, pos값만 사용

(Matrix 귀속시 플레이어 scale값에 따라 Gun의 scale도 같이 바뀜)

  b. 아이템 획득 표현을 위해 Player 참조는 Gun 생성 이후 별도 실행

  c. 마우스와 Gun이 이루는 각도에 따라 scale값을 변경하여 이미지 Flip

더보기
#pragma once

class Player;

class Gun
{
public:
	Quad* quad;
	Vector2 dir;
	//
	Player* player;
	vector<Bullet*> bullets;

public:
	Gun();
	~Gun();
	//
	void Update();
	void Render();
	void PostRender();
	//
	void SetPlayer(Player* player);
	//
	void SetDir(Vector2 d) { dir = d; }
	void SetPos();
	void SetAngle();
	//
	void InitBullets();
	void Fire();
};
////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "Gun.h"

Gun::Gun()
	:quad(nullptr), dir(0, 0), player(nullptr)
{
	quad = new Quad(L"Textures/gun.png");
	InitBullets();
}

Gun::~Gun()
{
	delete quad;
	quad = nullptr;
	//
	for (auto bullet : bullets)
		delete bullet;
	bullets.clear();
}

void Gun::Update()
{
	SetPos();
	SetAngle();
	//
	Fire();
	//
	quad->Update();
	//
	for (auto bullet : bullets)
		bullet->Update();
}

void Gun::Render()
{
	quad->Render();
	//
	for (auto bullet : bullets)
		bullet->Render();
}

void Gun::PostRender()
{
	//ImGui::Text("[Gun]");
}

void Gun::SetPlayer(Player* player)
{
	this->player = player;
}

void Gun::SetPos()
{
	quad->pos = player->quad->pos;
}

void Gun::SetAngle()
{
	float angle = dir.Angle();
	//
	if (angle > (PI / 2))
		quad->scale.y = -1;
	else
		quad->scale.y = 1;
	//
	quad->angle = angle;
}

void Gun::InitBullets()
{
	for (int i = 0; i < 50; ++i)
	{
		Bullet* bullet = new Bullet(this);
		bullet->quad->isActive = false;
		//
		bullets.emplace_back(bullet);
	}
}

void Gun::Fire()
{
	if (MouseDown(0))
	{
		for (auto bullet : bullets)
		{
			if (bullet->quad->isActive == false)
			{
				bullet->ResetPos(); // 총알 발사 위치, 방향 초기화(총 위치)
				bullet->dir = dir; // 총알 발사 방향 설정
				bullet->quad->isActive = true; // 총알 Activation
				return; // 1발만 쏘고 함수 종료
			}
		}
	}
}

 

4. Bullet class

  a. Gun class에서 생성시 Gun*를 인자로 받아 생성과 동시에 참조 할당

  b. 참조된 Gun*는 Bullet의 Pos 초기화에 사용

  c. quad->isActive(bool) 변수를 사용하여 발사 및 충돌 처리시 On/Off

더보기
#pragma once

class Gun;

class Bullet
{
public:
	Gun* gun;
	Quad* quad;
	RectCollider* rect;
	//
	Vector2 dir;
	float speed;

public:
	Bullet(Gun* gun);
	~Bullet();
	//
	void Update();
	void Render();
	//
	void Move();
	void ResetPos();
};
////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "Bullet.h"

Bullet::Bullet(Gun* gun)
	:gun(gun), dir(0, 0), speed(800.0f)
{
	quad = new Quad(L"Textures/bullet.png");
	//
	rect = new RectCollider({ quad->texture->GetSize() }, (Transform*)quad);
}

Bullet::~Bullet()
{
	delete quad;
	quad = nullptr;
	//
	delete rect;
	rect = nullptr;
}

void Bullet::Update()
{
	if (!quad->isActive)
		return;
	//
	Move();
	//
	quad->Update();
	//
	rect->Update();
}

void Bullet::Render()
{
	if (!quad->isActive)
		return;
	//
	quad->Render();
	//
	rect->Render();
}

void Bullet::Move()
{
	quad->pos.x += dir.x * speed * DELTA;
	quad->pos.y += dir.y * speed * DELTA;
}

void Bullet::ResetPos()
{
	quad->pos = gun->quad->pos;
	dir = {};
	quad->isActive = false;
}

 

5. Enemy class

  a. BlendState를 이용한 피격 이펙트

더보기
#pragma once

class Enemy
{
public:
	Quad* quad;
	RectCollider* rect;
	//
	int hp;
	int maxHp;
	//
	float hitTime;
	//
public:
	Enemy();
	~Enemy();
	//
	void Update();
	void Render();
	void PostRender();
	//
	void Damage(int damage);
	void HitTime();
};
////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "Enemy.h"

Enemy::Enemy()
	:hp(100), maxHp(100), hitTime(0)
{
	quad = new Quad(L"Textures/enemy.png");
	quad->pos = { WinWidth - quad->texture->GetSize().x * 0.5f, quad->texture->GetSize().y * 0.5f };
	//
	rect = new RectCollider({ quad->texture->GetSize() }, (Transform*)quad);
}

Enemy::~Enemy()
{
	delete quad;
	quad = nullptr;
	//
	delete rect;
	rect = nullptr;
}

void Enemy::Update()
{
	quad->Update();
	rect->Update();
	//
	HitTime();
}

void Enemy::Render()
{
	if(hitTime)
		AdditiveBlendState->SetState();
	else
		AlphaBlendState->SetState();
	//
	quad->Render();
	rect->Render();
}

void Enemy::PostRender()
{
	ImGui::Text("[Enemy]");
	ImGui::Text("HP : %d / %d", hp, maxHp);
}

void Enemy::Damage(int damage)
{
	hp -= damage;
	hitTime = 0.1f;
}

void Enemy::HitTime()
{
	if (hitTime == 0)
		return;

	hitTime -= DELTA;
	if (hitTime <= 0)
		hitTime = 0;
}

 

6. Collision

  a. RectCollider::IsCollision() 함수 구현(AABB 충돌)

더보기
bool RectCollider::IsCollision(Collider* collider)
{
	// case : RectCollider
	RectCollider* col = dynamic_cast<RectCollider*>(collider);
	// 함수를 실행하는 Collider // ex) Bullet
	float l1 = pos.x - (size.x * 0.5f);
	float t1 = pos.y + (size.y * 0.5f);
	float r1 = pos.x + (size.x * 0.5f);
	float b1 = pos.y - (size.y * 0.5f);
	// 인자로 받은 Collider // ex) Enemy
	float l2 = col->pos.x - (col->size.x * 0.5f);
	float t2 = col->pos.y + (col->size.y * 0.5f);
	float r2 = col->pos.x + (col->size.x * 0.5f);
	float b2 = col->pos.y - (col->size.y * 0.5f);
	//
	if (l1 <= r2
		&& r1 >= l2
		&& t1 >= b2
		&& b1 <= t2)
	{
		return true;
	}
	return false;
}

 

7. PlayScene

  a. Player->Gun->Bullet 순으로 접근하여 Enemy와 충돌 체크

더보기
#pragma once

class DungreedLike : public Scene
{
public:
	Player* player;
	Enemy* enemy;

public:
	DungreedLike();
	~DungreedLike();

private:
	virtual void Update() override;
	virtual void Render() override;
	virtual void PostRender() override;
	//
	void InitObjects();
	void Collision();
	void CollisionBullet();
};
////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"
#include "DungreedLike.h"

DungreedLike::DungreedLike()
	:player(nullptr)
{
	InitObjects();
}

DungreedLike::~DungreedLike()
{
	delete player;
	delete enemy;
}

void DungreedLike::Update()
{
	player->Update();
	enemy->Update();
	//
	Collision();
}

void DungreedLike::Render()
{
	//AdditiveBlendState->SetState();
	AlphaBlendState->SetState();
	player->Render();
	enemy->Render();
}

void DungreedLike::PostRender()
{
	ImGui::Text("[Scene]");
	player->PostRender();
	enemy->PostRender();
}

void DungreedLike::InitObjects()
{
	player = new Player;
	enemy = new Enemy;
}

void DungreedLike::Collision()
{
	CollisionBullet();
}

void DungreedLike::CollisionBullet()
{
	int bulletCount = player->gun->bullets.size();
	for (int i = 0; i < bulletCount; ++i)
	{
		Bullet* tmp = player->gun->bullets[i];
		// 비활성 상태의 총알은 충돌체크를 건너뛴다
		if (tmp->quad->isActive == false)
			continue;

		if (tmp->rect->IsCollision(enemy->rect))
		{ // 총알이 Enemy와 충돌하면
			int bulletDamage = 1;
			enemy->Damage(bulletDamage);
			tmp->ResetPos();
			return;
		}
		else
		{ // 총알이 화면범위를 벗어나면
			if ((tmp->quad->pos.x < 0 || tmp->quad->pos.y < 0) ||
				(tmp->quad->pos.x > WinWidth || tmp->quad->pos.y > WinHeight))
			{
				tmp->ResetPos();
				return;
			}
		}
	}
}