Game Programming in C++

Game Programming in C++ Chapter2 - Game Manager

skyho 2025. 9. 5. 04:31
반응형

산자이 마드하브 저 / 박주항 역 ❘ 에이콘

 

책의 소스 코드를 직접 작성해보면서 정리한 글입니다. (실습환경: windows)

책 소스 코드: GitHub - gameprogcpp/code: Game Programming in C++ Code

 

GitHub - gameprogcpp/code: Game Programming in C++ Code

Game Programming in C++ Code. Contribute to gameprogcpp/code development by creating an account on GitHub.

github.com


1. Game Manager 역할(기능)

기능 설명
게임 초기화 및 종료 관리 SDL, SDL_image 초기화, 윈도우/렌더러 생성 및 제거
게임 루프 관리 processInput() → updateGame() → generateOutput()
Actor 관리 게임 내 오브젝트(Actor)의 생명주기 관리
SpriteComponent 관리 렌더링 되는 모든 Sprite 관리
텍스처 관리 이미지 로딩 및 재사용(텍스처 캐싱)

 

이전에 작성했던 Game Class에서 Actor/Sprite 관리가 추가되었다.

반면 Draw 기능은 제거 되었다.

updateGame()은 세부 동작이 직접 업데이트 하는 형태에서 Actor를 호출하는 형태로 변경될 것이다.

 

Game Manager가 SpriteComponent를 가지고 있는 것은,

엄밀하게 말하면 Component 구조를 벗어난 것인데, 이렇게 하는 이유는 효율성을 위한 타협이다.

Game Manager에서 Sprite Component를 관리하면, 원하는 순서대로 텍스처를 그릴 수 있다.

Actor를 통해 동작시켜도, 원하는 순서대로 그릴 수 있지만, 그러기 위해서는

'Actor 순회 → SpriteComponent 찾기 → 우선순위 정렬' 등 추가적인 작업이 필요하다.

 

2. GameManager.hpp

#pragma once

#include <string>
#include <vector>
#include <unordered_map>
#include <map>
#include "SDL3/SDL.h"

class GameManager
{
public:
	GameManager();
	bool initialize(int w = 1024, int h = 768);
	void runLoop();
	void shutdown();
	void addActor(class Actor* actor);
	void removeActor(class Actor* actor);
	void addSprite(class SpriteComponent* sprite);
	void removeSprite(class SpriteComponent* sprite);
	const int getWindowWidth() const;
	const int getWindowheight() const;

private:
	void processInput();
	void updateGame();
	void generateOutput();

	SDL_Texture* getTexture(const std::string& fileName);
	std::vector<SDL_Texture*> getTextures(const std::vector<std::string>& files);
	bool loadData();
	void unloadData();

	SDL_Window* _sdl_window;
	int _window_width;
	int _window_height;
	bool _running;
	SDL_Renderer* _sdl_renderer;
	Uint64 _ticks_cnt;
	std::unordered_map<std::string, SDL_Texture*> _textures;
	std::vector<class Actor*> _actors;
	std::vector<class Actor*> _pending_actors;
	std::multimap<int, class SpriteComponent*> _sprite_components;
};

 

3. GameManager.cpp

#include <algorithm>
#include "SDL3_image/SDL_image.h"
#include "GameManager.hpp"
#include "Actor.hpp"
#include "BGSpriteComponent.hpp"
#include "AnimationComponent.hpp"

GameManager::GameManager() : _sdl_window(nullptr), _window_width(1024), _window_height(768), _running(true), _sdl_renderer(nullptr), _ticks_cnt(0)
{
}

//initialize SDL and Create window
bool GameManager::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_width = w;
	_window_height = h;

	return true;
}

void GameManager::runLoop()
{
	while (_running)
	{
		processInput();
		updateGame();
		generateOutput();
	}
}

void GameManager::shutdown()
{
	unloadData();
	SDL_DestroyRenderer(_sdl_renderer);
	SDL_DestroyWindow(_sdl_window);
	SDL_Quit();
}

