我为C ++中的Udacity纳米度编写了一个简单的2人Pong游戏。
该程序具有以下类结构(相对于UML来说是新的,因此,此处的任何提示也将非常受欢迎):
在项目结束时,由于需要添加更多功能,我的Game类开始做很多事情(检测棋盘边缘的碰撞,球偏转的物理原理,渲染场景,跟踪得分等)。
在编写代码时,我不清楚如何将这些内容封装在自己的类中,并且继续扩展Game类感觉更容易,更快捷,更省力。
代码现在可以工作了,但是我知道可以改进其OOP设计。你将从哪里开始?非常感谢您的想法!
编辑:将根据以下给出的宝贵建议来更新代码。对进度感兴趣的人可以在这里链接到仓库。
这是源代码(来自原始帖子):
main.cpp
#include "Game.h"

int main(int argc, char* argv[])
{
    Game game;  
    return game.execute();
}

GameObjects.h
#pragma once
#include <SDL.h>
#include <SDL_image.h>
#include <memory>
#include <iostream>
#include <random>

struct Vect_2D {

    int x;
    int y;
};

struct Circle {

    int x;
    int y;
    int r;
};

template <typename T>
class GameObjects{

protected:

    Vect_2D _velocity;
    int _speed;

    int _curX;
    int _curY;
    SDL_Texture* _texture;  // TODO need to destroy textures // SDL_DestroyTexture(t);
    T _boundingBox;

public:
    // Constructors
    GameObjects() = default;
    GameObjects(GameObjects& other) = delete;
    GameObjects& operator=(GameObjects& other) = delete;
    GameObjects(GameObjects&& other) = delete;
    GameObjects& operator=(GameObjects&& other) = delete;

    // Destructor
    ~GameObjects() { SDL_DestroyTexture(_texture); }

    // Accessors
    Vect_2D velocity() { return _velocity; }
    int curX() { return _curX; }
    int curY() { return _curY; }
    T boundingBox() { return _boundingBox;  }
    SDL_Texture* getTexture() { return _texture;  }

    // Modifiers
    void setTexture(SDL_Texture* t) { _texture = t; }
    void setVelocity(Vect_2D v) { _velocity = v;  }
    void setSpeed(int s) { _speed = s;  }
    void curX(int n);
    void curY(int n);
    
    // Special functions
    virtual void render(SDL_Renderer* renderer) = 0;
    void move();
    void updateBoundingBox();
};

class Ball : public GameObjects<Circle> {

private:
    std::mt19937 _mt;
    std::random_device _rdevice;
public:

    Ball() = default;
    Ball(const int& x, const int& y, const int& r);

    // Functions
    void setRandomVelocity();
    void render(SDL_Renderer* renderer) override;
    
};

class Platform : public GameObjects<SDL_Rect> {

public:

    Platform() = default;
    Platform(const int& x, const int& y, const int& w, const int& h);

    // Functions
    void moveUp();
    void moveDown();
    void stop();
    void render(SDL_Renderer * renderer) override;
};

template <typename T>
void GameObjects<T>::move() {
    _curX += _velocity.x;
    _curY += _velocity.y;
    updateBoundingBox();
}

template <typename T>
void GameObjects<T>::curX(int n) {
    _curX = n;
    updateBoundingBox();
}

template <typename T>
void GameObjects<T>::curY(int n) {
    _curY = n;
    updateBoundingBox();
}

GameObjects.cpp
#include "GameObjects.h"

Platform::Platform(const int& x, const int& y, const int& w, const int& h) {

    _velocity.x = 0;
    _velocity.y = 0;

    setSpeed(7);

    _curX = x;
    _curY = y;

    //_boundingBox = SDL_Rect();
    _boundingBox.x = x;
    _boundingBox.y = y;
    _boundingBox.w = w;
    _boundingBox.h = h;
}

Ball::Ball(const int& x, const int& y, const int& r) {

    setSpeed(8);

    //setRandomVelocity();

    _curX = x;
    _curY = y;

    _boundingBox.r = r;

    // Account for the fact that textures are drawn at top left, 
    // but circle x,y is in centre of circle.
    _boundingBox.x = x + r;
    _boundingBox.y = y + r;
    
}

// Sets a random y velocity going towards left of right. left/right speed remains the same as before.
void Ball::setRandomVelocity() {
    int a = _rdevice();
    std::cout << "random seed = " << a << "\n";
    _mt.seed(a);
    std::uniform_int_distribution<int> dist(0, 1);

    if (dist(_mt) == 1)
        _velocity.x = _speed;
    else
        _velocity.x = -_speed;

    std::uniform_int_distribution<int> dist2(-3, 3);
    _velocity.y = dist2(_mt);

}

void Ball::render(SDL_Renderer* renderer) {
    SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0XFF, 0XFF);

    SDL_Rect newPos = { _curX, _curY, 15, 15 };
    SDL_RenderCopy(renderer, _texture, NULL, &newPos);

    //SDL_RenderPresent(renderer);
}

void Platform::render(SDL_Renderer* renderer) {
    SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0XFF, 0XFF);

    SDL_Rect newPos = { _curX, _curY, 13, 73 };
    SDL_RenderCopy(renderer, _texture, NULL, &newPos);

    //SDL_RenderPresent(renderer);
}

void Platform::moveUp() {
    setVelocity(Vect_2D{ 0, -_speed });
}

void Platform::moveDown() {
    setVelocity(Vect_2D{ 0, _speed });
}

void Platform::stop() {
    setVelocity(Vect_2D{ 0, 0 });
}

template<>
void GameObjects<Circle>::updateBoundingBox() {
    _boundingBox.x = _curX + _boundingBox.r;
    _boundingBox.y = _curY + _boundingBox.r;
}

template<>
void GameObjects<SDL_Rect>::updateBoundingBox() {
    _boundingBox.x = _curX;
    _boundingBox.y = _curY;
}

Timer.h
// Thread safe timer class

#pragma once
#include <chrono>
#include <mutex>
#include <iostream>
#include <future>

using std::chrono::steady_clock;

class Timer
{

private:
    std::mutex mtx;
    std::future<void> _ftr;

    bool _isRunning;
    bool _completed;
    void delay(const std::chrono::milliseconds& ms);

public:
    Timer() : _isRunning(false), _completed(false) {};

    bool isRunning();
    bool isCompleted();
    bool start(const std::chrono::milliseconds& ms);
};

Timer.cpp
#include "Timer.h"

void Timer::delay(const std::chrono::milliseconds& ms) {
    std::unique_lock<std::mutex> lck(mtx);
    _completed = false;

    _isRunning = true;

    lck.unlock();
    auto time_started = steady_clock::now();

    std::this_thread::sleep_for(ms);

    lck.lock();
    _isRunning = false;
    _completed = true;
}

bool Timer::isRunning() {
    std::unique_lock<std::mutex> lck(mtx);
    return _isRunning;
}

bool Timer::isCompleted() {
    std::unique_lock<std::mutex> lck(mtx);
    return _completed;
}

bool Timer::start(const std::chrono::milliseconds& ms) {
    if (isRunning()) {
        return false;
    }
    else {
        _ftr = std::async(&Timer::delay, this, ms);
        return true;
    }
}

CollisionDetection.h
#pragma once
#include <cmath>
#include "GameObjects.h"

class CollisionDetection
{
public:
    static int square_of_distance(int x1, int y1, int x2, int y2);
    static void detectCollision(const Circle& item1, const SDL_Rect& item2, int& collisionX, int& collisionY);

};

CollisionDetection.cpp
#include "CollisionDetection.h"
#include <iostream>

// Checks if circle and rectangle have collided. Returns 2 ints representing where on x and y they collided. Both will be -1, -1 if no collision.
void CollisionDetection::detectCollision(const Circle& circle, const SDL_Rect& rectangle, int& collision_x, int& collision_y) {

    collision_x = -1;
    collision_y = -1;

    int rectCollidePointY = 0;
    int rectCollidePointX = 0;

    // Check where on the y axis the circle is in relation to the rectangle
    if (circle.y > rectangle.y + rectangle.h)  rectCollidePointY = rectangle.y + rectangle.h;   // circle below rectangle
    else if (circle.y < rectangle.y) rectCollidePointY = rectangle.y;                           // circle above rectangle
    else rectCollidePointY = circle.y;                                                          // circle somewhere in the middle of rectangle in y axis

    // Check where on the x axis the circle is in relation to the rectangle
    if (circle.x > rectangle.x + rectangle.w)  rectCollidePointX = rectangle.x + rectangle.w;   // circle to the right of whole rectangle
    else if (circle.x < rectangle.x) rectCollidePointX = rectangle.x;                           // circle to the left of whole rectangle
    else rectCollidePointX = circle.x;                                                          // circle somewhere in the middle of rectangle in x axis

    int d = square_of_distance(circle.x, circle.y, rectCollidePointX, rectCollidePointY);

    if (d < pow(circle.r, 2)) {
        collision_x = rectCollidePointX;
        collision_y = rectCollidePointY;
        return;
    }
}

int CollisionDetection::square_of_distance(int x1, int y1, int x2, int y2) {
    return static_cast<int>(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}

Game.h
#pragma once
#include <SDL.h>
#include <SDL_image.h>
#include <iostream>
#include <vector>
#include <string>
#include <SDL_ttf.h>
#include <chrono>
#include <mutex>
#include <future>
#include <sstream>
#include <iomanip>

#include "GameObjects.h"
#include "CollisionDetection.h"
#include "Timer.h"


enum class GameState {
    kMainMenu,
    kPreStart,
    kStart,
    kScoreScreen,
};

class Game{

private:
    bool _running;
    int _frames;
    uint32_t _timeAtLaunch;

    Timer _threadSafeTimer;

    std::vector<int> _scoresVector;

    GameState _state;
    bool _gameStarted;

    SDL_Window* _mainWindow;
    SDL_Renderer* _renderer;

    const int GAME_WIDTH = 600;
    const int GAME_HEIGHT = 400;

    std::unique_ptr<Ball> _ball;
    std::unique_ptr<Platform> _leftPlatform;
    std::unique_ptr<Platform> _rightPlatform;

    std::vector<TTF_Font*> _fonts;      // global font
    void renderText(SDL_Texture* text_texture, int xpos, int ypos);
    void updateScoreTextTure();

    SDL_Texture* _countdownTimer;
    SDL_Texture* _scoresTexture;
    SDL_Texture* _controlsTexture;

    SDL_Texture* loadTexture(std::string path);
    bool loadMedia();
    SDL_Texture* loadFromRenderedText(std::string textureTex, SDL_Color textColor, TTF_Font* font);

    void checkAndReactToBallCollisions(int& winner);
    void checkAndReactToPlatformCollisions();

    bool init();
    void onEvents(SDL_Event* event);
    void gameLoop();
    void render();
    void cleanUp();
    void start();
    
public:
    Game();
    int execute(); // Launch game

    
};

Game.cpp
#include "Game.h"
#include "CollisionDetection.h"

using std::cout;
using std::endl;


Game::Game() {
    cout << "Game object initialized." << endl;

    _state = GameState::kMainMenu;
    _scoresVector = { 0, 0 };

    _ball = std::make_unique<Ball>(GAME_WIDTH / 2, GAME_HEIGHT / 2, 8);
    _leftPlatform = std::make_unique<Platform>(7, 150, 13, 73);
    _rightPlatform = std::make_unique<Platform>(580, 150, 13, 73);

    _running = true;
    _gameStarted = false;
    _frames = 0;
}

int Game::execute() {
    cout << "Launching game." << endl;

    init();

    SDL_Event e;
    cout << "Starting game..." << endl;
    while (_running) {
        while (SDL_PollEvent(&e)) {
            onEvents(&e);
        }
        gameLoop();
        render();
    }

    cleanUp();
    return 0;
}

bool Game::init() {
    cout << "Initializing game." << "\n" << std::flush;

    // Init SDL
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        std::cout << "SDL couldn't initialize! SDL_Error: " << SDL_GetError() << "\n";
        return false;
    }

    // Create Window
    if ((_mainWindow = SDL_CreateWindow("Pong by Can", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        GAME_WIDTH, GAME_HEIGHT, SDL_WINDOW_SHOWN)) == NULL) {
        return false;
    }

    // Create renderer for window
    _renderer = SDL_CreateRenderer(_mainWindow, -1, SDL_RENDERER_ACCELERATED);
    if (_renderer == NULL) {
        std::cout << "Renderer could not be created.  SDL_Error: " << SDL_GetError() << "\n";
        return false;
    }

    //Initialize PNG loading
    int imgFlags = IMG_INIT_PNG;
    if (!(IMG_Init(imgFlags) & imgFlags))
    {
        std::cout << "SDL_image could not initialize! SDL_image Error: " << IMG_GetError() << "\n";
        return false;
    }

    // Initialize SDL TTF (text render)
    if (TTF_Init() == -1) {
        std::cout << "Failed to initialise SDL_ttf. SDL_ttf error: " << TTF_GetError() << "\n";
    }

    // Load media
    if (loadMedia() == false) {
        return false;
    }

    _timeAtLaunch = SDL_GetTicks();
    
    return true;
}

