2
\$\begingroup\$

I just completed my pong game. I'm trying follow the CS50 Introduction to Game Design course. The course uses LUA and LÖVE, but I'm trying to just transfer the concepts to C++ and SDL2. The game is pretty straight forward. There are 2 paddles on each side, one controlled by the player, the other by an AI. there is a ball in the middle that will travel towards a player and bounce off walls and paddles. If the ball passes a paddle, the opposing player gets a point. First to 10 wins.

I know I could have created an input, update, and render loop for each state, but I decided against it because there isn't too much that differs between states.

I did, however, try to split the Paddle, Ball, and Game struct into their own files but I had a lot of trouble getting it to work.

For instance, my Texture and SDLInit files work fine right now, but when I tried to move the AssetManager struct to it's own header file, the Texture class would suddenly have an issue with undefined variables or issues with the std namespace. I couldn't quite figure it out.

And if I tried keeping the AssetManager in main but move Paddle to it's own file, then there would be an issue with the Paddle::render not knowing what the AssetManager is. I tried declaring struct AssetManager; at the top of Paddle.h but it didnt seem to help.

Questions about the actual game code:

  1. Every time I the ball hits a paddle, I increase it's x velocity by a small percentage. However, I ran into the issue of tunneling when the ball got too fast. I looked it up and learned about continuous collision detection to help fix the problem. A lot of examples of seemed very complicated but I thought I understood the gist of it. My solution was to draw lines from opposite corners of the current ball position to the future ball position and see if either of those lines intersected a paddle. The solution seems to work fine, but is there a more efficient or better way I could have avoided the tunneling problem?
  2. Is there a better way of doing this:

    if (game.ball_.yPos_ <= 0 || game.ball_.yPos_ + game.ball_.height_ >= VIRTUAL_HEIGHT)
    {
        //move ball based on which wall was hit
        if (game.ball_.yPos_ <= 0)
        {
            game.ball_.yPos_ = 0;
        }
        else
            ...
    }
    

    Where I want to know if either wall gets hit because I need to play sound or change the ball direction, but I still need to know which wall so that I can move the balls position properly.

Besides this, any general input or feedback would also be greatly appreciated.

main.h

#include <string>
#include <random>

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include <SDL_mixer.h>

#include "SDLInit.h"
#include "Texture.h"

constexpr int SCREEN_WIDTH = 1024;
constexpr int SCREEN_HEIGHT = 576;

constexpr int VIRTUAL_WIDTH = 432;
constexpr int VIRTUAL_HEIGHT = 243;

constexpr int PADDLE_WIDTH = 5;
constexpr int PADDLE_HEIGHT = 20;
constexpr int PADDLE_SPEED = 200;

constexpr int BALL_SIZE = 4;

std::mt19937& random_engine()
{
    static std::mt19937 mersenne(std::random_device{}());
    return mersenne;
}

int getRandomNumber(int x, int y)
{
    std::uniform_int_distribution<> dist{ x,y };
    return dist(random_engine());
}

struct AssetManager
{
    std::unique_ptr<SDL_Window, sdl_deleter> window_{ SDL_CreateWindow("Pong", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN) };
    std::unique_ptr<SDL_Renderer, sdl_deleter> renderer_{ SDL_CreateRenderer(window_.get(), -1, SDL_RENDERER_PRESENTVSYNC) };
    std::unique_ptr<TTF_Font, sdl_deleter> smallFont_{ TTF_OpenFont("resources/font.ttf", 8) };
    std::unique_ptr<TTF_Font, sdl_deleter> largeFont_{ TTF_OpenFont("resources/font.ttf", 16) };
    std::unique_ptr<TTF_Font, sdl_deleter> scoreFont_{ TTF_OpenFont("resources/font.ttf", 32) };
    std::unique_ptr<Mix_Chunk, sdl_deleter> paddleHitSound_{ Mix_LoadWAV("resources/paddlehit.wav") };
    std::unique_ptr<Mix_Chunk, sdl_deleter> wallHitSound_{ Mix_LoadWAV("resources/wallhit.wav") };
    std::unique_ptr<Mix_Chunk, sdl_deleter> scoreSound_{ Mix_LoadWAV("resources/score.wav") };

    SDL_Color whiteFont_ = { 0xff, 0xff, 0xff };
    int fontWrapWidth_ = SCREEN_WIDTH;

    LTexture welcomeText_ = { from_text, smallFont_.get(), renderer_.get(), "Welcome to Pong!", whiteFont_, fontWrapWidth_ };
    LTexture startGameText_ = { from_text, smallFont_.get(), renderer_.get(), "Press Enter to begin.", whiteFont_, fontWrapWidth_ };
    LTexture serveText_ = { from_text, smallFont_.get(), renderer_.get(), "Press Enter to serve.", whiteFont_, fontWrapWidth_ };
    LTexture restartText_ = { from_text, smallFont_.get(), renderer_.get(), "Press Enter to restart.", whiteFont_, fontWrapWidth_ };

};

struct Paddle
{
    Paddle(int xPos, float yPos, int width, float height)
        : xPos_(xPos), yPos_(yPos), width_(width), height_(height) {}

    void update(float deltaTime)
    {
        if (yVel_ < 0)
        {
            yPos_ = std::max(0.0f, yPos_ + yVel_ * deltaTime);
        }
        else
        {
            yPos_ = std::min(VIRTUAL_HEIGHT - height_, yPos_ + yVel_ * deltaTime);
        }
    }

    void render(AssetManager &assets)
    {
        SDL_SetRenderDrawColor(assets.renderer_.get(), 255, 255, 255, 255);
        SDL_Rect rect_{ xPos_, static_cast<int>(yPos_), width_, static_cast<int>(height_) };
        SDL_RenderFillRect(assets.renderer_.get(), &rect_);
    }

    int xPos_;
    float yPos_;
    int width_;
    float height_ = 0.0f;
    float yVel_ = 0.0f;
};

struct Ball
{
    Ball(float xPos, float yPos, int width, int height)
        : xPos_(xPos), yPos_(yPos), width_(width), height_(height) {}

    float xPos_;
    float yPos_;
    int width_;
    int height_;
    float xVel_ = 0.0f;
    float yVel_ = 0.0f;

    bool hasCollision(Paddle paddle, float deltaTime)
    {
        SDL_Rect paddleRect{ paddle.xPos_, static_cast<int>(paddle.yPos_), paddle.width_, static_cast<int>(paddle.height_) };

        //if ball is going to upper left or bottom right,
        //draw lines using top right and bottom left corners
        //from current position to future position
        if ((xVel_ < 0 && yVel_ <= 0) || (xVel_ > 0 && yVel_ >= 0))
        {
            //get x and y pos or current and future frame
            int currUpRightX = static_cast<int>(xPos_ + width_);
            int currUpRightY = static_cast<int>(yPos_);
            int futureUpRightX = static_cast<int>(xPos_ + width_ + (xVel_ * deltaTime));
            int futureUpRightY = static_cast<int>(yPos_ + (yVel_ * deltaTime));

            int currBotLeftX = static_cast<int>(xPos_);
            int currBotLeftY = static_cast<int>(yPos_ + height_);
            int futureBotLeftX = static_cast<int>(xPos_ + (xVel_ * deltaTime));
            int futureBotLeftY = static_cast<int>(yPos_ + height_ + (yVel_ * deltaTime));

            if (SDL_IntersectRectAndLine(&paddleRect, &currUpRightX, &currUpRightY, &futureUpRightX, &futureUpRightY) ||
                SDL_IntersectRectAndLine(&paddleRect, &currBotLeftX, &currBotLeftY, &futureBotLeftX, &futureBotLeftY))
            {
                return true;
            }
        }

        //if ball is going to upper right or bottom left,
        //draw lines using top left and bottom right corners
        //from current position to future position
        if ((xVel_ < 0 && yVel_ > 0) || (xVel_ > 0 && yVel_ < 0))
        {
            //get x and y pos or current and future frame
            int currUpLeftX = static_cast<int>(xPos_);
            int currUpLeftY = static_cast<int>(yPos_);
            int futureUpLeftX = static_cast<int>(xPos_ + (xVel_ * deltaTime));
            int futureUpLeftY = static_cast<int>(yPos_ + (xVel_ * deltaTime));

            int currBotRightX = static_cast<int>(xPos_ + width_);
            int currBotRightY = static_cast<int>(yPos_ + height_);
            int futureBotRightX = static_cast<int>(xPos_ + width_ + (xVel_ * deltaTime));
            int futureBotRightY = static_cast<int>(yPos_ + height_ + (yVel_ * deltaTime));

            if (SDL_IntersectRectAndLine(&paddleRect, &currUpLeftX, &currUpLeftY, &futureUpLeftX, &futureUpLeftY) ||
                SDL_IntersectRectAndLine(&paddleRect, &currBotRightX, &currBotRightY, &futureBotRightX, &futureBotRightY))
            {
                return true;
            }
        }
        return false;
    }

