Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Using a Class for a Sprite Handler

This thread is locked; no one can reply to it. rss feed Print
Using a Class for a Sprite Handler
David_at_wedu
Member #7,407
June 2006

So the notorious book that I've been reading for working with Allegro presents its sprite handler as a struct. Since C++ seems to be more in line with what's being used in the "industry" nowadays, I wanted to construct a class for my sprite handler. I've run into some problems though; the main one being that when I attempt to draw_sprite() to make sure it's being loaded, I get the following compile error:

base operand of `->' has non-pointer type `Sprite'

Here's the code in my program:

1#include <string>
2#include <iostream>
3#include <allegro.h>
4 
5using namespace std;
6 
7class Sprite
8{
9public:
10 Sprite(char bmp_name[10]);
11 ~Sprite();
12
13 
14 string bmp_name;
15 BITMAP* sprite_img;
16 int x, y;
17 int width, height;
18 int xspeed, yspeed;
19 int xcount, ycount;
20 int xdelay, ydelay;
21 int curframe, maxframe;
22 int framecount, framedelay, animdir;
23};
24 
25Sprite::Sprite(char bmp_name[10])
26{
27 allegro_message("Constructor Called");
28 sprite_img = load_bitmap(bmp_name, NULL);
29}
30 
31Sprite::~Sprite()
32{
33// allegro_message("Destructor Called");
34 destroy_bitmap(sprite_img);
35}
36 
37int main()
38{
39 allegro_init();
40 install_keyboard();
41 set_color_depth(16);
42 set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
43
44 Sprite foo("tank.bmp");
45 
46 draw_sprite(screen, foo->sprite_img, 200, 200);
47
48 while (!key[KEY_ESC]);
49
50 allegro_exit();
51 return 0;
52}
53END_OF_MAIN();

Can someone help me find the problem? ???

Kitty Cat
Member #2,815
October 2002
avatar

Change Sprite foo("tank.bmp"); to Sprite *foo = new Sprite("tank.bmp");, remove allegro_exit (you don't need it), and add delete foo; before returning from main.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

David_at_wedu
Member #7,407
June 2006

Thanks, Kitty Cat. Figured it was something simple. Always is. :)

ixilom
Member #7,167
April 2006
avatar

Actually, he could have replaced the -> with a period, like

draw_sprite(screen, foo.sprite_img, 200, 200);

Allthough the new/delete is to prefer and should be a habit.
It is okay to declare/create a class like he did in the first place, but it will only be available in the scope of that function.

___________________________________________
Democracy in Sweden? Not since 2008-Jun-18.
<someone> The lesbians next door bought me a rolex for my birthday.
<someone> I think they misunderstood when I said I wanna watch...

Kitty Cat
Member #2,815
October 2002
avatar

Quote:

Actually, he could have replaced the -> with a period

He could, but placing objects on the stack like that is considered bad form. And later on if he makes it a global, he shouldn't construct it before he's in a graphics mode so he'd need to wait to create it anyway.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

ixilom
Member #7,167
April 2006
avatar

Quote:

He could, but placing objects on the stack like that is considered bad form. And later on if he makes it a global, he shouldn't construct it before he's in a graphics mode so he'd need to wait to create it anyway.

I know, I was just saying he could with the code provided ;D

___________________________________________
Democracy in Sweden? Not since 2008-Jun-18.
<someone> The lesbians next door bought me a rolex for my birthday.
<someone> I think they misunderstood when I said I wanna watch...

David_at_wedu
Member #7,407
June 2006

Appreciate all the responses guys!

I've actually advanced my code a little bit, but have now hit another wall. As you saw in my previous code, I was using a class as a sprite-handler. Once Kitty Cat helped me iron out that bug, it worked like a charm, and I even updated my code to the point of being able to move the sprite around, complete with direction changes. But now I want to animate it, and I'm kind of stuck.

With the code I have, I'm calling load_bitmap() each time the class is instantiated, but on that same token, every additional sprite is another object. So I'm kind of stuck as to how to modify my class to do what I need.

