GFX совместно с SDL. Урок 3: Спрайты |
- Statistics
- Participants
- Translate into Russian
- Translation result
- 13% translated in draft.
Hello and welcome to this yet-another-tutorial. This lesson will be about the things that most 2D games use - sprites. In a way sprites are like objects. I'm sure that at some point in your life you have seen or have played Super Mario or some other similar 2D game. Think about all the enemies, blocks, coins, etc. that you saw in that game. All these are called sprites. The main character (Mario) is a sprite, the enemies are sprites, the coins are sprites, the tubes are sprites, even these things that you can stand on are sprites. Everything that impacts the game/player/monster in some way (eg: you can touch it) is basically a sprite. The background is simply a background - you can't touch it in any way. Sometimes some sprites are parts of the background (eg: a sun that is moving/rotating, but can't affect you in any way). Let's call them passive sprites. And let's call the sprites that can affect the player (and the player itself) active sprites. In our little sprite demo that we will create today, we won't seperate active and passive sprites. Altough the vikings could very well be active sprites and the sun a passive sprite. Active sprites need to collide with each other. And if a collision occurs, respond. That's all called collision detection and response. More on it in a future lesson. Besides being active and passive, sprites can also be animated or non-animated. And all sprites can have the possibility to change their x and y location on the screen. Sprite animation doesn't only mean that a sprite moves from point A to point B, but MOVES from point A to point B. Examples of sprite animation can be: a ball rolling (rolling animation while changing the ball position), somebody walking (animation of legs moving while moving the position), a blinking traffic light, a lighthouse, etc, etc.
The sprite system that we will build today will consist of 2 parts: the sprite base, that we will use to store all the frames of the animation, and the sprite itself. The sprite itself is the thingy that moves on the screen. The reason why we have 2 classes in our sprite system is really simple. Usually in a game you have many sprites that look the same. In our sprite classes we will load all the animation data (frames) into the sprite base and then each individual sprite will access it from there. If we would load in all the data (images) with each sprite, then that would boost up the memory requirements a lot.
(Our) sprites can also be partly transparent. The sprite system that we will be using will (currently) only support RGB transparency. That means that some part of the sprite (animation frame image), that is of a crentain color (like full green for example), will be transparent. RGB transparency in SDL is REALLY easy.
Этот маленький фрагмент будет нести некоторые значения RGB цвета прозрачности:
SDL_SetColorKey(surface, SDL_SRCCOLORKEY,
SDL_MapRGB(surface->format, r, g, b));
Where surface is the surface you want to modify and r, g and b are the r, g, b of RGB. Sometimes you may want to remove the transparency after setting it. To do that simply replace SDL_SRCCOLORKEY with 0.
Давайте напишем клас спрайтов!
Our 2 sprite classes will also have one helper structure called CSpriteFrame. CSpriteFrame is used to represent each frame of the animation. It contains one SDL_Structure for the image of the frame and one integer pause, that says how many milliseconds to pause after drawing the picture.
struct CSpriteFrame
{
SDL_Surface *image;
int pause;
};
So let's start with the sprite base class. It's called CSpriteBase. CSpriteBase has one function, init() to initalize the class - load in all the frames. CSpriteBase also has one pointer to the structure of type CSpriteFrame that we use to store the animation and some more variables that say whether the class is built (via init()), the number of frames in the animation and the width and height of the animation.
class CSpriteBase
{
public:
int init(char *dir);
CSpriteFrame *mAnim;
int mBuilt, mNumframes, mW, mH;
};
The init function is the core of the CSpriteBase class. init() takes one parameter - an array of characters to the directory to get the frame animation from. init() then looks for the file "info" from that directory and reads in some stuff. All the frame images should also be in that same directory that was passed to init.
Ok, now in init we first define 3 char arrays that we will later use to temporarily store some stuff. We will also have 4 (temporary) integers: pause, r, g and b and a FILE pointer fp.
int CSpriteBase::init(char *dir)
{
char buffer[255];
char filename[255];
char name[255];
int pause=0, r=0, g=0, b=0;
FILE *fp;
Now we use the sprintf function. sprintf() is just like printf(), only it prints the output into a string, not the screen. We print the full location of the info file into the string filename and then try to open it.
sprintf(filename, "%s/info", dir);
if((fp=fopen(filename, "r")) == NULL)
{
printf("ERROR opening file %s\n\n", filename);
return -1;
}
We'll then grab one line from the file into the string buffer. Since we know that this is the first line of the file and that it starts with "FILES: nr", where nr is the number of frames in the animation, we can then get the number using sscanf. sscanf is just like scanf, only it gets the input from a string. We'll store the number of frames in the class member mNumframes. And after all that we'll allocate space for an array or CSpriteFrames.
fgets(buffer, 255, fp);
sscanf(buffer, "FILES: %d", &mNumframes);
mAnim = new CSpriteFrame[mNumframes];
We'll set mBuilt to 1, so that the sprite classes can see that this class has been built. We will also set a temporary variable that we will use to keep track of the images to be loaded.
mBuilt = 1;
int count = 0;
Now we will loop through the info file while the eof (end of file) hasn't been reached and count < mNumframes. We'll grab one line of input and then check whether it's of any use or not (whether it starts with a comment sign, is blank, or not)
while(!feof(fp) && count<mNumframes)
{
fgets(buffer, 255, fp);
if(buffer[0] != '#' && buffer[0] != '\r' && buffer[0] != '\0'
&& buffer[0] != '\n' && strlen(buffer) != 0)
{
Since we now know that the line will be useful, we'll extract the name of the frame image, the milliseconds to pause after displaying it and the r, g and b of the transparent color. We'll then make the string filename equal the full path to the frame file and load it in.
sscanf(buffer, "%s %d %d %d %d", name, &pause, &r, &g, &b);
sprintf(filename, "%s/%s", dir, name);
Our image loading routine will be a little different than the one from the previous lesson. First we create a temporary SDL_Surface called temp and we load in the image with SDL_LoadBMP.
SDL_Surface *temp;
if((temp = SDL_LoadBMP(filename)) == NULL) return -1;
Now we'll check if the transparent color's r component is greater than or equal to zero. If so, we'll make temp transparent. But if r would be -1 or so, then we wouldn't have made the surface transparent.
if(r >= 0) SDL_SetColorKey(temp, SDL_SRCCOLORKEY,
SDL_MapRGB(temp->format, r, g, b));
Now we will make the current frame's image in the array of CSpriteFrames equal the just-loaded image, only in the same format as the screen surface. That makes blitting images onto the screen a little faster. We then free the memory that was held up by the temp surface.
mAnim[count].image = SDL_DisplayFormat(temp);
SDL_FreeSurface(temp);
We then make the pause integer in the mAnim array equal the pause value we got from the file. After that we make the width and the height equal the width/height of the frame image if they hadn't already been set.
mAnim[count].pause = pause;
if(!mW) mW = mAnim[count].image->w;
if(!mH) mH = mAnim[count].image->h;
count++;
}
}
// И в конце мы закрываем файл
fclose(fp);
return 0;
}
And that's it with CSpriteBase. The sprite class itself is called CSprite. The class header for CSprite will be a little bit bigger than the one of CSpriteBase. First we have some functions. init() is used to initalize the sprite object. It takes 2 parameters: a pointer to a CSpriteBase object and a *SDL_Surface. More on it soon. draw(), clearBG() and updateBG() are all used when drawing the sprite onto the screen. They too will be discussed really shortly.
class CSprite
{
public:
int init(CSpriteBase *base, SDL_Surface *screen);
void draw();
void clearBG();
void updateBG();
Now CSprite has some small inline functions as well. setFrame() modifies the integet mFrame, that's used to keep track of what frame to display next and getFrame returns the currently displayed frame.
void setFrame(int nr) { mFrame = nr; }
int getFrame() { return mFrame; }
setspeed() and getspeed() set and return the speed of the sprite. The mSpeed variable that these functions set and get is actually multiplied with the pause variable of the current frame. So, the bigger the value is, the longer the pause after drawing a frame lasts. If mSpeed would be 2, then the animation would be 2x slower. If it would be 0.5, then the animation would be 2x faster.
void setSpeed(float nr) { mSpeed = nr; }
float getSpeed() { return mSpeed; }
The functions toggleAnim(), startAnim() and stopAnim() toggle, start and stop the animation. rewind() sets the next-to-be-displayed frame variable back to zero.
void toggleAnim() { mAnimating = !mAnimating; }
void startAnim() { mAnimating = 1; }
void stopAnim() { mAnimating = 0; }
void rewind() { mFrame = 0; }
xadd() and yadd() can be used to add some value to the x and y pos of the sprite on the screen. xadd(1) would move the sprite 1 unit right on the screen, while yadd(-3) would move it 3 units up. xset() and yset() manually set the x or y positions of the sprite and set() sets both of them at once.
void xadd(int nr) { mX+=nr; }
void yadd(int nr) { mY+=nr; }
void xset(int nr) { mX=nr; }
void yset(int nr) { mY=nr; }
void set(int xx, int yy) { mX=xx; mY=yy; }