    void render(AssetManager &assets)
    {
        SDL_SetRenderDrawColor(assets.renderer_.get(), 255, 255, 255, 255);
        SDL_Rect rect_{ static_cast<int>(xPos_), static_cast<int>(yPos_), width_, height_ };
        SDL_RenderFillRect(assets.renderer_.get(), &rect_);
    }

    void reset()
    {
        xPos_ = VIRTUAL_WIDTH / 2 - BALL_SIZE / 2;
        yPos_ = VIRTUAL_HEIGHT / 2 - BALL_SIZE / 2;
        xVel_ = 0;
        yVel_ = 0;
    }

    void update(float deltaTime)
    {
        xPos_ += xVel_ * deltaTime;
        yPos_ += yVel_ * deltaTime;
    }

};

struct Game
{
    enum GameState
    {
        EXIT,
        START,
        SERVE,
        PLAY,
        DONE
    };

    Paddle player1_{ 10, VIRTUAL_HEIGHT / 2 - PADDLE_HEIGHT / 2, PADDLE_WIDTH, PADDLE_HEIGHT};
    Paddle player2_{ VIRTUAL_WIDTH - 10 - PADDLE_WIDTH, VIRTUAL_HEIGHT / 2 - PADDLE_HEIGHT / 2, PADDLE_WIDTH , PADDLE_HEIGHT};
    Ball ball_{ VIRTUAL_WIDTH / 2 - BALL_SIZE / 2 , VIRTUAL_HEIGHT / 2 - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE };

    int player1Score_ = 0;
    int player2Score_ = 0;
    int servingPlayer_ = getRandomNumber(1,2);
    int winningPlayer_ = 1;

    float thenTime_ = 0.0f;
    float nowTime_ = 0.0f;
    float deltaTime_ = 0.0f;
    GameState state_ = START;
};

void AI_Play(Game& game, Paddle& paddle)
{
    if (game.ball_.xPos_ > VIRTUAL_WIDTH / 5)
    {
        if (game.ball_.yPos_ + game.ball_.height_ > paddle.yPos_ + paddle.height_)
        {
            paddle.yVel_ = PADDLE_SPEED - 50;
        }
        else if (game.ball_.yPos_ < paddle.yPos_)
        {
            paddle.yVel_ = -(PADDLE_SPEED - 50);
        }
        else
        {
            paddle.yVel_ = 0;
        }
    }
}

bool render(Game& game, AssetManager& assets)
{
    SDL_SetRenderDrawColor(assets.renderer_.get(), 40, 45, 52, 0xff);
    SDL_RenderClear(assets.renderer_.get());

    if (game.state_ == Game::START)
    {
        assets.welcomeText_.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - assets.welcomeText_.width_ / 2, 10);
        assets.startGameText_.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - assets.startGameText_.width_ / 2, 20);
    }
    if (game.state_ == Game::SERVE)
    {
        LTexture playersServeText_ = { from_text, assets.smallFont_.get(), assets.renderer_.get(), "Player " + std::to_string(game.servingPlayer_) + "'s turn to serve.", assets.whiteFont_, assets.fontWrapWidth_ };
        playersServeText_.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - playersServeText_.width_ / 2, 10);
        assets.serveText_.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - assets.serveText_.width_ / 2, 20);
    }
    if (game.state_ == Game::DONE)
    {
        LTexture playerWonText_ = { from_text, assets.smallFont_.get(), assets.renderer_.get(), "PLAYER " + std::to_string(game.winningPlayer_) + "WINS!", assets.whiteFont_, assets.fontWrapWidth_ };
        playerWonText_.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - playerWonText_.width_ / 2, 10);
        assets.restartText_.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - assets.restartText_.width_ / 2, 20);
    }

    SDL_SetRenderDrawColor(assets.renderer_.get(), 255, 255, 255, 225);

    LTexture player1Score = { from_text, assets.scoreFont_.get(), assets.renderer_.get(), std::to_string(game.player1Score_), assets.whiteFont_, assets.fontWrapWidth_ };
    LTexture player2Score = { from_text, assets.scoreFont_.get(), assets.renderer_.get(), std::to_string(game.player2Score_), assets.whiteFont_, assets.fontWrapWidth_ };
    player1Score.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 - 50, VIRTUAL_HEIGHT / 8);
    player2Score.render(assets.renderer_.get(), VIRTUAL_WIDTH / 2 + 30, VIRTUAL_HEIGHT / 8);

    game.player1_.render(assets);
    game.player2_.render(assets);
    game.ball_.render(assets);

    SDL_RenderPresent(assets.renderer_.get());
    return true;
}
bool update(Game& game, AssetManager& assets)
{
    if (game.state_ == Game::PLAY)
    {
        if (game.ball_.hasCollision(game.player1_, game.deltaTime_) ||
            game.ball_.hasCollision(game.player2_, game.deltaTime_))
        {
            //if ball going was going to the left
            //move in front of left paddle and vice versa
            if (game.ball_.xVel_ < 0)
            {
                game.ball_.xPos_ = static_cast<float>(game.player1_.xPos_ + 5);
            }
            else
            {
                game.ball_.xPos_ = static_cast<float>(game.player2_.xPos_ - 4);
            }

            if (game.ball_.yVel_ < 0)
            {
                game.ball_.yVel_ = static_cast<float>(getRandomNumber(-150, 0));
            }
            else
            {
                game.ball_.yVel_ = static_cast<float>(getRandomNumber(0, 150));
            }

            //change direction and speed up ball
            game.ball_.xVel_ *= -1.05f;

            Mix_PlayChannel(-1, assets.paddleHitSound_.get(), 0);
        }

        //if hits walls
        if (game.ball_.yPos_ <= 0 || game.ball_.yPos_ + game.ball_.height_ >= VIRTUAL_HEIGHT)
        {
            //move ball based on which wall was hit
            if (game.ball_.yPos_ <= 0)
            {
                game.ball_.yPos_ = 0;
            }
            else
            {
                game.ball_.yPos_ = VIRTUAL_HEIGHT - 4;
            }

            game.ball_.yVel_ *= -1;

            Mix_PlayChannel(-1, assets.wallHitSound_.get(), 0);
        }

        //if score
        if (game.ball_.xPos_ <= 0 || game.ball_.xPos_ >= VIRTUAL_WIDTH - 4)
        {
            //if player2 scored
            if (game.ball_.xPos_ <= 0)
            {
                game.servingPlayer_ = 1;
                game.player2Score_++;
            }
            else
            {
                game.servingPlayer_ = 2;
                game.player1Score_++;
            }

            game.ball_.reset();
            game.state_ = Game::SERVE;
            Mix_PlayChannel(-1, assets.scoreSound_.get(), 0);
        }

        if (game.player1Score_ == 10)
        {
            game.winningPlayer_ = 1;
            game.state_ = Game::DONE;

        }
        if (game.player2Score_ == 10)
        {
            game.winningPlayer_ = 2;
            game.state_ = Game::DONE;
        }
    }

    AI_Play(game, game.player2_);
    game.ball_.update(game.deltaTime_);
    game.player1_.update(game.deltaTime_);
    game.player2_.update(game.deltaTime_);

    return true;
}
bool input(Game& game)
{
    SDL_Event e;

    game.nowTime_ = static_cast<float>(SDL_GetTicks());
    game.deltaTime_ = (game.nowTime_ - game.thenTime_) / 1000;
    game.thenTime_ = game.nowTime_;

    while (SDL_PollEvent(&e) != 0)
    {
        if (e.type == SDL_QUIT)
        {
            game.state_ = Game::EXIT;
            return false;
        }
        else if (e.type == SDL_KEYDOWN && e.key.repeat == 0)
        {
            switch (e.key.keysym.sym)
            {
            case SDLK_RETURN: 
                if (game.state_ == Game::START)
                {
                    game.state_ = Game::SERVE;
                } 
                else if (game.state_ == Game::SERVE)
                {
                    game.ball_.yVel_ = static_cast<float>(getRandomNumber(-50, 50));
                    if (game.servingPlayer_ == 1)
                    {
                        game.ball_.xVel_ = static_cast<float>(getRandomNumber(-200, -140));
                    }
                    else
                    {
                        game.ball_.xVel_ = static_cast<float>(getRandomNumber(140, 200));
                    }
                    game.state_ = Game::PLAY;
                }
                else if (game.state_ == Game::DONE)
                {
                    game.state_ = Game::SERVE;
                    game.ball_.reset();
                    game.winningPlayer_ = 0;
                    game.servingPlayer_ = getRandomNumber(1, 2);
                    game.player1Score_ = 0;
                    game.player2Score_ = 0;
                }
                break;

            case SDLK_UP: game.player1_.yVel_ -= PADDLE_SPEED; break;
            case SDLK_DOWN: game.player1_.yVel_ += PADDLE_SPEED; break;
            }
        }

        else if (e.type == SDL_KEYUP && e.key.repeat == 0)
        {
            switch (e.key.keysym.sym)
            {
            case SDLK_UP: game.player1_.yVel_ += PADDLE_SPEED; break;
            case SDLK_DOWN: game.player1_.yVel_ -= PADDLE_SPEED; break;
            }
        }
    }

    return true;
}