//  Load textures from image and text
bool Game::loadMedia() {
    
    _ball->setTexture(loadTexture("Resources/ball.png"));
    _leftPlatform->setTexture(loadTexture("Resources/plank.bmp"));
    _rightPlatform->setTexture(loadTexture("Resources/plank2.bmp"));

    _fonts.push_back(TTF_OpenFont("Resources/ARLRDBD.TTF", 28));
    if (_fonts[0] == NULL) {
        std::cout << "Failed to load 28 ARIAL ROUNDED font. SDL_ttf error: " << TTF_GetError() << "\n";
        return false;
    }

    _fonts.push_back(TTF_OpenFont("Resources/ARLRDBD.TTF", 14));
    if (_fonts[1] == NULL) {
        std::cout << "Failed to load 14 ARIAL ROUNDED font. SDL_ttf error: " << TTF_GetError() << "\n";
        return false;
    }

    return true;
}

SDL_Texture* Game::loadFromRenderedText(std::string textureText, SDL_Color textColor, TTF_Font* font) {

    SDL_Texture* newTexture = NULL;

    SDL_Surface* textSurface = TTF_RenderText_Blended(font, textureText.c_str(), textColor);
    if (textSurface == NULL) {
        std::cout << "Unable to render text surface. SDL_ttf error: " << TTF_GetError() << "\n";
    }
    else {
        newTexture = SDL_CreateTextureFromSurface(_renderer, textSurface);
        if (newTexture == NULL) {
            std::cout << "Unable to create texture from rendered text. SDL_ttf error: " << TTF_GetError() << "\n";
        }
        else {
        }
        SDL_FreeSurface(textSurface);
    }
    return newTexture;
}

SDL_Texture* Game::loadTexture(std::string path) {
    SDL_Texture* newTexture = NULL;

    SDL_Surface* loadedSurface = IMG_Load(path.c_str());
    if (loadedSurface == NULL) {
        std::cout << "Unable to load image " << path << ". SDL_image error: " << IMG_GetError() << "\n";
    }
    else {
        newTexture = SDL_CreateTextureFromSurface(_renderer, loadedSurface);
        if (newTexture == NULL) {
            std::cout << "Unable to create texture from " << path << ". SDL Error: " << SDL_GetError() << "\n";
        }
        SDL_FreeSurface(loadedSurface);
    }
    return newTexture;
}

void Game::onEvents(SDL_Event* event) {
    if (event->type == SDL_QUIT) {
        _running = false;
    }
    else if (event->type == SDL_KEYDOWN) {
        switch (event->key.keysym.sym)
        {
        case SDLK_UP:
            _rightPlatform->moveUp();
            break;
        case SDLK_DOWN:
            _rightPlatform->moveDown();
            break;
        case SDLK_w:
            _leftPlatform->moveUp();
            break;
        case SDLK_s:
            _leftPlatform->moveDown();
            break;
        case SDLK_SPACE:
            if (_state == GameState::kScoreScreen || _state == GameState::kMainMenu) _state = GameState::kPreStart;
            break;
        default:
            break;
        }
    }
    else if (event->type == SDL_KEYUP) {
        switch (event->key.keysym.sym)
        {
        case SDLK_UP:
            _rightPlatform->stop();
            break;
        case SDLK_DOWN:
            _rightPlatform->stop();
            break;
        case SDLK_w:
            _leftPlatform->stop();
            break;
        case SDLK_s:
            _leftPlatform->stop();
            break;
        default:
            break;
        }
    }
}

