Framework di gioco 2D in C ++ e SDL

Oct 14 2020

Sto realizzando per la prima volta un semplice framework / motore 2D basato su due libri e prima che il progetto diventi troppo grande vorrei sapere se ordino le cose nel modo giusto. Forse ci sono modi migliori per farlo. Ho provato ad applicare modelli in modo da poter disaccoppiare il più possibile e mantenere le cose astratte.

Ad esempio questa è la Gameclasse:

#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);
}

Oppure la classe Drawer (su cui ho qualche dubbio, il metodo DrawEntity è statico)

#include "Drawer.h"

void Drawer::DrawEntity(Entity& entity, TextureManager* texture_manager, SDL_Renderer * renderer){
      texture_manager->Draw(entity.GetSprite(), entity.GetPosition(), renderer);
}

Agisce come un mediatore tra TexureManager (che disegna le textures) ed Entity che ha tutte le informazioni come posizione, scala, ecc.

Infine la classe di input di esempio

#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;
}

Caricherò un diagramma delle dipendenze e il repository GitHub:

Github Repo con tutto il codice

https://github.com/grazianobolla/monke-engine

Risposte

GrazianoBolla Oct 15 2020 at 06:01

Aggiornare:

Bene, se qualcuno vuole sapere, ho apportato alcune modifiche per sempre, uno dei problemi principali che ho avuto è stato che non potevo davvero "connettere" GameState al motore principale stesso senza passare un puntatore al motore (che è disordinato) o singoli puntatori al InputHandler, Displayecc

Il fatto era che, ad esempio, InputHandler gestiva gli eventi Window e l'input da tastiera, quindi affinché un GameState avesse accesso ad esso, avrei dovuto creare un puntatore a Game :: InputHandler, quindi ho fatto alcune cose:

Per prima cosa ho creato una KeyboardHandleclasse che è totalmente indipendente dal motore stesso e la sua unica funzione è quella di ottenere l'input da tastiera, in questo modo posso rendere ogni stato del gioco indipendente KeyboardHandle, inoltre ogni stato del gioco ha un puntatore alla Displayclasse del motore , il che significa che dallo stato del gioco puoi accedere a informazioni come la dimensione di Windows o SDL_Renderersenza troppi problemi, aggiungi stati da in Enginemodo che il Displaypuntatore sia impostato in quel momento.

La GameStateclasse è la base per tutti i giochi, ora sembra così:

#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;
};

Come puoi vedere è quasi tutto indipendente da Engine, condivide solo il display in modo da poter rendere la grafica.

Ecco uno stato di esempio (disegnare un'immagine al centro dello schermo):

#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());
      }
};

E solo per divertimento ecco il diagramma delle dipendenze: