Code snippets

Top  Previous  Next

Planet Survivors

 

Believe it or not, this series is over! I needed quite a few months to create it but now we've got a project that includes four completely different games under a single umbrella. This month I have fixed some minor bugs and I have created the load / save system; we'll concentrate our efforts in this direction.

 

aum45_shot9

 

string name1_str = "                                       "; // up to 39 characters, feel free to increase the limit

string name2_str = "                                       "; // up to 39 characters, feel free to increase the limit

string name3_str = "                                       "; // up to 39 characters, feel free to increase the limit

string name4_str = "                                       "; // up to 39 characters, feel free to increase the limit

 

text slot1_txt

{

pos_x = 205;

pos_y = 180;

layer = 150;

font = system_font;

string = name1_str;

}

 

.............................

 

text slot4_txt

{

pos_x = 205;

pos_y = 480;

layer = 150;

font = system_font;

string = name4_str;

}

 

I have decided to create four saving slots; name1_str will store the name given to the first saving slot, name2_str will store the name from the second slot and so on. I have also defined four texts that will display the names on the "Load game" and "Save game" panels.

 

panel blackpanel_pan

{

bmap = blackpanel_pcx;

layer = 2;

pos_x = 0;

pos_y = 0;

flags = overlay, refresh;

}

 

panel savegame_pan

{

bmap = savegame_pcx;

layer = 100;

pos_x = 0;

pos_y = 0;

button = 198, 158, slot2_pcx, slot1_pcx, slot2_pcx, ps_save1, null, mouse_over;

button = 198, 258, slot2_pcx, slot1_pcx, slot2_pcx, ps_save2, null, mouse_over;

button = 198, 358, slot2_pcx, slot1_pcx, slot2_pcx, ps_save3, null, mouse_over;

button = 198, 458, slot2_pcx, slot1_pcx, slot2_pcx, ps_save4, null, mouse_over;

flags = overlay, refresh;

on_click = game_was_saved;

}

 

panel loadgame_pan

{

bmap = loadgame_pcx;

layer = 100;

pos_x = 0;

pos_y = 0;

button = 198, 158, slot2_pcx, slot1_pcx, slot2_pcx, ps_load1, null, mouse_over;

button = 198, 258, slot2_pcx, slot1_pcx, slot2_pcx, ps_load2, null, mouse_over;

button = 198, 358, slot2_pcx, slot1_pcx, slot2_pcx, ps_load3, null, mouse_over;

button = 198, 458, slot2_pcx, slot1_pcx, slot2_pcx, ps_load4, null, mouse_over;

flags = overlay, refresh;

on_click = cancel_loading;

}

 

I have defined a panel with a black bitmap; I'm using it to hide the game window when the player presses Esc in order to bring on the main menu. The following panels are used for saving and loading; each one of them has four buttons, which are our save / load slots. Whenever we click the savegame_pan panel, its associated game_was_saved() funtion is run; the same thing happens with loadgame_pan and function cancel_loading().

 

What happens when you press the Esc key during the game? This key used to shut down the game instantly, but now it has got a different role - it runs the function below:

 

function return_to_main()

{

if (main_pan.visible == on) {return;}

freeze_mode = 1;

blackpanel_pan.visible = on;

main_pan.visible = on;

mouse_mode = 2;

while (freeze_mode == 1)

{

   mouse_map = pointer1_tga;

   mouse_pos.x = pointer.x;

   mouse_pos.y = pointer.y;

   wait (1);

}

}

 

If the main panel is already visible, the function returns; otherwise, all the actions in the level are stopped and the black panel that hides what's happening in the level is displayed. We show the main panel as well; this panel has a bigger layer when compared to the black panel, so it will appear over it. We show the mouse pointer and then we allow the mouse to move for as long as the game is freezed.

 

function save_game()

{

loadgame_pan.visible = off;

savegame_pan.visible = on;

save_handle = file_open_read("save1.dat"); // open the file

if (save_handle != 0) // if the file exists

{

   file_str_read(save_handle, name1_str);

   slot1_txt.string = name1_str;

   slot1_txt.pos_x = 205 + (39 - str_len(name1_str)) * 5; // center the text, allow up to 39 characters, 5 = 10 / 2 (character width / 2)

   slot1_txt.visible = on;

   file_close(save_handle);

}

.....................................................

save_handle = file_open_read("save4.dat"); // open the file

if (save_handle != 0) // if the file exists

{

   file_str_read(save_handle, name4_str);

   slot4_txt.string = name4_str;

   slot4_txt.pos_x = 205 + (39 - str_len(name4_str)) * 5; // center the text, allow up to 39 characters, 5 = 10 / 2 (character width / 2)

   slot4_txt.visible = on;

   file_close(save_handle);

}

}

 

The function above will run whenever the player presses the "Save" button on the main menu panel. This function will hide the "Load game" panel if it was visible and will display the "Save game" panel. We open the file named save1.dat (if it exists) and we read the name of the saving slot from the file, assigning it to the slot1_txt text. We center the text on the saving slot, and then we make it visible. Finally, we close the file and the process repeats for all the four saving slots.

 

If you are wondering what's with those save1.dat... save4.dat you are not alone; I have asked myself why I used them as well... Hold on, now I remember: we need to save the name of the slot (the save game name) in a separate file because we want to display it on the "Load game" or "Save game" slots before loading / saving a new game. My demo comes with saved games named level1... level4 (guess why) but you can use any name for your save, as long as you make it shorter than or equal to 39 characters. How did I come up with this value? A character in my font is 10 pixels wide and the button has 400 pixels, which means that I can safely use 39 * 10 = 390 pixels out of those 400 without coming close to the boundaries.

 

function load_game()

{

media_stop(media_handle); // stop the soundtrack

savegame_pan.visible = off;

loadgame_pan.visible = on;

save_handle = file_open_read("save1.dat"); // open the file

if (save_handle != 0) // if the file exists

{

   file_str_read(save_handle, name1_str);

   slot1_txt.string = name1_str;

   slot1_txt.pos_x = 205 + (39 - str_len(name1_str)) * 5; // center the text, allow up to 39 characters, 5 = 10 / 2 (character width / 2)

   slot1_txt.visible = on;

   file_close(save_handle);

   save1_exists = 1;

}

else

{

   save1_exists = 0;

}

 

...................................................

save_handle = file_open_read("save4.dat"); // open the file

if (save_handle != 0) // if the file exists

{

  file_str_read(save_handle, name4_str);

  slot4_txt.string = name4_str;

  slot4_txt.pos_x = 205 + (39 - str_len(name4_str)) * 5; // center the text, allow up to 39 characters, 5 = 10 / 2 (character width / 2)

  slot4_txt.visible = on;

  file_close(save_handle);

  save4_exists = 1;

}

else

{

  save4_exists = 0;

}

}

 

Function load_game() will run each and every time you press the "Load" button on the main menu. It stops any soundtrack that might be playing at the moment, hides the "Save game" panel, displays the "Load game" panel and reads the data from save1.dat (if the file exists), storing the name of the saved game in name1_str. The string is passed to the slot1_txt text, which gets centered on the button and made visible. If the file exists, the variable save1_exists is set to 1; otherwise, it will be set to zero. We will use this variable a bit later; we need it because we wouldn't want our customers to click and load a saved game from a slot in which they didn't save anything, right? The same thing happens with the rest of the slots.

 

function ps_save1()

{

if (inkey_active == on) {return;}

if (players_shield <= 0) {return;}

slot1_txt.visible = on;

str_cpy(name1_str, "");

while (str_cmpi(name1_str, "") == 1)

{

   inkey(name1_str);

   wait (1);

}

slot1_txt.string = name1_str;

slot1_txt.pos_x = 205 + (39 - str_len(name1_str)) * 5;

save_handle = file_open_write("save1.dat");

file_str_write (save_handle, name1_str);

file_close(save_handle); // close the file

game_save("save", 1, sv_all - sv_info);

game_was_saved();

}

 

