Hands-On Game Development with WebAssembly
上QQ阅读APP看书,第一时间看更新

Moving the sprite

Now that we have learned how to animate our sprite in a frame-by-frame animation, we will learn how to move a sprite around on our canvas. I want to keep our spaceship animated, but I would prefer it not run in an explosion loop. In our sprites folder, I have included a simple four-stage animation that causes our ship's engines to flicker. The source code is quite lengthy, so I will introduce it in three parts: a preprocessor and global variable section, the show_animation function, and the main function.

Here is the code that defines the preprocessor directives and the global variables at the beginning of our cpp file:

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

#include <emscripten.h>
#include <stdio.h>

#define SPRITE_FILE "sprites/Franchise1.png"
#define EXP_FILE "sprites/Franchise%d.png"

#define FRAME_COUNT 4

int current_frame = 0;
Uint32 last_time;
Uint32 current_time;
Uint32 ms_per_frame = 100; // animate at 10 fps

SDL_Window *window;

SDL_Renderer *renderer;
SDL_Rect dest = {.x = 160, .y = 100, .w = 0, .h = 0 };
SDL_Texture *sprite_texture;
SDL_Texture *temp_texture;
SDL_Texture* anim[FRAME_COUNT];

Following the preprocessor directives and global variables, our cpp file contains a show_animation function that defines our game loop. Here is the code for our show_animation function:

void show_animation() {
current_time = SDL_GetTicks();
int ms = current_time - last_time;

if( ms >= ms_per_frame) {
++current_frame;
last_time = current_time;
}

if( current_frame >= FRAME_COUNT ) {
current_frame = 0;
}

SDL_RenderClear( renderer );
temp_texture = anim[current_frame];

dest.y--;

if( dest.y < -16 ) {
dest.y = 200;
}

SDL_RenderCopy( renderer, temp_texture, NULL, &dest );
SDL_RenderPresent( renderer );
}

The final part of our cpp file defines the main function. That is the initialization code in our WebAssembly module:

int main() {
char explosion_file_string[40];
SDL_Init( SDL_INIT_VIDEO );
SDL_CreateWindowAndRenderer( 320, 200, 0, &window, &renderer );
SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255 );
SDL_RenderClear( renderer );
SDL_Surface *temp_surface = IMG_Load( SPRITE_FILE );

if( !temp_surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

sprite_texture = SDL_CreateTextureFromSurface( renderer,
temp_surface );
SDL_FreeSurface( temp_surface );

for( int i = 1; i <= FRAME_COUNT; i++ ) {
sprintf( explosion_file_string, EXP_FILE, i );
SDL_Surface *temp_surface = IMG_Load( explosion_file_string );

if( !temp_surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

temp_texture = SDL_CreateTextureFromSurface( renderer,
temp_surface );

anim[i-1] = temp_texture;
SDL_FreeSurface( temp_surface );
}

SDL_QueryTexture( sprite_texture,
NULL, NULL,
&dest.w, &dest.h ); // query the width and
height

dest.x -= dest.w / 2;
dest.y -= dest.h / 2;

SDL_RenderCopy( renderer, sprite_texture, NULL, &dest );
SDL_RenderPresent( renderer );

last_time = SDL_GetTicks();
emscripten_set_main_loop(show_animation, 0, 0);
return 1;
}

This code is similar to our sprite_animation code. There are only a few modifications, and most of them are within the show_animation function:

void show_animation() {
current_time = SDL_GetTicks();

int ms = current_time - last_time;

if( ms >= ms_per_frame) {
++current_frame;
last_time = current_time;
}

if( current_frame >= FRAME_COUNT ) {
current_frame = 0;
}

SDL_RenderClear( renderer );
temp_texture = anim[current_frame];

dest.y--;

if( dest.y < -16 ) {
dest.y = 200;
}

SDL_RenderCopy( renderer, temp_texture, NULL, &dest );
SDL_RenderPresent( renderer );
}

We advance our frame whenever the value in ms, which tracks the milliseconds since the last frame change, exceeds ms_per_frame, which we set to a value of 100. Because the spaceship is moving, we still need to update our canvas every frame with the new spaceship position. We do this by modifying the dest.y value, which tells SDL where to render our spaceship on the y-axis. We subtract one from the dest.y variable every frame to move the spaceship up. We also perform a check to see whether this value has become smaller than -16. Because the sprite is 16-pixels high, this will happen when the sprite has moved entirely off the screen at the top. If this is the case, we need to move the sprite back down to the bottom of the game screen by setting the y value back to 200. In an actual game, to tie our movement directly to the frame rate like this would be a bad idea, but for this demonstration, it will be fine.