2
\$\begingroup\$

I did a big mistake to create procedural C++ breakout game on start and now im having trouble converting it into OOP.

As far as I can see I need classes: Loptica (eng. Ball), Splav (eng. Paddle), Blok (eng. Block), Logika (eng. Logic), Kolizija (eng, Collision), Level (eng. Level)

Definition:

#include "SDL.h"
#include "SDL_image.h"
#include <ctime>
#include <stdlib.h>
#include <iostream>
#include <Windows.h>
#include <utility>
#include <string>
#include <vector>
#include <algorithm>

#undef main
using namespace std;

SDL_Window *ekran;
SDL_Renderer * renderer = NULL;

SDL_Texture * pozadina = NULL; //background
SDL_Texture * splav = NULL; //paddle
SDL_Texture * loptica = NULL; //ball
SDL_Texture * block01 = NULL; //1st row of blocks
SDL_Texture * block02 = NULL; //2nd row of blocks
SDL_Texture * block03 = NULL; //3rd row of blocks
SDL_Texture * blockNeprobojni = NULL; //impenetrable block 

SDL_Rect rectPozadina; //background rect
SDL_Rect rectSplav; //paddle rect
SDL_Rect rectLoptica; //ball rect
SDL_Rect rectBlock01; //block01 rect
SDL_Rect rectBlock02; //block02 rect
SDL_Rect rectBlock03; //block03 rect
SDL_Rect rectBlockNeprobojni; //impenetrable  rect

SDL_Event problem;


const int sirinaEkrana = 1280, visinaEkrana = 720; 
//sirinaEkrana= screenWidth, visinaEkrana=screenHeight
int brojZivota = 3; //number of lives
int brojBlokova = 36; //total amount of blocks on screen
vector< pair<pair<int, int>, int > > koordinateBlokovaVektor; //array for storing coordinates and impact count on every block
bool igra = true; //bool value for while loop in int main()
int gornjiOdstoj = 20; //space between top wall and 1st row of blocks
int razmakRedova = 5; //space between blocks in a row
int razmakStupaca = 5; //space between blocks in column
int sirinaBloka = 102; //width of the block
int visinaBloka = 20; //height of the block


//sirinaSplavi = paddleWidth, visinaSplavi=paddleHeight
const int sirinaSplavi = 200, visinaSplavi = 10;
//splavPocetniX=starting point X of paddle, splavPocetniY=starting point Y of paddle
const int splavPocetniX = sirinaEkrana / 2 - 100;
const int splavPocetniY = visinaEkrana - visinaSplavi;

//sirinaLoptice= ballWidth, visinaLoptice=ballHeight
const int sirinaLoptice = 20, visinaLoptice = 20;
//lopticaPocetniX = starting point X of the ball, lopticaPocetniY = starting point Y of the ball, 
const int lopticaPocetniX = sirinaEkrana / 2;
const int lopticaPocetniY = visinaEkrana / 2;
//lopticaTrenutniX =current X point of the ball, lopticaTrenutniY =current Y point of the ball
int lopticaTrenutniX = 0, lopticaTrenutniY = 0; 

//level
int level = 1;
int brojRedovaBlokova = 3; //number of block rows
int brojBlokovaRed = 12; //number of blocks in a row

Main function:

int main(int argc, char*args[]) {

    loadGame();

    while (igra)
    {
        SDL_PollEvent(&problem);

        if (problem.type == SDL_QUIT)
            igra = false;

        Logika();
        Crtaj();

    }

    Quit();

    return 0;
}

Quit function:

void Quit() {

    SDL_Quit();
}

Loading a game:

void loadGame() {

    SDL_Init(SDL_INIT_EVERYTHING);
    ekran = SDL_CreateWindow("Breakout", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, sirinaEkrana, visinaEkrana, SDL_WINDOW_SHOWN);
    renderer = SDL_CreateRenderer(ekran, -1, 0);

    pozadina = IMG_LoadTexture(renderer, "Slike/pozadina1.png");
    loptica = IMG_LoadTexture(renderer,"loptica.bmp");
    splav = IMG_LoadTexture(renderer, "splav.bmp");
    block01 = IMG_LoadTexture(renderer, "block01.bmp");
    block02 = IMG_LoadTexture(renderer, "block02.bmp");
    block03 = IMG_LoadTexture(renderer, "block03.bmp");
    blockNeprobojni = IMG_LoadTexture(renderer, "blockNeprobojni.bmp");

    rectLoptica.x = lopticaPocetniX; rectLoptica.y =lopticaPocetniY; rectLoptica.w = sirinaLoptice; rectLoptica.h = visinaLoptice;
    rectSplav.x = splavPocetniX; rectSplav.y = splavPocetniY; rectSplav.w = sirinaSplavi; rectSplav.h = visinaSplavi;
    rectPozadina.x = 0; rectPozadina.y = 0; rectPozadina.w = sirinaEkrana; rectPozadina.h = visinaEkrana;

    Crtaj();

    srand(time(NULL));

    randomPocetniSmjer();
}

Game logic:

void Logika() {

#pragma region Paddle movement
    const Uint8 *tipka = SDL_GetKeyboardState(NULL);

    if (tipka[SDL_SCANCODE_A] && rectSplav.x > 0)
        rectSplav.x--;

    if (tipka[SDL_SCANCODE_D] && rectSplav.x < 1080) //1280-200(paddle width)
        rectSplav.x++;
#pragma endregion

#pragma region Ball movement

    rectLoptica.x += lopticaTrenutniX;
    rectLoptica.y += lopticaTrenutniY;
#pragma endregion

    //UPPER WALL
    if (rectLoptica.y < visinaLoptice) {

        lopticaTrenutniY = -lopticaTrenutniY;           
    }

    //DOWN WALL, RESET
    if (rectLoptica.y > visinaEkrana) {
        Sleep(2000); 
        randomPocetniSmjer();

        brojZivota--;
        if (brojZivota == 0) {
            igra = false;

        }

    }

    //LEFT WALL
    if (rectLoptica.x < sirinaLoptice) {

        lopticaTrenutniX = -lopticaTrenutniX;   
    }

    //RIGHT WALL
    if (rectLoptica.x > sirinaEkrana - sirinaLoptice) {

        lopticaTrenutniX = -lopticaTrenutniX;           
    }

    //checking collision of paddle and ball
    if (provjeraKolizijeSplavi(rectSplav, rectLoptica) == true) {

        lopticaTrenutniY = -lopticaTrenutniY; 
    }


    //checking collision of ball and block
    int velicinaPolja = koordinateBlokovaVektor.size();

    for(int i=0;i<velicinaPolja;i++) {

        if (provjeraKolizijeBloka(koordinateBlokovaVektor[i].first.first, koordinateBlokovaVektor[i].first.second,koordinateBlokovaVektor[i].second, rectLoptica) == true) {
            lopticaTrenutniY = -lopticaTrenutniY;
            koordinateBlokovaVektor[i].first.first = -1; 
            koordinateBlokovaVektor[i].first.second = -1;
            koordinateBlokovaVektor[i].second--; 
            break;
        }
    }

    Sleep(2);
}

Drawing function (doesnt work as it should with deleting blocks):

void Crtaj() {

    SDL_RenderClear(renderer);

    SDL_RenderCopy(renderer, pozadina, NULL, &rectPozadina);
    SDL_RenderCopy(renderer, splav, NULL, &rectSplav);
    SDL_RenderCopy(renderer, loptica, NULL, &rectLoptica);

    SDL_SetRenderTarget(renderer, pozadina);
    SDL_SetRenderTarget(renderer, loptica);
    SDL_SetRenderTarget(renderer, splav);

    int prviRedBrojac = 0, drugiRedBrojac = 11, treciRedBrojac = 23;
#pragma region LVL 1 
    for (int j = 0; j < brojBlokovaRed; j++) {

        int block1X = j*sirinaBloka + j*razmakRedova, block1Y = gornjiOdstoj;
        int block2X = j*sirinaBloka + j*razmakRedova, block2Y = gornjiOdstoj + visinaBloka + razmakStupaca;
        int block3X = j*sirinaBloka + j*razmakRedova, block3Y = gornjiOdstoj + 2 * visinaBloka + 2 * razmakStupaca;

        spremiKoordinate(block1X, block1Y,1);

        if (koordinateBlokovaVektor[j].first.first != -1 && koordinateBlokovaVektor[j].first.second != -1 && koordinateBlokovaVektor[j].second != 0) {
            rectBlock01 = initRectBlock(block1X, block1Y, sirinaBloka, visinaBloka);
            SDL_RenderCopy(renderer, block01, NULL, &rectBlock01); 
            SDL_SetRenderTarget(renderer, block01);
            prviRedBrojac++;
        }
        else {
            SDL_SetRenderTarget(renderer, NULL);
        }

        if ((j + 1) % 3 == 0) { //if its a impenetrable block 

            spremiKoordinate(block2X, block2Y,-1); //store -1 if its imprenetrable

            if (koordinateBlokovaVektor[j+1].first.first != -1 && koordinateBlokovaVektor[j+1].first.second != -1 && koordinateBlokovaVektor[j+1].second != 0) {
                rectBlock02 = initRectBlock(block2X, block2Y, sirinaBloka, visinaBloka);
                SDL_RenderCopy(renderer, blockNeprobojni, NULL, &rectBlock02); 
                SDL_SetRenderTarget(renderer, blockNeprobojni);
                drugiRedBrojac++;
            }
            else {
                SDL_SetRenderTarget(renderer, NULL);
            }

        }
        else {

            spremiKoordinate(block2X, block2Y,2);

            if (koordinateBlokovaVektor[j+1].first.first != -1 && koordinateBlokovaVektor[j+1].first.second != -1 && koordinateBlokovaVektor[j+1].second != 0) {
                rectBlock02 = initRectBlock(block2X, block2Y, sirinaBloka, visinaBloka);
                SDL_RenderCopy(renderer, block02, NULL, &rectBlock02); 
                SDL_SetRenderTarget(renderer, block02);
                drugiRedBrojac++;
            }
            else {
                SDL_SetRenderTarget(renderer, NULL);
            }
        }

        spremiKoordinate(block3X, block3Y, 1);
        if (koordinateBlokovaVektor[j+2].first.first != -1 && koordinateBlokovaVektor[j+2].first.second != -1 && koordinateBlokovaVektor[j+2].second != 0) {
            rectBlock03 = initRectBlock(block3X, block3Y, sirinaBloka, visinaBloka);
            SDL_RenderCopy(renderer, block01, NULL, &rectBlock03); 
            SDL_SetRenderTarget(renderer, block01);
        }
        else {
            SDL_SetRenderTarget(renderer, NULL);
        }


    }

#pragma endregion

    SDL_SetRenderTarget(renderer, NULL);
    SDL_RenderPresent(renderer);    

}

Storing coordinates in array:

void spremiKoordinate(int x, int y,int brojUdaraca) { 

    if (koordinateBlokovaVektor.size() < 36 ) {
        koordinateBlokovaVektor.push_back(make_pair(make_pair(x,y), brojUdaraca));
        sort(koordinateBlokovaVektor.begin(), koordinateBlokovaVektor.end());
    }
}

Initialize SDL_Rect:

SDL_Rect initRectBlock(int x, int y, int sirina, int visina) { 

    SDL_Rect blok;
    blok.x = x;
    blok.y = y;
    blok.w = sirina;
    blok.h = visina;            

    return blok;
}

Define starting direction of the ball:

void randomPocetniSmjer() { 

    //reset ball
    rectLoptica.x = lopticaPocetniX;
    rectLoptica.y = lopticaPocetniY;

    //reset paddle
    rectSplav.x = splavPocetniX;
    rectSplav.y = splavPocetniY;


    int smjerKretanja = (rand() % 2 + 1);
    int kutGibanjaX1 = -1;
    int kutGibanjaY1 = 1;

    switch (smjerKretanja)
    {
    case 1:
        lopticaTrenutniX = -kutGibanjaX1;
        lopticaTrenutniY = -kutGibanjaY1;
        break;

    case 2:
        lopticaTrenutniX = kutGibanjaX1;
        lopticaTrenutniY = -kutGibanjaY1;
        break;

    default:
        break;
    }

}

Next in code is collision:

bool tocnaUnutarSplavi(int objektX, int objektY, SDL_Rect loptica) {

    if ((objektX + sirinaSplavi >= loptica.x && objektY >= loptica.y) && (objektX <= loptica.x + visinaLoptice && objektY <= loptica.y + visinaLoptice)) {

        return true;
    }   

    else
        return false;

}
bool tocnaUnutarBloka(int objektX, int objektY, SDL_Rect loptica) {

    if ((objektX + sirinaBloka >= loptica.x && objektY >= loptica.y) && (objektX <= loptica.x + sirinaLoptice && objektY <= loptica.y + visinaLoptice)) {
        return true;
    }
    else
        return false;

}


bool provjeraKolizijeSplavi(SDL_Rect splav, SDL_Rect r2) {

    //when ball hits paddle for direction DOWNLEFT
    if (tocnaUnutarSplavi(splav.x, splav.y, r2) == true) {
        cout << "KOLIZIJA: Splav[" << rectSplav.x << "," << rectSplav.y << "]" << endl;
        return true;
    }

    //when ball hits paddle for direction DOWNRIGHT
    else if (tocnaUnutarSplavi(splav.x, splav.y + splav.h, r2) == true) {
        return true;
    }

    else
        return false;
}

bool provjeraKolizijeBloka(int x, int y, int brojUdaraca, SDL_Rect r2) {

    //when ball hits block from direction UPLEFT
    if (tocnaUnutarBloka(x, y, r2) == true) {
        return true;
    }

    //when ball hits block from direction UPRIGHT
    else if (tocnaUnutarBloka(x, y + visinaBloka, r2) == true) {
        return true;
    }

    else
        return false;
}

If someone could give me some direction to start with because im just confused how to even convert it into OOP .

\$\endgroup\$
  • \$\begingroup\$ I don't know if it suits your needs but I made this breakout clone using SDL in the past It's from my noob days into programming, so it's mostly shitty, so take it as it is. Maybe it will guide you in the direction you seek. \$\endgroup\$ – RandomOne Oct 10 '18 at 19:39
2
\$\begingroup\$

This is really not that bad. Despite the variable names not being in English, I'm able to get the gist of it. Here are some useful things that I think would improve the situation.

Avoid Globals

Your first header is mainly a list of global variables. Globals are difficult to work with because they can be changed by any code anywhere in your app. So when a problem occurs with a global, good luck tracking down where it occurs since it could be anywhere. If you put your global data into classes, you'll have minimized the number of places where it could be going wrong.

Let's start with the textures you use. Textures make up a large part of most video games. It would probably make sense to have an object which handles textures. It could load them from disk, give them to code to use, cache recently used textures, etc. I would probably make either a TextureFactory class, or a TextureCache class to handle these sorts of tasks. It might not be necessary here since you only have about 5-10 textures, but any more than that and you'll probably want something to organize them.

Use struct or class to Combine Data

Notice how after the texture declarations, you have corresponding declarations of rectangles. This is a clue that you should be creating either a struct or class that holds both the texture and its rectangle.

Next you declare a bunch of variables for things like the number of lives, current level, the number of blocks on screen, spacing of various elements. These fall into 2 broad categories - game state, and rendering constants. I would make 2 classes for these: GameState which holds things like the number of lives left and the current level, and GameRenderer which holds data related to rendering. I notice there's an SDL_Renderer, so you should probably also put that into your GameRenderer class.

You make a lot of use of std::pair for storing coordinates and impact counts. std::pair is a difficult class to deal with because it's so non-specific. Its data members, first and second are meaningless. You should make a Point or Vector class to hold coordinates. You should then make a Block struct or class to hold a Point or Vector and an impact count. Then your koordinateBlockovaVektor vector would be declared as just:

std::vector<Block> koordinateBlokovaVektor;

Naming

I don't speak your language, but I assume Logika is Logic and Crtaj is Draw? If so, I would recommend using more descriptive names, like UpdateGameState() and RenderGame() (or whatever that would be in your native tongue).

I would also avoid putting the type name into the variable's name. So a std::vector doesn't need to have Vektor in it's name. If you had a class or struct for your textures, you could simply call the rectangle the bounds or frame or something along those lines. Something like this:

struct GameTexture {
    SDL_Texture* image;
    SDL_Rect bounds;
};

Then to access one, you could do:

GameTexture pozadina;
// ... fill it out ...
SDL_RenderCopy(renderer, pozadina.image, NULL, &pozadina.bounds);
\$\endgroup\$
  • \$\begingroup\$ Thanks for all good tips you gave me, my question is: when we talk about global variables, in this case I want to define some const int's like ball width,height, number of blocks per lvl etc. Where do I store them in this case?RenderGame() or at definition of Block? \$\endgroup\$ – Marko Petričević Oct 11 '18 at 6:20
  • \$\begingroup\$ It depends on how you use them. If a Block draws itself, then I'd put it in Block, but if a Renderer draws a Block, I'd put it into Renderer. \$\endgroup\$ – user1118321 Oct 11 '18 at 16:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.