4
\$\begingroup\$

For the past few days I've been trying to learn how to animate 2D sprites using SDL2. For that purpose, I've taken a 6 frames animation from a Pokémon game and made a PNG image out of it :

Leaf 6 frames animation

The way I figured out would work fine is is by creating an array leaf_frames (Leaf being the name of the character I used) containing the position and size of each frame in the image, and have another array leaf_frames_indices containing the index of the frame in leaf_frames to be displayed at a certain time into the animation. Each time I want to display a frame, I will simply tell the SDL_RenderCopy function which portion of the image to copy on the screen using those arrays and the current frame (given by current_frame). This is how I implemented it.

main.c:

#include <stdio.h>
#include <stdlib.h>

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

#include "config.h"


void keyboard_poll (SDL_Event *, int *);
SDL_Texture *load_image (SDL_Renderer *, const char *);


#define len(x) (sizeof(x) / sizeof(x[0]))
#define eprintf(...) fprintf(stderr, __VA_ARGS__)
#define key_pressed(keys, keysold, key) (keys[key] && !keysold[key])
#define key_released(keys, keysold, key) (!keys[key] && keysold[key])


void
keyboard_poll (SDL_Event *event, int *keys)
{
    SDL_Scancode keycode = event->key.keysym.scancode; /* not necessary, but makes the following code easier to read and write */

    if (keycode < SDL_NUM_SCANCODES) { /* out of bounds safety check */
        if (event->type == SDL_KEYDOWN) keys[keycode] = 1;
        if (event->type == SDL_KEYUP)   keys[keycode] = 0;
    }
}

SDL_Texture *
load_image (SDL_Renderer *renderer, const char *image_path)
{
    SDL_Surface *image_surface = IMG_Load(image_path);
    if (!image_surface) {
        eprintf("Could not load image %s : %s\n", image_path, IMG_GetError());
        return NULL;
    }

    SDL_Texture *image_texture = SDL_CreateTextureFromSurface(renderer, image_surface);
    SDL_FreeSurface(image_surface);

    return image_texture;
}

int
main (void)
{
    int retval = EXIT_SUCCESS;

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        eprintf("Cannot initialize SDL : %s\n", SDL_GetError());
        retval = EXIT_FAILURE;
        goto cleanup_exit;
    }

    int img_flags = IMG_INIT_PNG; /* check whether what was asked to be initialized has been initialized or not, ignore the rest */
    if ((IMG_Init(img_flags) & img_flags) != img_flags) {
        eprintf("Cannot initialize SDL_image : %s\n", IMG_GetError());
        retval = EXIT_FAILURE;
        goto cleanup_sdl;
    }

    SDL_Window *window = SDL_CreateWindow(CONF_APP_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, CONF_WINDOW_WIDTH, CONF_WINDOW_HEIGHT, 0);
    if (!window) {
        eprintf("Cannot create window : %s\n", SDL_GetError());
        retval = EXIT_FAILURE;
        goto cleanup_img;
    }

    SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
    if (!renderer) {
        eprintf("Cannot create renderer : %s\n", SDL_GetError());
        retval = EXIT_FAILURE;
        goto cleanup_window;
    }

    SDL_Texture *leaf_sprite = load_image(renderer, "leafchan-animated.png");
    if (!leaf_sprite) {
        retval = EXIT_FAILURE;
        goto cleanup_renderer;
    }

    /* initialization ends here, declare some "quick and dirty" data to be used with the animation */

    SDL_Rect leaf_frames[] = { /* the array contains the coordinates of each frame */
        {.h = 19, .w = 16, .x =  0, .y = 0},
        {.h = 19, .w = 16, .x = 16, .y = 0},
        {.h = 19, .w = 16, .x = 32, .y = 0},
        {.h = 19, .w = 16, .x = 48, .y = 0},
        {.h = 19, .w = 16, .x = 64, .y = 0},
        {.h = 19, .w = 16, .x = 80, .y = 0}
    };

    SDL_Rect destrect = {
        .x = 0,
        .y = 0,
        .h = CONF_WINDOW_HEIGHT,
        .w = CONF_WINDOW_WIDTH
    };

    int leaf_frames_indices[] = {0, 1, 2, 3, 4, 5, 4, 5, 4, 5, 4, 5, 4, 3, 2, 1, 0};
    int current_frame = 0;
    int total_frames = len(leaf_frames_indices);

    int running = 1;
    int should_quit = 0; /* might be better off in a flags[] array ? */
    SDL_Event event;

    int keys[SDL_NUM_SCANCODES] = {0}; /* group those two in a struct ? */
    int keysold[SDL_NUM_SCANCODES] = {0};

    /* actual game loop */

    while (running) {

        /* read all events, update keyboard state */

        memcpy(keysold, keys, sizeof(keys));

        while (SDL_PollEvent(&event)) {
            keyboard_poll(&event, keys);

            if (event.type == SDL_QUIT)
                should_quit = 1;
        }

        /* react to events */

        if (key_released(keys, keysold, CONF_KEY_EXIT) || should_quit)
            running = 0;

        /* draw */

        SDL_SetRenderDrawColor(renderer, CONF_CLEAR_COLOR, 255);
        SDL_RenderClear(renderer);

        SDL_RenderCopy(renderer, leaf_sprite, &leaf_frames[leaf_frames_indices[current_frame]], &destrect);

        SDL_RenderPresent(renderer);

        /* dirty time management, for demo purposes */

        SDL_Delay(100);

        /* used to be a 4 lines if else statement, but I just find one-liners to be more elegant, personal taste */
        current_frame = (current_frame < (total_frames - 1)) ? current_frame + 1 : 0;
    }

    /* cleanup */

    SDL_DestroyTexture(leaf_sprite);
cleanup_renderer:
    SDL_DestroyRenderer(renderer);
cleanup_window:
    SDL_DestroyWindow(window);
cleanup_img:
    IMG_Quit();
cleanup_sdl:
    SDL_Quit();
cleanup_exit:
    return retval;
}

config.h:

#define CONF_APP_NAME       "Animations SDL2"

#define CONF_WINDOW_WIDTH   128
#define CONF_WINDOW_HEIGHT  152

#define CONF_PIXELFORMAT    SDL_PIXELFORMAT_RGBA8888
#define CONF_CLEAR_COLOR    0, 5, 25

#define CONF_KEY_EXIT       SDL_SCANCODE_ESCAPE

And sure enough once I start it the animation runs perfectly :

Animated preview

Now that everything works fine, and that I'm pretty satisfied with my error handling, I'm still unsure as for the animation method I used and how I could group all of these things.

My first improvement suggestion would be to create a struct and have all of the animation-related data inside it, but then I feel that's a bit too specific to animations, and would not mix well with functions that use static sprites. What I could do as well, is to have a Sprite struct which would work for both animated and static sprites via an element .animated specifying whether or not the sprite is animated, so the drawing function knows how to handle it. Or maybe I could consider every sprite to be an animation, with static sprites only having one frame, which would spare me the need to write multiple implementations of drawing functions.

How should I go about this? I'm unsure how to pack all this together and have it be consistent. Or maybe there's a better way to deal with animations?

Note : I'm using the C99 standard.

\$\endgroup\$
2
\$\begingroup\$

you can improve the time management by adding a millisecond counter and testing against that. This allows other things to run/animate as well while the sprite is waiting on the next frame. delay is usually a very bad idea for timing when doing interactive programs or when multiple things are being delayed.

int current_frame = 0;
int total_frames = len(leaf_frames_indices);
uint32 frame_last_changed = 0;

uint32 time = 0;

...

while (running) {
    time = SDL_GetTicks(); 
    //...


    if(frame_last_changed+100 > time){

        current_frame = (current_frame < (total_frames - 1)) ? current_frame + 1 : 0;
        frame_last_changed = time;
    }
}

The 100 delay can for example be added to the animation struct so animations can have different timings. A special value of 0 can mean not animated.

\$\endgroup\$
  • \$\begingroup\$ I am aware that delays are bad for time management, and indeed I do not use them when making actual games. In that case I really only wanted to focus on the animation itself, plus I'm using fixed time-steps in my other projects or "ticks", hence the lack of time-based values in my animation code, but in the end it doesn't make much of a difference. I really like the idea of 0 delay = no animation though, I'll implement that. Thanks ! \$\endgroup\$ – saeleko Jul 31 '17 at 14:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.