int main(int argc, char* args[])
{
    sdl sdlinit;
    sdl_img img;
    sdl_ttf ttf;
    sdl_mix music;

    AssetManager assets;
    Game game;

    SDL_RenderSetLogicalSize(assets.renderer_.get(), VIRTUAL_WIDTH, VIRTUAL_HEIGHT);

    while(game.state_ != Game::EXIT)
    {
        while (input(game) && update(game, assets) && render(game, assets))
        { }
    }

    return 0;
}

SDLInit.h

#pragma once

class sdl
{
public:
    sdl();

    sdl(sdl&& other) noexcept;


    ~sdl();

    auto operator=(sdl&& other) noexcept->sdl&;

    // Non-copyable
    sdl(sdl const&) = delete;
    auto operator=(sdl const&)->sdl& = delete;

    friend void _swap(sdl& a, sdl& b) noexcept;

private:

    bool _own = false;
};

class sdl_img
{
public:
    sdl_img();

    sdl_img(sdl_img&& other) noexcept;

    ~sdl_img();

    auto operator=(sdl_img&& other) noexcept->sdl_img&;

    // Non-copyable
    sdl_img(sdl_img const&) = delete;
    auto operator=(sdl_img const&)->sdl_img& = delete;

    friend void _swap(sdl_img& a, sdl_img& b) noexcept;

private:

    bool _own = false;
};

class sdl_ttf
{
public:
    sdl_ttf();

    sdl_ttf(sdl_ttf&& other) noexcept;


    ~sdl_ttf();

    auto operator=(sdl_ttf&& other) noexcept->sdl_ttf&;

