[C++/ SDL2.0 - 똥피하기만들기] - 03. main 정리
오늘은 main에 있는걸 클래스로 빼보려고 합니다.
헤더와 소스파일로 Game.h / Game.cpp를 추가했습니다.
기본 몸체만 만들어주고 일단.
private에는 멤버변수, 즉 우리가 글로벌로 빼둔 것들을 넣을거고
public에서는 멤버함수, 총 4가지의 함수를 사용해서(기본) 게임을 실행할거에요
1. init() - 게임을 실행한다(딱 게임이 시작할때 한번만 실행할 녀석들을 여기에 넣을거에요)
예를들면, sdl, window, render생성 등등
2. update() - 게임을 하면서 유저한테 입력받은걸 실시간으로 실행시켜줄 녀석들 이에요.
예를들면, 나중에 넣겠지만, 움직이는키 등등
3. render() - 게임을 하면서 모든 이미지는 이 함수가 관리할겁니다.
예를들면, buffer를 앞뒤로 바꿔주는거라던지, 코인 이미지를 띄운거라던지 등등
4. shutdown() - 이 함수는 우리가 플레이어가 죽었는데, 죽었을때 플레이어를 그림만 지우고
실제 메모리를 지우지 않을경우, 메모리가 어딘가로 붕 떠버릴거에요 (memory leak). 이런 경우를 방지하기위해
메모리를 지워주는 함수입니다.
보시면 update의 parameter에는 double deltaTime을 넣었습니다.
이유가 updata함수는 유저 인풋을 받는 함수잖아요? 근데 만약에
A라는 사람의 컴퓨터는 최신형인데 B라는 사람의 컴퓨터는 좀 구형입니다.
A는 1초에 160frame을 뽑는데, B는 1초에 60밖에 못뽑아요 만약에.
아무리 컴퓨터 차이가 난다고 해도, A는 1초에 w키 눌러서 100미터를 가버리고
B는 30미터밖에 못가면, 이거 컴퓨터 안좋은사람들은 많이 억울할겁니다.
그런걸 방지하기위해 deltaTime을 넣어줘서 deltaTime으로 유저들에게 형평성을 줄겁니다.
나중에 다룰 문제이니까 나중에 더 자세하게 설명할게요 :)
이제 몸체를 만들러 cpp 파일로 갑니다.
몸체를 만들었으니 이제, main에 있는걸 하나하나씩 가져오겠습니다.
전체적인 코드는 이렇습니다.
Game.h
#ifndef _CGAME_H_
#define _CGAME_H_
#include <SDL.h>
#include <SDL_image.h>
class Game
{
private:
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
SDL_Renderer* m_pRenderer = nullptr;
SDL_Window* m_pWindow = nullptr;
SDL_Surface* m_pSurface = nullptr;
SDL_Texture* m_pTexture = nullptr;
public:
bool init();
void update(double deltaTime);
void render();
void shutDown();
};
#endif
저는 헤더가드는 이걸로 바꿧는데, #pragma once 쓰셔도 괜찮습니다.
private에 보시면, 윈도우 크기, 그리고 render, window, surface, texture를 담을 변수들을 만들어 줬고,
(여기서 nullptr을 안넣으셔도 괜찮습니다.)
아래 public은 그대로 입니다.
Game.cpp
#include "Game.h"
#include <iostream>
using namespace std;
bool Game::init()
{
// Init SDL
if (SDL_Init(SDL_INIT_EVERYTHING) == 0)
{
cout << "SDL INIT SUCCESS" << endl;
//Create Window - 윈도우는 캔버스임
m_pWindow = SDL_CreateWindow("Poo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, 0);
if (m_pWindow != nullptr)
{
cout << "WINDOW INIT SUCCESS" << endl;
//Create Renderer - 렌더러는 화가임
m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, 0);
if (m_pRenderer != nullptr)
{
cout << "RENDERER INIT SUCCESS" << endl;
}
//만약 렌더러 이닛에 실패하면, 윈도우부수고, sdl도 나가야함 - 안그러면 memory leak
else
{
cout << "RENDERER INIT FAILED" << endl;
SDL_DestroyWindow(m_pWindow);
SDL_Quit();
return false;
}
}
//만약 윈도우 이닛에 실패하면 sdl 나가야함 - 안그러면 memory leak
else
{
cout << "WINDOW INIT FAILED" << endl;
SDL_Quit();
return false;
}
// Create Image / jpg와 png flag 비트를 합쳐서 하나로 뭉쳐준다.
int iImgInitFlags = IMG_INIT_JPG | IMG_INIT_PNG;
int iResult = IMG_Init(iImgInitFlags);
if ((iResult & iImgInitFlags) == iImgInitFlags)
{
cout << "IMAGE INIT SUCCESS" << endl;
}
else
{
cout << "IMAGE INIT FAILED" << endl;
SDL_DestroyRenderer(m_pRenderer);
SDL_DestroyWindow(m_pWindow);
SDL_Quit();
return false;
}
}
//만약 SDL 이닛에 실패하면 그냥 실패했다고 띄워만 줘도 됌.
else
{
cout << "SDL INIT FAILED" << endl;
return false;
}
//Create Surface
m_pSurface = IMG_Load("Assets/Coin.png");
if (m_pSurface == nullptr)
{
cout << "Assets / Coin.png - IMG LOAD FAIELD(surface)" << endl;
return false;
}
//Create Texture
m_pTexture = SDL_CreateTextureFromSurface(m_pRenderer, m_pSurface);
if (m_pTexture == nullptr)
{
cout << "TEXTURE LOAD FAIELD" << endl;
SDL_FreeSurface(m_pSurface);
return false;
}
SDL_FreeSurface(m_pSurface);
return true;
}
void Game::update(double deltaTime)
{
}
void Game::render()
{
while (true)
{
//Back buffer에 지울 색을 정해주고, 지운다.
SDL_SetRenderDrawColor(m_pRenderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
SDL_RenderClear(m_pRenderer);
//Back buffer에 그린다.
SDL_Rect m_srcRect;
m_srcRect.x = 0;
m_srcRect.y = 0;
m_srcRect.w = 95;
m_srcRect.h = 97;
SDL_Rect m_dstRect;
m_dstRect.x = SCREEN_WIDTH * 0.5;
m_dstRect.y = SCREEN_HEIGHT * 0.5;
m_dstRect.w = 95;
m_dstRect.h = 97;
SDL_RenderCopy(m_pRenderer, m_pTexture, &m_srcRect, &m_dstRect);
//Back buffer를 앞으로 빼준다.
SDL_RenderPresent(m_pRenderer);
}
}
void Game::shutDown()
{
cout << "SYSTEM SHUT DOWN" << endl;
SDL_DestroyRenderer(m_pRenderer);
SDL_DestroyWindow(m_pWindow);
SDL_Quit();
}
좀 길어서 헷갈릴 수 있지만, 한줄 한줄 읽어보겠습니다.
한번만 만들어줄 코드는 init() 함수에 넣었고,
update()에는 아직 저희가 유저한테 받는게 없으니 그냥 비워 두었고,
render에는 while()문을 그대로 가져와서 그림을 그려주고 있고
void CGame::shutDown()
{
cout << "SYSTEM SHUT DOWN" << endl;
SDL_DestroyRenderer(m_pRenderer);
SDL_DestroyWindow(m_pWindow);
SDL_Quit();
}
여기서는 하나하나씩 종료를 해주고 있습니다.
만든 순서의 반대인 화가->윈도우->SDL순서대로 종료를 하고있습니다.
그리고 메인함수는 이렇게 변했습니다.
main.cpp
#include "Game.h"
int main(int argc, char* args[])
{
Game GameInst;
bool bResult = GameInst.init();
if (bResult == true)
{
GameInst.render();
}
GameInst.shutDown();
return 0;
}
한줄한줄 보겠습니다.
Game GameInst;
bool bResult = GameInst.init();
Game이라는 클래스로 객체를 생성하고
객체를 사용해 init()함수를 불러온걸 bResult라는 변수에 담아둡니다.
여기서 담아주는 이유가
저희가 만약에 init()에 실패했는데 update나 render를 불러올 필요가 있을까요?
정답은 없습니다 입니다.
그래서 그 바로 아랫줄에
if (bResult == true)
{
GameInst.render();
}
init에 성공했을때만 render를 부르게 해놨습니다.
update도 여기에 들어가는데, 아직 사용 안하니까 안부르겠습니다.
그리고 성공하던 실패하던 상관없이, shutdown을해서 메모리를 지워줘야하니까 마지막에
GameInst.shutDown();
shutdown까지 불러주고 끝났습니다.
main함수가 간결해지니 한결 보기 좋네요 :)
이제 잘 되나 실행을 해보고 잘 컴파일이 되실거라 믿고 끝내겠습니다.