2

I'm working on a program using SDL. I have the following variable instantiated outside of my main function:

std::vector<SDL_Rect[TOTAL_ACTIVITIES][TOTAL_DIRECTIONS][ANIMATION_FRAMES]> gPersonSpriteClips;

(where the all-caps items are constant integers). It's a vector of arrays, because I intend to do a lot of sprite development later. For now, I just have a few placeholder sprites. So, the 3D array captures all aspects of a single human body-type spritesheet, and when I pull in the sheet I'll have the code split it up into multiple body types...

I have a class, "Character", which makes use of the array when it is preparing to render. Here is the call to render myCharacter in main:

myCharacter.render(bodySpritesheetTexture, &*gPersonSpriteClips[myCharacter.getBody()]);

Character.getBody() returns the vector index for the body type used by this instance of character. Here is the function declaration in Character.h:

void render(LTexture& characterSpriteSheetTexture, SDL_Rect spriteClips[TOTAL_ACTIVITIES][TOTAL_DIRECTIONS][ANIMATION_FRAMES]);

Here is the function itself in Character.cpp:

void Character::render(LTexture& characterSpriteSheetTexture, SDL_Rect spriteClips[TOTAL_PERSON_ACTIVITIES][TOTAL_CARDINAL_DIRECTIONS][PERSON_ANIMATION_FRAMES])
{
    characterSpriteSheetTexture.render( mPosition.x, mPosition.y, &spriteClips[mActivity][mDirection][mAnimations[mActivity][mCurrentFrame]] );
}

mActivity is the enum for "which activity is presently being performed" by the character. mDirection is which direction he's facing. mAnimations lists frame orders (because some of these animations use the same sprites several times in various orders).

Finally, the render function for LTexture is taken right out of LazyFoo's SDL tutorials.

characterSpriteSheetTexture.render() is calling a function with the following declaration (in LTexture.h):

void render( int x, int y, SDL_Rect* clip = NULL, int targetHeight = NULL, int targetWidth = NULL, double angle = 0.0, SDL_Point* center = NULL, SDL_RendererFlip flip = SDL_FLIP_NONE );

So it's expecting an argument of type SDL_Rect* clip. That function makes a call to:

SDL_RenderCopyEx( gRenderer, mTexture, clip, &renderQuad, angle, center, flip );

I get an error when I compile the code, pointing me to a place inside the SDL libraries. I've successfully employed LTexture's render function by passing indexes from one-dimensional arrays of SDL_Rect into it, so I'm very confident that the problem exists in my vector argument.

Ignoring disagreements about naming conventions...... what am I doing wrong here, and what's the proper way to achieve what I'm trying to do?

Granted, I'll probably simplify my scheme for animating things significantly in the future; this is my first shot at generating a player character. Anyway, I'd like to know the mechanics of what's going wrong in this case.

Last thing, please don't suggest that I use boost. I'd really like to avoid boost for this program.

ETA:

Here is the declaration of mAnimations in Character.h:

std::vector<int> mAnimations[TOTAL_ACTIVITIES];

Here is the initialization of it in the default constructor:

for (int i = 0; i < TOTAL_ACTIVITIES; i++)
    {
        mAnimations[i].resize(ANIMATION_FRAMES);
        for (int j = 0; j < ANIMATION_FRAMES; j++)
        {
            mAnimations[i][j] = j;
        }
    }
boxcartenant
  • 259
  • 1
  • 14
  • in absence of other relevant parts of your code I've made an attempt to help based solely on what's in the original question. Let me know if this helps. – Geezer Aug 22 '18 at 17:17
  • @SkepticalEmpiricist Yeah, feel free to let me know what part you'd like to know more about (for example, if you think it would be valuable to see the full render function in LTexture, the actual error message, etc). I tried to give everything leading up to this part, directly. I'll help where I can. And thanks! – boxcartenant Aug 22 '18 at 17:31
  • maybe just see how `mAnimations` is declared in your code. In the meantime let me know if my edited answer helps any. – Geezer Aug 22 '18 at 17:41
  • @SkepticalEmpiricist I made an ETA. I'll take a look at your edited answer and come back to you in a bit. – boxcartenant Aug 22 '18 at 17:46

2 Answers2

2

Change your global array definition like this:

using SdlRectArray = std::array<std::array<std::array<SDL_Rect, ANIMATION_FRAMES>, TOTAL_DIRECTIONS>, TOTAL_ACTIVITIES>;
std::vector<SdlRectArray> gPersonSpriteClips;

See here for why not to store arrays in a std::vector or any other container.

Accordingly, define your Character::render() method like this:

void render(LTexture& characterSpriteSheetTexture, SdlRectArray& spriteClips)
{
    characterSpriteSheetTexture.render( mPosition.x, mPosition.y, &spriteClips[mActivity][mDirection][mAnimations[mActivity][mCurrentFrame]] );
}

And then you can call it like this:

myCharacter.render(bodySpritesheetTexture, gPersonSpriteClips[myCharacter.getBody()]);

as there's no need for doing &*.

Geezer
  • 5,339
  • 14
  • 28
1

It is difficult to tell from your question what the exact error is, but it seems likely that the root cause is "array decay".

In C++ (and in C), array arguments are almost always treated as pointers to the start of the array. For a multidimensional array, this means that the multiple-indexing you expect can't work, because the language has stripped away the size and shape information necessary.

The sole exception is that reference parameters do not decay in this way. However, if you rely on this, you (and anyone else who works on your code) will need to remember to use that exact reference parameter every time they pass the multidimensional array to another function.

In modern C++, this issue is typically fixed using std::array<>, which was designed for the purpose. In your case, that would be very verbose, so I'd suggest a type alias:

typedef std::array< std::array< std::array<SDL_Rect,
  ANIMATION_FRAMES>, TOTAL_DIRECTIONS>, TOTAL_ACTIVITIES> SpriteClip;

std::vector<SpriteClip> gSpriteClips;

Alternatively, you could write a short class:

class SpriteClip {
  SDL_Rect m_data[TOTAL_ACTIVITIES][TOTAL_DIRECTIONS][ANIMATION_FRAMES];
public:
  SDL_Rect& get(activity, direction, frame) {
    return m_data[activity][direction][frame];
  }
};

This would let you change the representation easily -- for example, if you decided that you wanted different sprites to have different numbers of activities and frames.

comingstorm
  • 23,012
  • 2
  • 38
  • 64