i'm new and still learning OOP and SDL for educational purpose.
so, i have a variable SDL_Renderer renderer. this variable needs to be initiated only once, and i initiate it in GameManager class.
and i have another class named Texture that needs that renderer.
this Texture will be used frequently.
so how do i pass this renderer? do i have to call GameManager in the Texture class? but if i do that, it means that i makeGameManager everytime i use the Texture right? or there is another way around?
thank you for helping me, i'm really sorry if my question is vague or not clear.
EDIT
this is Texture class
class Texture
{
public:
Texture();
~Texture();
int getWidth();
int getHeight();
bool loadFromFile(std::string path);
bool loadTextFromFile(std::string text, SDL_Color textColor, TTF_Font* font);
void render(int x, int y, SDL_Rect* clip = NULL);
void free();
bool lockTexture();
bool unlockTexture();
void* getPixels();
int getPitch();
private:
int vWidth;
int vHeight;
SDL_Texture* vTexture;
SDL_Renderer* renderer;
void* pPixels;
int pPitch;
};
this is the initiator
Texture::Texture()
{
vTexture = NULL;
vWidth = 0;
vHeight = 0;
renderer = GameManager::getRenderer();
}
this is GameManager class
class GameManager
{
public:
GameManager();
~GameManager();
bool intializeGame();
void gameLoop();
static SDL_Renderer* getRenderer();
private:
SDL_Window* window = NULL;
static SDL_Renderer* renderer;
TTF_Font* font = NULL;
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
};
the getRenderer() just a getter to pass the renderer
and this is my Main
int main(int argc, char* args[])
{
GameManager gameManager;
gameManager.intializeGame();
Texture charTexture;
SDL_Rect rect;
bool text = charTexture.loadFromFile("foo.png");
if (!text)
{
printf("texture not loaded");
}
rect.x = 0;
rect.y = 0;
rect.w = charTexture.getWidth();
rect.h = charTexture.getHeight();
while (true)
{
SDL_SetRenderDrawColor(GameManager::getRenderer(), 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(GameManager::getRenderer());
charTexture.render(10, 10, &rect);
SDL_RenderPresent(GameManager::getRenderer());
}
return 0;
}
i hope it's not confusing.
Disclaimer : I've never used SDL. This might be terrible, but it's based on what you gave me.
The important thing is the ownership. This is a shared ownership example. It's quite simple and takes the burden of figuring out when to destroy SDL_Renderer off of you.
class GameManager {
//BLABGLABLA
public:
std::shared_ptr<SDL_Renderer> getRenderer();
private:
std::shared_ptr<SDL_Renderer> renderer
}
class Texture
{
public:
Texture(std::shared_ptr<SDL_Renderer> theRenderer, //some other args)
private:
std::shared_ptr<SDL_Renderer> renderer;
}
So based on the name of the classes alone, you probably want GameManager to own the renderer, but you also want Texture to have access to it.
One way you can do that is to have a shared_ptr member in both classes, and to pass the renderer to texture in its constructor.
Basically the renderer object you initialized in GameManager, will only be destroyed when the last shared_ptr pointing to it is destroyed.
Related
I've been following LazyFoo's SDL tutorials (and also adding my own organization and coding style). When I got to his animation tutorial I decided to make a separate class to store the variables and methods related to the animation algorithm, rather than having global variables. He uses an array of SDL_Rects to define the boundaries of different sprites on a sprite sheet, so I used an SDL_Rect pointer to store the array in my custom class. When I compiled everything I didn't see an animation, when I compiled the original source code I did. When I started debugging things, I found that when I was rendering the sprites, the rects were actually full of garbage, even though when I initialize them the rects are just fine. I've tried to simplify the problem so many times, but every approach I take to recreate the bug in a simpler environment actually works as expected! So with that in mind I apologize for the large amount of code, because I can't seem to reduce the problem.
texture.h
#ifndef TEXTURE_H
#define TEXTURE_H
#include <SDL2/SDL.h>
#include <string>
class Animation {
public:
Animation(SDL_Renderer* renderer);
~Animation();
void load(std::string path, int frames, SDL_Rect* clips),
free(),
render(int x, int y),
next_frame();
private:
SDL_Renderer* _renderer=NULL;
SDL_Rect* _clips=NULL;
SDL_Texture* _texture=NULL;
int _frame=0, _frames=0, _width=0, _height=0;
};
#endif
texture.cpp
#include <stdio.h>
#include <SDL2/SDL_image.h>
#include "texture.h"
#include "error.h"
Animation::Animation(SDL_Renderer* renderer) {
_renderer = renderer;
}
Animation::~Animation() {
free();
_renderer = NULL;
}
void Animation::load(std::string path, int frames, SDL_Rect* clips) {
free();
SDL_Texture* texture = NULL;
SDL_Surface* surface = IMG_Load(path.c_str());
if (!surface)
throw ErrorIMG("Could not load image "+path);
SDL_SetColorKey(surface, SDL_TRUE,
SDL_MapRGB(surface->format, 0, 0xFF, 0xFF));
texture = SDL_CreateTextureFromSurface(_renderer, surface);
if (!texture)
throw ErrorSDL("Could not create texture from image "+path);
_width = surface->w;
_height = surface->h;
SDL_FreeSurface(surface);
_frames = frames;
_clips = clips;
printf("clips[%d]: w: %d h: %d\n", 0, _clips[0].w, _clips[0].h);
}
void Animation::free() {
if (_texture) {
SDL_DestroyTexture(_texture);
_texture = NULL;
_clips = NULL;
_frames = 0;
_frame = 0;
_width = 0;
_height = 0;
}
}
void Animation::render(int x, int y) {
SDL_Rect crect = _clips[_frame/4];
printf("in render (clips[%d]): w: %d, h: %d\n", _frame/4, crect.w, crect.h);
SDL_Rect render_space = {x, y, crect.w, crect.h};
SDL_RenderCopy(_renderer, _texture, &_clips[_frame], &render_space);
}
void Animation::next_frame() {
SDL_Rect crect = _clips[_frame/4];
printf("in next frame (clips[%d]): w: %d, h: %d\n", _frame/4, crect.w, crect.h);
++_frame;
if (_frame/4 >= _frames)
_frame = 0;
}
game.h
#ifndef GAME_H
#define GAME_H
#include "texture.h"
class Game {
public:
Game();
~Game();
void main();
private:
void load_media();
SDL_Window* _window=NULL;
SDL_Renderer* _renderer=NULL;
Animation* _anim=NULL;
const int SCREEN_WIDTH=640, SCREEN_HEIGHT=480;
};
#endif
game.cpp
#include <SDL2/SDL_image.h>
#include "game.h"
#include "error.h"
void Game::main() {
load_media();
bool has_quit = false;
SDL_Event event;
while (!has_quit) {
while (SDL_PollEvent(&event))
if (event.type == SDL_QUIT)
has_quit = true;
SDL_SetRenderDrawColor(_renderer, 0xff, 0xff, 0xff, 0xff);
SDL_RenderClear(_renderer);
_anim->render(100, 100);
_anim->next_frame();
SDL_RenderPresent(_renderer);
}
}
Game::Game() {
if (SDL_Init(SDL_INIT_VIDEO))
throw ErrorSDL("SDL could not initialize");
_window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (!_window)
throw ErrorSDL("Window could not be created");
Uint32 render_flags = SDL_RENDERER_ACCELERATED;
render_flags |= SDL_RENDERER_PRESENTVSYNC;
_renderer = SDL_CreateRenderer(_window, -1, render_flags);
if (!_renderer)
throw ErrorSDL("Renderer could not be created");
SDL_SetRenderDrawColor(_renderer, 0xff, 0xff, 0xff, 0xff);
if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG))
throw ErrorIMG("SDL_image could not initialize");
}
Game::~Game() {
delete _anim;
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
_renderer = NULL;
_window = NULL;
IMG_Quit();
SDL_Quit();
}
void Game::load_media() {
const int nclips = 4;
SDL_Rect clips[nclips];
for (int i=0; i < nclips; i++) {
clips[i].x = i*64;
clips[i].y = 0;
clips[i].w = 64;
clips[i].h = 164;
}
_anim = new Animation(_renderer);
_anim->load("sheet.png", nclips, &clips[0]);
}
You're storing a pointer to a temporary. The SDL_Rect* clips pointer that you pass to Animation::load is assigned to the member _clips used after the function returns. For this to work correctly, the data that is pointed to needs to live for as long as the Animation class is using it. The problem arises here:
void Game::load_media() {
const int nclips = 4;
SDL_Rect clips[nclips];
...
_anim->load("sheet.png", nclips, &clips[0]);
}
In this piece of code, clips is a local variable. That means it gets destroyed at the end of load_media(), and the memory contents at that location will become garbage.
There are a number of ways you could fix this. A simple one would be to use std::vector<SDL_Rect> instead of SDL_Rect*. std::vector can safely be copied and manages its internals for you. Your new code could look like:
class Animation {
...
std::vector<SDL_Rect> _clips;
...
}
void Animation::load(std::string path, int frames, std::vector<SDL_Rect> clips) {
...
_clips = clips;
...
}
void Game::load_media() {
const int nclips = 4;
std::vector<SDL_Rect> clips;
clips.resize(nclips);
...
_anim->load("sheet.png", nclips, clips);
}
And dont forget to #include <vector>. Documentation for std::vector is here. Note that std::vector has a size() method that can probably replace frames everywhere it appears.
The stack-allocated Game::load_media()::clips array disappears when it goes out of scope. Make a copy in Animation::load() instead of only storing a pointer.
I'm using C++ with the SDL2 library to create a game. I'm using the SDL_ttf extension to be able to use ttf fonts and I'm trying to create my own class that would be more effective for multiple texts on the screen. The code I currently have starts out good, then crashes after about 15 seconds of running. I added more text and now it crashes after about 5 or 7 seconds. I'm looking for advice on how to solve this problem. my full Font class is as follows:
Font.h
#pragma once
#include "Graphics.h"
#include <string>
class Font
{
public:
Font(std::string path, SDL_Renderer* renderer);
~Font();
void FreeText();
void LoadText(int size, RGB_COLOR color, std::string text);
void Draw(int x, int y, Graphics& gfx, int size, RGB_COLOR color, std::string text);
private:
int width,height;
TTF_Font* font;
SDL_Texture* mTexture;
SDL_Renderer* renderer;
std::string path;
};
Font.cpp
#include "Font.h"
Font::Font(std::string path, SDL_Renderer* renderer)
:
font(NULL),
mTexture(NULL),
renderer(renderer),
path(path)
{
printf("Font con..\n");
}
Font::~Font()
{
}
void Font::LoadText(int size, RGB_COLOR color, std::string text)
{
font = TTF_OpenFont(path.c_str(), size);
SDL_Color c = {color.RED, color.GREEN, color.BLUE};
SDL_Surface* loadedSurface = TTF_RenderText_Solid(font, text.c_str(), c);
mTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
width = loadedSurface->w;
height = loadedSurface->h;
SDL_FreeSurface(loadedSurface);
}
void Font::FreeText()
{
SDL_DestroyTexture(mTexture);
mTexture = NULL;
}
void Font::Draw(int x, int y, Graphics& gfx, int size, RGB_COLOR color, std::string text)
{
FreeText();
LoadText(size, color, text);
SDL_Rect rect = {x, y, width * gfx.GetGameDims().SCALE, height * gfx.GetGameDims().SCALE};
gfx.DrawTexture(mTexture, NULL, &rect);
}
My Graphics class just handles the actual drawing as well as dimensions of the game (screen size, tile size, color struct, gamestates, etc) So when I'm calling gfx.Draw it calls SDL_RenderCopy function.
Within my Game class I have a pointer to my Font class. (its called in my Game constructor) Then font->Draw() is called every frame; which destroys the original SDL_Texture, Loads the new text, then renders it on the screen.
My ultimate goal is to have my font class set up to where I choose the color and size from my draw function. Not sure what to check from this point on..
Any suggestions? Ideas?
This is what I get (which is what I want) but then it crashes.
I've managed to get it working. After searching a little more on SDL_ttf, I realized that in my FreeFont() function I was clearing out the SDL_Texture, however I did nothing with the TTF_Font.
Adding these lines in that function did the trick:
TTF_CloseFont(font);
font = NULL;
First question here, and answer is probably very simple, but i can't figure it out. To the point:
In my project i created 2 classes: "GlobalVairables" and "SDLFunctions".
Obviously, in first one i want to store globals which i could relate to in any other class, and in the second on i got few functions using those globals. Here's code:
GlobalVariables.h
#pragma once
class GlobalVariables
{
public:
GlobalVariables(void);
~GlobalVariables(void);
const int SCREEN_WIDTH;
const int SCREEN_HEIGHT;
//The window we'll be rendering to
SDL_Window* gWindow;
//The surface contained by the window
SDL_Surface* gScreenSurface;
//The image we will load and show on the screen
SDL_Surface* gHelloWorld;
};
and GlobalVariables.cpp
#include "GlobalVariables.h"
GlobalVariables::GlobalVariables(void)
{
const int GlobalVairables::SCREEN_WIDTH = 640;
const int GlobalVariables::SCREEN_HEIGHT = 480;
SDL_Window GlobalVairables:: gWindow = NULL;
SDL_Surface GlobalVariables:: gScreenSurface = NULL;
SDL_Surface GlobalVariables:: gHelloWorld = NULL;
}
GlobalVariables::~GlobalVariables(void)
{
}
and here is one function in SDLFunction.cpp, that uses "gWindow" and 2 other variables:
gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
My problem is, that on debugging, i get
error C2065: 'gWindow' : undeclared indentifier
Of course, in SDLFunctions.cpp i got "#include "GlobalVariables.h" ". Also, those variables are public, so it's not this (probably).
Can someone tell what's wrong? Is there some simple solution, or do i have to reorganize it, and shouldn't use globals? Please help.
First of all, your variables are members of every instance of the class, and as such, are not globals in usual meaning. You might want to declare them static. What is even better, do not create a class for them at all - instead, put them into namespace. Something like following (in your .h file):
namespace globals {
static const unsigned int SCREEN_WIDTH = 640;
static const unsigned int SCREEN_HEIGHT = 1024;
}
Than you can reference them in your code in a following manner:
int dot = globals::SCREEN_WIDTH;
I have this image:
I want to generate the set of sprites, each 32x32 in size. How can I do this with C++, no library used.
Texture class:
class Texture
{
protected:
// We cannot create or copy base class texture objects
// We will only ever have pointers or references to base
// class texture objects used in our program (and these
// will refer to derived class textures
Texture() = default;
Texture(Texture const &) = default;
Texture & operator=(Texture const &) = default;
virtual void LoadFromFile(std::string const & strFileName) = 0;
virtual void LoadFromResource(unsigned int rid) = 0;
public:
virtual ~Texture(){}
// virtual Rect const & GetBounds() const = 0;
virtual int Width() const = 0;
virtual int Height() const = 0;
};
using TEXTURE_PTR = std::shared_ptr<Texture>;
Sprite class:
class Sprite
{
private:
virtual void draw_impl(Canvas & c) = 0;
TEXTURE_PTR m_pTexture;
protected:
// Ensure that Sprite objects can only be constructed by derived classes
explicit Sprite(TEXTURE_PTR pt = nullptr,POINT2f const & p = { 0, 0 });
// All Sprite objects have a position state variable
POINT2f m_position;
// Sprite objects can only be copied by derived class objects
Sprite(const Sprite&) = default;
Sprite& operator=(const Sprite&) = default;
public:
virtual ~Sprite(){}
void OnDraw(Canvas & c);
void SetPosition(POINT2f const & pos);
POINT2f const & GetPosition() const;
void SetTexture(TEXTURE_PTR pt);
};
AND i create the sprite this way:
TEXTURE_PTR pLightning = std::make_shared<Texture>("resource//Lightning.bmp", RGB(255, 0, 255));
std::shared_ptr<Sprite> pSpark = std::make_shared<Sprite>(pLightning);
How can I generate 9 sprites from the above image with this method?
Edit
I come up with these code but still doesn't work
class WinTexture : public Texture
{
protected:
HBITMAP m_hbmImage;
HBITMAP m_hbmMask;
BITMAP m_bmParam;
virtual void LoadFromResource(UINT rid);
virtual void LoadFromFile(std::string const & strFileName);
void CreateMask(DWORD dwTransparent);
public:
// Construct from Windows Resource
WinTexture(UINT uid, COLORREF dwTransparent);
// Constructor from file load
WinTexture(std::string const & strFilename, COLORREF dwTransparent);
//Contruct from other Texture
WinTexture(std::shared_ptr<WinTexture> wt, int xStart,int yStart, int w, int h);
virtual ~WinTexture();
// Inherited interface
// virtual Rect const & GetBounds() const;
virtual int Width() const;
virtual int Height() const;
HBITMAP ImageHandle() const;
HBITMAP MaskHandle() const;
};
with this, I want to make a constructor to create from other WinTexture:
WinTexture::WinTexture(std::shared_ptr<WinTexture> wt, int xStart, int yStart, int w, int h)
: Texture(), // as above
m_hbmImage(NULL),
m_hbmMask(NULL) {
HDC hdcMem1 = CreateCompatibleDC(0);
HDC hdcMem2 = CreateCompatibleDC(0);
m_hbmImage = CreateBitmap(w, h, 1, 1, NULL);
//m_hbmImage = CreateCompatibleBitmap(hdcMem2, 1, 1);
SelectObject(hdcMem1, wt->ImageHandle());
SelectObject(hdcMem2, m_hbmImage);
BitBlt(hdcMem2, xStart, yStart, w, h,hdcMem1, 0, 0, SRCCOPY);
BitBlt(hdcMem1, xStart, yStart, w, h, hdcMem2, 0, 0, SRCINVERT);
//SaveDC(hdcMem2);
DeleteDC(hdcMem1);
DeleteDC(hdcMem2);
CreateMask(RGB(0, 0, 0));
}
EDIT
Currently, I have created this class from Sprite:
class TexturedSprite : public Sprite
{
private:
TEXTURE_PTR m_pTexture;
virtual void draw_impl(Canvas & c);
protected:
public:
explicit TexturedSprite(TEXTURE_PTR pt = nullptr, POINT2f pos = { 32, 32});
explicit TexturedSprite(int xStart,int yStart, int w, int h,TEXTURE_PTR pt = nullptr, POINT2f pos = { 32, 32 });
virtual ~TexturedSprite(){}
void SetTexture(TEXTURE_PTR pt);
};
I can't figure out how to implement the second constructor, to copy a part of input texture (pt):
TexturedSprite::TexturedSprite(int xStart, int yStart, int w, int h, TEXTURE_PTR pt , POINT2f pos )
:Sprite(pos)
{
HDC hdcMem1 = CreateCompatibleDC(0);
HDC hdcMem2 = CreateCompatibleDC(0);
//How to assign values to DC?
BitBlt(hdcMem1, 32, 32, w, h, hdcMem2, xStart, yStart, SRCCOPY);
DeleteDC(hdcMem1);
DeleteDC(hdcMem2);
}
At least as I read things right now, your basic intent is to load the texture, then create the individual sprites by copying 32x32 pixel pieces of the texture into the individual sprites. Unless you intend to manipulate the sprites from separate threads (which strikes me as unlikely) I'd avoid doing that.
Instead, I'd take note of a couple of the last parameters you supply to BitBlt:
BitBlt(hdcMem2, xStart, yStart, w, h,hdcMem1, 0, 0, SRCCOPY);
The 0, 0 just before the SRCCOPY specify the location in the source bitmap to use as the starting point of the BitBlt.
This lets you load the texture once, and just use that single texture for all the sprites it contains. Drawing an individual sprite at a particular location only requires that you specify the X, Y coordinates of that sprite within the source bitmap (specifically, the top, left-hand corner of that sprite) and draw a 32x32 chunk starting from there. If you want to define individual sprite objects, you can certainly do that, but each just needs to store something like a shared_ptr to the loaded texture, and the X, Y coordinates of its piece of the texture.
class Sprite {
shared_ptr<Texture> tex;
int x, y;
public:
Sprite(shared_ptr<Texture> t, int x, int y) tex(t), x(x), y(y) {}
void draw(HDC dc, int dest_x, int dest_y) {
BitBlt(dc, dest_x, dest_y, 32, 32, *tex, x, y, SRCCOPY);
}
};
If you really don't want to use any libraries you Need to manually decode the BMP file. Look at this Wikipedia entry for more Information about the structure of the BMP file Format.
My code:
window.cpp
Window::Window(int w, int h, const char *title, const char *icon)
{
height = h;
width = w;
if(SDL_Init( SDL_INIT_EVERYTHING ) == 0)
{
SDL_WM_SetCaption(title, NULL);
SDL_WM_SetIcon(SDL_LoadBMP(icon),NULL);
screen = SDL_SetVideoMode(width, height, 32,
SDL_SWSURFACE | SDL_RESIZABLE | SDL_DOUBLEBUF);
if(screen == NULL)
{
running = false;
return;
}
fullscreen = false;
}
else
running = false;
return;
}
Window::Window()
{
const SDL_VideoInfo* info = SDL_GetVideoInfo();
screenWidth = info->current_w;
screenHeight = info->current_h;
Window(640, 480, "Flatgu game", "rsc/img/icon.bmp");
}
window.h
class Window
{
public:
Window();
~Window();
int getWidth() {return width;}
int getHeight() {return height;}
bool isFullscreen() {return fullscreen;}
void toggleFullscreen();
private:
Window(int w, int h, const char *title, const char *icon);
bool fullscreen, running;
int height, width, screenWidth, screenHeight;
SDL_Surface *screen;
};
It compiles fine, but then, after compiling, I'm getting this ugly error:
What's the reason of my problem? Why do I get so weird numbers?
My aim is to store original screen resolution for further use (like toggling to fullscreen), and I have to do this before calling SDL_SetVideoMode(). That's why it is in the constructor.
You have a problem with calling SDL Video Functions before actually initializing SDL.
SDL_Init( SDL_INIT_EVERYTHING )
has to be called before
SDL_GetVideoInfo();
In your case you call SDL_GetVideoInfo(); first
const SDL_VideoInfo* info = SDL_GetVideoInfo(); //<-- calls SDL_GetVideoInfo();
screenWidth = info->current_w;
screenHeight = info->current_h;
Window(640, 480, "Flatgu game", "rsc/img/icon.bmp"); //<-- initializes SDL
So the solution is simple; make the call SDL_Init( SDL_INIT_EVERYTHING ) immediately at the start of your program, then you can call SDL_GetVideoInfo(); as much as you like.
You will have to restructure your class Window slightly.
To get the best video mode call SDL_GetVideoInfo before setting up the video (before calling SDL_SetVideoMode).
But you still have to initialize the video subsystem before calling it (SDL_Init(SDL_INIT_VIDEO)).
I know this is old, but there's a big mistake in the code.
Window(640, 480, "Flatgu game", "rsc/img/icon.bmp");
creates a nameless instance of a Window, so the instance that calls it will still have uninitialized variables. It looks like you were trying to use delegating constructors, but in that case the call to the other constructor must be in the member initializer list.
See this page.