void GameManager::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 GameManager::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;
	}

	for (auto& actor : _actors)
	{
		actor->update(delta_time);
	}

	_ticks_cnt = SDL_GetTicks(); // update tick count for next frame
}

void GameManager::generateOutput()
{
	for (const auto& sprite : _sprite_components)
	{
		sprite.second->draw(_sdl_renderer);
	}

	SDL_RenderPresent(_sdl_renderer);
}

SDL_Texture* GameManager::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;
}

bool GameManager::loadData()
{
	Actor* bg_actor = new Actor(this);
	bg_actor->setPosition(static_cast<float>(_window_width / 2), static_cast<float>(_window_height / 2));

	std::vector<std::string> files = { "Assets/Farback01.png" , "Assets/Farback02.png" };
	std::vector<SDL_Texture*> textures = getTextures(files);
	if (textures.empty())
		return false;
	BGSpriteComponent* bg = new BGSpriteComponent(bg_actor);
	bg->setBGTextures(textures);
	bg->setScrollSpeed(-100.0f);

	files = { "Assets/Stars.png" , "Assets/Stars.png" };
	textures = getTextures(files);
	if (textures.empty())
		return false;
	bg = new BGSpriteComponent(bg_actor);
	bg->setBGTextures(textures);
	bg->setScrollSpeed(-200.0f);

	Actor* ship = new Actor(this);
	ship->setScale(1.5f);
	ship->setPosition(100.0f, 384.0f);
	files = { "Assets/Ship01.png" , "Assets/Ship02.png", "Assets/Ship03.png", "Assets/Ship04.png" };
	textures = getTextures(files);
	if (textures.empty())
		return false;
	AnimationComponent* ani_comp = new AnimationComponent(ship);
	ani_comp->setAnimationTextures(textures);

	return true;
}

void GameManager::unloadData()
{
	if (!_textures.empty())
	{
		for (const auto& t : _textures) SDL_DestroyTexture(t.second);
		_textures.clear();
	}

	for (auto actor : _actors)
	{
		delete actor;
	}
}

void GameManager::addActor(Actor* actor)
{
	_actors.emplace_back(actor);
}

void GameManager::removeActor(Actor* actor)
{
	// Is it in pending actors?
	auto iter = std::find(_pending_actors.begin(), _pending_actors.end(), actor);
	if (iter != _pending_actors.end())
	{
		// Swap to end of vector and pop off (avoid erase copies)
		std::iter_swap(iter, _pending_actors.end() - 1);
		_pending_actors.pop_back();
	}

	// Is it in actors?
	iter = std::find(_actors.begin(), _actors.end(), actor);
	if (iter != _actors.end())
	{
		// Swap to end of vector and pop off (avoid erase copies)
		std::iter_swap(iter, _actors.end() - 1);
		_actors.pop_back();
	}
}

void GameManager::addSprite(class SpriteComponent* sprite)
{
	const int draw_order = sprite->getDrawOrder();
	_sprite_components.insert({ draw_order, sprite });
}

void GameManager::removeSprite(class SpriteComponent* sprite)
{
	const int draw_order = sprite->getDrawOrder();
	auto iter = _sprite_components.find(draw_order);
	while (iter != _sprite_components.end())
	{
		if (iter->second == sprite)
		{
			_sprite_components.erase(iter);
			break;
		}
		else
		{
			iter++;
		}
	}
}

const int GameManager::getWindowWidth() const
{
	return _window_width;
}

const int GameManager::getWindowheight() const
{
	return _window_height;
}

std::vector<SDL_Texture*> GameManager::getTextures(const std::vector<std::string>& files)
{
	std::vector<SDL_Texture*> textures;
	for (const auto& file : files)
	{
		SDL_Texture* texture = getTexture(file);
		if (texture == nullptr)
		{
			SDL_Log("Failed to load %s", file.c_str());
			return {};
		}

		textures.emplace_back(texture);
	}

	return textures;
}
반응형