I thought about perhaps storing multiple frames per instance, maybe in some sort of container like a vector, but I'm just not sure exactly how to do it. Can any of you guys help me out on this? ???

(again, I apologize for my blatant newbie-ness!)

Edit: I actually did some tinkering and figured out how to store my multiple frames in a vector. It probably sound corny to the vets here, but I'm quite proud of my accomplishment! :)

Now I have a new problem... Animation... Given my code, does anyone have a suggestion as to how I could animate my sprite? I tried to do it with a for loop and a rest() call, but when I apply it to all directions, it slows the sprite's movement to a crawl and doesn't seem to animate properly.

Here's my code:

1#include <vector>
2#include <allegro.h>
3 
4using namespace std;
5 
6#define WHITE makecol(255,255,255)
7#define RED makecol(255,0,0)
8#define BLACK makecol(0,0,0)
9 
10#define DISPMODE GFX_AUTODETECT_WINDOWED
11#define DISPDEPTH 32
12#define DISPW 600
13#define DISPH 900
14 
15class Sprite
16{
17public:
18 Sprite(char bmp_name[30], int x, int y);
19 ~Sprite();
20 void AddFrame(char bmp_name[30]);
21 
22 char bmp_name[30];
23 BITMAP* sprite_img;
24 int x, y;
25 int width, height;
26 int xspeed, yspeed;
27 int xcount, ycount;
28 int xdelay, ydelay;
29 int curframe, maxframe;
30 int framecount, framedelay, animdir;
31 vector<BITMAP*> frames;
32};
33 
34Sprite::Sprite(char bmp_name[30], int sx, int sy)
35{
36 AddFrame(bmp_name);
37 x = sx;
38 y = sy;
39 xspeed = 2;
40 yspeed = 2;
41 xcount = 0;
42 ycount = 0;
43 xdelay = 0;
44 ydelay = 0;
45 curframe = 0;
46 maxframe = 4;
47 framecount = 0;
48 framedelay = 5;
49 animdir = 0;
50}
51 
52 void Sprite::AddFrame(char bmp_name[30])
53 {
54 
55 int s;
56 sprite_img = load_bitmap(bmp_name, NULL);
57 if (sprite_img == NULL)
58 {
59 allegro_message("Error loading BITMAP");
60 return;
61 }
62 frames.push_back(sprite_img);
63 s = frames.size();
64 width = sprite_img->w;
65 height = sprite_img->h;
66 
67 }
68 
69Sprite::~Sprite()
70{
71 int s, x;
72 s = frames.size();
73 for (x=0;x<s;x++)
74 {
75 destroy_bitmap(frames[x]);
76 }
77}
78 
79void initialize()
80{
81 allegro_init();
82 install_keyboard();
83 install_timer();
84 set_color_depth(DISPDEPTH);
85 set_gfx_mode(DISPMODE, DISPW, DISPH, 0, 0);
86}
87 
88void updatesprite(BITMAP* buffer, Sprite* spr, int dir)
89{
90 float angle;
91 int x;
92 textout_ex(buffer, font, "My Project (ESC to quit)", 0, 0, WHITE, -1);
93 textprintf_ex(buffer, font, 0, 10, WHITE, -1, "sprite1 x, y: %i,%i", spr->x, spr->y);
94 textprintf_ex(buffer, font, 0, 20, WHITE, -1, "sprite1 width,height: %i,%i", spr->width, spr->height);
95 switch (dir)
96 {
97 case 0 :
98 rotate_sprite(buffer, spr->frames[0], spr->x, spr->y, itofix(-64));
99 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
100 clear_bitmap(buffer);
101 break;
102 case 1 :
103 draw_sprite(buffer, spr->frames[0], spr->x, spr->y);
104 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
105 clear_bitmap(buffer);
106 break;
107 case 2 :
108 rotate_sprite(buffer, spr->frames[0], spr->x, spr->y, itofix(64));
109 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
110 clear_bitmap(buffer);
111 break;
112 case 3 :
113 draw_sprite_h_flip(buffer, spr->frames[0], spr->x, spr->y);
114 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
115 clear_bitmap(buffer);
116 break;
117 default :
118// for (x=0;x<spr->frames.size();x++)
119// {
120 draw_sprite(buffer, spr->frames[x], (SCREEN_W/2-spr->width/2), 600);
121 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
122// rest(50);
123// }
124 clear_bitmap(buffer);
125 spr->x = (SCREEN_W/2-spr->width/2);
126 spr->y = 600;
127 }
128 rest(10);
129}
130 
131int main()
132{
133 initialize();
134 int dir;
135 BITMAP* buffer = create_bitmap(600, 900);
136 
137 Sprite* sprite1 = new Sprite("sprites_pacman1.bmp", 75, 75);
138 sprite1->AddFrame("sprites_pacman2.bmp");
139 sprite1->AddFrame("sprites_pacman3.bmp");
140 sprite1->AddFrame("sprites_pacman4.bmp");
141 
142 while (!key[KEY_ESC])
143 {
144 if (key[KEY_LEFT])
145 {
146 dir = 3;
147 sprite1->x -= sprite1->xspeed;
148 }
149 if (key[KEY_RIGHT])
150 {
151 dir = 1;
152 sprite1->x += sprite1->xspeed;
153 }
154 if (key[KEY_UP])
155 {
156 dir = 0;
157 sprite1->y -= sprite1->yspeed;
158 }
159 if (key[KEY_DOWN])
160 {
161 dir = 2;
162 sprite1->y += sprite1->yspeed;
163 }
164 
165 if (sprite1->y < 0)
166 {
167 sprite1->y = 0;
168 }
169 if (sprite1->y > SCREEN_H-sprite1->width)
170 {
171 sprite1->y = SCREEN_H-sprite1->width;
172 }
173 if (sprite1->x > SCREEN_W-sprite1->width)
174 {
175 sprite1->x = SCREEN_W-sprite1->width;
176 }
177 if (sprite1->x < 0)
178 {
179 sprite1->x = 0;
180 }
181 updatesprite(buffer, sprite1, dir);
182 }
183 delete sprite1;
184 
185 return 0;
186}
187END_OF_MAIN();

_Dante
Member #7,398
June 2006
avatar

You basically have two choices, each has its own merits and drawbacks. You can either use multiple images, blitting the appropriate one at the appropriate time, or you can gather them all together into a single sheet, and blit the portion of the image that contains the correct frame.

Regardless of which one you choose, you need a more sophisticated object, one that defines a number of frames that your game can pick from. If you're like me and prefer a data-driven design, you can store all that information in a separate file and build a loader into your sprite class; that's a lot more flexible, and allows a game designer (if you have one) to create sprites without your intervention. However, if you're all alone and embedding the data into your code doesn't bother you, continue doing what you're doing, except load up several images into a single sprite - perhaps with a variable argument list:

1class Sprite
2{
3public:
4 Sprite(const char *spriteFile)
5 {
6 // read spec from sprite file and load indicated images
7 }
8 
9 -or-
10 
11 Sprite(const char *imageFile, ...)
12 {
13 // read arg list, load named images
14 }
15 
16 void drawFrame(int frame, int x, int y)
17 {
18 assert(frame < nImages);
19 draw_sprite(screen, img[frame], x, y);
20 }
21 
22private:
23 BITMAP **img;
24 int nImages;
25};

-----------------------------
Anatidaephobia: The fear that somehow, somewhere, a duck is watching you

David_at_wedu
Member #7,407
June 2006

Quote:

You basically have two choices, each has its own merits and drawbacks. You can either use multiple images, blitting the appropriate one at the appropriate time, or you can gather them all together into a single sheet, and blit the portion of the image that contains the correct frame.

Would using multiple, small sprites really have that adverse of an effect on animation though? What I've found is if I pull out the animation entirely, the program moves just fine. Once I put the animation in, it cuts the speed by half. The only other thing I tried was removing a call to "clear_bitmap()" following my blit() where I sent the buffer I'm updating to the screen, but without doing clear_bitmap(), I get copious amounts of artifacting (looks like my character has a motion trail).

Also, what does the ** mean in the code example you presented? I know a single * denotes a pointer, so what does the double-dose mean?

