#ifndef SDLPP_HPP #define SDLPP_HPP #include #include #include #include #include namespace SDLPP { class Window { public: Window() : Window("SDL Window", 640, 480, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED) { } Window(const std::string &window_name) : Window(window_name, 640, 480, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED) { } Window(const std::string &window_name, uint32_t width, uint32_t height) : Window(window_name, width, height, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED) {} Window(const std::string &window_name, uint32_t width, uint32_t height, uint32_t posx, uint32_t posy) { window = SDL_CreateWindow(window_name.c_str(), posx, posy, width, height, SDL_WINDOW_SHOWN); if( window == NULL ) { std::cerr << "SDL could not create a window! SDL_Error: " << SDL_GetError() << std::endl; throw "Couldn't create window"; } } ~Window() { SDL_DestroyWindow(window); } SDL_Window *getWindowPtr() { return window; } private: SDL_Window *window = NULL; }; class Renderer { public: Renderer() = delete; Renderer(Window &window) { renderer = SDL_CreateRenderer(window.getWindowPtr(), -1, SDL_RENDERER_ACCELERATED); if(renderer == NULL ) { std::cerr << "SDL could not create a renderer! SDL_Error: " << SDL_GetError(); throw "Couldn't create renderer"; } SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); } ~Renderer() { SDL_DestroyRenderer(renderer); } SDL_Renderer *getRendererPtr() { return renderer; } std::pair getDimensions() { int width = 0, height = 0; SDL_GetRendererOutputSize(renderer, &width, &height); return {width, height}; } private: SDL_Renderer *renderer = NULL; }; class Texture { public: Texture() = delete; Texture(std::shared_ptr &renderer, const std::string &img_path) : Texture(renderer, img_path, "") {} Texture(std::shared_ptr &renderer, const std::string &img_path, const std::string &color_key) { SDL_Surface *surface = IMG_Load(img_path.c_str()); if( surface == NULL ) { std::cerr << "Unable to load image '" << img_path << "': IMG Error: " << IMG_GetError() << std::endl; throw "IMG_Load error"; } if( !color_key.empty() ) { auto colors = getColorsHEX(color_key); SDL_SetColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, std::get<0>(colors), std::get<1>(colors), std::get<2>(colors))); } texture = SDL_CreateTextureFromSurface(renderer->getRendererPtr(), surface); if( texture == NULL ) { std::cerr << "Unable to create texture from '" << img_path << "'! SDL Error: " << SDL_GetError() << std::endl; throw "Texture error"; } SDL_FreeSurface(surface); } ~Texture() { SDL_DestroyTexture(texture); } SDL_Texture *getTexturePtr() { return texture; } private: int hex2num(char c) { if(c <= '9') return c - '0'; switch(c) { case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; default: return 15; } } std::tuple getColorsHEX(const std::string &color) { int red = 0, green = 0, blue = 0; const char *color_ptr = color.c_str(); if(color_ptr[0] == '#') color_ptr++; red = hex2num(color_ptr[0])*16 + hex2num(color_ptr[1]); green = hex2num(color_ptr[2])*16 + hex2num(color_ptr[3]); blue = hex2num(color_ptr[4])*16 + hex2num(color_ptr[5]); return {red, green, blue}; } SDL_Texture *texture = NULL; }; class CollisionPolygon { public: CollisionPolygon(double x, double y) { original_x = x; original_y = y; position_x = 0; position_y = 0; } virtual ~CollisionPolygon() {} virtual bool colidesWith(const CollisionPolygon &other) const = 0; virtual bool isCircle() const = 0; virtual bool isInfinite() const = 0; virtual int topmost() const = 0; virtual int bottommost() const = 0; virtual int leftmost() const = 0; virtual int rightmost() const = 0; virtual void updateCollision(int x, int y, int w, int h) { position_x = original_x * w + x; position_y = original_y * h + y; } int getX() const { return position_x; } int getY() const { return position_y; } protected: double original_x; double original_y; int position_x; int position_y; }; class RenderObject { public: RenderObject(std::shared_ptr &r) : renderer(r) {} virtual ~RenderObject() {} virtual void render() = 0; virtual int leftmost() = 0; virtual int topmost() = 0; virtual int collisionPushX() = 0; virtual int collisionPushY() = 0; virtual int collisionWidth() = 0; virtual int collisionHeight() = 0; virtual std::pair,std::pair> getDoubleRect() = 0; virtual void setPos(double x, double y) = 0; bool colidesWith(const RenderObject &other) const { if(!hasCollisions() || !other.hasCollisions()) { return false; } for( const auto &x : collisions ) { for( const auto &y : other.getCollisions() ) { if(x->colidesWith(*y)) return true; } } return false; } template void addCollision(const T &p) { collisions.push_back(std::make_shared(p)); collisions.back()->updateCollision(collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight()); } bool hasCollisions() const { return !collisions.empty(); } const std::vector> &getCollisions() const { return collisions; } void setTexture(std::shared_ptr &t) { texture = t; } void setTexture(const std::string &img_path) { texture = std::make_shared(renderer, img_path); } // per second, relative to window width void setMovementSpeed(double speed) { movementSpeed = speed; } void addMovement(int x, int y) { movementDirection.first += x; movementDirection.second += y; } void clearColided() { colidedWith.clear(); } void addColided(std::shared_ptr &obj) { colidedWith.push_back(obj); } std::vector> &getColidedWith() { return colidedWith; } void setId(uint64_t input_id) { id = input_id; } uint64_t getId() { return id; } virtual void move(int ticks) = 0; virtual void updateSizeAndPosition() = 0; protected: std::vector> collisions; std::shared_ptr texture; std::shared_ptr renderer; double movementSpeed; std::pair movementDirection; std::vector> colidedWith; uint64_t id; }; class Scene { public: Scene(std::shared_ptr &r) : renderer(r) { SDL_SetRenderDrawColor(renderer->getRendererPtr(), 0xFF, 0xFF, 0xFF, 0xFF); prev_ticks = SDL_GetTicks(); } template void addObject(std::shared_ptr &obj) { renderObjects.push_back(obj); if(obj->hasCollisions()) { collisionObjects.push_back(obj); } } std::shared_ptr getObject(int index) { return renderObjects[index]; } void movement() { int now_ticks = SDL_GetTicks(); for( const auto &x : renderObjects ) { x->move(now_ticks - prev_ticks); } prev_ticks = now_ticks; } std::vector> getCollisions(RenderObject &r) { std::vector> ret{}; for(const auto &x : collisionObjects) { if(x->colidesWith(r)) { ret.push_back(x); } } return ret; } void renderScene() { SDL_RenderClear(renderer->getRendererPtr()); SDL_RenderCopy(renderer->getRendererPtr(), background->getTexturePtr(), NULL, NULL); for( const auto &x : renderObjects ) { x->render(); } } void presentScene() { SDL_RenderPresent(renderer->getRendererPtr()); } void setBackground(std::shared_ptr bg) { background = bg; } void setBackground(const std::string &img_path) { background = std::make_shared(renderer, img_path); } void updateSizeAndPosition() { for( auto &x : renderObjects ) { x->updateSizeAndPosition(); } } private: std::vector> renderObjects; std::vector> collisionObjects; std::shared_ptr renderer; std::shared_ptr background; int prev_ticks = 0; }; class RectangleRender : public RenderObject { public: RectangleRender() = delete; virtual ~RectangleRender() {}; RectangleRender(double x, double y, double w, double h, std::shared_ptr &r) : RenderObject(r) { auto dimensions = renderer->getDimensions(); auto smaller_dimension = dimensions.first < dimensions.second ? dimensions.first : dimensions.second; rect.x = x * dimensions.first; rect.y = y * dimensions.second; rect.w = w * smaller_dimension; rect.h = h * smaller_dimension; x_ = x; y_ = y; w_ = w; h_ = h; } RectangleRender(int x, int y, int w, int h, std::shared_ptr &r, std::shared_ptr &t) : RectangleRender(x, y, w, h, r) { setTexture(t); } RectangleRender(int x, int y, int w, int h, std::shared_ptr &r, const std::string &img_path) : RectangleRender(x,y,w,h,r) { auto texture = std::make_shared(r, img_path); setTexture(texture); } virtual void render() { SDL_RenderCopy(renderer->getRendererPtr(), texture->getTexturePtr(), NULL, &rect); } virtual void move(int ticks) { auto dimensions = renderer->getDimensions(); auto addx = static_cast(movementSpeed * movementDirection.first)*(static_cast(ticks)/1000); auto addy = static_cast(movementSpeed * movementDirection.second)*(static_cast(ticks)/1000); x_ += addx; y_ += addy; rect.x = x_ * dimensions.first; rect.y = y_ * dimensions.second; for( auto &x : collisions ) { x->updateCollision(collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight()); } } virtual std::pair,std::pair> getDoubleRect() { return {{x_,y_}, {w_,h_}}; } virtual void setPos(double x, double y) { auto dimensions = renderer->getDimensions(); x_ = x; y_ = y; rect.x = x_ * dimensions.first; rect.y = y_ * dimensions.second; } virtual int leftmost() { return rect.x; } virtual int topmost() { return rect.y; } virtual int collisionPushX() { return rect.x; } virtual int collisionPushY() { return rect.y; } virtual int collisionWidth() { return rect.w; } virtual int collisionHeight() { return rect.h; } virtual void updateSizeAndPosition() { auto dimensions = renderer->getDimensions(); auto smaller_dimension = dimensions.first < dimensions.second ? dimensions.first : dimensions.second; rect.x = x_ * dimensions.first; rect.y = y_ * dimensions.second; rect.w = w_ * smaller_dimension; rect.h = h_ * smaller_dimension; } protected: double x_; double y_; double w_; double h_; SDL_Rect rect; }; class CircleRender : public RenderObject { public: CircleRender() = delete; virtual ~CircleRender() {}; CircleRender(int x, int y, int rad, std::shared_ptr &r) : RenderObject(r) { x_ = x; y_ = y; rad_ = rad; } CircleRender(int x, int y, int rad, std::shared_ptr &r, std::shared_ptr &t) : CircleRender(x,y,rad,r) { setTexture(t); } CircleRender(int x, int y, int rad, std::shared_ptr &r, const std::string &img_path) : CircleRender(x,y,rad,r) { auto texture = std::make_shared(r, img_path); setTexture(texture); } virtual void render(); virtual int leftmost() { return x_-rad_; } virtual int topmost() { return y_-rad_; } virtual int collisionPushX() { return x_; } virtual int collisionPushY() { return y_; } private: int x_; int y_; int rad_; }; class Rect : public CollisionPolygon { public: Rect(int x, int y, int w, int h) : CollisionPolygon(x, y) { w_ = w; h_ = h; } virtual ~Rect() {} virtual bool colidesWith(const CollisionPolygon &other) const override; virtual bool isCircle() const override { return false; } virtual bool isInfinite() const override { return false; } virtual int topmost() const override { return getY(); } virtual int bottommost() const override { return getY() + pixel_h; }; virtual int leftmost() const override { return getX(); } virtual int rightmost() const override { return getX() + pixel_w; } virtual void updateCollision(int x, int y, int w, int h) override { position_x = original_x * w + x; position_y = original_y * h + y; pixel_w = w_ * w; pixel_h = h_ * h; } private: double w_; double h_; int pixel_w; int pixel_h; }; class Circle : public CollisionPolygon { public: Circle(int x, int y, int rad) : CollisionPolygon(x, y) { rad_ = rad; } virtual ~Circle() {} virtual bool colidesWith(const CollisionPolygon &other) const; virtual bool isCircle() const { return true; } virtual bool isInfinite() const { return false; } virtual int topmost() const { return getY() - rad_; } virtual int bottommost() const { return getY() + rad_; }; virtual int leftmost() const { return getX() - rad_; } virtual int rightmost() const { return getX() + rad_; } private: int getRadius() const { return rad_; } int rad_; }; bool init(); bool init(uint32_t SDL_OPTIONS); bool init(uint32_t SDL_OPTIONS, int IMAGE_OPTIONS); template void testPolymorphism(T &obj); } // end of namespace SDLPP #endif