c++ - Can't draw multiple objects using SDL? - Stack Overflow

admin2025-05-02  2

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.

Share Improve this question edited Jan 2 at 14:25 genpfault 52.2k12 gold badges91 silver badges151 bronze badges asked Jan 2 at 11:49 Michael ScottMichael Scott 744 bronze badges 14
  • A little note about your design: I recommend you don't store the renderer in the game object. Instead pass it as an argument to the 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
  • The main reason it wouldn't build if you copied the code is because I cut out all of the imports. All the code does is create a window, two instances of the GameObject class at different positions, and try to draw both of them to the screen. The result is only the most recent instance of the GameObject class being drawn and any others not appearing to draw. I don't understand what more I could give that would help you find the problem. – Michael Scott Commented Jan 2 at 12:05
  • 1 As for the problem, it's likely an issue with the code not following the rules of three, five or zero (assuming that part is really missing from your actual code). – Some programmer dude Commented Jan 2 at 12:07
  • Thanks for the suggestion to not store the renderer in the class, I'll change it once the problem is fixed. – Michael Scott Commented Jan 2 at 12:07
  • Ah, I'm an idiot. AceClub and AceSpades were the names of the objects and images before I changed them to placeholders for the code block. Sorry for the confusion – Michael Scott Commented Jan 2 at 12:09
 |  Show 9 more comments

2 Answers 2

Reset to default 7

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 GameObjects, 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.

转载请注明原文地址:http://www.anycun.com/QandA/1746122454a91983.html