And any suggestions/comments on my animation issues? ???

_Dante
Member #7,398
June 2006
avatar

Quote:

Would using multiple, small sprites really have that adverse of an effect on animation though?

It wouldn't have an adverse effect whatsoever - I just wouldn't personally do it that way. That's just me. If using an animation is cutting your speed in half, though, you're doing something wrong (maybe doing it twice).

Quote:

Also, what does the ** mean in the code example you presented?

It's a pointer to a pointer. If you're not very familiar with pointers, you need to get familiar with them very soon. If you have an array of objects (or anything else), and you don't know how large that array will be, you have to use a dynamic array.

So this:
int fixed[10];
becomes this:
int *dynamic = (int *)malloc(10 * sizeof(int));

That works for primitive types, but a BITMAP must be referred to with a pointer to begin with, since that single object itself is created dynamically (plus you don't want it to go out of scope and disappear). But because you potentially need more than one, you point to pointers.

So this:
BITMAP *fixed[10];
dynamic[0] = load_bitmap("bitmap0.bmp", NULL);
dynamic[1] = load_bitmap("bitmap1.bmp", NULL);
...
becomes this:

BITMAP **dynamic = (BITMAP **)malloc(10 * sizeof(BITMAP *));
dynamic[0] = load_bitmap("bitmap0.bmp", NULL);
dynamic[1] = load_bitmap("bitmap1.bmp", NULL);
...

Obviously I'm oversimplifying and throwing out error checking, but that's the gist of it. This is really, really fundamental. If you don't fully understand pointers you'll run into trouble very quickly. Find some tutorials, buy a copy of K&R, do whatever you have to do, but you definitely need to be very comfortable with this sort of construction.

-----------------------------
Anatidaephobia: The fear that somehow, somewhere, a duck is watching you

David_at_wedu
Member #7,407
June 2006

All of what you're saying makes sense, but it doesn't seem like any of it would affect the speed of my program's execution. Would a pointer to a pointer really be faster than accessing a vector element like I am now?

If you could offer some suggestions for changes to my code, it would be much appreciated. I feel like I'm asking you to solve my problem, but that isn't my intention. I just catch onto concepts better when they're shown in context.

Here's the source below. A lot of the stuff I tried to fix my problem (to no avail) is still in there, just commented out (like using a rectfill instead of clear_bitmap):

1#include <vector>
2#include <allegro.h>
3 
4using namespace std;
5 
6#define WHITE makecol(255,255,255)
7#define RED makecol(255,0,0)
8#define BLACK makecol(0,0,0)
9 
10#define DISPMODE GFX_AUTODETECT_WINDOWED
11#define DISPDEPTH 32
12#define DISPW 600
13#define DISPH 900
14 
15class Sprite
16{
17public:
18 Sprite(char bmp_name[30], int x, int y);
19 ~Sprite();
20 void AddFrame(char bmp_name[30]);
21 
22 char bmp_name[30];
23 BITMAP* sprite_img;
24 int x, y;
25 int width, height;
26 int xspeed, yspeed;
27 int xcount, ycount;
28 int xdelay, ydelay;
29 int curframe, maxframe;
30 int framecount, framedelay, animdir;
31 vector<BITMAP*> frames;
32};
33 
34Sprite::Sprite(char bmp_name[30], int sx, int sy)
35{
36 AddFrame(bmp_name);
37 x = sx;
38 y = sy;
39 xspeed = 2;
40 yspeed = 2;
41 xcount = 0;
42 ycount = 0;
43 xdelay = 0;
44 ydelay = 0;
45 curframe = 0;
46 maxframe = 4;
47 framecount = 0;
48 framedelay = 5;
49 animdir = 0;
50}
51 
52 void Sprite::AddFrame(char bmp_name[30])
53 {
54 
55 int s;
56 sprite_img = load_bitmap(bmp_name, NULL);
57 if (sprite_img == NULL)
58 {
59 allegro_message("Error loading BITMAP");
60 return;
61 }
62 frames.push_back(sprite_img);
63 s = frames.size();
64 width = sprite_img->w;
65 height = sprite_img->h;
66 
67 }
68 
69Sprite::~Sprite()
70{
71 int s, x;
72 s = frames.size();
73 for (x=0;x<s;x++)
74 {
75 destroy_bitmap(frames[x]);
76 }
77}
78 
79void initialize()
80{
81 allegro_init();
82 install_keyboard();
83 install_timer();
84 set_color_depth(DISPDEPTH);
85 set_gfx_mode(DISPMODE, DISPW, DISPH, 0, 0);
86}
87 
88void updatesprite(BITMAP* buffer, Sprite* spr, int dir)
89{
90 float angle;
91// int x = 0;
92// textout_ex(buffer, font, "My Project (ESC to quit)", 0, 0, WHITE, -1);
93// textprintf_ex(buffer, font, 0, 10, WHITE, -1, "sprite1 x, y: %i,%i", spr->x, spr->y);
94// textprintf_ex(buffer, font, 0, 20, WHITE, -1, "sprite1 width,height: %i,%i", spr->width, spr->height);
95 int x = 0;
96 for (x=0;x<spr->frames.size();x++)
97 {
98 switch (dir)
99 {
100 case 0 :
101 rotate_sprite(buffer, spr->frames[x], spr->x, spr->y, itofix(-64));
102 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
103// clear_bitmap(buffer);
104 rectfill(buffer, (spr->x-spr->width+5), (spr->y-spr->height+5), (spr->x+spr->width+5), (spr->y+spr->height+5), BLACK);
105 break;
106 case 1 :
107 draw_sprite(buffer, spr->frames[x], spr->x, spr->y);
108 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
109 rectfill(buffer, (spr->x-spr->width+5), (spr->y-spr->height+5), (spr->x+spr->width+5), (spr->y+spr->height+5), BLACK);
110// clear_bitmap(buffer);
111 break;
112 case 2 :
113 rotate_sprite(buffer, spr->frames[x], spr->x, spr->y, itofix(64));
114 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
115 rectfill(buffer, (spr->x-spr->width+5), (spr->y-spr->height+5), (spr->x+spr->width+5), (spr->y+spr->height+5), BLACK);
116// clear_bitmap(buffer);
117 break;
118 case 3 :
119 draw_sprite_h_flip(buffer, spr->frames[x], spr->x, spr->y);
120 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
121 rectfill(buffer, (spr->x-spr->width+5), (spr->y-spr->height+5), (spr->x+spr->width+5), (spr->y+spr->height+5), BLACK);
122// clear_bitmap(buffer);
123 break;
124 default :
125 draw_sprite(buffer, spr->frames[x], (SCREEN_W/2-spr->width/2), 600);
126 blit(buffer, screen, 0, 0, 0, 0, 600, 900);
127 rectfill(buffer, (spr->x-spr->width+5), (spr->y-spr->height+5), (spr->x+spr->width+5), (spr->y+spr->height+5), BLACK);
128// clear_bitmap(buffer);
129 spr->x = (SCREEN_W/2-spr->width/2);
130 spr->y = 600;
131 }
132 rest(1);
133 }
134 
135// blit(buffer, screen, 0, 0, 0, 0, 600, 900);
136// clear_bitmap(buffer);
137// rest(30);
138}
139 
140int main()
141{
142 initialize();
143 int dir;
144 BITMAP* buffer = create_bitmap(600, 900);
145 
146 Sprite* sprite1 = new Sprite("sprites_pacman1.bmp", 75, 75);
147 sprite1->AddFrame("sprites_pacman2.bmp");
148 sprite1->AddFrame("sprites_pacman3.bmp");
149 sprite1->AddFrame("sprites_pacman4.bmp");
150 
151 while (!key[KEY_ESC])
152 {
153 if (key[KEY_LEFT])
154 {
155 dir = 3;
156 sprite1->x -= sprite1->xspeed;
157 }
158 if (key[KEY_RIGHT])
159 {
160 dir = 1;
161 sprite1->x += sprite1->xspeed;
162 }
163 if (key[KEY_UP])
164 {
165 dir = 0;
166 sprite1->y -= sprite1->yspeed;
167 }
168 if (key[KEY_DOWN])
169 {
170 dir = 2;
171 sprite1->y += sprite1->yspeed;
172 }
173 
174 if (sprite1->y < 0)
175 {
176 sprite1->y = 0;
177 }
178 if (sprite1->y > SCREEN_H-sprite1->width)
179 {
180 sprite1->y = SCREEN_H-sprite1->width;
181 }
182 if (sprite1->x > SCREEN_W-sprite1->width)
183 {
184 sprite1->x = SCREEN_W-sprite1->width;
185 }
186 if (sprite1->x < 0)
187 {
188 sprite1->x = 0;
189 }
190 updatesprite(buffer, sprite1, dir);
191 }
192 delete sprite1;
193 
194 return 0;
195}
196END_OF_MAIN();

Also, I know my animation process is kind of Frankenstein'ed... Any suggestions on a good tutorial for doing animation that preferably deals with their sprites as a class?

Mariusz
Member #1,634
November 2001

This suggestion may not be what you looking for, but this is what I have ended up doing in my project.

If you want to go OOP simply create a class for everything. So you would end up having a class for Frames, Animation, AnimationMgr, Objects, Tiles, Sprites, etc.

Think of this way, Tiles and Sprites are different, but they share common attributes and functions. Therefore, create an Object class and derive from it Tile and Sprite.

Object class would have an Animation Manager that could contain more then one animation. After all, a sprite would have a number of animations, such as waling, shooting, dying, etc. Tiles can also have multiple animations based on the events.
The Animation Manager (like the name says) manages animations. The Animation class only worries abut frames that build up this animation. While frame class contains the information in regards to a frame.

Frame could have an ID, Name, Delay, Counter, BITMAP
Animation then can have a list or an array of frames, current frame, etc.
Animation Manager can have a list of animations, current animation, etc.
Object has Animation Manager, along with other attributes.
Tile inherits Object class and implements its own attributes and functions.
Sprite, just like a Tile class does the same thing with a difference of its own needs.

From that you can then derive a Player class, Enemy, or whatever else you may need. Best of all, each derived object will be able to handle animations just like any other object.
If you feel that some of the Objects will not be in need for an animation, then all you do is create a single Animation with a single frame and that is it.

David_at_wedu
Member #7,407
June 2006

Quote:

Frame could have an ID, Name, Delay, Counter, BITMAP
Animation then can have a list or an array of frames, current frame, etc.
Animation Manager can have a list of animations, current animation, etc.
Object has Animation Manager, along with other attributes.
Tile inherits Object class and implements its own attributes and functions.
Sprite, just like a Tile class does the same thing with a difference of its own needs.

From that you can then derive a Player class, Enemy, or whatever else you may need. Best of all, each derived object will be able to handle animations just like any other object.
If you feel that some of the Objects will not be in need for an animation, then all you do is create a single Animation with a single frame and that is it.

I get what you're saying... I think really my major problem is figuring out how to make part a relate to part b, and for part c to use part b when necessary. I have experience with OOP, though limited, but my biggest hurdle currently is my main source (in terms of reference material) is loaded with questionable practices. So I've been doing code my own way, in a manner of speaking, as not to duplicate the author's mistakes; instead, I'm making a lot of my own.

The best example I can give is my issue with doing animations. I've still yet to find a straight forward tutorial anywhere online.

Not to mention I am baffled by the performance hit between when I have my animation running (though messy in execution) and when my sprites are static and don't animate. From what I can tell about my code, I'm not breaking any major rules of programming, so such a hit in performance shouldn't be happening... But again, it just illustrates my inexperience in the matter, and a lack of a direct mentor doesn't help. :(

aadfo824
Member #7,265
May 2006

Game Programming All In One 2e? Me too. I recall it saying that the rotate and fliping algorithisms are long and complex. Maybe you should create all of the images at runtime, and then draw_sprite the right one. Use more of his code, it works. The BITMAP array that holds animations actually works. If you want, I'll post my sprite handler, also a class, but closer to what the book said.

Go to: