Game Programming in C++
Game Programming in C++ Chapter2 - 배경 스크롤
skyho
2025. 8. 30. 02:17
반응형

책의 소스 코드를 직접 작성해보면서 정리한 글입니다. (실습환경: windows)
책 소스 코드: GitHub - gameprogcpp/code: Game Programming in C++ Code
1. 배경 스크롤(background scroll) 원리
이미지를 계속 이어 붙이면 된다.

2. 텍스처(texture)
도형에 이미지를 입히는 것, 이때 사용되는 이미지를 텍스처라 한다.
배경 텍스처를 저장하기 위해서 다음과 같은 자료구조를 사용한다.
struct BGTexture
{
SDL_Texture* sdl_texture;
float x, y;
float scroll_speed;
};
3. 소스 코드
chapter1과 동일하다.
- Main.cpp
- Game.hpp
- Game.cpp
4. 프로그래 흐름
- loadData()에서 png 파일을 이용해서 texture를 만든다.
- updateGame()에서 delta time을 이용해 배경 스크롤을 진행한다.
- generateOutput()에서 update된 세상을 그리면 된다.

5. Main.cpp
#include "Game.hpp"
int main(int argc, char** argv)
{
Game game;
bool success = game.initialize();
if (success)
{
game.runLoop();
}
game.shutdown();
return 0;
}
6. Game.hpp
chapter1과 비교하면, Texture 관련 함수와 변수들이 추가되었다.
- 새로운 자료구조: BGTexture
- 새로우 함수: loadData(), getTexture(), setTexture(), deleteTexture(), drawTexture()
- BGTexture 관리를 위한 STL
#include <string>
#include <vector>
#include <unordered_map>
#include "SDL3/SDL.h"
struct BGTexture
{
SDL_Texture* sdl_texture;
float x, y;
float scroll_speed;
};
class Game
{
public:
Game();
bool initialize(int w = 1024, int h = 768);
void runLoop();
void shutdown();
private:
void processInput();
void updateGame();
void generateOutput();
bool loadData(); // loading the background imamge and converting to image surface to texture
SDL_Texture* getTexture(const std::string& fileName); //convert to image surface to texture
BGTexture* setBGTexture(SDL_Texture* sdl_texture, float x, float y, float scroll_speed);
void unloadData();
void drawTexture();
SDL_Window* _sdl_window;
int _window_w;
int _window_h;
bool _running;
SDL_Renderer* _sdl_renderer;
Uint64 _ticks_cnt;
std::unordered_map<std::string, SDL_Texture*> _textures; //texture list
std::vector<BGTexture*> _bg_space_textures; //background
std::vector<BGTexture*> _bg_star_textures; //background
};
7. Game.cpp
#include "Game.hpp"
#include "SDL3_image/SDL_image.h"
Game::Game() : _sdl_window(nullptr), _window_w(1024), _window_h(768), _running(true), _sdl_renderer(nullptr), _ticks_cnt(0)
{
}
//initialize SDL and Create window
bool Game::initialize(int w, int h)
{
bool sdl_result = SDL_Init(SDL_INIT_VIDEO);
if (!sdl_result)
{
SDL_Log("Unable to initialize SDL[%d]: [%s]", sdl_result, SDL_GetError());
return false;
}
_sdl_window = SDL_CreateWindow(
"Game Programming in C++ (Chapter 2)", // Window title
w, // Width of window
h, // Height of window
0 // Flags (0 for no flags set)
);
if (_sdl_window == nullptr)
{
SDL_Log("Failed to create window: %s", SDL_GetError());
return false;
}
_sdl_renderer = SDL_CreateRenderer(
_sdl_window,
NULL // the name of rendering driver to init, if it is NULL, SDLL will choose one.
);
if (_sdl_renderer == nullptr)
{
SDL_Log("Failed to create renderer: %s", SDL_GetError());
return false;
}
if (!loadData())
{
SDL_Log("Failed to load data");
return false;
}
_window_w = w;
_window_h = h;
return true;
}
void Game::runLoop()
{
while (_running)
{
processInput();
updateGame();
generateOutput();
}
}
void Game::shutdown()
{
unloadData();
SDL_DestroyRenderer(_sdl_renderer);
SDL_DestroyWindow(_sdl_window);
SDL_Quit();
}
void Game::processInput()
{
SDL_Event event;
while (SDL_PollEvent(&event)) //if event is occurred in queue, it will return TRUE
{
switch (event.type)
{
case SDL_EVENT_QUIT: //windows x icon is pressed
_running = false;
break;
}
}
const bool* state = SDL_GetKeyboardState(NULL);
if (state[SDL_SCANCODE_ESCAPE]) //Esc is pressed
{
_running = false;
}
}
void Game::updateGame()
{
Uint64 current_time = SDL_GetTicks();
while (current_time < _ticks_cnt + 16) //16ms
{
current_time = SDL_GetTicks();
}
//delta time = last frame - current frame
//converting to per second
float delta_time = (SDL_GetTicks() - _ticks_cnt) / 1000.0f;
//delta time validation
//maximum delta time is 0.05 second
if (delta_time > 0.05f)
{
delta_time = 0.05f;
}
//backgroud scrolling implementation
for (auto& bg : _bg_space_textures)
{
// Update the x offset
bg->x += bg->scroll_speed * delta_time;
// If this is completely off the screen, reset offset to
// the right of the last bg texture
if (bg->x < -_window_w)
{
bg->x = static_cast<float>((_bg_space_textures.size() - 1) * _window_w - 1);
}
}
for (auto& star : _bg_star_textures)
{
// Update the x offset
star->x += star->scroll_speed * delta_time;
// If this is completely off the screen, reset offset to
// the right of the last bg texture
if (star->x < -_window_w)
{
star->x = static_cast<float>((_bg_star_textures.size() - 1) * _window_w - 1);
}
}
_ticks_cnt = SDL_GetTicks(); // update tick count for next frame
}
void Game::generateOutput()
{
drawTexture();
SDL_RenderPresent(_sdl_renderer);
}
SDL_Texture* Game::getTexture(const std::string& fileName)
{
SDL_Texture* texture = nullptr;
// Is the texture already in the map?
auto iter = _textures.find(fileName);
if (iter != _textures.end())
{
texture = iter->second;
}
else
{
// Load from file
SDL_Surface* surface = IMG_Load(fileName.c_str());
if (!surface)
{
SDL_Log("Failed to load texture file %s", fileName.c_str());
return nullptr;
}
// Create texture from surface
texture = SDL_CreateTextureFromSurface(_sdl_renderer, surface);
SDL_DestroySurface(surface);
if (!texture)
{
SDL_Log("Failed to convert surface to texture for %s", fileName.c_str());
return nullptr;
}
_textures.emplace(fileName.c_str(), texture);
}
return texture;
}
BGTexture* Game::setBGTexture(SDL_Texture* texture, float x, float y, float scroll_speed)
{
BGTexture* bg = new BGTexture;
bg->sdl_texture = texture;
bg->x = x;
bg->y = y;
bg->scroll_speed = scroll_speed;
return bg;
}
bool Game::loadData()
{
auto initializeBGTexture = [this](const std::string& file, float x, float y, float scroll_speed, std::vector<BGTexture*>& bg_textures) -> bool {
SDL_Texture* texture = getTexture(file);
if (texture == nullptr)
{
SDL_Log("Failed to load %s", file.c_str());
return false;
}
BGTexture* bg = setBGTexture(texture, x, y, scroll_speed);
bg_textures.emplace_back(bg);
return true;
};
if(!initializeBGTexture(
"Assets/Farback01.png",
0, 0, -100.0f,
_bg_space_textures))
return false;
if(!initializeBGTexture(
"Assets/Farback02.png",
(float)_window_w, 0, -100.0f,
_bg_space_textures))
return false;
if(!initializeBGTexture(
"Assets/Stars.png",
0, 0, -200.0f,
_bg_star_textures))
return false;
if(!initializeBGTexture(
"Assets/Stars.png",
(float)_window_w, 0, -200.0f,
_bg_star_textures))
return false;
return true;
}
void Game::unloadData()
{
if (!_textures.empty())
{
for (auto t : _textures) SDL_DestroyTexture(t.second);
_textures.clear();
}
if (!_bg_space_textures.empty())
{
for (auto bg : _bg_space_textures) delete bg;
_bg_space_textures.clear();
}
if (!_bg_star_textures.empty())
{
for (auto star : _bg_star_textures) delete star;
_bg_star_textures.clear();
}
}
void Game::drawTexture()
{
// Draw each background texture
for (auto& bg : _bg_space_textures)
{
SDL_FRect r;
r.w = static_cast<float>(_window_w);
r.h = static_cast<float>(_window_h);
r.x = bg->x;
r.y = bg->y;
// Draw this background
SDL_RenderTexture(
_sdl_renderer,
bg->sdl_texture,
nullptr,
&r
);
}
for (auto& star : _bg_star_textures)
{
SDL_FRect r;
r.w = static_cast<float>(_window_w);
r.h = static_cast<float>(_window_h);
r.x = star->x;
r.y = star->y;
// Draw this background
SDL_RenderTexture(
_sdl_renderer,
star->sdl_texture,
nullptr,
&r
);
}
}
7. 결과

반응형