2020. 3. 29. 20:30ㆍ개발일지
MoveCommand 클래스는 MoveComponent 클래스에 내포되어 있으며, 상속받은 자식클래스들은 가상함수 Move() 함수를 구현하도록 강제한다. 명령을 수행하는 함수는 이 클래스를 상속받고 구현한다.
class MoveCommand
{
public:
MoveCommand(AMoveComponent* _Owner = nullptr)
: m_MoveDistance(50.f), m_JumpPower(500.f), m_MoveTime(0.1f), OwnerComponent(_Owner), m_CurDistance(0.f), m_CurRadian(0.f){}
virtual ~MoveCommand() {}
virtual AVECTOR Move() = 0; //상속받은 자식들이 이 Move()함수를 구현하도록 강제한다.
public:
AMoveComponent* OwnerComponent;
float m_MoveDistance;
float m_JumpPower;
float m_CurRadian;
float m_CurDistance;
float m_MoveTime;
};
MoveCommand를 상속받은 클래스들.
class STOP : public MoveCommand
{
public:
STOP(AMoveComponent* _Owner) : MoveCommand(_Owner) {}
virtual AVECTOR Move();
};
class RIGHT : public MoveCommand
{
public:
RIGHT(AMoveComponent* _Owner) : MoveCommand(_Owner) {}
virtual AVECTOR Move();
};
class LEFT : public MoveCommand
{
public:
LEFT(AMoveComponent* _Owner) : MoveCommand(_Owner) {}
virtual AVECTOR Move();
};
class UP : public MoveCommand
{
public:
UP(AMoveComponent* _Owner) : MoveCommand(_Owner) {}
virtual AVECTOR Move();
};
class DOWN : public MoveCommand
{
public:
DOWN(AMoveComponent* _Owner) : MoveCommand(_Owner) {}
virtual AVECTOR Move();
};
MoveComponent 클래스는 하나의 현재상태만을 가지도록 MoveCommand* m_MoveState 포인터를 멤버로 가진다.
class AMoveComponent : public AComponentBase
{
public:
AMoveComponent(AGAMEACTOR* _Owner);
virtual ~AMoveComponent();
private:
class MoveCommand
{
//////
};
protected:
MoveCommand* m_MoveState; //현재 상태
float m_ElipsedTime;
AVECTOR m_StartPos;
std::unordered_map<std::string, MoveCommand*> MoveMap; // 상태들을 저장한 해쉬맵
public:
AVECTOR MoveUpdate();
void Move(std::string _Direction);
float GetMoveDistance() { return m_MoveState->m_MoveDistance; }
bool OnMoving() { return m_MoveState != MoveMap["S"]; }
};
MoveComponent에서 각각의 상태를 표현하는 MoveCommand 하위 객체들을 생성하고 그 포인터들을 맵에 저장한다.
AMoveComponent::AMoveComponent(AGAMEACTOR* _Owner) : m_ElipsedTime(0.f)
{
m_OwnerActor = _Owner;
MoveMap["L"] = new LEFT(this);
MoveMap["R"] = new RIGHT(this);
MoveMap["U"] = new UP(this);
MoveMap["D"] = new DOWN(this);
MoveMap["S"] = new STOP(this);
m_MoveState = MoveMap["S"];
}
명령 패턴으로 MoveUpdate() 에 매 업데이트마다 현재 상태를 확인하는 if() ~ else if() 문들을 작성하지 않아도 되도록 되었다. 새로운 명령 객체를 생성할땐 MoveCommand를 상속받는 객체만 구현하면 되고, 호출하는 부분은 코드의 수정이 필요 없게 되었다.
AVECTOR AMoveComponent::MoveUpdate()
{
AVECTOR DeltaPos = m_MoveState->Move();
m_ElipsedTime += AGAMETIME::DeltaTime();
return DeltaPos;
}
이동 로직
거리 = 시간 * 속도 공식을 이용하여 한 프레임동안 이동할 거리를 구해 이동시켰다.
이동시 점프를 표현하기 위해 MoveTime 동안 sin 함수를 따라 2PI 만큼 이동시켰다.
AVECTOR AMoveComponent::LEFT::Move()
{
//MoveTime 시간동안 MoveDistance를 이동한다
//거리 = 시간 * 속도
//한프레임에 이동할 거리 = 한 프레임 시간 * 속도
//속도 = 거리 / 시간
float MoveX = -AGAMETIME::DeltaTime() * (m_MoveDistance / m_MoveTime);
m_CurDistance += abs(MoveX);
if (m_CurDistance <= m_MoveDistance)
OwnerComponent->m_OwnerActor->MovePosX(MoveX);
//MoveTime시간동안 sin곡선을 따라 2PI 이동.
float Radian = 2 * AMATH::PI * (OwnerComponent->m_ElipsedTime / m_MoveTime);
float JumpY = m_JumpPower * sinf(Radian) * AGAMETIME::DeltaTime();
OwnerComponent->m_OwnerActor->MovePosY(JumpY);
if (OwnerComponent->m_ElipsedTime >= m_MoveTime)
{
OwnerComponent->m_MoveState = OwnerComponent->MoveMap["S"];
m_CurDistance = 0.f;
//X 오차 보정
OwnerComponent->m_OwnerActor->SetPosX(OwnerComponent->m_StartPos.X - m_MoveDistance);
//Y 오차 보정
OwnerComponent->m_OwnerActor->SetPosY(OwnerComponent->m_StartPos.Y);
return{ AVECTOR(0.f, 0.f) };
}
return { AVECTOR(MoveX, 0.f) };
}
오차 누적 없애기.
고정된 값으로 한번에 이동하는 것이 아닌 프레임별로 이동을 누적해가기 때문에 이동의 마지막 프레임에서 오차가 생길 수 있다. 이동 횟수가 많아질수록 오차가 누적되어 점점 위치에서 벗어나게 될 수 있기때문에 마지막 프레임에서 원래 목표로 하던 위치로 이동시켜 주어 오차를 없앴다.
if (OwnerComponent->m_ElipsedTime >= m_MoveTime)
{
OwnerComponent->m_MoveState = OwnerComponent->MoveMap["S"];
m_CurDistance = 0.f;
//X 오차 보정
OwnerComponent->m_OwnerActor->SetPosX(OwnerComponent->m_StartPos.X - m_MoveDistance);
//Y 오차 보정
OwnerComponent->m_OwnerActor->SetPosY(OwnerComponent->m_StartPos.Y);
return{ AVECTOR(0.f, 0.f) };
}
키를 입력받고 호출하는 부분. 키를 입력받았을때 이미 이동중이라면 키 입력을 무시한다.
void APLAYER::Input()
{
if (MoveComponent->OnMoving())
return;
if (AGAMEINPUT::DOWN(L"L"))
{
MoveComponent->Move("L");
}
if (AGAMEINPUT::DOWN(L"R"))
{
MoveComponent->Move("R");
}
if (AGAMEINPUT::DOWN(L"U"))
{
MoveComponent->Move("U");
}
if (AGAMEINPUT::DOWN(L"D"))
{
MoveComponent->Move("D");
}
}
MoveComponent와 Camera의 디커플링
Move 컴포넌트가 카메라의 존재를 몰라도 카메라를 이동시킬 수 있도록 DeltaPosition을 리턴받아 카메라를 이동시킨다.
void APLAYER::Update()
{
Input();
m_DeltaPos = MoveComponent->MoveUpdate();
PlayerCamera->CameraMove(m_DeltaPos);
}