2D Game Framework in C ++ und SDL
Ich mache zum ersten Mal ein einfaches 2D-Framework / eine einfache 2D-Engine basierend auf zwei Büchern und bevor das Projekt zu groß wird, würde ich gerne wissen, ob ich die Dinge richtig bestelle. Vielleicht gibt es dafür bessere Möglichkeiten. Ich habe versucht, Muster anzuwenden, um so viel wie möglich zu entkoppeln und die Dinge abstrakt zu halten.
Zum Beispiel ist dies die GameKlasse:
#include "Game.h"
void Game::Create(int width, int height, const char * title, int window_flag, int renderer_flag){
display.CreateWindow(800,600,"Game", SDL_WINDOW_SHOWN, SDL_RENDERER_ACCELERATED);
}
void Game::Start(){
Uint32 first_frame_ticks; float delta_time = 0;
SDL_Renderer * renderer = display.GetRenderer();
while(input_handler.GetLastEvent() != SDL_QUIT){
first_frame_ticks = SDL_GetTicks();
bool state_manager_empty = state_manager.isEmpty();
//update
input_handler.Update();
if(!state_manager_empty) state_manager.GetGameState()->onUpdate(delta_time);
//render
SDL_RenderClear(renderer);
if(!state_manager_empty) state_manager.GetGameState()->onRender();
SDL_RenderPresent(renderer);
delta_time = (SDL_GetTicks() - first_frame_ticks) / 1000.0f;
}
display.Clear();
}
void Game::AddState(GameState* state){
state->Set(display.GetRenderer(), &input_handler);
state_manager.Add(state);
}
Oder die Drawer-Klasse (an der ich einige Zweifel habe, die DrawEntity-Methode ist statisch)
#include "Drawer.h"
void Drawer::DrawEntity(Entity& entity, TextureManager* texture_manager, SDL_Renderer * renderer){
texture_manager->Draw(entity.GetSprite(), entity.GetPosition(), renderer);
}
Es fungiert als Vermittler zwischen TexureManager (der Texures zeichnet) und Entity, das alle Informationen wie Position, Skalierung usw. enthält.
Zum Schluss die Beispieleingabeklasse
#include "InputHandler.h"
#include <cstdio>
InputHandler::InputHandler(){
keyboard_state = SDL_GetKeyboardState(NULL);
printf("Event handler initialized\n");
}
void InputHandler::Update(){
SDL_Event ev;
while(SDL_PollEvent(&ev)){
if(ev.type == SDL_KEYUP || ev.type == SDL_KEYDOWN){
keyboard_state = SDL_GetKeyboardState(NULL);
}else last_event = ev.type;
}
}
bool InputHandler::isKeyPressed(SDL_Scancode key){
if(keyboard_state[key]) return true;
else return false;
}
Uint32 InputHandler::GetLastEvent(){
return last_event;
}
Ich werde ein Abhängigkeitsdiagramm sowie das GitHub-Repo hochladen:
Github Repo mit dem ganzen Code
https://github.com/grazianobolla/monke-engine
Antworten
Aktualisieren:
Nun, wenn jemand es wissen möchte, habe ich einige Änderungen endgültig vorgenommen. Eines der Hauptprobleme war, dass ich die GameStates nicht wirklich mit der Haupt-Engine selbst "verbinden" konnte, ohne einen Zeiger auf die Engine zu übergeben (was ist chaotisch) oder einzelne Hinweise auf die InputHandler, Displayetc.
Die Sache war, dass der InputHandler zum Beispiel die Fensterereignisse und die Tastatureingabe handhabte. Damit ein GameState darauf zugreifen konnte, musste ich einen Zeiger auf Game :: InputHandler erstellen, also habe ich einige Dinge getan:
Zuerst habe ich eine KeyboardHandleKlasse erstellt, die völlig unabhängig von der Engine selbst ist und deren einzige Funktion darin besteht, Tastatureingaben zu erhalten. Auf diese Weise kann ich jeden Spielstatus unabhängig machen KeyboardHandle, auch jeder Spielstatus hat einen Zeiger auf die Engine- DisplayKlasse, das heißt Damit Sie vom Spielstatus aus auf Informationen wie die Fenstergröße zugreifen können oder SDL_Rendererohne große Probleme, fügen Sie Status aus dem hinzu, Enginesodass der DisplayZeiger in diesem Moment gesetzt ist.
Die GameStateKlasse ist die Basis für alle Spiele, jetzt sieht es so aus:
#pragma once
#include "KeyboardHandle.h"
#include "TextureManager.h"
#include "Display.h"
class GameState{
protected:
KeyboardHandle keyboard;
TextureManager texture_manager;
Display * screen_display;
public:
GameState(Display * display){ this->screen_display = display; }
virtual void onStart() = 0;
virtual void handleInput() = 0;
virtual void onUpdate(float) = 0;
virtual void onRender() = 0;
};
Wie Sie sehen können, ist fast alles unabhängig von der Engine, es teilt nur die Anzeige, damit wir die Grafiken rendern können.
Hier ist ein Beispielstatus (Zeichnen eines Bildes in der Mitte des Bildschirms):
#include "GameState.h"
#include "Entity.h"
class DefaultState : public GameState{
Entity splash_logo;
public:
DefaultState(Display * display) : GameState(display){ }
void onStart(){
texture_manager.Load("textures/splash_logo.png", "splash_logo", screen_display->GetRenderer());
splash_logo = Entity("splash_logo", Vector2i(0, 0), Vector2i(336, 336), 1.0f);
float center_x = (screen_display->GetWindowWidth() / 2) - splash_logo.GetCollisionBox().w / 2;
float center_y = (screen_display->GetWindowHeight() / 2) - splash_logo.GetCollisionBox().h / 2;
splash_logo.SetPosition({center_x, center_y});
}
void handleInput(){
}
void onUpdate(float delta_time){
}
void onRender(){
texture_manager.Draw(splash_logo.GetSprite(), splash_logo.GetPosition(), screen_display->GetRenderer());
}
};
Und nur zum Spaß hier ist das Abhängigkeitsdiagramm: