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.
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();
.
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?
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:
InitAudioDevice()
after calling InitWindow()
.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.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.
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:
InitAudioDevice()
after calling InitWindow()
. This only needs calling once per program.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.PlayMusicStream(music)
UpdateMusicStream(music)
for the music to continue playing.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