   I fail at making games!
  At first I was going to post this earlier, but I decided not to post it.  So I am posting it now.Short:  I love programming engines/wrappers.  I cannot wrap my head around a game though.Long:  I love programming in C++.  I am using SDL, so I decided to write a little engine/wrapper with all kinds of different classes (relating to or not to SDL) (image/animation class, video class, renderer class, timer class, etc.).  I LOVE making this thing.  I always read over it and add all kinds of things and always add new classes and functionality.Now, I finished some important classes and I decided to make a little game.  A while ago I had made a "crazy pong" game with multiple balls and power-ups.  I decided to remake it using my new engine with menues and stuff so it would become a normal game.BUT - I just can't wrap my head around it.  I got some sample images going, but now I just can't do it.  I had started many times with many different games and had failed all the time (except for that "crazy pong" hack-up I did).I made 250 of the "Very Hard" balls bounce all around the window flawlessly.  But it just doesn't seem right.  It just seems...  hacked together again.  I just can't seem to grasp it correctly:// enginenamespace crazy_pong {    void engine::on_keydown(SDL_keysym key) {   if (key.sym == SDLK_ESCAPE)  this->request_quit();    }    void engine::on_quit() {   m_quit = true;    }    engine::engine() {   m_quit = false;   m_video.initialize_window(WINDOW_W, WINDOW_H, "!!!Crazy Pong!!!");   m_renderer.resize_viewport(0, 0, WINDOW_W, WINDOW_H);   m_background.load_image("Bliss.bmp");   m_ball.load_image("HardBall.bmp", 255, 0, 255);   //m_ball.set_alpha(128);   for (unsigned i = 0; i < NUM_BALLS; i++) {  m_balls.pos.x = (float)benefit::rand(0, WINDOW_W - m_ball.raw_image()->w);  m_balls.pos.y = (float)benefit::rand(0, WINDOW_H - m_ball.raw_image()->h);  m_balls.vel.x = (float)benefit::rand(-5, 5);  //if (benefit::rand(0, 1) == 1) //m_balls.vel.x *= -1;  m_balls.vel.y = (float)benefit::rand(-5, 5);  //if (benefit::rand(0, 1) == 1) //m_balls.vel.y *= -1;   }    }    engine::~engine() {}    int engine::run(int argc, char *argv[]) {   while (!m_quit) {  this->update();  if (WINDOW_W == 640 && WINDOW_H == 480) m_renderer.clear(m_background);  else m_renderer.clear(255, 255, 255);  for (unsigned i = 0; i < NUM_BALLS; i++) { m_balls.update(); if (m_balls.pos.x < 0.0f || m_balls.pos.x > (float)WINDOW_W - m_ball.raw_image()->w)m_balls.vel.x *= -1; if (m_balls.pos.y < 0.0f || m_balls.pos.y > (float)WINDOW_H - m_ball.raw_image()->h)m_balls.vel.y *= -1; m_renderer.render(m_ball, (int)m_balls.pos.x, (int)m_balls.pos.y);  }  m_renderer.update();   }   return 0;    }}The engine itself looks ok, but then when it comes to the game elements it looks like I just squished it all together, doesn't it?So my actual question is, what should I read/practice?  All I'm looking for is some kind of tips or hints on how to design this game and how to actually aproach making it.It seems as if I jumped into something I cannot fully grasp on how to design and then implement.  I got the ideas going:Spawn random power-ups.Bounce ball and move it.If the ball hits a power-up make some kind of change.But not the code for it.So, all in short, I'm looking for some kind of designing/implementing tips.  (preferebly online or by you, without purchase - but if it is absolutely needed then I guess I can shell out some money).Thank you for reading this and helping!   
