3장 벡터와 물리 연습문제 3.3
문제
뉴턴 물리를 사용하도록 MoveComponent
를 수정해본다.
- 조건1:
MoveComponent
가 멤버 변수로서 질량, 힘의 총합, 그리고 속도를 가지도록 변경한다. - 조건2:
Update
함수에서 전방 이동에 대한 코드를 변경해서 힘으로부터 가속도를, 가속도로부터 속도를, 그리고 속도로부터 위치를 계산한다. - 조건3:
MoveComponent
에 힘을 설정하는 메소드를 구현한다.Vector2
를 파라미터로 받는AddForce
함수를 추가하고 힘의 총합 변수에 그 힘을 더하 방식을 권장한다.- 이방식을 통해서 충격력의 경우
AddForce
를 한번만 호출하고, (중력같은) 일정 하게 작용하는 힘에 대해서는 프레임마다AddForce
를 호출해야 한다.
풀이
문제에서 구체적으로 어떤 리소스를 사용할지와 요구기능을 어떻게 시연해 보일지에 대해서는 구체적으로 지정되어 있지는 않아서 명확한 개발 목표 설정읠 위해서 임의로 몇 가지 요구사항을 더 추가해보았다.
- 추가1: 리소스는 2장에 나온 로켓을 활용할 것이며, 물체의 진행 방향에 맞게 엑터가 회전할 수 있어야 한다.
- 추가2: 스페이스바를 누르는 동안 로켓은 우측 상단으로 추진력을 얻게되고, 화면 하단 방향으로 항상 중력이 일정하게 작용해야한다. 조금 더 현실성을 높이기 위해서 공기저항력이 일정한 계수로 항상 작용한다. 그 외의 다른 움직임은 존재하지 않는다.
Exercise_3_3 전체 소스
구현 결과물
1단계
- 조건1:
MoveComponent
가 멤버 변수로서 질량, 힘의 총합, 그리고 속도를 가지도록 변경한다.- 다음과 같이
MoveComponent
클래스에mMass
,mForces
,mVelocity
를 추가했다. - 추가2 조건을 만족시키기 위해서 중력가속도 변수
GA
와 공기저항계수mAirResistance
변수를const
변수로 선언하고, 생성자에서 초기화 한다.
- 다음과 같이
- 조건3:
MoveComponent
에 힘을 설정하는 메소드를 구현한다.MoveComponent
클래스에AddForce(Vector2)
함수를 구현했다.
MoveComponent.h의 선언
#pragma once
#include "Component.h"
#include "Math.h"
class MoveComponent : public Component
{
public:
// 먼저 업데이트되도록 갱신 순서값을 낮춤
MoveComponent(class Actor* owner, int updateOrder = 10);
void Update(float deltaTime) override;
// 물리연산을 위한 멤버함수
void AddForce(Vector2 force) { mForces += force; }
// ... (Get,Set함수 생략)...
private:
float mMass;
Vector2 mForces;
Vector2 mAccele;
Vector2 mVelocity;
// 중력 가속도(gravitational acceleration)
const Vector2 GA;
// 공기 저항
const float mAirResistance;
};
MoveComponent.cpp의 MoveComponent::MoveComponent(Actor*, int)
MoveComponent::MoveComponent(Actor* owner, int updateOrder)
:Component(owner, updateOrder)
, mMass(1.0f)
, mForces(Vector2(0.0f, 0.0f))
, mAccele(Vector2(0.0f, 0.0f))
, mVelocity(Vector2(0.0f, 0.0f))
, GA(Vector2(0.0f, 300.0f)) // 중력가속도를 화면 하단 방향으로 300.0f로 임의 설정
, mAirResistance(0.01f) // 공기저항 계수를 임의로 0.01f로 설정
{
}
2단계
- 추진력벡터, 질량, 입력받을 key는 우주선
Ship
의 생성자에서 설정해줄 수 있다.
Ship.cpp 의 Ship::Ship(Game*)
Ship::Ship(Game* game)
: Actor(game)
{
AnimSpriteComponent* asc = new AnimSpriteComponent(this);
std::vector<SDL_Texture*> anims = {
game->GetTexture("../Assets/Ship01.png"),
game->GetTexture("../Assets/Ship02.png"),
game->GetTexture("../Assets/Ship03.png"),
game->GetTexture("../Assets/Ship04.png")
};
asc->SetAnimTextures(anims);
// Create InputComponent and set keys/force
InputComponent* ic = new InputComponent(this);
ic->SetForceKey(SDL_SCANCODE_SPACE);
ic->SetDForce(Vector2(1500.0f,-20000.0f));
ic->SetMass(30.0f);
}
3단계
InputComponent
클래스에서 "우주선의 움직임과 관련된 기존코드"를 "추진력을 입력받으면AddForce()
를 호출하는 코드"로 수정하였다.
InputComponent.h의 선언
class InputComponent : public MoveComponent
{
public:
InputComponent(class Actor* owner);
void ProcessInput(const uint8_t* keyState) override;
// ... (Get,Set함수 생략)...
private:
// 추진력(Driving Force)
Vector2 mDForce;
// 추진력을 입력하는 키
int mForceKey;
};
InputComponent.cpp의 InputComponent::ProcessInput(const uint8_t*)
void InputComponent::ProcessInput(const uint8_t* keyState)
{
// Actor의 상태가 paused라면 Actor 입력 안받음
if (mOwner->GetState() == Actor::EPaused)
return;
// ForceKey가 입력되면 mDForce만큼의 힘이 더해짐.
if (keyState[mForceKey])
{
AddForce(mDForce);
}
}
4단계
- 조건2:
Update
함수에서 전방 이동에 대한 코드를 변경해서 힘으로부터 가속도를, 가속도로부터 속도를, 그리고 속도로부터 위치를 계산한다.MoveComponent::Update(float)
함수에서 물리 연산을 진행한다. (여기서는 크게 고려대상은 아니지만 물리 연산을 진행하는Update
함수는 가변 시간 단계 문제를 고려하여서 고정 레이트로 호출되어야 한다.)- 중력: \(F_g = mg\)
- 공기저항: \(F_r = -kv\)
- 가속도: \(\vec{a} = F_{all} / m\)
- 속도: \(\vec{v} = \vec{a} * dt\)
- 위치: \(pos = pos_{현재} + \vec{v} * dt\)
- 추가1: 리소스는 2장에 나온 로켓을 활용할 것이며, 물체의 진행 방향에 맞게 엑터가 회전할 수 있어야 한다.
- 물체의 회전을 위해서 아래의 공식을 활용하여 회전 값 계산
- 엑터의 회전값: \(\theta = \arctan( \vec{y} / \vec{x} )\)
MoveComponent.cpp의 MoveComponent::Update(float)
void MoveComponent::Update(float deltaTime)
{
// 매 프레임마다 중력 작용
AddForce(GA * mMass);
// 매 프레임마다 공기저항력 작용
AddForce(mVelocity * mAirResistance * (-1.0f));
// 가속도 = 힘의 총량 / 질량
mAccele = mForces * (1/mMass);
// 속도 = 가속도 * 시간
mVelocity += mAccele * deltaTime;
// 속도벡터로 actor의 회전값 계산. SDL의 y 좌표는 아랫쪽이 양수이기 때문에 y값 반전
float rot = Math::Atan2(-mVelocity.y, mVelocity.x);
mOwner->SetRotation(rot);
Vector2 pos = mOwner->GetPosition();
//SDL의 y좌표계는 아래쪽 방향이 양의방향이기 때문에 y값은 빼준다
pos.x += mVelocity.x * deltaTime;
pos.y += mVelocity.y * deltaTime;
mOwner->SetPosition(pos);
mForces = Vector2(0.0f, 0.0f);
}