This is the function that runs when the player presses the first button on the "Save game" panel; trust me when I tell you that the other function are similar. The first line gets out of the function if inkey is already active (if the player has pressed the same button twice). The same thing happens if the player wants to save a game in which the player is dead. We display the text that will contain the name of the saved game, we reset its associate name1_str string, and then we wait inside the while loop until we are sure that the player has typed something in, storing the input inside name1_str.

 

Moving outside of the while loop now; we center the text on the screen and then we create and open the file named save1.dat, write the name of the save inside it. Finally, we save the game (using Conitec's "game_save" instruction) inside the save1.sav file. The last line runs the game_was_saved() function which hides some panels, makes sandwiches, and so on.

 

function ps_load1()

{

if (save1_exists == 0) // the slot is empty?

{

   return; // get out of here!

}

game_load("save", 1);

game_was_loaded();

if (level_number == 1)

{

   media_play ("track1.wav", null, 50); // play the first soundtrack (3D space + logical level)

}

if (level_number == 3)

{

   media_loop("track2.wav", null, 100); // play the second soundtrack (shooter leve)

}

}

 

The function above (as well as its other three sisters) runs when you press a button on the "Load game" panel. It checks is the saved game exists or not; if it does exist, it loads the data that was stored inside the save1.sav file, and then runs the game_was_loaded() function that does some cleanup. If the save was taken from the first level, we play the first soundtrack; otherwise, if the save was taken from the third (shooter) level, we play the second sound track in a loop. Why did we do that? Well, we have stopped the soundtracks and they can't be resumed that easily (unless you are using several smaller-sized melody slices or more complicated code) so we simply start the tracks again when we load the 1st or the 3rd level.

 

function game_was_saved()

{

if (players_shield <= 0)

{

   savegame_pan.visible = off;

   return;

}

else

{

   sleep (2);

   slot1_txt.visible = off;

   slot2_txt.visible = off;

   slot3_txt.visible = off;

   slot4_txt.visible = off;

   savegame_pan.visible = off;

   blackpanel_pan.visible = off;

   main_pan.visible = off;

   if (level_number == 1)

   {

      if (human == null)

     {

       mouse_mode = 0;

       mouse_map = pointer1_tga;

     }

     else

    {

      mouse_mode = 2;

      mouse_map = logical_pcx;

     }

   }

   if (level_number == 2)

   {

     mouse_mode = 0;

   }

   if (level_number == 3)

   {

    mouse_mode = 0;

   }

   sleep (1);

   freeze_mode = 0;

}

}

 

This function runs at the end of the saving process, making various tweaks, depending on the level number. If the player is dead, the function hides the "Save game" panel and exits; otherwise, it waits for 2 seconds, and then it hides the texts that hold the names of the saved games, as well as the main panel. If we are at the first level and the "human" pointer is null, this means that we were still playing the " 3D space game" part of the first level, so we hide the mouse pointer (the code that is used for the ship will display it) and then we set the proper pointer1_tga mouse pointer. If "human" isn't null, we are playing the "logical game" part of the first level, because "human" is the little guy that comes out of the ship and is created as soon as the ship lands. We show the mouse pointer and we set for it the logical_pcx bitmap.

 

If we are playing the second level, we hide the mouse pointer because it isn't needed. The same thing happens if we are playing the third level. We give the player a second to remember where he / she was when she has decided to save the game, and then we unfreeze it.

 

function cancel_loading()

{

loadgame_pan.visible = off;

slot1_txt.visible = off;

slot2_txt.visible = off;

slot3_txt.visible = off;

slot4_txt.visible = off;

}

 

This function will run if the player clicks the "Load game" panel. I have written "click the bottom of the screen if you have got here by mistake" on the panel, but the truth is that you can click any area of the panel, excluding the buttons, if you have clicked "Load" on the main panel by mistake. If that has happened, the loading panel and its associated texts will be made invisible.

 

function game_was_loaded()

{

loadgame_pan.visible = off;

savegame_pan.visible = off;

slot1_txt.visible = off;

slot2_txt.visible = off;

slot3_txt.visible = off;

slot4_txt.visible = off;

main_pan.visible = off;

sleep (1);

freeze_mode = 0;

}

 

The last function will clean up the mess after loading a saved game; it hides some panels and texts, waits for a second and then resumes the game.

 

Please allow me to close this series with a few advices that are applicable whenever you create a full game:

 

- Try to avoid "starter" functions; they will make your life harder in the end;

 

- Try to use as many local variables as possible. Avoid as many global variables as you can;

 

- Do as many initializations as possible inside function main; you are going to restart the game by running main() anyway (unless you are using some strange, more complicated method).

 

 

 

Fountain

 

aum45_shot10

 

This fountain uses particles that stop (don't pass through) when they encounter a wmb entity or a level block and create a nice looking mist when they touch the surface of the water.

 

action fountain

{

   my.invisible = on;

   my.passable = on;

   while(1)

   {

      effect(particle_fountain, 15 * time, my.x, nullvector);

      wait(1);

   }

}

 

The action above must be attached to a small model; it makes it invisible and passable and then it generates particles using the particle_fountain function. The number of particles that is generated is time-corrected.

 

function particle_fountain()

{

   temp.x = random(6) - 3;

   temp.y = random(6) - 3;

   temp.z = random(10) + 10; // play with this value

   vec_set(my.vel_x,temp);

   my.bmap = drop_tga;

   my.size = 1.5;

   my.flare = on;

   my.bright = on;

   my.move = on;

   my.streak = on; // if your engine version supports it

   my.gravity = 2;

   my.alpha = 80;

   my.lifespan = 50;

   my.function = fade_particles;

}

 

The particles have a random speed on x and y (-3 to 3) and a positive random speed on z (10 to 20). Please note that I am using the "streak" flag which makes the particles look much better but it might not be available in your engine version; try to use a different particle bitmap if you can't use streak. The particles have a gravity of 2 and a lifespan of 50; the function that drives them is named fade_particles.

 

function fade_particles()

{

   my.alpha -= 2 * time;

   if(content(my.x) == content_solid) // the particle is inside a solid?

   {

      effect(fountain_mist, 1, my.x, nullvector); // generate mist

      my.lifespan = 0; // don't allow the particles to continue their movement if they have hit something solid

   }

   if(my.alpha < 0)

   {

      my.lifespan = 0;

   }

}

 

The first line of code decreases the transparency of the particles. If one of the particles ends up inside a solid (a wmb entity or a level block) it will generate a mist particle using the fountain_mist function and will disappear. For good results you should use fountains with thick walls because the water drops travel at big speeds and might penetrate the walls of the fountain if they are too thin. The last few lines in the function destroy the particle when its transparency factor goes below zero.

 

function fountain_mist()

{

   temp.x = random(2) - 1;

   temp.y = random(2) - 1;

   temp.z = random(1) + 1; // play with this value

   vec_set(my.vel_x,temp);

   my.bmap = smoke123_tga;

   my.size = 5;

   my.lifespan = 50;

   my.flare = on;

   my.bright = on;

   my.move = on;

   my.gravity = 0;

   my.alpha = 70;

   my.function = fade_mist;

}

 

The mist has a random speed on x and y (-1 to 1) and a positive random speed on z (1 to 2). The function that drives them is named fade_mist:

 

function fade_mist()

{

   my.alpha -= 3 * time;

   my.size += 0.5 * time;

   if(my.alpha < 0)

   {

      my.lifespan = 0;

   }

}

 

This function decreases the transparency of the mist particle while increasing its size. As soon as alpha goes below zero, the mist particle is removed. You can play with the temp.z value inside fountain_mist() and with alpha and size inside fade_mist() until you get a better looking mist. I'm not a fan of excessive particle use but here's an example of what you can get with a slightly different set of values:

 

aum45_shot11