본문으로 바로가기

3장 벡터와 물리 연습문제 3.1 ~ 3.2

연습 3.1

문제

그림 3.16의 삼각형은 아래의 세점 A, B, C로 그려진 것이다.
\(A = <-1, 1>\)
\(B = <2, 4>\)
\(C = <3, 3>\)

3장에서 설명했던 벡터 연산을 사용해서 \(\theta\)를 계산하라.

풀이

Exercise_3_1전체 소스

3장 에서 다룬 삼각함수와 내적으로 풀수 있는 문제이다.

  • 내적은 각의 코사인과 관계가 있다. 그래서 두 벡터 사이의 각도는 다음과 같이 표현할 수 있다.

$$\vec{a}\cdot\vec{b} = ||\vec{a}|| ||\vec{b}||\cos{\theta}$$

  • 따라서 \(\theta\)는 아래와 같다.

$$ \theta = \arccos{\left( \frac{\vec{a}\cdot\vec{b}}{||\vec{a}|| ||\vec{b}||} \right)} $$

  • 이를 코드로 나타내면 아래와 같다.

Exercise_3_1의 main.cpp

#include "Math.h"
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
  Vector2 a, b;
  float result;

  a = Vector2(2.0f, 4.0f) - Vector2(-1.0f, 1.0f);
  b = Vector2(3.0f, 3.0f) - Vector2(-1.0f, 1.0f);

  result = Math::Acos(
      Vector2::Dot(a, b) /
      (a.Length() * b.Length()));

  cout << "라디안: " << result << " / " << result / Math::Pi << "π" << endl
      << "도: " << result / Math::Pi * 180 << "°"<< endl;

  return 0;
}

출력 결과

라디안: 0.32175 / 0.102416π  
도: 18.4349°

연습 3.2

문제

3장 게임 프로젝트는 현재 우주선이 운석과 부딪히지 않는다. 아래의 조건에 맞춰서 운석과 우주선이 충돌하도록 구현하라.

  • 조건1: CircleComponent를 만들고 반지름을 지정해야한다.
  • 조건2: Ship::UpdateActor에서 모든 운석과의 충돌을 테스트한다.
  • 조건3: 우주선이 운석과 충돌하면 우주선의 위치를 화면의 중심으로 초기화하고 회전값은 0으로 설정한다.
  • 조건4: 추가 기능으로 우주선이 운석과 충돌한 후 1~2초 정도 화면에 출력되지 않게 구현한다.

풀이

Exercise_3_2전체 소스

  • 조건1: CircleComponent를 만들고 반지름을 지정해야한다.
    • 3장에서 레이저와 운석의 충돌검사를 하는 코드와 동일하게 Ship의 생성자에서 CircleComponent를 추가하여서 구현함.

Ship.cpp에서 Ship::Ship(Game*)

