0. 단위벡터

길이가 1인 벡터.

벡터를 벡터의 크기로 나눠주면 단위벡터가 된다.

일반적으로 게임에서는 단위벡터를 유용하게 사용한다.

 

1. 연산자 오버로딩

벡터의 연산을 쉽게 할 수 있도록 기초 연산자들을 오버로딩한다.

만약 오류가 발생하면 cmath 헤더가 추가되었는지 확인이 필요하다.

struct Vector
{
	Vector() {}
	Vector(float x, float y) : x(x), y(y) { }

	Vector operator+(const Vector& other)
	{
		Vector ret;
		ret.x = x + other.x;
		ret.y = y + other.y;
		return ret;
	}
	Vector operator-(const Vector& other)
	{
		Vector ret;
		ret.x = x - other.x;
		ret.y = y - other.y;
		return ret;
	}

	Vector operator*(float value)
	{
		Vector ret;
		ret.x = x * value;
		ret.y = y * value;
		return ret;
	}

	void operator+=(const Vector& other)
	{
		x += other.x;
		y += other.y;
	}
	void operator-=(const Vector& other)
	{
		x -= other.x;
		y -= other.y;
	}
	void operator*=(float value)
	{
		x *= value;
		y *= value;
	}

	float x = 0.f;
	float y = 0.f;
		
};

 

2. 벡터의 크기를 구하는 함수 구현하기

	float LengthSquared()
	{
		// 루트 내부를 계산
		return x * x + y * y;
	}
	float Length()
	{
		// 제곱근을 계산
		return ::sqrt(LengthSquared());
	}

두 단계로 나누어 하는 이유는 제곱근을 계산하는 것이 메모리 비용이 많이 들기 때문이다.

 

3. 벡터 정규화(단위벡터 구하기)

	void Normalize()
	{
		float length = Length();

		if (length < 0.0000000000f)
			return;

		x /= length;
		y /= length;
	}

길이가 아주 짧은 벡터라면 함수를 종료시킨다.

이 함수는 사용하는 벡터를 정규화하는 함수이다. 때에 따라서 벡터를 정규화한 값을 리턴하는 방법으로 만들 수 있다.

 

* 코드를 수정하지 않고 Pos를 벡터로 대체하는 방법

Types.h에 아래 코드를 작성한다.

using Pos = Vector;

 

 

4. 발사하는 유도체를 유도탄으로 만들기

아래 코드를 삭제한다.

// 
	_pos.x += deltaTime * _stat.speed * cos(_angle);
	_pos.y -= deltaTime * _stat.speed * sin(_angle);
더보기
void Missile::Update()
{
	float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();

	//
	if (_target == nullptr)
	{
		// 0.2초 이전이라면 기존의 발사 방법을 사용
		_pos.x += _stat.speed * deltaTime * cos(_angle);
		_pos.y -= _stat.speed * deltaTime * sin(_angle);

		// 타겟이 없다면 타겟을 찾는다
		_sumTime += deltaTime;

		// 누적시간 이후 몬스터를 발견하면 추적
		if (_sumTime >= 0.2f)
		{
			const vector<Object*>& objects = GET_SINGLE(ObjectManager)->GetObjects();
			for (auto object : objects)
			{
				if (object->GetObjectType() == ObjectType::Monster)
				{
					_target = object;
					break;
				}
			}
		}
	}
	else
	{
		// 타겟이 있다면 따라간다
		// 타겟의 위치에서 나의 위치를 뺀다
		Vector dir = _target->GetPos() - GetPos();
		dir.Normalize();

		_pos += dir * _stat.speed * deltaTime;

	}

(1) 누적시간

float _sumtime을 헤더에 추가한다.

_sumTime 이후 미사일이 자동으로 추적되도록 할 것이다.

0.2초가 경과하기 전에는 기존의 방식으로 발사된다.

 

(2) 몬스터 조준

if (_sumTime >= 0.2f)
		{
			const vector<Object*>& objects = GET_SINGLE(ObjectManager)->GetObjects();
			for (auto object : objects)
			{
				if (object->GetObjectType() == ObjectType::Monster)
				{
					_target = object;
					break;
				}
			}
		}

ObjectManager에서 object들을 동적배열로 들고 있는 objects를 순회할 것이다.

object의 타입이 몬스터이면 타겟을 그 object로 설정한다.

 

(3) 타겟 추적

	else
	{
		// 타겟이 있다면 따라간다
		// 타겟의 위치에서 나의 위치를 뺀다
		Vector dir = _target->GetPos() - GetPos();
		dir.Normalize();

		Vector moveDir =  dir * _stat.speed * deltaTime;
		_pos += moveDir;
	}

이 코드에서 dir이라는 벡터는 (벡터 = 도착점 - 시작점)의 정보를 가지고 있다.

이 벡터를 정규화를 통해 크기가 1인 단위벡터로 만들어준다.

이어 거리 = 속력 * 시간의 공식에 곱해주면, 거리 = 속도 * 시간이 된다.

(속도는 속력과 이동하는 방향의 정보를 둘 다 가지는 벡터이다.)

 

 

(4) 몬스터를 1마리만 생성하여 테스트

GameScene.cpp에서 몬스터를 1마리만 스폰하여 테스트 할 예정이므로 코드를 아래와 같이 수정한다.

	// 몬스터 5마리 생성
	{
		//for (int32 i = 0; i < 5; i++)
		{
			float randY = 50 + rand() % 300;
			Monster* monster = GET_SINGLE(ObjectManager)->CreateObject<Monster>();
			monster->SetPos(Pos{ (float)(100), randY });
			GET_SINGLE(ObjectManager)->Add(monster);
		}
	}

 

 

5. 댕글링 포인터(Dangling Pointer) 문제

한 미사일이 발사되어서 객체를 delete하기 직전에 다른 미사일을 발사하면, 두번째 미사일이 이상하게 동작하는 것을 볼 수 있다.

이것은 _target이 유효하지 않은 메모리 주소를 가리키고 있기 때문이다.

이를 댕글링 포인터라고 한다.

스마트 포인터가 아닌 일반적인 포인터를 사용할 때는 객체가 제거되는 순간을 고려할 필요가 있다.

아래의 두 가지 방법을 사용하여 메모리 오염을 방지할 수 있다.

 

(1) 스마트 포인터 사용하기

(2) 객체에 고유 ID를 부여하기

 

 

6. 미사일과 몬스터의 충돌 판정 개선하기

충돌 판정을 피타고라스 방정식이 아닌 벡터를 이용하여 수정할 수 있다.

Missile.cpp의 update 함수를 수정한다.

		// 미사일의 위치 p1
		Vector p1 = GetPos();

		// 몬스터의 위치 p2
		Vector p2 = object->GetPos();

		Vector dir = p2 - p1;

		float dist = dir.Length();