#ifndef SDLPP_HPP #define SDLPP_HPP #include #include #include #include #include #include #include #include #include #include namespace SDLPP { int hex2num(char c); std::tuple getColorsHEX(const std::string &color); SDL_Color getSDLColorHEX(const std::string &color); std::tuple getColorsSDLColor(const SDL_Color &color); SDL_Color getSDLColorTuple(const std::tuple &tuple); 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() const { int width = 0, height = 0; SDL_GetRendererOutputSize(renderer, &width, &height); return {width, height}; } int getWidth() const { return getDimensions().first; } int getHeight() const { return getDimensions().second; } int getSmallerSide() const { auto dimensions = getDimensions(); return dimensions.first < dimensions.second ? dimensions.first : dimensions.second; } int getLargerSide() const { auto dimensions = getDimensions(); return dimensions.first > dimensions.second ? dimensions.first : dimensions.second; } void setBlendMode(SDL_BlendMode blendMode) { SDL_SetRenderDrawBlendMode(renderer, blendMode); } void setRenderColiders(bool render) { render_coliders = render; } bool getRenderColiders() { return render_coliders; } private: SDL_Renderer *renderer = NULL; bool render_coliders = false; }; class Font { public: Font() = delete; Font(const std::string &font, int size) { font_ptr = TTF_OpenFont(font.c_str(), size); } ~Font() { TTF_CloseFont( font_ptr ); } const TTF_Font *getFont() const { return font_ptr; } TTF_Font *getFont() { return font_ptr; } void setOutline(int size) { TTF_SetFontOutline(font_ptr, size); } int getOutline() { return TTF_GetFontOutline(font_ptr); } void setStyle(int style) { TTF_SetFontStyle(font_ptr, style); } void setHinting(int hinting) { TTF_SetFontHinting(font_ptr, hinting); } private: TTF_Font *font_ptr; }; 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))); } setTextureFromSurface(renderer, surface); } Texture(std::shared_ptr &renderer, Font &font, const std::string &text, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", const int outline_size = -1) { if(outline_size != -1) { font.setOutline(outline_size); } int og_outline = 0; SDL_Surface *bg_surface = NULL; if((og_outline = font.getOutline()) != 0) { bg_surface = TTF_RenderUTF8_Blended( font.getFont(), text.c_str(), getSDLColorHEX(outline_color) ); if( bg_surface == NULL ) { std::cerr << "Unable to render text '" << text << "': TTF Error: " << TTF_GetError() << std::endl; throw "TTF_RenderUTF8_Shaded error"; } font.setOutline(0); } SDL_Surface *surface = TTF_RenderUTF8_Blended( font.getFont(), text.c_str(), getSDLColorHEX(color) ); if( surface == NULL ) { std::cerr << "Unable to render text '" << text << "': TTF Error: " << TTF_GetError() << std::endl; throw "TTF_RenderUTF8_Shaded error"; } if(og_outline != 0) { SDL_Rect rect = {og_outline, og_outline, surface->w, surface->h}; SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND); SDL_BlitSurface(surface, NULL, bg_surface, &rect); SDL_FreeSurface(surface); surface = bg_surface; bg_surface = NULL; font.setOutline(og_outline); } setTextureFromSurface(renderer, surface); } ~Texture() { SDL_DestroyTexture(texture); } SDL_Texture *getTexturePtr() { return texture; } private: void setTextureFromSurface(std::shared_ptr &renderer, SDL_Surface *surface) { texture = SDL_CreateTextureFromSurface(renderer->getRendererPtr(), surface); if( texture == NULL ) { std::cerr << "Unable to create texture from surface! SDL Error: " << SDL_GetError() << std::endl; throw "Texture error"; } SDL_FreeSurface(surface); } 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 { return infinite; } virtual void setInfinite() { infinite = true; } 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; } virtual void render(Renderer &renderer, const std::tuple &color) = 0; virtual void render(Renderer &renderer) = 0; int getX() const { return position_x; } int getY() const { return position_y; } void setColor(const std::string &color) { sdl_color = getSDLColorHEX(color); } protected: double original_x; double original_y; int position_x; int position_y; bool infinite = false; SDL_Color sdl_color; }; class Scene; 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 rightmost() = 0; virtual int bottommost() = 0; virtual int collisionPushX() = 0; virtual int collisionPushY() = 0; virtual int collisionWidth() = 0; virtual int collisionHeight() = 0; virtual void specialAction(int code) = 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() || getHidden() || other.getHidden()) { 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) { if(polygon) polygon.reset(); texture = std::make_shared(renderer, img_path); } virtual void setColor(const std::string &color) = 0; void setTexture(Font &font, const std::string &text, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", int outline_size = -1) { texture = std::make_shared(renderer, font, text, color, outline_color, outline_size); } // 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 resetMovementX() { movementDirection.first = 0; } void resetMovementY() { movementDirection.second = 0; } 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; } void setHidden(bool hid) { hidden = hid; } bool getHidden() const { return hidden; } void destroy() { setHidden(true); kill = true; } bool getKilled() { return kill; } void setColiderColor(const std::string &color) { colider_color = getColorsHEX(color); } virtual void move(int ticks) = 0; virtual void custom_move(int ticks) = 0; virtual void updateSizeAndPosition() = 0; virtual SDL_Rect getRect() = 0; void setPermanent(bool perm) { permanent = perm; } bool getPermanent() const { return permanent; } virtual void centerX() = 0; protected: std::vector> collisions; std::shared_ptr texture; std::shared_ptr renderer; std::shared_ptr polygon; double movementSpeed; std::pair movementDirection; std::vector> colidedWith; uint64_t id; bool hidden = false; bool kill = false; std::tuple colider_color = {0x00, 0xFF, 0xFF, 0xFF}; uint64_t scene_id; bool permanent = false; private: void setSceneID(int id) { scene_id = id; } friend Scene; }; class Scene { public: Scene(std::shared_ptr &r) : renderer(r) { SDL_SetRenderDrawColor(renderer->getRendererPtr(), 0xFF, 0xFF, 0xFF, 0xFF); prev_ticks = SDL_GetTicks(); } void addObject(const std::shared_ptr &obj) { render_mutex.lock(); renderObjects.push_back(obj); obj->setSceneID(++max_object_id); if(obj->hasCollisions()) { collisionObjects.push_back(obj); } if(renderObjects.size() == 1) { leftmost_obj = obj; rightmost_obj = obj; } else { auto rect = obj->getDoubleRect(); auto leftmost_rect = leftmost_obj->getDoubleRect(); if(rect.first.first < leftmost_rect.first.first) leftmost_obj = obj; auto rightmost_rect = rightmost_obj->getDoubleRect(); if(rect.first.first + rect.second.first > rightmost_rect.first.first + rightmost_rect.second.first) rightmost_obj = obj; } render_mutex.unlock(); } std::shared_ptr getObject(int index) { return renderObjects[index]; } void movement() { checkKilled(); render_mutex.lock(); int now_ticks = SDL_GetTicks(); for( const auto &x : renderObjects ) { x->move(now_ticks - prev_ticks); } prev_ticks = now_ticks; render_mutex.unlock(); } std::vector> getCollisions(RenderObject &r) { if(r.getHidden()) return {}; std::vector> ret{}; for(const auto &x : collisionObjects) { if(x->colidesWith(r)) { ret.push_back(x); } } return ret; } void renderScene(bool clear_scene = true) { checkKilled(); render_mutex.lock(); if(clear_scene) SDL_RenderClear(renderer->getRendererPtr()); if(background && background->getTexturePtr()) SDL_RenderCopy(renderer->getRendererPtr(), background->getTexturePtr(), NULL, NULL); for( const auto &x : renderObjects ) { x->render(); } render_mutex.unlock(); } 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() { checkKilled(); render_mutex.lock(); for( auto &x : renderObjects ) { x->updateSizeAndPosition(); for( auto &col : x->getCollisions() ) { col->updateCollision(x->collisionPushX(), x->collisionPushY(), x->collisionWidth(), x->collisionHeight()); } } render_mutex.unlock(); } void moveEverything(double x, double y) { checkKilled(); render_mutex.lock(); for( auto &obj : renderObjects ) { auto curPos = obj->getDoubleRect(); obj->setPos( curPos.first.first + x, curPos.first.second + y ); } render_mutex.unlock(); } const std::shared_ptr &leftmost() { return leftmost_obj; } const std::shared_ptr &rightmost() { return rightmost_obj; } std::pair getDimensions() const { return renderer->getDimensions(); } int getWidth() const { return renderer->getWidth(); } int getHeight() const { return renderer->getHeight(); } Renderer &getRenderer() { return *renderer; } void setPrevTicks(int ticks) { prev_ticks = ticks; } private: void checkKilled() { render_mutex.lock(); std::vector killed; for( long unsigned int i = 0; i < renderObjects.size(); i++ ) { if(renderObjects[i]->getKilled()) killed.push_back(i); } std::reverse(killed.begin(), killed.end()); for(auto &index : killed) { renderObjects.erase(renderObjects.begin() + index); } render_mutex.unlock(); } std::vector> renderObjects; std::vector> collisionObjects; std::shared_ptr renderer; std::shared_ptr background; int prev_ticks = 0; std::shared_ptr leftmost_obj; std::shared_ptr rightmost_obj; uint64_t max_object_id = 0; std::mutex render_mutex; }; class Rect : public CollisionPolygon { public: Rect(double x, double y, double w, double 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 int topmost() const override { return (!isInfinite() || original_y != -1) * getY() + isInfinite() * -1; } virtual int bottommost() const override { return (!isInfinite() || h_ != -1) * (getY() + pixel_h) + isInfinite() * -1; }; virtual int leftmost() const override { return (!isInfinite() || original_x != -1) * getX() + isInfinite() * -1; } virtual int rightmost() const override { return (!isInfinite() || w_ != -1) * (getX() + pixel_w) + isInfinite() * -1; } 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; } virtual void render(Renderer &renderer, const std::tuple &color) override { auto rect = getRect(); // outline with desired color at 50% opacity SDL_SetRenderDrawColor(renderer.getRendererPtr(), std::get<0>(color), std::get<1>(color), std::get<2>(color), 0x80); SDL_RenderDrawRect(renderer.getRendererPtr(), &rect); // fill with desired color at 25% opacity SDL_SetRenderDrawColor(renderer.getRendererPtr(), std::get<0>(color), std::get<1>(color), std::get<2>(color), 0x40); SDL_RenderFillRect(renderer.getRendererPtr(), &rect); } virtual void render(Renderer &renderer) override { auto rect = getRect(); SDL_SetRenderDrawColor(renderer.getRendererPtr(), sdl_color.r, sdl_color.g, sdl_color.b, sdl_color.a); SDL_RenderFillRect(renderer.getRendererPtr(), &rect); } private: SDL_Rect getRect() { if( !isInfinite() ) return {leftmost(), topmost(), pixel_w, pixel_h}; SDL_Rect r = {0,0,0,0}; if((r.x = leftmost()) == -1) r.x = 0; if((r.y = topmost()) == -1) r.y = 0; if(rightmost() == -1) r.w = std::numeric_limits::max(); else r.w = pixel_w; if(bottommost() == -1) r.h = std::numeric_limits::max(); else r.h = pixel_h; return r; } double w_; double h_; int pixel_w; int pixel_h; }; class Circle : public CollisionPolygon { public: Circle(double x, double y, double rad) : CollisionPolygon(x, y) { original_rad = rad; } virtual ~Circle() {} virtual bool colidesWith(const CollisionPolygon &other) const override; virtual bool isCircle() const override { return true; } virtual int topmost() const override { return getY() - rad_; } virtual int bottommost() const override { return getY() + rad_; }; virtual int leftmost() const override { return getX() - rad_; } virtual int rightmost() const override { return getX() + rad_; } virtual void updateCollision(int x, int y, int w, int h) override { position_x = original_x * w + x; position_y = original_y * h + y; rad_ = original_rad * w; } virtual void render(Renderer &renderer, const std::tuple &color) override { std::vector rect = {leftmost(), topmost(), rightmost(), bottommost()}; auto center_x = getX(); auto center_y = getY(); auto radsq = rad_ * rad_; for(int i = rect[0]; i <= rect[2]; i++) { auto xdiff = center_x - i; auto xdist = xdiff * xdiff; auto allowed_rad = sqrt(radsq - xdist); SDL_SetRenderDrawColor(renderer.getRendererPtr(), std::get<0>(color), std::get<1>(color), std::get<2>(color), 0x40); SDL_RenderDrawLine(renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y + allowed_rad); SDL_SetRenderDrawColor(renderer.getRendererPtr(), std::get<0>(color), std::get<1>(color), std::get<2>(color), 0x80); SDL_RenderDrawLine(renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y - allowed_rad + 2); SDL_RenderDrawLine(renderer.getRendererPtr(), i, center_y + allowed_rad, i, center_y + allowed_rad - 2); } SDL_SetRenderDrawColor(renderer.getRendererPtr(), 0xFF, 0, 0, 0xFF); SDL_RenderDrawLine(renderer.getRendererPtr(), center_x, center_y, center_x + rad_, center_y); SDL_RenderDrawLine(renderer.getRendererPtr(), center_x, center_y, center_x, center_y + rad_); SDL_RenderDrawLine(renderer.getRendererPtr(), center_x, center_y, center_x - rad_, center_y); SDL_RenderDrawLine(renderer.getRendererPtr(), center_x, center_y, center_x, center_y - rad_); } virtual void render(Renderer &renderer) override { std::vector rect = {leftmost(), topmost(), rightmost(), bottommost()}; auto center_x = getX(); auto center_y = getY(); auto radsq = rad_ * rad_; for(int i = rect[0]; i <= rect[2]; i++) { auto xdiff = center_x - i; auto xdist = xdiff * xdiff; auto allowed_rad = sqrt(radsq - xdist); SDL_SetRenderDrawColor(renderer.getRendererPtr(), sdl_color.r, sdl_color.g, sdl_color.b, sdl_color.a); SDL_RenderDrawLine(renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y + allowed_rad); } } private: int getRadius() const { return rad_; } double original_rad; int rad_; }; 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 dimension = renderer->getSmallerSide(); rect.x = x * dimension; rect.y = y * dimension; rect.w = w * dimension; rect.h = h * dimension; og_x = x_ = x; og_y = y_ = y; og_w = w_ = w; og_h = h_ = h; } RectangleRender(double x, double y, double w, double h, std::shared_ptr &r, std::shared_ptr &t) : RectangleRender(x, y, w, h, r) { setTexture(t); } RectangleRender(double x, double y, double w, double h, std::shared_ptr &r, const std::string &img_or_color, bool is_polygon = false) : RectangleRender(x,y,w,h,r) { if(!is_polygon) { setTexture(img_or_color); } else { setColor(img_or_color); } } virtual void setColor(const std::string &color) { if(texture) texture.reset(); polygon = std::make_shared(0,0,1,1); polygon->setColor(color); polygon->updateCollision(collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight()); } virtual void specialAction(int /*UNUSED*/) {}; virtual void render() { if(texture != NULL && !getHidden()) SDL_RenderCopy(renderer->getRendererPtr(), texture->getTexturePtr(), NULL, &rect); if(hasCollisions() && renderer->getRenderColiders() && !getHidden()) { for(const auto &col : getCollisions()) col->render(*renderer, colider_color); } if(polygon) { polygon->render(*renderer); } } virtual void move(int ticks) { if(permanent) return; auto dimension = renderer->getSmallerSide(); 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; custom_move(ticks); rect.x = x_ * dimension; rect.y = y_ * dimension; for( auto &x : collisions ) { x->updateCollision(collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight()); } if(polygon) polygon->updateCollision(collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight()); } virtual void custom_move(int ticks) {} virtual std::pair,std::pair> getDoubleRect() { return {{x_,y_}, {w_,h_}}; } virtual void setPos(double x, double y) { auto dimension = renderer->getSmallerSide(); x_ = x; y_ = y; rect.x = x_ * dimension; rect.y = y_ * dimension; } virtual int leftmost() { return rect.x; } virtual int topmost() { return rect.y; } virtual int rightmost() { return rect.x + rect.w; } virtual int bottommost() { return rect.y + rect.h; } 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() { if(centerx) actuallyCenterX(); auto dimension = renderer->getSmallerSide(); rect.x = x_ * dimension; rect.y = y_ * dimension; rect.w = w_ * dimension; rect.h = h_ * dimension; if(polygon) polygon->updateCollision(collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight()); } virtual SDL_Rect getRect() { return rect; } virtual void centerX() { centerx = true; updateSizeAndPosition(); } protected: void actuallyCenterX() { auto width = renderer->getWidth(); auto height = renderer->getHeight(); if(width > height) { std::cout << "WIDTH IS LARGER!" << std::endl; std::cout << "og_x: " << og_x << std::endl; auto multiplier = static_cast(width)/static_cast(height); std::cout << "MULTIPLIER: " << multiplier << std::endl; x_ = og_x * multiplier; w_ = og_w * multiplier; std::cout << "X_: " << x_ << std::endl; } else { x_ = og_x; w_ = og_w; } } double og_x; double og_y; double og_w; double og_h; double x_; double y_; double w_; double h_; bool centerx = false; 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_; }; 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