Ship::Ship(Game* game)
    : Actor(game)
{
    SpriteComponent* sc = new SpriteComponent(this, 150);
    sc->SetTexture(game->GetTexture("../Assets/ShipWithThrust.png"));

    // Create InputComponent and set keys/speed
    InputComponent* ic = new InputComponent(this);
    ic->SetForwardKey(SDL_SCANCODE_W);
    ic->SetBackKey(SDL_SCANCODE_S);
    ic->SetClockwiseKey(SDL_SCANCODE_A);
    ic->SetCounterClockwiseKey(SDL_SCANCODE_D);
    ic->SetMaxForwardSpeed(300.0f);
    ic->SetMaxAngularSpeed(Math::TwoPi);

    // 충돌 검사를 위해서 CircleComponent 추가와 반지름 설정
    mCircle = new CircleComponent(this);
    mCircle->SetRadius(30.0f);
}
  • 조건2: Ship::UpdateActor에서 모든 운석과의 충돌을 테스트한다.
    • ShipShip::UpdateActor(float)에서 모든 운석에 대한 교차검사를 시행한다.
    • 운석 객체들의 벡터는 GetGame()->GetAsteroids()를 통해서 가져온다.
    • Intersect() 함수를 통해서 우주선과 운석의 교차여부를 판단하고, 우주선과 운석이 교차한다면 우주선의 상태를 EPaused로 설정하였다.
    • EPaused로 설정한 이유는 밑에서 설명하겠지만 EPaused로 설정된 엑터는 화면에서 사라지고 나타나도록 구현하기 위함이다. 만약EDead로 설정할 시 죽은 객체로 간주되어 삭제되어 다시 생성을 해야하는 문제가 생긴다.
  • 조건3: 우주선이 운석과 충돌하면 우주선의 위치를 화면의 중심으로 초기화하고 회전값은 0으로 설정한다.
    • SetPosition()함수와 SetRotation() 위치와 회전값을 초기화 하였다.
  • 조건4: 추가 기능으로 우주선이 운석과 충돌한 후 1~2초 정도 화면에 출력되지 않게 구현한다.
    • 리스폰 시간을 설정하는 변수 mRespawnTime은 1.5초로 설정하였다. mRespawnTime이 양수일 경우 우주선은 EPaused상태를 계속 유지한다.
    • 이렇게 게임을 하다보면 리스폰 되자마자 우주선이 운석과 충돌하는 다소 짜증나는 상황을 맞이하게 될때가 많다. 문제에서 요구한 것에서 추가적으로 리스폰 후 일정시간 동안 무적상태를 유지할 수 있는 기능을 추가했다. mInvincibleTime 변수를 통해서 운석과 충돌 후 3.0초 동안은 충돌검사를 하지 않도록 설계하였다.
    • EPaused 상태의 엑터는 화면에 출력되지 않기 위해서 SpriteComponent::Draw(SDL_Renderer*) 함수도 일부 수정하였다.
    • 추가적으로 EPaused 상태에서는 엑터를 조작(움직임, 레이저발사)할 수 없도록 InputComponent::ProcessInput(const uint8_t*)함수를 일부 수정

Ship.cpp에서 Ship::UpdateActor(float) 전체 수정

void Ship::UpdateActor(float deltaTime)
{
    mLaserCooldown -= deltaTime;
    mRespawnTime -= deltaTime;
    mInvincibleTime -= deltaTime;

    // 리스폰 시간이 남아있을 경우 Active 상태가 될 수 없다.
    if (mRespawnTime >= 0)
        return;

    SetState(EActive);

    // 부활후 무적시간이 남아있을 경우 운석 교차 검사 안함
    if (mInvincibleTime >= 0)
        return;

    // 운석과 교차여부 검사
    for (auto ast : GetGame()->GetAsteroids())
    {
        if (Intersect(*mCircle, *(ast->GetCircle())))
        {
            // 우주선과 운석이 교차한다면
            // 우주선은 paused 상태로 설정
            SetState(EPaused);
            SetPosition(Vector2(512.0f, 386.0f));
            SetRotation(0.0f);
            mRespawnTime = 1.5f;
            mInvincibleTime = 3.0f;
            break;
        }
    }
}

SpriteComponent.cpp에서 void SpriteComponent::Draw(SDL_Renderer*) 일부 수정

void SpriteComponent::Draw(SDL_Renderer* renderer)
{
    // Actor의 상태가 EActive가 아니 라면 Actor를 그리지 않음.
    if (mOwner->GetState() != Actor::EActive)
        return;
// ... (이하 동일) ...

InputComponent.cpp에서 void InputComponent::ProcessInput(const uint8_t*) 일부 수정

void InputComponent::ProcessInput(const uint8_t* keyState)
{
    // Actor의 상태가 EActive가 아니 라면 입력을 받지 않음.
    if (mOwner->GetState() == Actor::EPaused)
        return;
// ... (이하 동일) ...

구현 결과물