Making Breakout - Code Structure help requested

Started by
77 comments, last by Kylotan 6 years, 7 months ago
I'm following the list of games a begginner should make suggested by Alpha_ProgDes and I'm about to start making Breakout, though this time I'll go the hardcore way with C++ & SDL instead of Unreal Engine.
I'll figure out  how to write the classes and make it work, though I wanted to ask you guys about what I will need to make and how it should be organized, so that I have a clear roadmap that I can breeze trough step by step, without hitting any dead ends :D
So this are my initial thoughts:
●I will need a "Timer" class which keeps track of ms since last frame
●I will need a "Game" class which has a Timer and takes care of initializing the SDL_Window and SDL_Renderer, also with a destructor that takes care to destroy them.
●I will need a "TextureManager" class which containst a map<string, Texture*> to which I can ask for a texture by its name from within other classes, so pretty much every Actor (taking unreal naming for semplicity here) need to include this "TextureManager". Also texture manager destructor takes care of destroying all the textures.
●I need a base Actor class which will contain member variables x,y centered pivot coordinates for the actor sprite, SDL_Rect for the sprite position/size and a Texture* for the sprite, also virtual draw() and virtual update() methods
Well that all I can think for now, I don't have a very clear idea for how the input handling should be done, is just like a super giant switch inside the game loop, or it is like a class "InputManager" that keep track of which actor is interested in which key input and forward the inputs to the approriete actors or... mh... no idea really how it should be done :S
So, whatever feedback or suggestion or "Structure Roadmap" you can present me to put me on the right tracks, is very welcome! :P 
I only need knowledge of a reasonably correct structure and I will fill in the blanks :)

 

Advertisement

My first suggestion is that you don't need to have a list of classes up-front. If we tried to do that for AAA games we'd never start coding.

My other comments:

  • I like to have a separate App class which contains a Game class. Game is for, well, gamey things. I'd create the window and other input/output in the App, as well as an instance of the Game.
  • Actors shouldn't need TextureManagers. There's no part of 'acting' that needs to look up textures. If you want Actors to be responsible for drawing themselves, that's fine (at least at this level), but you should pass in the texture to use in each case.
  • Input handling for a game like this can stay simple. I'd start with a function that is called from the main loop, with the paddle passed in as an argument. Call methods on the paddle to implement movement, based on input state. If you have the separate Game/App system like I do, then the App's input handling can call through to the Game's input handling.

I usually start at the global picture, instead of the small details. What data do you have, what activities must be done (at high level, so "game map", "textures", "character", and "drawing the screen" is more than enough.

Make big building blocks (eg on a single sheet of paper) where those things roughly go. It should give you an overview of the program as a whole, even though your picture may be wrong, but the only way to find out is by trying to build it.

You will hit dead ends, but it's part of the process. Programs are so complicated, it is impossible to get all the details right on the first try. Don't be afraid of it, learn to recognize it happening, and how to recover from it.

Finally, nobody says you must do all coding in one project. If you want to figure out how to do some detail, eg movement with keys or so, and you don't know how, use paper and your grey matter to work it out. If it needs experimenting, make a small side-program for experiments. When done, apply the knowledge in your main project (which may include code of the experiment, but in general rewriting improves code).

A classic issue for a lot of people (myself included) is the tendency to over engineer things. Frankly the best way to learn what you need is to just make something and set small goals, milestones if you will. I used to create these really complicated classes before even knowing what I needed, but often it either got thrown away or simply went unused or the architecture changed somewhat so I had to rewrite it anyway. For example, since you're making a breakout game you might just want to start simple, "well I need a game loop and a window, now I need to draw something onscreen, now I need to make a ball, okay now the ball needs to bounce against the edges of the screen and re-spawn."

The first two things you mentioned are fine, for the texture manager I would question if you even need one. Is the game complex enough that sprites require managing? Honestly in most arcade games that isn't the case, sprites may be used only once and the entire game contains a small enough number of them for you to simply load them all in some function and pass them around. Important questions are things like -when- to load them too, if you can you might as well load them all at the start. Frankly unless you KNOW for a fact that you need something to be complex, it is almost always better to start off simple and build up from there.

Thank you for answering guys :)

Quote

The first two things you mentioned are fine, for the texture manager I would question if you even need one. Is the game complex enough that sprites require managing?

Frankly unless you KNOW for a fact that you need something to be complex, it is almost always better to start off simple and build up from there.

I see that point, though my point would be that all the games in the list of games a begginner should make seems like they don't require a TextureManager as well, so even if I don't need a TextureManager right now or to learn how to make a Singleton, when do I get to learn it then?! :P

I am kind of throwing extra stuff in exactly because the project is simple, so the scale is small and I can learn this concepts in realive comfort/safety.

So from my point of view, I actually value something that over-complicate the project if it is just for the sake of learning a new thing, basically making this game is not really the main goal. So if you guys think I could research and apply an interesting design pattern to this project feel free to suggest, because for me is an opportunity to fiinally learn a new/useful thing :P 

3 minutes ago, MarcusAseth said:

I see that point, though my point would be that all the games in the list of games a begginner should make seems like they don't require a TextureManager as well, so even if I don't need a TextureManager right now or to learn how to make a Singleton, when do I get to learn it then?!

I am kind of throwing extra stuff in exactly because the project is simple, so the scale is small and I can learn this concepts in realive comfort/safety.

So from my point of view, I actually value something that over-complicate the project if it is just for the sake of learning a new thing, basically making this game is not really the main goal. So if you guys think I could research and apply an interesting design pattern to this project feel free to suggest, because for me is an opportunity to fiinally learn a new/useful thing  

Which is fine, I experiment with ideas myself on a regular basis, the problem is it becomes easy for you to start doing that for everything. Particularly when it is that, experimenting, you can quickly get in over your head and make the code a mess of things you aren't sure are good architecture, and dig yourself into a corner that it feels frustrating to get out of.

Sometimes it might be better to experiment on standalone tech demos and things, or to save writing something complex like that until you actually need it. That's a bit of an unspoken rule that people don't often actually explain, software only gets complicated because it needs to. If you could write the next Battlefield game just by making a function that loads all the game data at the start and not bother with subsystems dedicated to managing things, then they probably would.

You might want to consider that the faster you finish a game the faster you get to try another project as well, and possibly explore more concepts or have more challenging rules to write for.

6 minutes ago, MarcusAseth said:

all the games in the list of games a begginner should make seems like they don't require a TextureManager as well, so even if I don't need a TextureManager right now or to learn how to make a Singleton, when do I get to learn it then?!

  1. Maybe you never need a TextureManager....
  2. You CERTAINLY don't need a Singleton.
  3. On that list, Ikari Warriors and Super Mario Bros have sufficient numbers of sprites and graphics to make some sort of image asset management worthwhile.
  4. The point of following a list like this in order is that each one introduces new challenges. As the article says, this time around you'll be learning "Lessons of pong, powerups, maps (brick arrangements)". Those are the things you probably want to focus on - how should they be implemented?

Done some progress, I'm thinking to paste here the code I write during this project in case someone notices and point out some ugly stuff I may end up doing so that I avoid ending up with bad coding habits.

I'll put it into spoiler just to not flood the entire page with a single reply :S

The only problem I am currently having is inside Game.cpp, code below:


#define BACKGROUND "Graphics/Background.png";
#define PADDLE "Graphics/Paddle.png";
#define BALL "Graphics/Ball.png";

Game::Game(SDL_Window* Window, SDL_Renderer* Renderer)
	:Window{ Window }, Renderer{ Renderer }
{
	LoadImage(BACKGROUND);
	LoadImage(PADDLE);
	LoadImage(BALL);
}

LoadImage takes a const char* but those 3 macro are not expanding into that for some reason, since VS is giving me a red squigly line... what argument type should that function take in order to work with my Macros? :S


 

Spoiler

 

main.cpp



#include <iostream>
#include "SDL2\SDL.h"
#include "SDL2\SDL_image.h"
#include "App.h"

int main(int argc, char* argv[])
{
	App GameClient;

	while (GameClient.IsRunning())
	{

	}

	return 0;
}

App.h



#pragma once
#include "SDL2\SDL.h"
#include "Game.h"
class App
{
	bool Running = false;
	Uint32  Width = 1280;
	Uint32  Height = 960;
	Uint32  WinFlags = 0;
	Uint32  RenFlags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC;
	SDL_Window* Window;
	SDL_Renderer* Renderer;
	Game* GameInstance;

	bool Init();
	bool CreateWindow();
	bool CreateRenderer();
public:
	App();
	~App();
	
	App(const App&) = delete;
	App& operator=(const App&) = delete;
	
	App(App&&) = delete;
	App& operator=(App&&) = delete;
	
	////
	
	inline bool IsRunning()const { return Running; }
};

App.cpp



#include "App.h"
#include "Utility.h"


App::App()
	:Window{ nullptr }, Renderer{ nullptr }
{
	Running = Init();
	
	if (Running)
	{GameInstance = new Game(Window, Renderer);}
}


App::~App()
{
	if (Window) { SDL_DestroyWindow; }
	if (Renderer) { SDL_DestroyRenderer; }
	delete GameInstance;
	SDL_Quit();
}

bool App::Init()
{
	if ( 0 != SDL_Init(SDL_INIT_EVERYTHING)){ return Error(SDL_GetError()); }

	return CreateWindow() ? (CreateRenderer() ? true : false) : (false);
}

bool App::CreateWindow()
{
	Window = SDL_CreateWindow("Breakout",
							  SDL_WINDOWPOS_CENTERED,
							  SDL_WINDOWPOS_CENTERED,
							  Width, Height, WinFlags);
	return Window ? true : Error(SDL_GetError());
}

bool App::CreateRenderer()
{
	Renderer = SDL_CreateRenderer(Window, -1, RenFlags);
	return Renderer ? true : Error(SDL_GetError());
}

