Music

The good news about "regular" music is that many of its constituent parts are repeating several times during the song, wasting space and increasing the size of the game. More than that, you might have a few quality music loops and you want to create a great song with them. If you were looking for code to achieve at least one of these goals, stop looking: you have discovered music.wdl.

My example uses 4 wav loops (for a total of 1Mb) and it is used to create a song that would have needed about 3Mb. Imagine what you can create with 8 loops (2Mb) and how much space you can save! The idea is (once again) simple: we create a small file in any text editor and we write some numbers inside it; here is the content of my music.dat file (you can use any other name):

1 1 4 4 3 3 1 2 2 3 3

I have told you that I'm using only 4 loops; the data inside the file tells the engine to play "1" twice, "4" twice, "3" twice, "1" once, "2" twice, "3" twice. As soon as one of the loops has stopped, the one that follows it in music.dat will start to play. If the numbers repeat a lot you will save a lot of space, get it?

Let's see the wonderful piece of code that changes the Acknex engine into a sequencer:

var number_of_pieces = 11; // will play 11 music pieces (defined in music.dat)
var file_handle;
var music_handle = 0;
var piece_number;

function start_music()
{
file_handle = file_open_read("music.dat"); // open the file
while (number_of_pieces > 0)
{
piece_number = file_var_read (file_handle);
if (piece_number == 1)
{
music_handle = snd_play (luke1_snd, 40, 0);
}
if (piece_number == 2)
{
music_handle = snd_play (luke2_snd, 40, 0);
}
if (piece_number == 3)
{
music_handle = snd_play (luke3_snd, 40, 0);
}
if (piece_number == 4)
{
music_handle = snd_play (luke4_snd, 40, 0);
}
number_of_pieces -= 1;
while (snd_playing (music_handle) != 0) {wait (1);}
wait (1);
}
file_close (file_handle);
}

Is this simple or what? First we set the number of musical pieces to 11 (it is the number of figures in music.dat), then we open the file and we read the first number. If piece_number = 1 we will play the 1st loop, it piece_number = 2 we play the second loop and so on. This line:

while (snd_playing (music_handle) != 0) {wait (1);}

makes sure that a single loop is playing at a time. The loops will be played in the order indicated by the figures in music.dat and when number_of_pieces = 0 the song will stop and the file will be closed.

But what should I do if I want to use let's say... 8 loops?
Define the wav files, add if (piece_number == 5..8) etc inside the start_music function and use numbers from 1 to 8 inside the music.dat file. If you want, you can generate random piece_number values and torture your neighbours with the music that is created this way.

Skeletons Inc.

I'm not sure about you, but I feel pitty for the old skeleton trying to find its way out... What, you haven't played Sk. Inc. yet?

The goal of this game is to get enough points in order to move to the next level (pretty original, huh?). The number of lives and points and their corresponding texts are displayed with the code below:

panel skinc_pan // displays the numbers
{
pos_x = 0;
pos_y = 0;
digits = 120, 575, 4, skinc_font, 1, lives;
digits = 650, 575, 4, skinc_font, 1, points;
flags = refresh, visible;
}

text skinc_txt // displays "Lives:" and "Points:"
{
pos_x = 0;
pos_y = 575;
font = skinc_font;
string = skinc_str;
flags = visible;
}

Function main is as simple as possible:

function main()
{
level_load (skinc_wmb);
wait (2); // wait for the level to be loaded
clip_size = 0; // show all the triangles for all the models
fps_max = 30; // lock the frame rate to 30 fps
}

The camera is attached to an arrow.mdl placed in the level, using the code below:

action init_camera
{
my.invisible = on;
while (player == null) {wait (1);}
vec_set (camera.pos, my.pos);
while (1)
{
camera.arc = max (10, (camera.arc - 2 * time * key_equals)); // press "+" and "-"
camera.arc = min (70, (camera.arc + 2 * time * key_minusc)); // to zoom in / out
vec_set (temp.x, player.x);
vec_sub (temp.x, my.x);
vec_to_angle (camera.pan, temp); // rotate towards the player
wait (1);
}
}

The camera rotates towards the player all the time; the aparent distance between the camera and the player can be changed by pressing "+" and "-". Why aparent distance? Because I'm not changing the distance, but camera.arc which gives a similar effect. Let's look at the action associated to the player:

action skeleton // the player
{
while (key_any == 1) {wait (1);}
my.z = 30;
player = me;
falling = 0;

The first line waits until all the keys are released; this is useful when we restart the game. The skeleton is placed with its feet on the floor; play with 30 if you use another model and / or level. The variable named falling will be set to 1 if the player is falling and to 0 if it is sitting or walking on solid ground.

while (lives > 0)
{
if (key_cuu == 1)
{
my.pan = 0;
my.skill10 = my.x;
while ((my.x < my.skill10 + step_width) && (falling == 0))
{
ent_cycle("walk", my.skill20);
my.skill20 += animation_speed * time;
my.skill20 %= 100;
my.x += speed * time;
check_content();
wait (1);
}
}

As long as the player is alive, if we press the "up" arrow, the player will rotate in that direction (pan = 0) and then it will store its current x position in its skill10. As long as the player hasn't walked a distance equal or greater than step_width and if it sits on solid ground, everything inside the while loop will run. This includes the animation and the movement, which is done by simply chaging player's x coordinate, because we don't need to check for collisions with other entities. The function check_content will detect if the player is standing on solid ground or not.

The code for the rest of the movement keys (down, left, right) is similar so I will let you look at it; we will meet me again close to the end of this function.

if (key_cud == 1)
{
my.pan = 180;
my.skill10 = my.x;
while ((my.x > my.skill10 - step_width) && (falling == 0))
{
ent_cycle("walk", my.skill20); // play walk frames animation
my.skill20 -= animation_speed * time; // reversed "walk" animation
my.skill20 %= 100; // loop the animation
my.x -= speed * time;
check_content();
wait (1);
}
}
if (key_cul == 1)
{
my.pan = 90;
my.skill10 = my.y;
while ((my.y < my.skill10 + step_width) && (falling == 0))
{
ent_cycle("walk", my.skill20); // play walk frames animation
my.skill20 += animation_speed * time; // "walk" animation speed
my.skill20 %= 100; // loop the animation
my.y += speed * time;
check_content();
wait (1);
}
}
if (key_cur == 1)
{
my.pan = 270;
my.skill10 = my.y;
while ((my.y > my.skill10 - step_width) && (falling == 0))
{
ent_cycle("walk", my.skill20); // play walk frames animation
my.skill20 += animation_speed * time; // "walk" animation speed
my.skill20 %= 100; // loop the animation
my.y -= speed * time;
check_content();
wait (1);
}
}
wait (1);
}

See? I told you that the rest of the code is similar. Let's take a look at the lines below; if this code runs the player has lost all the lives:

my.skill20 = 0;
while (my.skill20 < 100) // the player is dead
{
ent_cycle("death", my.skill20); // play death frames animation
my.skill23 += 5 * time; // "death" animation speed
wait (1);
}
}

We reset skill20, because it was used for the "walk" animation and "death" should start from its first frame.

Function check_content() tests the floor and sets falling to 0 if the player walks or stand on solid ground, otherwise falling = 1.
function check_content()
{
vec_set (temp, my.pos);
temp.z -= 64;
if (content (temp) != content_solid) // if it is a hole
{
exclusive_global;
lives -= 1;
falling = 1;
snd_play (falling_sound, 70, 0);
while (player.z > -1000)
{
player.z -= 35 * time;
wait (1);
}
sleep (2);
if (lives > 0)
{
points = 0;
main();
}
}
else
{
falling = 0; // walking on solid ground
}
}

The first line stores player's coordinates in temp, then we substract 64 quants from temp.z; this point should be placed below player's feet (and inside the floor block, you got that right). If content (temp) sees a hole, the player will fall, so we stop all the other instances of the action, we decrease the number of lives, we set falling to 1 (it is needed in action skeleton), we play a falling sound, we move him downwards until its height is about -1000 quants, we wait for 2 seconds, we reset the score, we restart the level, we... oh, I should stop now.

If content(temp) is solid, falling is set to 0 so the player will be able to move ok.

Every sphere that adds points to the score has this action attached to it:

action bonus1
{
my.passable = on;
my.transparent = on;
my.light = on;
my.bright = on;
my.lightgreen = 250;
my.lightrange = 0;
while (vec_dist (player.x, my.x) > 30)
{
my.pan += 3 * time; // rotate if the player is far
wait (1);
}
snd_play (bonus_sound, 30, 0);
while (my.scale_x < 3)
{
my.scale_x += 0.1 * time;
my.scale_y = my.scale_x;
my.scale_z = my.scale_x;
my.z += 0.5 * time;
my.alpha -= 2 * time;
wait (1);
}
points += 10;
ent_remove (me);
}

The sphere is passable, transparent and is illuminated by a (green) light. If the player is farther than 30 quants, the sphere will rotate. When the player has come close, we play a sound and then we increase the scale of the sphere until it reaches 3, we move it upwards, we decrease its alpha. We add 10 points to the score and then we remove the sphere.

The game would be boring if we wouldn't use a few tricks; the first one is:

action block_breakable
{
while (vec_dist (player.x, my.x) > 70) {wait (1);}
sleep (1.5); // wait 1.5 seconds
ent_morph (my, "block2.wmb"); // morph the initial block into this one
snd_play (break_sound, 50, 0);
sleep (0.1);
ent_morph (my, "block3.wmb"); // then this block
sleep (0.1);
ent_morph (my, "block4.wmb"); // and finally this block
sleep (0.1);
while (my.z > -500) // now move the final block downwards
{
my.z -= 15 * time;
wait (1);
}
ent_remove (me); // and then remove it
}

This block will break in small pieces if you walk over it, so you will have a limited set of options to complete the level. It sits quietly in the dark until the player has come closer than 70 quants, then waits for 1.5 seconds, then morphs into another block, plays a sound, waits 0.1 seconds, morphs into another block and so on. The last block will be moved downwards until its z is about 500 quants and then it will be removed. You can be sure that the evil check_content() function discussed above will set falling to 1 if you spend too much time sitting on one of these blocks.

The second (and sadly, the last) block is:

action block_end
{
my.invisible = on;
my.passable = on;
while (points < 100) {wait (1);} // will be invisible until the player has 100 points
my.passable = off;
my.invisible = off;
snd_play (finished_sound, 30, 0);
sleep (0.5);
my.invisible = on;
snd_play (finished_sound, 30, 0);
sleep (0.5);
my.invisible = off;
snd_play (finished_sound, 30, 0);
}

This block is like a bridge that gets lowered when you have managed to gather 100 points, but wait! It isn't a bridge and it isn't lowered... Anyway, it is the red block that allows you to get to the end of the level. The block will be invisible and passable until the player has 100 points; as soon as this happens, the block will flash, a sound will be played several times and the player will be able to move towards the flag:

action flag
{
my.passable = on;
while (player == null) {wait (1);}
while (vec_dist (my.x, player.x) > 50) {wait (1);}
player.light = on;
player.lightred = 250;
player.lightrange = 100;
sleep (3); // wait for 3 seconds
exit; // shut down the engine
}

If the player has come close enough to the flag, it will glow red light (it has to celebrate it somehow, right?), and after 3 seconds the engine will shut down. I know that this is a sad idea for "Next Level" but if some of your customers complain about it you can tell them that this game was only a demo and you can give them their money back. Not all of them will complain so you will get something anyway...