Return to home

Introduction

In this lesson, we will learn a bit more foreach syntax, learn how to effectively use the mod operator, and try to animate some characters using raylib

Foreach on a range of numbers

The foreach statement so far has given us a way to loop over all values in an array. But there is another use of foreach we have not yet seen. We call this a foreach range statement. This loops over a sequence of numbers, incremented by one.

foreach(i; 0 .. 10) {
    writeln(i);
}

The above statement prints the integers starting with 0, and ending with 9. The value 10 does not get used, as it is the upper limit.

This is useful when we want to loop over indexes in an array, or loop over coordinates, or any kind of sequence of numbers that we don't have handy inside an array already. If you remember our tic-tac-toe program, and how the foreach loops had to use an indexed foreach, we can replace them with this style loop:

foreach(r; 0 .. 3) {
    foreach(c; 0 .. 3) {
        // process row r and col c
    }
}

The Mod operator

I briefely went over the modulus or mod operator in an earlier lesson. This is the operator that divides two numbers, and gives you the remainder.

The syntax looks like this:

int x = y % 5;

What this means is, take y, divide it by 5, and assign the remainder to x.

What can this help us accomplish in code? Imagine that you are running your game, but only want to update something every 5 frames. You can do this using an if statement like:

numFrames += 1;
if(numFrames % 5 == 0) {
    // do something
}

We will see there are other uses once we learn about animation.

Loading a texture from an image

In raylib, we can draw more than shapes. You can also load images from files, and load them into the video card hardware. These images can then be used to draw to the screen, directly from the image, as a texture.

To draw a texture, first we must load the texture. This is done with the function LoadTexture. It accepts as a parameter a filename to load (make sure to include the extension if any). The function returns a Texture2D value which can then be used to draw the texture to the screen anywhere we want.

InitWindow(500, 500, "test");
// before drawing loop
Texture2D frame1 = LoadTexture("frame1.png");
...
    // inside drawing loop
    DrawTexture(frame1, xpos, ypos, Colors.WHITE);

A few notes on this example:

Building game assets

Game assets include images, sounds, levels, and other files. Basically all files that aren't the actual program. You want assets to be separate from your program, because then you have the easy ability to edit or modify them without having to rebuild your entire program.

In real game development companies, assets constitute a huge portion of the effort for creating games. It takes years and many artists to build the images, sounds, music, and other assets for the game. But for our purpposes, we are just trying to make a simple game.

There are quite a few sites online that provide free game assets. One of my favorites is Kenney.nl. If you are looking for ideas on making a game, or trying to see how simple games are made, this site provides a lot of the tools you would need to do it.

When downloading assets for a 2d game, make sure you pick the 2D filter. The assets you download will be in compressed zipfile, which you would expand. Then you can copy the assets you want into your project folder.

Note that your game loads assets from the directory in which it is run. This typically means the directory the executable lives in. If you try to load an asset such as a texture, and it doesn't work, you will see an error message listed on the screen. Pay attention to the message to see what needs to be done to fix the problem.

Animating a character

Let's pick a set of 3 frames for animating, and use the knowledge we learned here to load the assets and animate the character.

I downloaded the "Toon Characters 1" pack from Kenney, and picked the first character to animate her running (there are a lot of animations in this pack)

Next, I unpacked the zipfile, and copied the assets needed into my project. They were called "character_femaleAdventurer_run0.png", "character_femaleAdventurer_run1.png", and "character_femaleAdventurer_run2.png". To make things easy, I just renamed them "frame1.png", "frame2.png", "frame3.png".

Now that we have the files in place, we can write the code to animate it:

import raylib;

void main()
{
    InitWindow(500, 500, "Animate");
    Texture2D[3] frames;
    frames[0] = LoadTexture("frame1.png");
    frames[1] = LoadTexture("frame2.png");
    frames[2] = LoadTexture("frame3.png");

    SetTargetFPS(60);

    int idx = 0;
    int fcount = 0;
    while(!WindowShouldClose())
    {
        fcount = (fcount + 1) % 5;
        if(fcount == 0)
        {
            idx = (idx + 1) % frames.length;
        }
        BeginDrawing();
        ClearBackground(Colors.WHITE);
        DrawTexture(frames[idx], 0, 0, Colors.WHITE);
        EndDrawing();
    }
}

Let's examine the different parts of this program. First, I declare a static array of Texture2D with a length of 3. This means that the array will always be exactly 3 elements long.

Second, I load the 3 textures, using the filenames "frame1.png", "frame2.png", and "frame3.png". These 3 files go into the 3 elements of the array, with indexes 0, 1, and 2.

The idx variable will serve as the current frame being displayed for the animation. It will only ever have the value 0, 1, or 2. We will see later how that is kept.

The fcount variable works as a timer to count the number of frames that should go by before the index is updated.

Inside the loop, which runs every frame of the game (60 times a second), fcount is set to one more, but modulus 5. This means, it is set to the remainder of itself + 1 divided by 5. Once fcount is 4, adding 1 equals 5, and the remainder becomes 0. This means fcount will have the seqence 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, ....

Whenever fcount is 0 (once every 5 frames), idx is incremented in the same way, except the divisor is the number of textures in the frames array. In this case, 3, and so idx which changes once every 5 frames, has the sequence 0, 1, 2, 0, 1, 2, 0, 1, ....

One thing to recognize here, is that I could have written:

idx = (idx + 1) % 3;

This is shorter to write, and basically equivalent. However, this code is brittle! If I add a new frame, now I have to go into the code and change the 3 to a 4. Or if I subtract a frame, I need to change it to a 2. Using the information that the variables already contain helps you write code that is easy to change.

And finally, now that we have updated our game state (which frame we are on), we will draw the state using the DrawTexture function on a white background.

Since we have 3 cells of animation, and each one takes 5 frames, this means the full animation repeats every 15 frames. This means that the animation will repeat 4 times every second, since 15 goes into 60 four times. Determining the right numbers to pick is partly calculation and partly art. Experiment with these numbers to see how it looks!

Exercise

For the exercise, let's add a second animation, but run it at a faster or slower pace. In order to do this, change fcount to just increment, and instead use the mod operator in the if statement:

fcount += 1;
if(fcount % 5 == 0) {
   ...
}

Now, you get the same result, but we can use the fcount to run an animation every 7 frames, or every 2 frames, or whatever number we want. Experiment with different speeds to see how it looks. You can even use a variable for the number to use, and use keypresses to alter the variable to speed up or slow down the animations.

Each separate animation needs its own index, but you can animate the same 3 textures. It costs the GPU almost nothing to draw the same texture multiple times.

If you give, up, you can

Return to home

©2023 Steven Schveighoffer