Game.h



#pragma once
#include <map>
#include <string>
#include "SDL2\SDL.h"

class Game
{
	SDL_Window* Window;
	SDL_Renderer* Renderer;
	std::map<std::string, SDL_Texture*> Textures;

	bool LoadImage(const char* path);
public:
	Game(SDL_Window* Window, SDL_Renderer* Renderer);
	~Game();

	Game(const Game&) = delete;
	Game& operator=(const Game&) = delete;

	Game(Game&&) = delete;
	Game& operator=(Game&&) = delete;

	////

	SDL_Texture* GetTexture(const char* path)const;
};

Game.cpp



#include "Game.h"
#include "Utility.h"
#include "SDL2\SDL_image.h"

#define BACKGROUND "Graphics/Background.png";
#define PADDLE "Graphics/Paddle.png";
#define BALL "Graphics/Ball.png";

Game::Game(SDL_Window* Window, SDL_Renderer* Renderer)
	:Window{ Window }, Renderer{ Renderer }
{
	LoadImage(BACKGROUND);
	LoadImage(PADDLE);
	LoadImage(BALL);
}


Game::~Game()
{
	for (auto& elem : Textures)
	{ SDL_DestroyTexture(elem.second); }
}

bool Game::LoadImage(const char * path)
{
	if (Textures.find(path) != Textures.end())
	{ return  Error("Ettempting to Load an already Loaded Texture."); }

	SDL_Surface* Surface = IMG_Load(path);
	if (Surface)
	{
		Textures[path] = SDL_CreateTextureFromSurface(Renderer, Surface);
		SDL_FreeSurface(Surface);
		return true;
	}
	else
	{ return Error(SDL_GetError()); }
}

SDL_Texture* Game::GetTexture(const char* path)const
{
	auto& Texture = Textures.find(path);
	return (Texture != Textures.end()) ? Texture->second : nullptr;
}

Actor.h



#pragma once
#include "SDL2\SDL.h"
#include "Game.h"

enum class PivotMode: Uint8 {CENTER,TOP_LEFT};

class Actor
{
SDL_Texture* RequestTexture(const char* path)const;
void SetSpriteRect(Uint32 x, Uint32 y, PivotMode InputMode);
protected:
	Uint32 XCenter;
	Uint32 YCenter;
	SDL_Rect SpriteRect;
	SDL_Texture* Sprite;
	Game* GameRef;
public:
	Actor(Game* GameRef, PivotMode InputMode, Uint32 x, Uint32 y, const char* path);
	virtual ~Actor();

	Actor(const Actor&) = delete;
	Actor& operator=(const Actor&) = delete;

	Actor(Actor&&) = delete;
	Actor& operator=(Actor&&) = delete;

	////
};

Actor.cpp



#include "Actor.h"

#define BACKGROUND "Graphics/Background.png";
#define PADDLE "Graphics/Paddle.png";
#define BALL "Graphics/Ball.png";


Actor::Actor(Game* GameRef,PivotMode InputMode, Uint32 x, Uint32 y, const char* path)
	:GameRef{ GameRef }
{
	if (GameRef)
	{Sprite = RequestTexture(path);}

	if (Sprite)
	{SetSpriteRect(x, y, InputMode);}
}


Actor::~Actor()
{
}

SDL_Texture* Actor::RequestTexture(const char* path)const
{
	return GameRef->GetTexture(path);
}

void Actor::SetSpriteRect(Uint32 x, Uint32 y, PivotMode InputMode)
{
	SDL_QueryTexture(Sprite, NULL, NULL, &SpriteRect.w, &SpriteRect.h);
	switch (InputMode)
	{
		case PivotMode::CENTER:
		{
			SpriteRect.x = x - SpriteRect.w / 2;
			SpriteRect.y = y - SpriteRect.h / 2;
			XCenter = x;
			YCenter = y;
		}break;
		case PivotMode::TOP_LEFT:
		{
			SpriteRect.x = x;
			SpriteRect.y = y;
			XCenter = x + SpriteRect.w / 2;
			YCenter = y + SpriteRect.h / 2;
		}break;
	}
}

 


 

 

 

53 minutes ago, MarcusAseth said:

Done some progress, I'm thinking to paste here the code I write during this project in case someone notices and point out some ugly stuff I may end up doing so that I avoid ending up with bad coding habits.

 

Don't use macros for constants. Use "const" or "constexpr" (if your version of C++ has it). 

What is the actual error you're getting when you compile? The "red underlines" produced by Visual Studio are produced by the Intellisense compiler, which is (a) different from the compiler that actually builds your code and (b) often wrong.

I didn't had tried to compile so far (until you asked), otherwise I would have realized that I shouldn't put the ';' at the end of the macro, which was expanding the ';' into LoadImage("Graphics/Background";); :P

Anyway I'll go with constexpr then, thanks :D

 

This topic is closed to new replies.

Advertisement