    // Non-copyable
    sdl_ttf(sdl_ttf const&) = delete;
    auto operator=(sdl_ttf const&)->sdl_ttf& = delete;

    friend void _swap(sdl_ttf& a, sdl_ttf& b) noexcept;

private:

    bool _own = false;
};

class sdl_mix
{
public:
    sdl_mix();

    sdl_mix(sdl_mix&& other) noexcept;

    ~sdl_mix();

    auto operator=(sdl_mix&& other) noexcept->sdl_mix&;

    // Non-copyable
    sdl_mix(sdl_mix const&) = delete;
    auto operator=(sdl_mix const&)->sdl_mix& = delete;

    friend void _swap(sdl_mix& a, sdl_mix& b) noexcept;

private:

    bool _own = false;
};

struct sdl_deleter
{
    void operator()(SDL_Window* p) noexcept;
    void operator()(SDL_Renderer* p) noexcept;
    void operator()(TTF_Font* p) noexcept;
    void operator()(Mix_Chunk* p) noexcept;
};

SDLInit.cpp

#include <iostream>

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include <SDL_mixer.h>

#include "SDLInit.h"

//Initialize SDL
sdl::sdl()
{
    auto const result = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
    if (result != 0)
        std::cout << "SDL could not initialize";
}

sdl::sdl(sdl&& other) noexcept
{
    _swap(*this, other);
}

sdl::~sdl()
{
    if (_own)
        SDL_Quit();
}

auto sdl::operator=(sdl&& other) noexcept -> sdl&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl& a, sdl& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

//Initialize SDL Image
sdl_img::sdl_img()
{
    auto const result = IMG_Init(IMG_INIT_PNG);
    if (result == 0)
        std::cout << "SDL Image could not initialize";
    if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"))
        std::cout << "Could not set texture filtering";
}

sdl_img::sdl_img(sdl_img&& other) noexcept
{
    _swap(*this, other);
}

sdl_img::~sdl_img()
{
    if (_own)
        IMG_Quit();
}

auto sdl_img::operator=(sdl_img&& other) noexcept -> sdl_img&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl_img& a, sdl_img& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

//Initialize SDL True Type Fonts
sdl_ttf::sdl_ttf()
{
    auto const result = TTF_Init();
    if (result != 0)
        std::cout << "SDL TTF could not initialize";
}

sdl_ttf::sdl_ttf(sdl_ttf&& other) noexcept
{
    _swap(*this, other);
}

sdl_ttf::~sdl_ttf()
{
    if (_own)
        TTF_Quit();
}

auto sdl_ttf::operator=(sdl_ttf&& other) noexcept -> sdl_ttf&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl_ttf& a, sdl_ttf& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

//Initialize SDL Mixer
sdl_mix::sdl_mix()
{
    auto const result = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
    if (result == -1)
        std::cout << "SDL Mixer could not initialize";
}

sdl_mix::sdl_mix(sdl_mix&& other) noexcept
{
    _swap(*this, other);
}

sdl_mix::~sdl_mix()
{
    if (_own)
        Mix_Quit();
}

auto sdl_mix::operator=(sdl_mix&& other) noexcept -> sdl_mix&
{
    _swap(*this, other);
    return *this;
}

void _swap(sdl_mix& a, sdl_mix& b) noexcept
{
    using std::swap;
    swap(a._own, b._own);
}

//used with std::unique_ptr
void sdl_deleter::operator()(SDL_Window* p) noexcept
{
    if (p)
        SDL_DestroyWindow(p);
}

void sdl_deleter::operator()(SDL_Renderer* p) noexcept
{
    if (p)
        SDL_DestroyRenderer(p);
}

void sdl_deleter::operator()(TTF_Font* p) noexcept
{
    if (p)
        TTF_CloseFont(p);
}
void sdl_deleter::operator()(Mix_Chunk* p) noexcept
{
    if (p)
        Mix_FreeChunk(p);
}

Texture.h

#pragma once
//used to tag how the texture was rendered
enum TextureType { from_surface, from_text };

class LTexture
{
public:

