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();
'Windows API & 게임 수학' 카테고리의 다른 글
2-4. 벡터의 외적을 활용하기 - 범위, 쿨타임 표시 (0) | 2023.10.30 |
---|---|
2-3. 벡터의 내적을 이용해 마우스를 따라다니는 몬스터 구현 (0) | 2023.10.30 |
2-1. 삼각함수를 이용하여 여러 각도로 발사하기 (1) | 2023.10.26 |
1-11. 번외 - 충돌 판정(Collision) 구현하기 (0) | 2023.10.24 |
1-10. 번외 - 점수(Score) 구현하기 (0) | 2023.10.24 |