Quote:The engine itself looks ok, but then when it comes to the game elements it looks like I just squished it all together, doesn't it?No, it looks just fine.If it works, it works, and you should just keep going with it. Honestly, the only way your going to learn how to design your game code is by actually jumping in and doing it. Eventually, you'll figure out how to best design the code.And if you think your code is sloppy, you should see the code for my old Breakout clone. Your eyes will bleed and melt, at the same time.   ;Quote:Original post by Stompy9999Quote:The engine itself looks ok, but then when it comes to the game elements it looks like I just squished it all together, doesn't it?No, it looks just fine.If it works, it works, and you should just keep going with it. Honestly, the only way your going to learn how to design your game code is by actually jumping in and doing it. Eventually, you'll figure out how to best design the code.And if you think your code is sloppy, you should see the code for my old Breakout clone. Your eyes will bleed and melt, at the same time.If you see my current code your eyes will bleed and melt and splode.   ;  I would have some more error checking, especially for loading images. Maybe add some more comments (//engine doesn't count as a comment [grin]). I'm not a big fan of the m_ stuff.The only other thing I would say is the engine might be taking on too much work that the ball class should be doing (like initializing position).   ;  First, get it to work any way you can. Then, if you still have the patience for it, throw it all out and start over from scratch. This is a luxury you have when you're coding just for yourself. You'll have more fun than you'd think, and you'll learn a lot about design.   ;  This thread is a danger to eyes everywhere.;  As has been said before, when you are beginning game programming, the most important thing is getting it working. It has also been said before, but since you are the only one programming it, don't worry about the design.At least your trying out an OO design. I did pure C code for my first few games. Slap a few functions together, and you have yourself a game[lol]:// Breakoutz arcade game// Programmed by Joseph Cortes#include <allegro.h>#define BLACK makecol(0, 0, 0)#define WHITE makecol(255, 255, 255)#define RED makecol(200, 0, 0)#define GREEN makecol(0, 200, 0)#define BLUE makecol(0, 0, 200)#define TITLE 1#define GAME 2#define END 3#define ROWS 5#define COLS 16struct{   int state;   int gamerunning;   int score;   int lives;   int nscore, oscore;   } gamedata;struct{   int x1, x2;   int y1, y2;   } player;struct{   int alive;   int x, y;   int vx, vy;   } ball;struct{    int alive;    int x1, y1, x2, y2;    int color;    } brick[ROWS][COLS];BITMAP *buffer;SAMPLE *bump, *clap, *lose, *OneUp, *explode;int i, c;const int volume = 100, pan = 128, pitch = 1000;void GameInit(void){    allegro_init();    install_keyboard();    install_timer();    srand(time(NULL));    set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0);    install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, "");    text_mode(-1);   buffer = create_bitmap(640, 480);    clear(buffer);   bump = load_sample("beep1.wav");    clap = load_sample("applause.wav");    lose = load_sample("glass_break.wav");    OneUp = load_sample("mm_1up_sfx.wav");    explode = load_sample("explosion.wav");  gamedata.gamerunning = 1;    gamedata.state = TITLE;    gamedata.score = 0;    gamedata.lives = 10;    gamedata.oscore = 0;   player.x1 = 280;    player.x2 = 320;    player.y1 = 440;    player.y2 = 450;   ball.alive = 0;   for(i = 0; i < ROWS; i++)    {  for(c = 0; c < COLS; c++)   {brick[c].alive = 1;  brick[c].x1 = c * 40;  brick[c].y1 = i * 20;  brick[c].x2 = brick[c].x1 + 40;  brick[c].y2 = brick[c].y1 + 20;}  }   for(i = 0; i < COLS; i++)    {  brick[0].color = RED;   brick[1].color = GREEN;   brick[2].color = BLUE;   brick[3].color = GREEN;   brick[4].color = RED;  }  }int inside(int x, int y, int left, int right, int top, int bottom){    if(x >= left && x <= right && y >= top && y <= bottom)  return 1; return 0;    }void HandleGame(void){   int still_alive = 0;    if(!ball.alive)    { ball.x = player.x1 + 20;  ball.y = 435; }    else if(ball.alive)    { ball.x += ball.vx;  ball.y += ball.vy;    for(i = 0; i < ROWS; i++)  {   for(c = 0; c < COLS; c++) {if(brick[c].alive){   if(inside(ball.x, ball.y, brick[c].x1, brick[c].x2, brick[c].y1, brick[c].y2))   { ball.vy = -ball.vy; brick[c].alive = 0;  gamedata.score += 50 - (i * 10);  play_sample(bump, volume, pan, pitch, FALSE);    }   } }   }    if(inside(ball.x, ball.y, player.x1, player.x2, player.y1, player.y2))  {  if(ball.x >= player.x1 && ball.x <= player.x1 + 10) {   ball.vx = -5;    ball.vy = -2;   } else if(ball.x >= player.x1 + 10 && ball.x <= player.x1 + 20) {   ball.vx = -2;    ball.vy = -4;   } else if(ball.x >= player.x1 + 20 && ball.x <= player.x1 + 30) {   ball.vx = 2;    ball.vy = -4;   } else if(ball.x >= player.x1 + 30 && ball.x <= player.x1 + 40) {   ball.vx = 5;    ball.vy = -2;   }  play_sample(bump, volume, pan, pitch, FALSE); }  if(ball.x < 0 || ball.x > 640)  {ball.vx = -ball.vx;play_sample(bump, volume, pan, pitch, FALSE);  }   if(ball.y < 0)  {ball.vy = -ball.vy;   play_sample(bump, volume, pan, pitch, FALSE);  }  if(ball.y > 480)  {ball.alive = 0;gamedata.lives--;    play_sample(lose, volume, pan, pitch, FALSE);  }   }   for(i = 0; i < ROWS; i++)    {  for(c = 0; c < COLS; c++)   {if(brick[c].alive)  {  still_alive = 1;break;  }} if(still_alive) break;}  if(!still_alive)    { for(i = 0; i < ROWS; i++) for(c = 0; c < COLS; c++)brick[c].alive = 1;  ball.alive = 0;    play_sample(clap, volume, pan, pitch, FALSE); }   if(gamedata.lives <= 0)  gamedata.state = END; gamedata.nscore = gamedata.score;   if(gamedata.nscore >= gamedata.oscore + 1000)    { gamedata.lives++;  gamedata.oscore = gamedata.nscore;  play_sample(OneUp, volume, pan, pitch, FALSE); }   }void DrawGame(void){    clear(buffer);    switch(gamedata.state)    {  case TITLE:textout_centre(buffer, font, "B R E A K O U T Z", 320, 100, WHITE);  textout_centre(buffer, font, "Press Enter To Play", 320, 300, WHITE);  textout_centre(buffer, font, "A Stompy Studios Game", 320, 400, WHITE);    break;case GAME:rectfill(buffer, player.x1, player.y1, player.x2, player.y2, WHITE);    for(i = 0; i < ROWS; i++)  {   for(c = 0; c < COLS; c++) { if(brick[c].alive)   rectfill(buffer, brick[c].x1, brick[c].y1, brick[c].x2, brick[c].y2, brick[c].color); }   }    circlefill(buffer, ball.x, ball.y, 5, WHITE);    textprintf(buffer, font, 1, 1, WHITE, "Score: %d", gamedata.score);  textprintf(buffer, font, 1, 10, WHITE, "Lives: %d", gamedata.lives);    break;case END:textout_centre(buffer, font, "G A M E  O V E R", 320, 100, WHITE);  textprintf_centre(buffer, font, 320, 240, WHITE, "Your Score: %d", gamedata.score);  textout_centre(buffer, font, "Programmed by Joseph Cortes", 320, 300, WHITE);  textout_centre(buffer, font, "Play more games at: www.freewebs.com/stompystudios", 320, 400, WHITE);    break;    }   acquire_screen();    blit(buffer, screen, 0, 0, 0, 0, 640, 480);    release_screen();    }void Reset(void){    gamedata.state = TITLE;gamedata.lives = 10;    gamedata.score = 0;    gamedata.nscore = 0;    gamedata.oscore = 0;   player.x1 = 280;    player.x2 = 320;   ball.alive = 0;   for(i = 0; i < ROWS; i++)   for(c = 0; c < COLS; c++)  brick[c].alive = 1; rest(2000);  }void HandleInput(void){    switch(gamedata.state){    case TITLE:  if(key[KEY_ESC]) gamedata.gamerunning = 0;    if(key[KEY_ENTER]) gamedata.state = GAME;    break;  case GAME:  if(key[KEY_ESC]) gamedata.gamerunning = 0;    if(key[KEY_LEFT])   {    player.x1 -= 5;  if(player.x1 < 0)    player.x1 = 0;player.x2 = player.x1 + 40;    } if(key[KEY_RIGHT])   {    player.x1 += 5;  if(player.x1 + 40 > 640)    player.x1 = 600;player.x2 = player.x1 + 40;    } if(key[KEY_SPACE])   {    if(!ball.alive) {ball.alive = 1;    ball.vx = 0;    ball.vy = -5;}    }  break;  case END:  if(key[KEY_ESC]) gamedata.gamerunning = 0;    if(key[KEY_ENTER]) Reset();    break;   }}void GameMain(void){    HandleInput();   if(gamedata.state == GAME)  HandleGame(); DrawGame();   rest(10);    }int main(){    GameInit();   while(gamedata.gamerunning)GameMain();    destroy_sample(bump);    destroy_sample(clap);    destroy_sample(lose);    destroy_sample(OneUp);  destroy_bitmap(buffer);    allegro_exit();   return 0;    }END_OF_MAIN();After programming like that for several games, I started to learn how to correctly design my games.   ;  Stompy has a point.  The best way of writing an engine is to first write the game that would use an engine.  If you are finding the engine hard to use to write your game, then the engine isn't doing it's job of making your life easier.My eyes were opened relatively recently to the powers of test driven development, and even if you don't do that certainly client driven development is pretty cool.   Basically you write your client code (pong game) calling in to the engine how you would like it to work, to the point of calling a function that does not exist yet.  Comment and define the function contract.  Get a skeleton of that functions in there, get it compiling, then get the function working as you would expect from the contract.  Lather rince and repeat.   ;  The code is not that bad, but you're right it could be a lot better. The problem is that OOP is not easy: it's not enough to just put the code and the data inside classes. When you design, you must think: What are the basic types that I need to implement? What data and method do they need to encapsulate so they are really working,indepedent modules?For example:>>m_ball.load_image("HardBall.bmp", 255, 0, 255);Does a ball really need to have a LoadTexture(...) method to load a texture from a file? Wouldn't it be better to have an indepedent texture class that loads from a file, and the ball just hold a pointer or reference to an existing texture? Here is an example of a more correct approach. It's not perfect either, a lot of things can be improved(like using smart pointers instead of raw ones, or making even heavier use of polymorphism to avoid using enums), but you will get the idea. The basic types that are needed to implement what you want are:A vector class, just to make math easier:class Vec2{public:float m_x,m_y;Vec2(float x,float y){m_x=x;m_y=y;}Vec2(){m_x=0;m_y=0;}Vec2 operator+(Vec2 &v){return Vec2(v.m_x+m_x,v.m_y+m_y);}Vec2 operator*(float s){return Vec2(m_x*s,m_y*s);}};Vec2 GetRandomVector(Vec2 min,Vec2 max){Vec2 result;result.m_x=benefit::rand(min.m_x,max.m_x);result.m_y=benefit::rand(min.m_y,max_m.y)return result;}A texture class that can load from files.class Texture{public:void LoadFromFile(std::string name){//Loading here...}};Now that we're done with "helper" classes, on to the basic game classes. What exactly do we want? A 2D level of certain size. We can add entities to that level. Right now, there are 2 types: balls and powerups.We can add balls to the level, that move and bounce on its edges.We can spawn different "types" of powerups, picking them from a list of available powerups for that level.The balls collide with the powerups, and when they do, a certain action is performed on them.So, first we implement an abstract Entity class. The entities have a certain position,velocity,size, a texture, and they can belong to a level. Also, they must be able to update and draw themselves.class Level;//forward declerationclass Entity{public:Vec2 m_size;Vec2 m_position;Vec2 m_velocity;Texture* m_texture;Level *m_level;Entity(Vec2 position,Vec2 velocity,Texture* texture){m_position=position;m_velocity=velocity;m_texture=texture;m_level=NULL;//not belonging to a level yet}void SetLevel(Level *level){m_level=level;}virtual void Update(float dt)=0;virtual void Draw()=0;};Now, to the powerup object. It inherits from Entity of course, with the addition of a PowerupAction enum that defines what "kind" of powerup it is. Of course, now we actually implement the Update(if actually needed) and Draw() methods.enum PowerupAction{Increase_Health,Increase_Points};class Powerup:public Entity{public:PowerupAction m_action;Powerup(Vec2 position,Vec2 velocity,Texture* texture,PowerupAction action):Entity(position,velocity,texture){m_action=action;}virtual void Update(float dt){}virtual void Draw(){//Draw the powerup sprite...}};The Ball object. We add some special properties to it, like "health" or "points" that can increase when it gets a powerup. We also implement Update() and Draw() so that the ball moves according to its speed and draws itself. We also need 2 other methods: -DoCollisions(), which performs collision test between the ball and the level it belongs to. Also, checks collisions between the ball and all the powerups in that level. If a collision to a powerup is detected we call the...-...ApplyPowerup() method, which checks the "kind" of the powerup, and changes the properties of the ball accordingly.class Ball:public Entity{public:float m_health;int m_points;Ball(Vec2 position,Vec2 velocity,Texture* texture):Entity(position,velocity,texture){m_health=100.0;m_points=0;}void DoCollisions();void ApplyPowerup(PowerupAction action){switch (action){case Increase_Health:m_health=m_health+5.0;break;case Increase_Points:m_points=m_points+100;break;}}virtual void Update(float dt){m_position=m_position+m_velocity*dt;//Move the ballDoCollisions();}virtual void Draw(){//Draw the ball sprite here using m_texture...}};//In the actual program, this has to be implemented after the Level class is definedvoid Ball::DoCollisions(){//Check against level boundariesif (m_position.m_x<0){m_position.m_x=0;m_velocity.m_x=-m_velocity.m_x;}if (m_position.m_x>m_level->m_size.m_x){m_position.m_x=m_level->m_size.m_x;m_velocity.m_x=-m_velocity.m_x;}if (m_position.m_y<0){m_position.m_y=0;m_velocity.m_y=-m_velocity.m_y;}if (m_position.m_y>m_level->m_size.m_y){m_position.m_y=m_level->m_size.m_y;m_velocity.m_y=-m_velocity.m_y;}//Check against powerupsfor (int i=0;i<m_level->powerups.size();++i){if (true)//implement collision with powerup rectangle here...{ApplyPowerup(m_level->powerups.m_action);}}}Finally, the level class. This is the class which we will really interact the most when writing the game. It "has" balls and powerups, and of course also the ability update and draw itself. All we have to do is add entities to it, and we expect from it to "know" how to update and draw them. It has:-A certain size. Not necessary the same size of the window. These two(level size and window size) are indepedent, the programmer may or may not decide that they should be equal.-A balls std::vector, which contains all the balls in the level.-An availabe_powerups vector. This contains a kind of powerup "templates". These are not actual powerup objects that are visible to the player, but they are used as templates to generate an actual powerup.-A powerups vector. These are the actual powerups.-Methods to add a ball, an availabe powerup and a powerup to the level.-An update() method. First, it updates all entities(balls and powerups). Also, every 5 seconds it spawns a new powerup, using a random available powerup as a "template".-A Draw() method that draws all entities.class Level{public:Vec2 m_size;std::vector<Ball> balls;std::vector<Powerup> available_powerups;std::vector<Powerup> powerups;float m_time;Level(Vec2 size){m_size=size;m_time=0;}void AddBall(Ball ball){ball.SetLevel(this);balls.push_back(ball);}void AddAvailablePowerup(Powerup &powerup){available_powerups.push_back(powerup);}void AddPowerup(Powerup powerup){powerup.SetLevel(this);powerups.push_back(powerup);}void Update(float dt){m_time=m_time+dt;for (int i=0;i<balls.size();++i) balls.Update(dt);//Update all ballsfor (int i=0;i<powerups.size();++i) powerups.Update(dt);//Update all powerups//Spawn new powerupif (m_time>5.0){Powerup power=available_powerups[benefit::random(0,available_powerups.size()-1];power.m_position=GetRandomVector(Vec2(0,0),m_size);AddPowerup( power );m_time=0.0;}}void Draw(){for (int i=0;i<balls.size();++i) balls.Draw();//Draw all ballsfor (int i=0;i<powerups.size();++i) powerups.Draw();//Draw all powerups}};That's it. Setting up a level now and firing it up is really easy. If you want to add a new type of entity later, that's easy too: You derive a class from Entity, define a new behaviour by implementing Update() and Draw(), and also add a vector to the level class that holds the objects of that new class. I won't write code for creating a window and handling its message loop, but the rest would be like:Level level(Vec2(640,480));//Create a level of 640x480Texture powerup_points_tex,powerup_health_tex,ball_tex;powerup_points_tex.LoadFromFile("Powerup_Points.bmp");powerup_health_tex.LoadFromFile("Powerup_Health.bmp");ball_tex.LoadFromFile("Ball.bmp");for (int i=0;i<250;++i)//Add 250 balls{Vec2 vel=GetRandomVector(Vec2(-5,-5),Vec2(5,5));Vec2 pos=GetRandomVector(Vec2(0,0),level.m_size);level.AddBall( Ball(pos,vel,&ball_tex) );}level.AddAvailablePowerup( Powerup(Vec2(0,0),Vec2(0,0),&powerup_health_tex,Increase_Health) );level.AddAvailablePowerup( Powerup(Vec2(0,0),Vec2(0,0),&powerup_points_tex,Increase_Points) );while (true)//Game loop{level.Update(0.01);//Update the whole levellevel.Draw();//Draw the whole level}