    LTexture();
    LTexture(TextureType type, SDL_Renderer* renderer, std::string const& path);
    LTexture(TextureType type, TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width);

    LTexture(LTexture&& other) noexcept
    {
        swap(*this, other);
    }

    ~LTexture();

    void render(SDL_Renderer* renderer, int x, int y, SDL_Rect* clip = nullptr, double angle = 0.0, SDL_Point* center = nullptr, SDL_RendererFlip flip = SDL_FLIP_NONE);
    void loadfromsurface(SDL_Renderer* renderer, std::string const& path);
    void loadfromtext(TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width);

    LTexture& operator=(LTexture&& other) noexcept
    {
        swap(*this, other);
        return *this;
    }
    friend void swap(LTexture& a, LTexture& b) noexcept;

    SDL_Texture* texture_ = nullptr;
    int width_ = 0;
    int height_ = 0;
    SDL_Rect button_ = {};
    bool own_ = false;

    LTexture(LTexture const&) = delete;
    auto operator=(LTexture const&) = delete;
};

Texture.cpp

#include <iostream>

#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>

#include "Texture.h"


LTexture::LTexture()
{
    own_ = true;
}

LTexture::LTexture(TextureType type, SDL_Renderer* renderer, std::string const& path)
{
    SDL_Surface *surface = IMG_Load(path.c_str());
    if (!surface)
        std::cout << "Failed to create surface";

    texture_ = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture_)
        std::cout << "Failed to create texture";
    width_ = surface->w;
    height_ = surface->h;

    SDL_FreeSurface(surface);

    own_ = true;
}

LTexture::LTexture(TextureType type, TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width)
{
    SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, width);
    if (!textSurface)
        std::cout << "Failed to create surface";

    texture_ = SDL_CreateTextureFromSurface(renderer, textSurface);
    if (!texture_)
        std::cout << "Failed to created texture";

    width_ = textSurface->w;
    height_ = textSurface->h;

    SDL_FreeSurface(textSurface);

    own_ = true;
}

void LTexture::render(SDL_Renderer* renderer, int x, int y, SDL_Rect* clip, double angle, SDL_Point* center, SDL_RendererFlip flip)
{
    SDL_Rect destRect = { x, y, width_, height_ };
    if (clip != nullptr)
    {
        destRect.w = clip->w;
        destRect.h = clip->h;
    }
    SDL_RenderCopyEx(renderer, texture_, clip, &destRect, angle, center, flip);
    //create a rectangle that coincides with texture to check for button presses
    button_ = { x, y, destRect.w, destRect.h };
}

void LTexture::loadfromsurface(SDL_Renderer* renderer, std::string const& path)
{
    SDL_Surface *surface = IMG_Load(path.c_str());
    if (!surface)
        std::cout << "Failed to create surface";

    texture_ = SDL_CreateTextureFromSurface(renderer, surface);
    if (!texture_)
        std::cout << "Failed to create texture";
    width_ = surface->w;
    height_ = surface->h;

    SDL_FreeSurface(surface);
}

void LTexture::loadfromtext(TTF_Font* font, SDL_Renderer* renderer, std::string const& text, SDL_Color color, int width)
{
    SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(font, text.c_str(), color, width);
    if (!textSurface)
        std::cout << "Failed to create surface";

    texture_ = SDL_CreateTextureFromSurface(renderer, textSurface);
    if (!texture_)
        std::cout << "Failed to created texture";

    width_ = textSurface->w;
    height_ = textSurface->h;

    SDL_FreeSurface(textSurface);
}

void swap(LTexture& a, LTexture& b) noexcept
{
    using std::swap;
    swap(a.texture_, b.texture_);
    swap(a.width_, b.width_);
    swap(a.height_, b.height_);
    swap(a.button_, b.button_);
    swap(a.own_, b.own_);
}

LTexture::~LTexture()
{
    if (texture_)
        SDL_DestroyTexture(texture_);
}

Here is the link to the LUA/LÖVE code this is based on.

\$\endgroup\$

Your Answer

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

Browse other questions tagged or ask your own question.