// Helper function to assist with the correct bounce physics of the ball when in contact with the platforms
void bounceBall(int x, int y, Platform* platform, Ball* ball) {
    int platformLeft;
    int platformRight;
    int platformTop;
    int platformBottom;

    int rectCenterX;
    int rectCenterY;

    // Move ball back one step
    int newX = ball->curX() - ball->velocity().x;
    int newY = ball->curY() - ball->velocity().y;
    
    ball->curX(newX);
    ball->curY(newY);

    // Figure out where from the centre point of rectangle the collision occured
    // Reflect ball away at this angle but keep its y velocity the same (only change y velocity if top/bottom of platform was hit)
    //  *       * O
    //  *        *
    //  * centre *
    //  *        *
    //  *        *
    platformLeft = platform->boundingBox().x;
    platformRight = platform->boundingBox().x + platform->boundingBox().w;
    platformTop = platform->boundingBox().y;
    platformBottom = platform->boundingBox().y + platform->boundingBox().h;

    rectCenterX = (platformLeft + platformRight) / 2;
    rectCenterY = (platformBottom + platformTop) / 2;

    int diffX = x - rectCenterX;
    int diffY = y - rectCenterY;
    int y_magnitude = abs(diffY / diffX);

    int y_dir_ball = ball->velocity().y < 0 ? -1 : 1;

    int direction_multiplierY = 1;

    if (y - ball->velocity().y >= platformBottom || y - ball->velocity().y <= platformTop) direction_multiplierY = -1;  // check if bottom or top of platform was hit

    // Calculate new y velocity
    int yVel = y_magnitude * y_dir_ball * direction_multiplierY;

    // Calculate new x velocity
    int xVel = ball->velocity().x * -1;

    ball->setVelocity({ xVel, yVel });
}

void Game::checkAndReactToPlatformCollisions() {
    // Left platform on boundary
    if (_leftPlatform->curY() < 0) {
        _leftPlatform->curY(0);
        _leftPlatform->stop();
    }
    if ((_leftPlatform->curY() + _leftPlatform->boundingBox().h) > (GAME_HEIGHT)) {
        _leftPlatform->curY(GAME_HEIGHT - _leftPlatform->boundingBox().h);
        _leftPlatform->stop();
    }

    // Right platform on boundary
    if (_rightPlatform->curY() < 0) {
        _rightPlatform->curY(0);
        _rightPlatform->stop();
    }
    if ((_rightPlatform->curY() + _rightPlatform->boundingBox().h) > (GAME_HEIGHT)) {
        _rightPlatform->curY(GAME_HEIGHT - _rightPlatform->boundingBox().h);
        _rightPlatform->stop();
    }

}

void Game::checkAndReactToBallCollisions(int& winner) {

    winner = -1;

    // Ball on boundary
    int ballDiameter = 2 * _ball->boundingBox().r;

    //LEFT
    if (_ball->curX() < 0) {                        // PLAYER 2 WINS
        /*_scoresVector[1]++;
        _ball->setVelocity({ 0, 0 });
        _state = GameState::kScoreScreen;
        updateScoreText();*/
        winner = 1;
    }
    //RIGHT
    else if (_ball->curX() > GAME_WIDTH - ballDiameter) { // PLAYER 1 WINS
        /*_scoresVector[0]++;
        _ball->setVelocity({ 0, 0 });
        _state = GameState::kScoreScreen;
        updateScoreText();*/
        winner = 0;
    }
    //TOP
    else if (_ball->curY() < 0) {
        int yVel = _ball->velocity().y;
        int xVel = _ball->velocity().x;

        _ball->setVelocity({ xVel, -yVel });
        _ball->curY(0);
    }
    //BOTTOM
    else if (_ball->curY() > GAME_HEIGHT - ballDiameter) {

        int yVel = _ball->velocity().y;
        int xVel = _ball->velocity().x;

        _ball->setVelocity({ xVel, -yVel });
        _ball->curY(GAME_HEIGHT - ballDiameter);
    }

    // Ball collision on platforms
    int x = -1;
    int y = -1;
    CollisionDetection::detectCollision(_ball->boundingBox(), _leftPlatform->boundingBox(), x, y);
    if (x != -1 && y != -1) {
        bounceBall(x, y, _leftPlatform.get(), _ball.get());
    }

    CollisionDetection::detectCollision(_ball->boundingBox(), _rightPlatform->boundingBox(), x, y);
    if (x != -1 && y != -1) {
        bounceBall(x, y, _rightPlatform.get(), _ball.get());
    }
}

void Game::gameLoop() {

    int winner = -1;

    switch (_state)
    {
        case GameState::kMainMenu:{
            _scoresVector = { 0, 0 };
            _leftPlatform->move();
            _rightPlatform->move();
            checkAndReactToPlatformCollisions();

        }break;
        case GameState::kPreStart:{
            _gameStarted = false;
            _ball->setVelocity({ 0,0 });
            _ball->curX(GAME_WIDTH / 2);
            _ball->curY(GAME_HEIGHT / 2);
            _threadSafeTimer.start(std::chrono::milliseconds(1500));
            _state = GameState::kStart;

        }break;
        case GameState::kStart:{
            if (_threadSafeTimer.isCompleted() == true && _gameStarted == false) {
                _ball->setRandomVelocity();
                _gameStarted = true;
            }

            _leftPlatform->move();
            _rightPlatform->move();
            checkAndReactToPlatformCollisions();
            _ball->move();
            checkAndReactToBallCollisions(winner);

            if (winner != -1) {
                _scoresVector[winner]++;
                _state = GameState::kScoreScreen;
            }
        }break;
        case GameState::kScoreScreen:{
            int a = 1;

        }break;
    }
}

void Game::updateScoreTextTure() {
    
    std::ostringstream oss;
    oss << "Score: " << std::setw(5) << std::right  << _scoresVector[0] << " - " << _scoresVector[1];
    SDL_Color white = { 255,255,255 };

    if (_scoresTexture != NULL) SDL_DestroyTexture(_scoresTexture);
    _scoresTexture = loadFromRenderedText(oss.str().c_str(), white, _fonts[0]);

    if (_scoresTexture == NULL) {
        std::cout << "Failed to change _scoresTexture texture \n";
    }
}


void Game::renderText(SDL_Texture* tt, int xpos, int ypos) {
    
    if (tt == NULL) return;

    int w=140;
    int h=40;

    SDL_QueryTexture(tt, NULL, NULL, &w, &h);

    SDL_Rect newPos = { xpos, ypos , w, h };
    SDL_RenderCopy(_renderer, tt, NULL, &newPos);
}

void print_FPS(uint32_t time_since_start, int frames) {
    int t = SDL_GetTicks();
    float fps = (static_cast<float>(frames)*1000) / (t - time_since_start);
    std::cout << "Avg FPS: " << std::setprecision(2) << fps << "\n";
}

