I got SDL to draw sprites and created a GameObject class that stores the relevant information to draw the sprite:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <SDL.h>
#include <SDL_image.h>
using namespace std;
class GameObject
{
private:
SDL_Texture* texture; // The sprite's texture
SDL_Rect rect; // Position and size of the sprite
SDL_Renderer* renderer; // Renderer to draw on
public:
GameObject(SDL_Renderer* renderer, const char* filepath, int x, int y) : texture(nullptr), renderer(renderer) {
rect.x = x;
rect.y = y;
SDL_Surface* surface = IMG_Load(filepath);
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
SDL_QueryTexture(texture, nullptr, nullptr, &rect.w, &rect.h); // Set size from texture
}
~GameObject() {SDL_DestroyTexture(texture);}
void draw() {
if (texture) {
SDL_RenderCopy(renderer, texture, nullptr, &rect);
}
}
};
int main(int, char**)
{
const short FPS = 60;
const int frameDelay = 1000 / FPS;
Uint32 frameStart;
int frameTime;
short testest = 0;
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window* window = SDL_CreateWindow("title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 960, SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Surface* screen = SDL_GetWindowSurface(window);
vector<GameObject> gameObjects;
GameObject TestObj1(renderer, "assets//test.png", 10, 20);
GameObject TestObj2(renderer, "assets//test.png", 80, 20);
gameObjects.push_back(TestObj1);
gameObjects.push_back(TestObj2);
bool quit = false;
while (!quit)
{
frameStart = SDL_GetTicks();
SDL_Event ev;
while (SDL_PollEvent(&ev))
{
switch (ev.type)
{
case SDL_QUIT:
quit = true;
break;
}
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
// Loop through game objects
for (auto& gameObject : gameObjects) {
gameObject.draw();
}
SDL_RenderPresent(renderer);
frameTime = SDL_GetTicks() - frameStart;
if (frameDelay > frameTime)
{
SDL_Delay(frameDelay - frameTime);
}
}
return 0;
}
However, whenever I try to draw multiple sprites to the screen it only draws the last one.
I initially thought it was an issue with the loop, but I replaced the loop with code that manually calls the draw function for each object and the results are the same. I now think it's an issue with how I set up the GameObject class itself, perhaps the x and y values are being overwritten which causes the sprites to draw on top of each other.
Edit: I changed the code so that one object had a different sprite with different dimensions, but it didn't change anything. Again, I think it's an issue with the final instance of GameObject somehow overriding the data of all the other instances, but I don't know enough about OOP to know how to fix it.
I got SDL to draw sprites and created a GameObject class that stores the relevant information to draw the sprite:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <SDL.h>
#include <SDL_image.h>
using namespace std;
class GameObject
{
private:
SDL_Texture* texture; // The sprite's texture
SDL_Rect rect; // Position and size of the sprite
SDL_Renderer* renderer; // Renderer to draw on
public:
GameObject(SDL_Renderer* renderer, const char* filepath, int x, int y) : texture(nullptr), renderer(renderer) {
rect.x = x;
rect.y = y;
SDL_Surface* surface = IMG_Load(filepath);
texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
SDL_QueryTexture(texture, nullptr, nullptr, &rect.w, &rect.h); // Set size from texture
}
~GameObject() {SDL_DestroyTexture(texture);}
void draw() {
if (texture) {
SDL_RenderCopy(renderer, texture, nullptr, &rect);
}
}
};
int main(int, char**)
{
const short FPS = 60;
const int frameDelay = 1000 / FPS;
Uint32 frameStart;
int frameTime;
short testest = 0;
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window* window = SDL_CreateWindow("title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 960, SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Surface* screen = SDL_GetWindowSurface(window);
vector<GameObject> gameObjects;
GameObject TestObj1(renderer, "assets//test.png", 10, 20);
GameObject TestObj2(renderer, "assets//test.png", 80, 20);
gameObjects.push_back(TestObj1);
gameObjects.push_back(TestObj2);
bool quit = false;
while (!quit)
{
frameStart = SDL_GetTicks();
SDL_Event ev;
while (SDL_PollEvent(&ev))
{
switch (ev.type)
{
case SDL_QUIT:
quit = true;
break;
}
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
// Loop through game objects
for (auto& gameObject : gameObjects) {
gameObject.draw();
}
SDL_RenderPresent(renderer);
frameTime = SDL_GetTicks() - frameStart;
if (frameDelay > frameTime)
{
SDL_Delay(frameDelay - frameTime);
}
}
return 0;
}
However, whenever I try to draw multiple sprites to the screen it only draws the last one.
I initially thought it was an issue with the loop, but I replaced the loop with code that manually calls the draw function for each object and the results are the same. I now think it's an issue with how I set up the GameObject class itself, perhaps the x and y values are being overwritten which causes the sprites to draw on top of each other.
Edit: I changed the code so that one object had a different sprite with different dimensions, but it didn't change anything. Again, I think it's an issue with the final instance of GameObject somehow overriding the data of all the other instances, but I don't know enough about OOP to know how to fix it.
The problem is that your GameObject
doesn't implement the rules of three, five or zero.
When you add elements to the vector, it might need to be resized. This involves creating new object and either copying or moving the old object into the new objects, and then the original object is destructed.
The problem with this is that your destructor free the texture, but you have two object that both point to the very same texture. When the original object is destructed, it free the texture for both the original and copy.
That in turn means that the copy no longer have a valid texture, the pointer it uses is invalid, and you will have undefined behavior.
The simplest solution is the rule of three, where you implement the GameObject
copy-constructor and copy-assignment operator.
A better solution (IMO) is the rule of zero, where you use smart pointers (like std::shared_ptr
or std::unique_ptr
, depending on if you want the ownership of the texture to be shared or not) and then you can skip copy- or move-constructors as well as the corresponding assignment operators, and also remove the destructor as it's no longer needed.
The second solution, using smart pointers and their built-in copy-/move-semantics is a better solution, since copying a SDL_Texture
seems to ber flawed (but possible, see this answer). It's just not designed to be fully copyable.
The first call to push_back
grows your vector from 0 to 1 elements.
Your second call to push_back
causes the vector to grow again, from 1 to 2 elements.
This second growth requires copying the existing element to the new backing store, which first copies the fields to a new object and then destroys the original GameObject, which frees your texture.
If you will only ever have a fixed number of GameObject
s, you can construct the vector in one go like so:
vector<GameObject> gameObjects{ TestObj1, TestObj2 };
That will avoid the copying.
A more scalable approach is std::shared_ptr
, which can correctly manage the texture for you. It will keep a count of the number of "alive" pointers to your texture and only free the image when the last object goes out of scope. Change the class definition of texture
to:
std::shared_ptr<SDL_Texture> texture;
and in your constructor, do
texture = std::shared_ptr<SDL_Texture>(SDL_CreateTextureFromSurface(renderer, surface), SDL_DestroyTexture);
This will create a std::shared_ptr
that will automatically call SDL_DestroyTexture
at the right time.
You can now remove your destructor and satisfy the rule of three/five/zero.
draw
function. That will make it more flexible, and allows multiple renderers (like an off-screen renderer, or separate renderers for the game and the UI). – Some programmer dude Commented Jan 2 at 12:04