본문 바로가기
-----ETC-----/C++로 배우는 게임 엔진 개발

[C++로 배우는 게임 엔진 개발] Day 12: 간단한 물리 엔진 구현 (AABB 충돌)

by cogito21_cpp 2024. 8. 1.
반응형

간단한 물리 엔진 구현 (AABB 충돌)

오늘은 SDL2를 사용하여 간단한 물리 엔진을 구현하고, AABB(축 정렬 경계 상자) 충돌 감지 방식을 사용하여 물리적인 상호작용을 처리하는 방법을 학습하겠습니다. AABB 충돌 감지는 간단하지만 효율적인 충돌 감지 방식입니다.

1. AABB 충돌 감지의 개념

AABB 충돌 감지는 사각형으로 된 객체가 충돌하는지 감지하는 방법입니다. 두 사각형의 위치와 크기를 비교하여 충돌 여부를 판단합니다.

2. AABB 충돌 감지 함수 작성

먼저, AABB 충돌 감지 함수를 작성하겠습니다.

 

헤더 파일 작성

include/Physics.h 파일을 생성하고 다음과 같이 작성합니다.

#ifndef PHYSICS_H
#define PHYSICS_H

#include <SDL2/SDL.h>

bool CheckAABBCollision(const SDL_Rect& rectA, const SDL_Rect& rectB);

#endif // PHYSICS_H

 

소스 파일 작성

src/Physics.cpp 파일을 생성하고 다음과 같이 작성합니다.

#include "Physics.h"

bool CheckAABBCollision(const SDL_Rect& rectA, const SDL_Rect& rectB) {
    // AABB 충돌 감지 로직
    if (rectA.x + rectA.w <= rectB.x) return false; // A의 오른쪽이 B의 왼쪽보다 왼쪽에 있다면
    if (rectA.x >= rectB.x + rectB.w) return false; // A의 왼쪽이 B의 오른쪽보다 오른쪽에 있다면
    if (rectA.y + rectA.h <= rectB.y) return false; // A의 아래쪽이 B의 위쪽보다 위에 있다면
    if (rectA.y >= rectB.y + rectB.h) return false; // A의 위쪽이 B의 아래쪽보다 아래에 있다면
    return true; // 위의 조건에 해당하지 않으면 충돌 중
}

3. 게임 엔진에 물리 엔진 추가

이제 게임 엔진에 AABB 충돌 감지 기능을 추가하고, 물리적인 상호작용을 처리하겠습니다.

 

헤더 파일 수정

include/GameEngine.h 파일을 수정하여 Physics 헤더를 포함하고, 필요한 변수를 추가합니다.

#ifndef GAMEENGINE_H
#define GAMEENGINE_H

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include "Physics.h"

class GameEngine {
public:
    GameEngine();
    ~GameEngine();
    bool Initialize(const char* title, int width, int height);
    void Run();
    void Shutdown();

private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    SDL_Rect player;
    SDL_Rect obstacle;
    bool isRunning;
    Uint32 frameStart;
    int frameTime;

    void HandleEvents();
    void Update();
    void Render();
};

#endif // GAMEENGINE_H

 

소스 파일 수정

src/GameEngine.cpp 파일을 다음과 같이 수정합니다.

#include "GameEngine.h"
#include <iostream>

const int FPS = 60;
const int FRAME_DELAY = 1000 / FPS;

GameEngine::GameEngine()
    : window(nullptr), renderer(nullptr), isRunning(false), frameStart(0), frameTime(0) {
    player = {100, 100, 50, 50};
    obstacle = {300, 100, 50, 50};
}

GameEngine::~GameEngine() {
    Shutdown();
}

bool GameEngine::Initialize(const char* title, int width, int height) {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return false;
    }

    window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
    if (window == nullptr) {
        std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
        return false;
    }

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (renderer == nullptr) {
        std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
        return false;
    }

    isRunning = true;
    return true;
}

void GameEngine::Run() {
    while (isRunning) {
        frameStart = SDL_GetTicks();

        HandleEvents();
        Update();
        Render();

        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }
}

void GameEngine::HandleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) {
            isRunning = false;
        }
        if (event.type == SDL_KEYDOWN) {
            switch (event.key.keysym.sym) {
                case SDLK_ESCAPE:
                    isRunning = false;
                    break;
                // 플레이어 이동 처리
                case SDLK_UP:
                    player.y -= 5;
                    break;
                case SDLK_DOWN:
                    player.y += 5;
                    break;
                case SDLK_LEFT:
                    player.x -= 5;
                    break;
                case SDLK_RIGHT:
                    player.x += 5;
                    break;
                default:
                    break;
            }
        }
    }
}

void GameEngine::Update() {
    // 충돌 감지 및 처리
    if (CheckAABBCollision(player, obstacle)) {
        std::cout << "Collision detected!" << std::endl;
    }
}

void GameEngine::Render() {
    // 화면을 검은색으로 지우기
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    // 플레이어 렌더링
    SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
    SDL_RenderFillRect(renderer, &player);

    // 장애물 렌더링
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_RenderFillRect(renderer, &obstacle);

    // 렌더링 결과를 화면에 출력
    SDL_RenderPresent(renderer);
}

void GameEngine::Shutdown() {
    if (renderer) {
        SDL_DestroyRenderer(renderer);
        renderer = nullptr;
    }
    if (window) {
        SDL_DestroyWindow(window);
        window = nullptr;
    }
    SDL_Quit();
}

4. 프로젝트 빌드 및 실행

  1. Visual Studio에서 CMake 프로젝트 열기:
    • Visual Studio를 실행하고, File -> Open -> CMake...를 선택합니다.
    • GameEngine 디렉토리를 선택하여 프로젝트를 엽니다.
  2. 프로젝트 빌드:
    • Visual Studio 상단의 Build 메뉴에서 Build All을 선택하여 프로젝트를 빌드합니다.
  3. 프로젝트 실행:
    • Debug 메뉴에서 Start Without Debugging을 선택하여 프로그램을 실행합니다.
    • 윈도우 창이 생성되고, 플레이어 사각형이 장애물 사각형과 충돌하면 콘솔에 "Collision detected!" 메시지가 출력됩니다.

마무리

오늘은 SDL2를 사용하여 간단한 물리 엔진을 구현하고, AABB 충돌 감지 방식을 사용하여 물리적인 상호작용을 처리하는 방법을 학습했습니다. 다음 단계에서는 SDL_mixer를 사용하여 사운드 시스템을 구현하고, 게임 내에서 사운드를 재생하는 방법을 배우겠습니다.

질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요.

Day 13 예고

다음 날은 "사운드 시스템 기초 (SDL_mixer)"에 대해 다룰 것입니다. SDL_mixer를 사용하여 사운드 시스템을 구현하고, 게임 내에서 효과음과 배경음을 재생하는 방법을 배워보겠습니다.

반응형