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

[C++로 배우는 게임 엔진 개발] Day 7: 게임 루프와 타이밍 관리

by cogito21_cpp 2024. 8. 1.
반응형

게임 루프와 타이밍 관리

오늘은 게임 루프와 타이밍 관리를 학습하여 프레임 속도를 제어하고 일정한 간격으로 게임 상태를 업데이트하는 방법을 배워보겠습니다. 이를 통해 게임이 일정한 프레임 속도로 실행되고, 부드럽게 동작하도록 합니다.

1. 게임 루프의 개념

게임 루프는 게임 엔진의 핵심으로, 다음과 같은 세 가지 주요 작업을 반복적으로 수행합니다:

  1. 입력 처리: 사용자 입력을 처리합니다.
  2. 게임 상태 업데이트: 게임 객체의 상태를 업데이트합니다.
  3. 렌더링: 그래픽스를 화면에 렌더링합니다.

2. 타이밍 관리

타이밍 관리는 게임 루프의 각 프레임이 일정한 시간 간격으로 실행되도록 보장합니다. 이를 통해 게임이 일관된 속도로 실행되며, 모든 기기에서 동일한 속도로 동작하게 됩니다.

SDL2와 GLFW를 사용하여 타이밍 관리를 구현해 보겠습니다.

3. SDL2를 사용한 타이밍 관리

헤더 파일 수정

include/GameEngine.h 파일에 타이밍 관리 변수를 추가합니다.

#ifndef GAMEENGINE_H
#define GAMEENGINE_H

#include <SDL2/SDL.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;
    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) {}

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;
                // 여기에 추가 입력 처리를 구현합니다.
                default:
                    break;
            }
        }
    }
}

void GameEngine::Update() {
    // 게임 상태 업데이트 로직을 여기에 추가합니다.
}

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

    // 빨간색 직사각형 그리기
    SDL_Rect rect = { 200, 150, 400, 300 };
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_RenderFillRect(renderer, &rect);

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

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

4. GLFW를 사용한 타이밍 관리

헤더 파일 수정

include/GameEngine.h 파일에 타이밍 관리 변수를 추가합니다.

#ifndef GAMEENGINE_H
#define GAMEENGINE_H

#include <GLFW/glfw3.h>

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

private:
    GLFWwindow* window;
    bool isRunning;
    double lastTime;
    double deltaTime;

    static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
    void HandleEvents();
    void Update();
    void Render();
};

#endif // GAMEENGINE_H

소스 파일 수정

src/GameEngine.cpp 파일에 타이밍 관리를 추가합니다.

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

const int FPS = 60;
const double FRAME_TIME = 1.0 / FPS;

GameEngine::GameEngine()
    : window(nullptr), isRunning(false), lastTime(0.0), deltaTime(0.0) {}

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

bool GameEngine::Initialize(const char* title, int width, int height) {
    if (!glfwInit()) {
        std::cerr << "GLFW Initialization failed!" << std::endl;
        return false;
    }

    window = glfwCreateWindow(width, height, title, NULL, NULL);
    if (!window) {
        std::cerr << "GLFW Window creation failed!" << std::endl;
        glfwTerminate();
        return false;
    }

    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, KeyCallback);

    isRunning = true;
    lastTime = glfwGetTime();
    return true;
}

void GameEngine::Run() {
    while (isRunning && !glfwWindowShouldClose(window)) {
        double currentTime = glfwGetTime();
        deltaTime += (currentTime - lastTime) / FRAME_TIME;
        lastTime = currentTime;

        while (deltaTime >= 1.0) {
            HandleEvents();
            Update();
            deltaTime--;
        }

        Render();
        glfwSwapBuffers(window);
    }
}

void GameEngine::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
    if (action == GLFW_PRESS) {
        switch (key) {
            case GLFW_KEY_ESCAPE:
                glfwSetWindowShouldClose(window, GLFW_TRUE);
                break;
            // 여기에 추가 입력 처리를 구현합니다.
            default:
                break;
        }
    }
}

void GameEngine::HandleEvents() {
    glfwPollEvents();
}

void GameEngine::Update() {
    // 게임 상태 업데이트 로직을 여기에 추가합니다.
}

void GameEngine::Render() {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // OpenGL 삼각형 그리기
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex2f(0.0f, 0.5f);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex2f(-0.5f, -0.5f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex2f(0.5f, -0.5f);
    glEnd();
}

void GameEngine::Shutdown() {
    if (window) {
        glfwDestroyWindow(window);
        window = nullptr;
    }
    glfwTerminate();
}

5. 프로젝트 빌드 및 실행

  1. Visual Studio에서 CMake 프로젝트 열기:
    • Visual Studio를 실행하고, File -> Open -> CMake...를 선택합니다.
    • GameEngine 디렉토리를 선택하여 프로젝트를 엽니다.
  2. SDL2/GLFW 라이브러리 설치:
    • SDL2 또는 GLFW 라이브러리를 프로젝트에 설정합니다.
  3. 프로젝트 빌드:
    • Visual Studio 상단의 Build 메뉴에서 Build All을 선택하여 프로젝트를 빌드합니다.

4. 프로젝트 실행:

  • Debug 메뉴에서 Start Without Debugging을 선택하여 프로그램을 실행합니다.
  • SDL2를 사용하는 경우, 윈도우 창이 생성되고 일정한 프레임 속도로 실행됩니다.
  • GLFW를 사용하는 경우, 윈도우 창이 생성되고 일정한 프레임 속도로 실행됩니다.

마무리

오늘은 게임 루프와 타이밍 관리를 학습하여 일정한 프레임 속도로 게임이 실행되도록 구현했습니다. 다음 단계에서는 2D 그래픽스 기초를 배우고, 화면에 스프라이트를 렌더링하는 방법을 학습하겠습니다.

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

Day 8 예고

다음 날은 "2D 그래픽스 기초 (SDL2)"에 대해 다룰 것입니다. 2D 그래픽스를 사용하여 스프라이트를 렌더링하는 방법을 배워보겠습니다.

반응형