Storing sprites from a sheet
I'm trying to code a simulation game in c++/allegro, and I'm having trouble storing the sprites... I made a SpriteSheet class with member BITMAP* m_sprites[10][16], there being 10 different characters and 16 frames of animation for each. getting m_sprites set up is no problem... its accessing m_sprites that's messing me up. What I would like to do is have a function such as:
BITMAP* SpriteSheet::getCharacter(int Row);
The return type is wrong, I know... I want to return a pointer to the first element of m_sprites[Row]... so I should be returning a pointer to a pointer... I don't know how to do that.
Also, the compiler keeps telling me that when I try to return m_sprites[Row] for the function above, I'm returning BITMAP*[16] and not a BITMAP*... so how would I return a pointer to the first element of m_sprites[Row]??
instead of declaring m_sprites as:
BITMAP* m_sprites[10][16];
declare them like so:
BITMAP* m_sprites;
then in your constructor:
m_sprites = new BITMAP[10][16];
m_sprites[0][0]. ...
m_sprites[1][0]. ...
then in your function that returns a row of sprites via pointer:
//...
return &m_sprites[Row];
dont forget to delete m_sprites:
delete [] m_sprites; m_sprites = 0;
Im not even sure if that will work, your method is kinda confusing to me, sorry.
The main thing to point out here is, are you actually creating the sprite sheet anywhere, i see you have
BITMAP* m_sprites[10][16];
As you may know that is only a 2-D array of pointers, you need to either assign address' to those pointers or use new on all of the different elements..
BITMAP* m_sprites[10][16];
declare them like so:
BITMAP* m_sprites;
then in your constructor:
m_sprites = new BITMAP[10][16];
m_sprites[0][0]. ...
m_sprites[1][0]. ...
then in your function that returns a row of sprites via pointer:
//...
return &m_sprites[Row];
dont forget to delete m_sprites:
delete [] m_sprites; m_sprites = 0;
Im not even sure if that will work, your method is kinda confusing to me, sorry.
The main thing to point out here is, are you actually creating the sprite sheet anywhere, i see you have
BITMAP* m_sprites[10][16];
As you may know that is only a 2-D array of pointers, you need to either assign address' to those pointers or use new on all of the different elements..
BITMAP** SpriteSheet::getCharacter(int Row);
will fix your compiler problem.
I'd suggest going about this differently though. What you're doing is not very extensible. Well, it's fine for a simple game, but it should be clear you can't add more characters easily or more frames of animation. Having lots of sprites on one image is fine, you just want to be more generic about it. If you're just starting out, stick with what you have and on your next game try making something like an animation class.
will fix your compiler problem.
I'd suggest going about this differently though. What you're doing is not very extensible. Well, it's fine for a simple game, but it should be clear you can't add more characters easily or more frames of animation. Having lots of sprites on one image is fine, you just want to be more generic about it. If you're just starting out, stick with what you have and on your next game try making something like an animation class.
BITMAP* m_sprites;
m_sprites = new BITMAP*[10][16];
This doesn't compile, throws : error C2440: '=' : cannot convert from 'BITMAP *(*)[16]' to 'BITMAP *'
Same problem I've been having... jdindia, do you know of a tutorial or article on creating an animation class that would help me? I've looked all over the web for something similar, never found anything
Reegan, the thing is with allegro, its all BITMAP*, not BITMAP... BITMAP actually has no size, judging from the errors the compiler just threw at me. I can load m_sprites up just fine, by doing m_sprites[x][y] = load_bitmap(blah blah)... its just getting to those sprites i'm having the problem with
m_sprites = new BITMAP*[10][16];
This doesn't compile, throws : error C2440: '=' : cannot convert from 'BITMAP *(*)[16]' to 'BITMAP *'
Same problem I've been having... jdindia, do you know of a tutorial or article on creating an animation class that would help me? I've looked all over the web for something similar, never found anything
Reegan, the thing is with allegro, its all BITMAP*, not BITMAP... BITMAP actually has no size, judging from the errors the compiler just threw at me. I can load m_sprites up just fine, by doing m_sprites[x][y] = load_bitmap(blah blah)... its just getting to those sprites i'm having the problem with
Quote:Original post by WackyWormer
BITMAP* m_sprites;
m_sprites = new BITMAP*[10][16];
This doesn't compile, throws : error C2440: '=' : cannot convert from 'BITMAP *(*)[16]' to 'BITMAP *'
Same problem I've been having... jdindia, do you know of a tutorial or article on creating an animation class that would help me? I've looked all over the web for something similar, never found anything
not
m_sprites = new BITMAP*[10][16];
this
m_sprites = new BITMAP[10][16];
take out the asterisk.
EDIT: As for animation classes, read the replys in this thread.
EDIT EDIT: Ah i see, was as you can see ive never programmed in allegro (direct3d for me please [smile])
In C++, we can return a proxy type instead of a raw pointer. This allows us freedom to change the internals of the class at a later stage.
An example:
The nested inner structure may look complex, but it is relatively simple. It stores a pointer to the array of sprites inside the SpriteSheet class (the typedef at the top comes in handy to simplify). It does some debug range checking on construction to ensure you do not create a row out of bounds. This could be done in the getCharacter() function instead if you like.
Using an inner type may seem overly complex, but look at what can happen next. Then we can change the internal representation to something more flexible with minimal change to the client code.
Here is a rough draft of a SpriteSheet that doesn't have a fixed size. It uses std::vector<> and some funky array indexing to do this.
The exact details aren't important here. The important this is the concept of how the inner class keeps implementation details hidden from the client. Notice that, apart from the constructor arguments, there is no change in the client code.
And it doesn't add a lot more actual code to your project. It tends to add a few extra lines, but not code that does actual work. But that is more a function of the verbosity of C++ than the technique itself.
An example:
class SpriteSheet{ static const int rows = 10, columns = 16; typedef BITMAP* Sheet[rows][columns];public: // constructors etc // ... struct Row { Row( Sheet *sheet, int row ) : sheet(sheet), row(row) { assert(row >= 0 && row < rows); } BITMAP *operator[](int index) { assert(index >= 0 && index < columns); return (*sheet)[row][index]; } private: Sheet *sheet; int row; }; Row getCharacter(int row) { return Row(&sprites, row); }private: Sheet sprites;};void draw(BITMAP *) { /* ... */ }int main(){ SpriteSheet sheet = SpriteSheet(/* ... */); SpriteSheet::Row character = sheet.getCharacter(4); draw(character[3]);}
The nested inner structure may look complex, but it is relatively simple. It stores a pointer to the array of sprites inside the SpriteSheet class (the typedef at the top comes in handy to simplify). It does some debug range checking on construction to ensure you do not create a row out of bounds. This could be done in the getCharacter() function instead if you like.
Using an inner type may seem overly complex, but look at what can happen next. Then we can change the internal representation to something more flexible with minimal change to the client code.
Here is a rough draft of a SpriteSheet that doesn't have a fixed size. It uses std::vector<> and some funky array indexing to do this.
class SpriteSheet{ typedef std::vector<BITMAP *> Sheet;public: SpriteSheet(const std::string &file, int rows, int columns) : rows(rows) { sprites.resize(rows * columns); // read form file } struct Row { Row( SpriteSheet *sheet, int row ) : sheet(sheet), row(row) { assert(row >= 0 && row < sheet->rows); } BITMAP *operator[](int index) { return sheet->elementAt(row, index); } private: SpriteSheet *sheet; int row; }; Row getCharacter(int row) { return Row(this, row); }private: int columns() { return sprites.size() / rows; } BITMAP *elementAt(int row, int column) { assert(column >= 0 && column < columns()); int index = (row * rows) + column; return sprites[index]; } Sheet sprites; int rows;};void draw(BITMAP *) { /* ... */ }int main(){ SpriteSheet sheet = SpriteSheet("whatever", 100, 200); SpriteSheet::Row character = sheet.getCharacter(4); draw(character[3]);}
The exact details aren't important here. The important this is the concept of how the inner class keeps implementation details hidden from the client. Notice that, apart from the constructor arguments, there is no change in the client code.
And it doesn't add a lot more actual code to your project. It tends to add a few extra lines, but not code that does actual work. But that is more a function of the verbosity of C++ than the technique itself.
Please read the whole post before deciding anything.
That means "a 2-dimensional, 10 by 16 in size, array of things, each of which is a pointer to a BITMAP".
If you want to store BITMAP objects directly in the array, then do so. (It might not be possible, depending on what a BITMAP actually is and how you create them.)
A pointer to a pointer to a BITMAP is simply a pointer to (a pointer to BITMAP). You make a pointer to (absolutely anything) with '(whatever it is)*', so a pointer to (a pointer to BITMAP) is a pointer to (BITMAP*), which is BITMAP**.
However, you probably don't really want to do that, because such a pointer has no way to "know" that it refers to a 'row' of the array. (In particular, if the calling code grabs the first row, and then tries to access the 20th element of that row, it will simply keep stepping through memory and grab an element that is actually in the second row of the array. This is because multi-dimensional, sized-ahead-of-time arrays are actually stored in a single solid block.)
To get some safety, you can use something like what rip-off posted, or you can use a pre-existing implementation, such as boost::multi_array. That one will also allow you to resize the array at runtime, too.
If you don't want that (but think carefully about that! Why limit yourself... where does the number of characters and animation frames come from?), you can use boost::array. That way you still gain some safety (for example, you can use .at() to access elements, and it will check that the requested element index is valid). It only implements a 1-dimensional array, but you can just make a boost::array of boost::arrays of whatever, and they will still "pack" neatly in memory with no overhead (just as if it were a plain old 2D array) and still offering the desired functionality. It also turns the arrays into real objects, which means that (for example) you can return one from a function. (Although you probably want to return a reference instead. :) )
Example:
Sorry, you can't make multi-dimensional array allocations like that. Multidimensional arrays depend on the compiler being able to prove, at compile time (i.e. without analyzing what code will actually run) what the size of the array will be in each dimension (because it's actually one chunk of memory, where it does some math to turn your [x][y] indexing into a single index). There's nowhere for that information to get recorded if the memory is allocated with 'new'. Unless you build a class that remembers that information alongside the memory allocation... but then you're gradually beginning to implement boost::multi_array, albeit poorly.
Quote:Original post by WackyWormer
I'm trying to code a simulation game in c++/allegro, and I'm having trouble storing the sprites... I made a SpriteSheet class with member BITMAP* m_sprites[10][16]
That means "a 2-dimensional, 10 by 16 in size, array of things, each of which is a pointer to a BITMAP".
If you want to store BITMAP objects directly in the array, then do so. (It might not be possible, depending on what a BITMAP actually is and how you create them.)
Quote:The return type is wrong, I know... I want to return a pointer to the first element of m_sprites[Row]... so I should be returning a pointer to a pointer... I don't know how to do that.
A pointer to a pointer to a BITMAP is simply a pointer to (a pointer to BITMAP). You make a pointer to (absolutely anything) with '(whatever it is)*', so a pointer to (a pointer to BITMAP) is a pointer to (BITMAP*), which is BITMAP**.
However, you probably don't really want to do that, because such a pointer has no way to "know" that it refers to a 'row' of the array. (In particular, if the calling code grabs the first row, and then tries to access the 20th element of that row, it will simply keep stepping through memory and grab an element that is actually in the second row of the array. This is because multi-dimensional, sized-ahead-of-time arrays are actually stored in a single solid block.)
To get some safety, you can use something like what rip-off posted, or you can use a pre-existing implementation, such as boost::multi_array. That one will also allow you to resize the array at runtime, too.
If you don't want that (but think carefully about that! Why limit yourself... where does the number of characters and animation frames come from?), you can use boost::array. That way you still gain some safety (for example, you can use .at() to access elements, and it will check that the requested element index is valid). It only implements a 1-dimensional array, but you can just make a boost::array of boost::arrays of whatever, and they will still "pack" neatly in memory with no overhead (just as if it were a plain old 2D array) and still offering the desired functionality. It also turns the arrays into real objects, which means that (for example) you can return one from a function. (Although you probably want to return a reference instead. :) )
Example:
class SpriteSheet { // We can use typedefs to make the type names easier to read and understand: typedef boost::array<BITMAP*, 16> character_animation; boost::array<character_animation, 10> all_animation; character_animation& getAnimationFor(int character) { return all_animation.at(character); } // Accessors usually come in pairs like this: const character_animation& getAnimationFor(int character) const { return all_animation.at(character); } // The effect is that you can tell the compiler "I promise that getAnimationFor() // will not change the object that it's called upon, but only if the calling // code promises not to change anything via the reference that it returns" // (which makes sense because that reference refers to part of the object). // etc. etc.
Quote:Original post by Reegan
m_sprites = new BITMAP[10][16];
Sorry, you can't make multi-dimensional array allocations like that. Multidimensional arrays depend on the compiler being able to prove, at compile time (i.e. without analyzing what code will actually run) what the size of the array will be in each dimension (because it's actually one chunk of memory, where it does some math to turn your [x][y] indexing into a single index). There's nowhere for that information to get recorded if the memory is allocated with 'new'. Unless you build a class that remembers that information alongside the memory allocation... but then you're gradually beginning to implement boost::multi_array, albeit poorly.
Quote:Original post by WackyWormer
This doesn't compile, throws : error C2440: '=' : cannot convert from 'BITMAP *(*)[16]' to 'BITMAP *'
Right, sorry. They're the 'same', but the compiler is doing some type safety checking and telling you what you're doing is bad. You have to force the type to get it to compile. That means
return (BITMAP **) m_sprites[Row]
There might be a way to get the compiler to return the proper type, but I doubt it. In general, I find that creating a single array and then indexing with [y*width+x] or [row*width+column] is easier.
And sorry, I don't know of an animation tutorial. It's a decent exercise though. You'll probably want to make the distinction between an instance of an animation (which would keep track of local time and the current frame) and the definition of the animation which would have the images, number of frames, framerate, etc. The responsibility of the animation instance would be to decide which particular frame was needed, and the responsibility of the animation definition would be to give the appropriate image information given a frame.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement