PC Games
• Orb Tutorials
• 2D shoot 'em up Latest Updates
SDL2 Versus game tutorial
Download keys for SDL2 tutorials on itch.io
The Legend of Edgar 1.37
SDL2 Santa game tutorial 🎅
SDL2 Shooter 3 tutorial
Tags • android (3) • battle-for-the-solar-system (10) • blob-wars (10) • brexit (1) • code (6) • edgar (9) • games (43) • lasagne-monsters (1) • making-of (5) • match3 (1) • numberblocksonline (1) • orb (2) • site (1) • tanx (4) • three-guys (3) • three-guys-apocalypse (3) • tutorials (17) • water-closet (4) Books The Honour of the Knights (Second Edition) (Battle for the Solar System, #1) When starfighter pilot Simon Dodds is enrolled in a top secret military project, he and his wingmates begin to suspect that there is a lot more to the theft of a legendary battleship and the Mitikas Empire's civil war than the Helios Confederation is willing to let on. Somewhere out there the Pandoran army is gathering, preparing to bring ruin to all the galaxy... |
Intermediate Tutorials Intermediate Game Tutorial #5 - Advanced animation
Introduction In this tutorial we will look at more advanced methods of animation. Note: This tutorial only deals with animating a sprite, it will not go through the steps of how to draw a sprite. If you want to attempt to draw your own sprites, you could try doing a Google search for "sprite tutorials" or "pixel art tutorials", but be warned that drawing sprites takes a lot of time and patience. Compile and run tutorial15. The program will read the animation data from a file and display the character on screen. Use the arrow keys (not the ones on the numeric pad) to move the character around. Pressing space will make the character jump and pushing down will make it crouch. Pressing Escape or closing the window will exit the program. An in-depth look We will store all our animation data in the following Animation structure: typedef struct Animation { char name[50]; int frameCount, active; int *frameTimer; SDL_Surface **frame; } Animation;Each animation that we load will have a name to help us idenitfy it. We also store the number of frames that the animation contains, whether or not this animation is active and pointers to the individual frames and how long each frame will last. Our Entity structure also contains a few extra variables: typedef struct Entity { int active, w, h, onGround; int thinkTime, face, state; int currentFrame, frameTimer; float x, y, dirX, dirY; Animation *currentAnim; void (*action)(void); void (*draw)(void); } Entity;face is used to determine which way the Entity is facing, which is either left or right. state is the Entity's current animation state, which will be standing, walking or crouching. The currentAnim points to the animation that the Entity is currently using and currentFrame and frameTimer is the frame index of the current animation and how long to display this frame for. Before we look at loading the animation data, we will look at the file itself: PLAYER_CROUCH_RIGHT 1 gfx/player/player_crouch_right.gif 6 PLAYER_CROUCH_LEFT 1 gfx/player/player_crouch_left.gif 6 PLAYER_STAND_RIGHT 1 gfx/player/player_stand_right.gif 6 PLAYER_STAND_LEFT 1 gfx/player/player_stand_left.gif 6 PLAYER_WALK_RIGHT 8 gfx/player/player_walk_right00.gif 6 gfx/player/player_walk_right01.gif 6 gfx/player/player_walk_right02.gif 6 gfx/player/player_walk_right03.gif 6 gfx/player/player_walk_right04.gif 6 gfx/player/player_walk_right05.gif 6 gfx/player/player_walk_right06.gif 6 gfx/player/player_walk_right07.gif 6 PLAYER_WALK_LEFT 8 gfx/player/player_walk_left00.gif 6 gfx/player/player_walk_left01.gif 6 gfx/player/player_walk_left02.gif 6 gfx/player/player_walk_left03.gif 6 gfx/player/player_walk_left04.gif 6 gfx/player/player_walk_left05.gif 6 gfx/player/player_walk_left06.gif 6 gfx/player/player_walk_left07.gif 6The file is of the following form: name of the Animation, the number of frames in the Animation and then the path to each of the frames and how long to display it for. Putting this information in a text file makes it readable and also requires no recompilation to add extra frames or change their duration. Now we will look at loading this Animation, which is handled in animation.c: void loadAnimationData(char *filename) { char frameName[255]; int id, i; FILE *fp; fp = fopen(filename, "rb"); if (fp == NULL) { printf("Failed to open animation file: %s\n", filename); exit(1); } while (!feof(fp)) { for (id=0;id<MAX_ANIMATIONS;id++) { if (animation[id].active == 0) { animation[id].active = 1; break; } } if (id == MAX_ANIMATIONS) { printf("More free slots for animation: %s\n", filename); exit(1); } fscanf(fp, "%s", animation[id].name); fscanf(fp, "%d", &animation[id].frameCount); /* Allocate space for the animation */ animation[id].frame = (SDL_Surface **) malloc(animation[id].frameCount * sizeof(SDL_Surface *)); if (animation[id].frame == NULL) { printf("Ran out of memory when creating the animation for %s\n", animation[id].name); exit(1); } /* Allocate space for the frame timer */ animation[id].frameTimer = (int *) malloc(animation[id].frameCount * sizeof(int)); if (animation[id].frameTimer == NULL) { printf("Ran out of memory when creating the animation for %s\n", animation[id].name); exit(1); } /* Now load up each frame */ for (i=0;i<animation[id].frameCount;i++) { fscanf(fp, "%s", frameName); animation[id].frame[i] = loadImage(frameName); if (animation[id].frame[i] == NULL) { printf("Failed to load animation frame %s\n", frameName); exit(1); } fscanf(fp, "%d", &animation[id].frameTimer[i]); } } }First, we open the animation file and then start reading all of the data from the file. Before we add an Animation we need to check to see if there is a free slot available for this new Animation. When we find a free slot we set the name of the animation and the number of frames the Animation contains. Next we allocate space for all the frame images and the timings for each one. We then loop through all the frames and load the image up for each one along with the timer for each frame. As we can see, there is nothing particularly special about this function. The only other function that we will look at in this file is the setAnimation function: void setAnimation(char *name, Entity *entity) { int i; for (i=0;i<MAX_ANIMATIONS;i++) { /* Loop through all the animations and find the one that matches the name */ if (strcmp(name, animation[i].name) == 0) { /* Set the animation and the frameTimer to 0 */ entity->currentAnim = &animation[i]; entity->currentFrame = 0; entity->frameTimer = entity->currentAnim->frameTimer[entity->currentFrame]; return; } } /* If the animation couldn't be found, then exit */ printf("Could not find animation %s\n", name); exit(1); }setAnimation takes two arguments, the name of the Animation we are looking for and the Entity to apply the Animation to. We loop through all of the animations and if we find an Animation whose name matches the one we are looking for then we set the Entity's currentAnim variable to point to this animation, set the Entity's currentFrame to 0, which is the first frame of any Animation and set the frameTimer to the frameTimer of this Animation frame. We will now look at how to apply these functions in player.c. player.c contains 4 functions, which we will look at in turn, starting with loadPlayer: void loadPlayer() { loadAnimationData("data/anim/player.dat"); }loadPlayer calls the loadAnimationData function and passes in the data file containing the player's animations. This function is called in the main function. Next we will look at initPlayer: void initPlayer() { player.x = player.y = 0; player.dirX = player.dirY = 0; player.thinkTime = 0; player.face = RIGHT; setAnimation("PLAYER_STAND_RIGHT", &player); }This function simply sets up the the player's coordinates, velocity and facing direction and sets the initial Animation to be standing right. It is important that this function is called after the player's Animation is loaded otherwise the call to setAnimation will fail to find the animation. The next function processes the player and updates their Animation: void doPlayer() { player.dirX = 0; /* Gravity always pulls the player down */ player.dirY += GRAVITY_SPEED; if (player.dirY >= MAX_FALL_SPEED) { player.dirY = MAX_FALL_SPEED; } if (input.left == 1) { player.face = LEFT; } else if (input.right == 1) { player.face = RIGHT; } if (input.down == 1 && player.onGround == 1) { if (player.face == RIGHT && player.state != CROUCH_RIGHT) { player.state = CROUCH_RIGHT; setAnimation("PLAYER_CROUCH_RIGHT", &player); } else if (player.face == LEFT && player.state != CROUCH_LEFT) { player.state = CROUCH_LEFT; setAnimation("PLAYER_CROUCH_LEFT", &player); } } if (input.left == 1 && input.down == 0) { player.dirX -= PLAYER_SPEED; if (player.state != WALK_LEFT) { player.state = WALK_LEFT; setAnimation("PLAYER_WALK_LEFT", &player); } } else if (input.right == 1 && input.down == 0) { player.dirX += PLAYER_SPEED; if (player.state != WALK_RIGHT) { player.state = WALK_RIGHT; setAnimation("PLAYER_WALK_RIGHT", &player); } } else if (input.left == 0 && input.right == 0 && input.down == 0) { if (player.face == RIGHT && player.state != STAND_RIGHT) { player.state = STAND_RIGHT; setAnimation("PLAYER_STAND_RIGHT", &player); } else if (player.face == LEFT && player.state != STAND_LEFT) { player.state = STAND_LEFT; setAnimation("PLAYER_STAND_LEFT", &player); } } if (input.jump == 1 && player.onGround == 1) { if (player.face == RIGHT && player.state != STAND_RIGHT) { player.state = STAND_RIGHT; setAnimation("PLAYER_STAND_RIGHT", &player); } else if (player.face == LEFT && player.state != STAND_LEFT) { player.state = STAND_LEFT; setAnimation("PLAYER_STAND_LEFT", &player); } player.dirY = -JUMP_HEIGHT; input.jump = 0; } player.onGround = 0; player.x += player.dirX; player.y += player.dirY; if (player.x < 0) { player.x = 0; } else if (player.x + player.w >= SCREEN_WIDTH) { player.x = SCREEN_WIDTH - player.w - 1; } if (player.y + player.h >= (SCREEN_HEIGHT / 2)) { player.onGround = 1; player.y = (SCREEN_HEIGHT / 2) - player.h; } }Firstly, we apply gravity to the player as in the previous tutorial. We then check the left and right movements to determine which way the player is facing. We do this first to help determine which Animation to choose. The next part checks which Animation we need to apply: if (input.down == 1 && player.onGround == 1) { if (player.face == RIGHT && player.state != CROUCH_RIGHT) { player.state = CROUCH_RIGHT; setAnimation("PLAYER_CROUCH_RIGHT", &player); } else if (player.face == LEFT && player.state != CROUCH_LEFT) { player.state = CROUCH_LEFT; setAnimation("PLAYER_CROUCH_LEFT", &player); } }If down is being pressed and the player is on the ground, then we need to set the crouch Animation. Before we do this though we need to check if the player's Animation is already crouching. We do this by checking the state variable. If the state is already set to CROUCH_RIGHT and the player is facing right then we will not reset the Animation. Otherwise we will call setAnimation to search fo the PLAYER_CROUCH_RIGHT Animation and set the state of the player to CROUCH_RIGHT. We apply the same logic to facing left. If down is not being pressed but left or right is, then we need to apply the walking Animation: if (input.left == 1 && input.down == 0) { player.dirX -= PLAYER_SPEED; if (player.state != WALK_LEFT) { player.state = WALK_LEFT; setAnimation("PLAYER_WALK_LEFT", &player); } }As we can see, the code is very similar to the code for crouching, except that we also move the player and the code for standing is exactly the same. Finally, we will look at the drawPlayer: void drawPlayer() { player.frameTimer--; if (player.frameTimer <= 0) { player.currentFrame++; if (player.currentFrame >= player.currentAnim->frameCount) { player.currentFrame = 0; } player.frameTimer = player.currentAnim->frameTimer[player.currentFrame]; player.w = player.currentAnim->frame[player.currentFrame]->w; player.h = player.currentAnim->frame[player.currentFrame]->h; } drawAnimation(player.currentAnim, player.currentFrame, player.x, player.y); }Each frame we decrement the player's frameTimer and if it is less than or equal to 0, we move to the next frame, wrapping around to frame index 0 if we reach the current Animation's frameCount. Finally, we call drawAnimation to draw the image to the screen. We will not look at the other files since there is nothing in them that has not been covered before. Conclusion This is one way to do Animation. Some games may hardcode animations into particular array indexes and search for them that way rather than doing a string comparison. In the next tutorial we will put together everything from previous tutorials to make a very basic platform game. Downloads |