void Game::render() {
    SDL_Color white = { 255,255,255 };
    int t1 = SDL_GetTicks();
    int w = 0;
    int h = 0;

    SDL_RenderClear(_renderer);

    if (_state == GameState::kMainMenu) {
        
        _controlsTexture = loadFromRenderedText("W/S", white, _fonts[1]);
        renderText(_controlsTexture, 20, 20);

        _controlsTexture = loadFromRenderedText("UP/DOWN", white, _fonts[1]);
        w = 0;
        SDL_QueryTexture(_controlsTexture, NULL, NULL, &w, NULL);
        renderText(_controlsTexture, 580-w, 20);

        _countdownTimer = loadFromRenderedText("Press SPACE to Start", white, _fonts[0]);
        SDL_QueryTexture(_countdownTimer, NULL, NULL, &w, NULL);
        renderText(_countdownTimer, GAME_WIDTH / 2 - (w / 2), 350);

        updateScoreTextTure();
    }
    else if (_state == GameState::kScoreScreen) {
        updateScoreTextTure();
        _countdownTimer = loadFromRenderedText("Press SPACE to re-match", white, _fonts[1]);
        SDL_QueryTexture(_countdownTimer, NULL, NULL, &w, NULL);
        renderText(_countdownTimer, GAME_WIDTH / 2 - (w / 2), 350);
    }

    _leftPlatform->render(_renderer);
    _rightPlatform->render(_renderer);
    _ball->render(_renderer);

    // Render scores
    SDL_QueryTexture(_scoresTexture, NULL, NULL, &w, NULL);
    renderText(_scoresTexture, GAME_WIDTH/2 - (w/2), 20);

    SDL_SetRenderDrawColor(_renderer, 0x30, 0x30, 0x30, 0xFF);
    SDL_RenderPresent(_renderer);
    _frames++;

    // Delay to keep FPS consistent
    int t2 = SDL_GetTicks() - t1;
    int ticks_per_frame = 1000 / 60;
    if (t2 < ticks_per_frame) SDL_Delay(ticks_per_frame - t2);
    print_FPS(_timeAtLaunch, _frames);
}

void Game::cleanUp() {
    cout << "End. Cleaning up..." << endl;

    for (auto f : _fonts) {
        TTF_CloseFont(f);
        f = NULL;
    }

    SDL_DestroyRenderer(_renderer);
    SDL_DestroyWindow(_mainWindow);
    //SDL_FreeSurface(_gameSurface);

    _renderer = NULL;
    _mainWindow = NULL;

    IMG_Quit();
    SDL_Quit();
    TTF_Quit();

}


评论

您是否尝试过介绍ECS?

@Sugar我简短地知道了ECS是什么,因此我觉得对于这种简单的游戏概念来说,这太过分了吗?我可能错了-很高兴对此进行调查。谢谢

“继续扩展Game类感觉更容易,更快,更省时间”,这是绝对正确的。问题在于它无法扩展。一旦超过特定的LoC数量,它就不再起作用,并且花费更多的时间来查找需要修复的代码部分,而不是解决实际问题。

#1 楼

总体观察结果
如果我是一名老师,我会给你A +的努力,而B-的实现。
从设计的角度来看,请尽量将游戏的逻辑与游戏的展示。真正的游戏公司将这样做,以便能够将相同的游戏分发到多个平台。这也将允许通过不同的图形包使用相同的游戏核心。尽管我怀疑模型视图控件(MVC)或模型视图视图模型(MVVM)是构建游戏的确切设计模式,但它是您要使用的一种概念。
设计面向对象程序时您想尝试遵循SOLID设计原则。 SOLID是五个设计原则的缩写,旨在使软件设计更易于理解,灵活和可维护。这将帮助您更好地设计对象和类。

单一职责原则-一个类应仅具有单一职责,即,仅对软件规范的一部分进行更改才能影响类的规范。
开放式封闭原理-规定软件实体(类,模块,功能等)应进行扩展,但应进行修改。
李斯科夫替代原理-对象程序中的子程序应可替换为其子类型的实例,而无需更改该程序的正确性。
接口隔离原则-指出不应强迫任何客户端依赖于不使用的方法。
反转原理-是解耦软件模块的一种特定形式。当遵循此原理时,从高级策略设置模块到低级依赖模块建立的常规依赖关系将颠倒过来,从而使高级别模块独立于低级模块实现细节。

类声明组织
您将很少是该行业中唯一从事项目工作的人,一个或多个人可能正在实现程序的逻辑,而一个或多个其他人可能正在实现程序的显示。随着时间的流逝,出现了一种编程约定,即公共属性和方法应该放在类声明的顶部,以便与您一起工作的程序员可以轻松地找到它们。通常,这也是该类实现中的组织。
减少头文件中的包含内容
仅包含编译为头文件所需的头文件,而在头文件中包含其他头文件编译所需的C ++源文件。这样做有多种原因,其中之一是面向对象设计的基本前提是封装,这意味着该类的内部结构受到保护。减少头文件中包含文件的另一个原因是,如何在C和C ++中实现包含文件,包含头中的代码实际上实际上是复制到要编译的C ++源文件的临时版本中。这意味着在它的所有7行下面的简单主程序实际上可能包含1000行以上的代码,其中大多数不需要编译,因为除了60行代码Game.h外,还有14行包含
#include "Game.h"

int main(int argc, char* argv[])
{
    Game game;
    return game.execute();
}

每个类声明都应在其自己的头文件中
文件GameObjects.h包含3个类声明和多个struct声明,而应该有3个头文件,GameObjects.h声明GameObject基类,ball.h包括GameObjects.h并声明球类,platform.h包括GameObjects.h。文件Game.h应该包含ball.hplatform.h而不是GameObjects.h。如果您可以找到一种在Game.h中不包含这些标头的方法,则可能会更好,一种想到的方法是在class ball;文件的顶部使用class platform;Game.h,然后编译器知道这些都是指向a的指针。班级,但不知道班级的详细信息。然后可以在ball.h中的platform.h之前包含Game.h文件和Game.cpp文件。
我修改了CollisionDetection.hCollisionDetection.cpp来演示我的意思:
CollisionDetection.h
#pragma once

struct Circle;
struct SDL_Rect;

class CollisionDetection
{
public:
    static int square_of_distance(int x1, int y1, int x2, int y2);
    static void detectCollision(const Circle& item1, const SDL_Rect& item2, int& collisionX, int& collisionY);

};

CollisionDetection.cpp
#include <cmath>
#include "GameObjects.h"
#include "CollisionDetection.h"
#include <iostream>

// Checks if circle and rectangle have collided. Returns 2 ints representing where on x and y they collided. Both will be -1, -1 if no collision.
void CollisionDetection::detectCollision(const Circle& circle, const SDL_Rect& rectangle, int& collision_x, int& collision_y) {

    collision_x = -1;
    collision_y = -1;

    int rectCollidePointY = 0;
    int rectCollidePointX = 0;

    // Check where on the y axis the circle is in relation to the rectangle
    if (circle.y > rectangle.y + rectangle.h)  rectCollidePointY = rectangle.y + rectangle.h;   // circle below rectangle
    else if (circle.y < rectangle.y) rectCollidePointY = rectangle.y;                           // circle above rectangle
    else rectCollidePointY = circle.y;                                                          // circle somewhere in the middle of rectangle in y axis

    // Check where on the x axis the circle is in relation to the rectangle
    if (circle.x > rectangle.x + rectangle.w)  rectCollidePointX = rectangle.x + rectangle.w;   // circle to the right of whole rectangle
    else if (circle.x < rectangle.x) rectCollidePointX = rectangle.x;                           // circle to the left of whole rectangle
    else rectCollidePointX = circle.x;                                                          // circle somewhere in the middle of rectangle in x axis

    int d = square_of_distance(circle.x, circle.y, rectCollidePointX, rectCollidePointY);

    if (d < pow(circle.r, 2)) {
        collision_x = rectCollidePointX;
        collision_y = rectCollidePointY;
        return;
    }
}

int CollisionDetection::square_of_distance(int x1, int y1, int x2, int y2) {
    return static_cast<int>(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}


评论


\ $ \ begingroup \ $
您说软件项目遵守SOLID设计原则足以使它们具有良好的类结构就足够了吗?我需要我可以依靠每次编写好的软件的东西!
\ $ \ endgroup \ $
–Eon
20-09-22在16:17

\ $ \ begingroup \ $
这是一个很好的开始。还可以继续使用UML,当您看到类本身在UML中变得太大时,这很好地表明它未遵循单一职责原则。大类可以是小类的集合。如果可能,请尝试使您的基类成为抽象类,但并非总是必要的。经验会让您变得更好。
\ $ \ endgroup \ $
–pacmaninbw
20-09-22在16:30