Return to home

Introduction

In this lesson we will learn some more about structs, and also learn about another feature in raylib.

If you remember from past lessons, structs are things the compiler can be used to group data together. Using a struct we can group related data together, and store it as one thing in a variable, or in an array. We can pass it as a parameter and return it from a function.

Structs also can contain functions which act on that data. Grouping functions and data together is called Object-Oriented Programming. Using OOP, we can define everything about a group of fields, and make it easy to use for coders using the type itself.

Member functions

A member function is simply a function defined within a struct. It has all the same characteristics of a normal function with one new feature -- it has access to the fields from an instance of that struct.

It is called the same way you might access a field, with the . operator. Using member functions, we can encapsulate functionality with the data:


struct Circle
{
    Vector2 center;
    float radius;
    Color color;
    void draw() {
        DrawCircleV(center, radius, color);
    }
}

This Circle struct contains 3 fields. It also contains a draw function. Notice inside the function, we do not have to specify where center, radius and color come from. Since we are in the struct, the compiler knows to look at the instance itself to find those variables.

We can say about a Circle struct that it "knows how to draw itself". Let's see how this works in action:

import raylib;

struct Circle
{
    Vector2 center;
    float radius;
    Color color;
    void draw() {
        DrawCircleV(center, radius, color);
    }
}

void main()
{
    InitWindow(500, 500, "circles");
    Circle[] circles;
    SetTargetFPS(60);
    while(!WindowShouldClose())
    {
        if(IsMouseButtonDown(MouseButton.MOUSE_BUTTON_LEFT))
        {
            circles ~= Circle(GetMousePosition(), 15, Colors.BLACK);
        }

        BeginDrawing();
        ClearBackground(Colors.WHITE);
        foreach(c; circles)
        {
            c.draw();
        }
        EndDrawing();
    }
}

Notice that we draw all the circles just by calling c.draw();.

Defining object behavior

By encapsulating functionality on an object instead of writing it as a separate function, or directly inside your main loop, your code becomes more understandable and harder to mess up. Especially when we start having multiple behaviors you want to define for an object, writing code around that object can get messy and distract from the true game logic.

For example, imagine a bullet that not only knows how to draw itself, but move itself as well. And maybe it has a way to tell if it's hit something. We don't need to look at all the code in the bullet type to understand what's happening. The names of the member functions convey the meaning of the code:

foreach(ref bul; bullets) {
    bul.move();
    if(bul.isOffScreen())
    {
        bul.remove();
    }
    else if(bul.hit(enemy))
    {
        enemy.remove();
        bul.remove();
    }
}

See how without knowing any of the code of a bullet, we can understand what is happening?

Adding sounds in raylib

All good games have sound in them, meaning they play sounds when things happen to engage the player. In raylib, using sound requires a few things:

  1. You must call InitAudioDevice() after calling InitWindow().
  2. You must load the sound outside the main loop, using Sound sound = LoadSound(filename) where filename is a string containing the filename to load, and sound is a variable you decide to identify the loaded sound.
  3. Whenever you want to play this sound, you use the function PlaySound(sound)

You should use a variable name for each sound that corresponds to the reason you will play the sound. Like if it's the sound a player makes when he dies, you can call it playerDeathSound or something similar. This will help you later when you want to understand which sound is being played.

A sound starts playing when you use PlaySound and it continues through subsequent frames until the sound ends. If you call PlaySound before the sound is done playing (like in a subsequent frame), then the sound will start over.

Adding music in raylib

Music is sound that continually plays in the background. A large difference between a sound and music is that music is typically playing in the background while the game is happening, and player actions don't typically affect the music.

In code, playing music is different from sound in that it's typically a very long audio file. Instead of one call to play the whole music, you start playing the music, and then every frame you have to refresh the music to go into the sound card. The steps to play music are:

  1. Just like sounds, you must call InitAudioDevice() after calling InitWindow(). This only needs calling once per program.
  2. You must load the music outside the main loop, using Music music = LoadMusicStream(filename) where filename is a string containing the filename to load, and music is a variable you decide to identify the loaded music.
  3. When you want to start playing music, you call PlayMusicStream(music)
  4. Every frame, while the music is being played, you must call UpdateMusicStream(music) for the music to continue playing.
  5. You stop playing the music by calling StopMusicStream(music)

There are other functions that can be called on music and sound objects. You can look at the Raylib cheatsheet to see the various functions.

Return to home

©2023-2024 Steven Schveighoffer