Beginner's corner

Top  Previous  Next

Alarm clock

The clock code below allows you to set the alarm time using hours and minutes; the alarm will yell its annoying sound for a minute.

The main panel displays the clock picture and the four buttons (arrows) used to set the alarm time: + - hours and + - minutes.

panel main_pan // main panel
{
    bmap = main_pcx;
    pos_x = 0;
    pos_y = 0;
    layer = 10;

    button = 260, 260, arrowup_pcx, arrowup_pcx, arrowup_pcx, null, hours_up, null;
    button = 280, 260, arrowdown_pcx, arrowdown_pcx, arrowdown_pcx, null, hours_down, null;
    button = 420, 260, arrowup_pcx, arrowup_pcx, arrowup_pcx, null, minutes_up, null;
    button = 440, 260, arrowdown_pcx, arrowdown_pcx, arrowdown_pcx, null, minutes_down, null;

    flags = overlay, refresh, d3d, visible;
}

Clock_text shows the current time like this: hh:mm ss and alarm_text show the alarm time like this hh:mm. You know that h comes from hours, m = minutes, s = seconds, right?

text clock_text
{
    layer = 20;
    pos_x = 270;
    pos_y = 200;
    font = lcd_font;
    string = clock_string;
    flags = visible;
}

text alarm_text
{
    layer = 20;
    pos_x = 300;
    pos_y = 260;
    font = lcd_font;
    string = alarm_string;
    flags = visible;
}

The most important part of the code is included in main:

function main()
{
    screen_color.red = 0;
    screen_color.green = 0;
    screen_color.blue = 0;
    mouse_map = mouse_pcx;
    mouse_mode = 2;
    while (1)
    {
         mouse_pos.x = pointer.x;
         mouse_pos.y = pointer.y;

This main function isn't loading any level; we set the background color to rgb = 000, we use mouse_pcx for the mouse pointer and we show the cursor. The first 2 lines inside the while loop make sure that we can move the mouse on the panel.

         str_for_num(clock_string, sys_hours);
         str_cat(clock_string, ":");
         if (sys_minutes < 10)
         {
              str_cat(clock_string, "0");
         }
         str_for_num(temp_string, sys_minutes); // read sys_minutes and convert it to string
         str_cat(clock_string, temp_string); // add minutes to clock_string
         str_cat(clock_string, " "); // leave a blank space before adding seconds
         if (sys_seconds < 10) // add a "0" if the seconds are displayed with a digit
         {
              str_cat(clock_string, "0");
         }
         str_for_num(temp_string, sys_seconds); // read sys_seconds and convert it to string
         str_cat(clock_string, temp_string); // add seconds to clock_string

We read sys_hours, we convert it from number to string and we put it in clock_string. We add ":" to the string; so far we have obtained "hh:". If sys_minutes < 10, the clock would display something like this: "23: 2" so we add a zero after ":" to make the clock show something like this: "23:02". Now we can read sys_minutes, we convert it to string, we add the minutes to clock_string and then we leave a space " " before adding the seconds. If sys_seconds < 10 we add another zero to the string and then we read sys_seconds, we convert it to string and then we add it to clock_string, obtaining something like this: "23:02 45".

Let's see the code that takes care of the alarm:

         str_for_num(alarm_string, alarm_hours); // convert alarm_hours to string
         str_cat(alarm_string, ":"); // add :
         if (alarm_minutes < 10) // add a "0" if the minutes are displayed with a digit
         {
              str_cat(alarm_string, "0");
         }
         str_for_num(temp_string, alarm_minutes); // convert alarm_minutes to string
         str_cat(alarm_string, temp_string); // add alarm_minutes to alarm_string

         if ((alarm_hours == sys_hours) && (alarm_minutes == sys_minutes)) // sound the alarm
         {
              start_alarm();
         }

I have set these (default) values for alarm_hours and alarm_minutes: 12 and 30. We convert alarm_hours to string, we add ":", we add a zero if alarm_minutes < 10, we convert alarm_minutes to string and then we add it to alarm_string. If alarm_hours (set by us) = sys_hours (the pc clock) and alarm_minutes (set by us) = sys_minutes (the pc clock) the alarm will start and it will run for a minute, until alarm_minutes != sys_minutes.

         wait (1);
    }
}

Function start_alarm is easy:

function start_alarm()
{
    exclusive_global; // don't allow multiple instance of this function to run
    while (snd_playing(alarm_handle)) {wait (1);}
    alarm_handle = snd_play (alarm_wav, 100, 0);
}

We don't allow multiple instances of this function to run and we play a sound in a loop, waiting for it to finish before starting to play it again.

The rest of the code is used to set the alarm time when we press one of the buttons (arrows):

function hours_up()
{
    if (alarm_hours < 23)
    {
         alarm_hours += 1;
    }
}

function hours_down()
{
    if (alarm_hours > 0)
    {
         alarm_hours -= 1;
    }
}

function minutes_up()
{
    if (alarm_minutes < 59)
    {
         alarm_minutes += 1;
    }
}

function minutes_down()
{
    if (alarm_minutes > 0)
    {
         alarm_minutes -= 1;
    }
}

There's nothing special here; if you click the hours up arrow, alarm_hours will be incremented until it reaches 23. The rest of the functions are similar.

Head shots

If you are looking for a simple method to make your models react in different ways depending on the bullet impact area, you came to the right place. It is quite normal to have an enemy soldier dead if we have managed to curl its hair with a head shot, right? On the other hand, it isn't normal to kill an enemy by shooting its leg once.

I'm going to reveal my method - it is fast and it works flawlessly. I like to call it the (put something here) method.

The idea is simple: we set some key vertices on our enemy model and then we get their height in a while loop. When a bullet hits the model, we get the height of the bullet and then we compare this value with the height of every key vertex. The closest vertex will trigger its specific animation. The code is as simple as it sounds (is this good news or bad news?)

I will use a text to display a string on the screen and two panels: one for the crosshair and one for the key vertices and for bullet's coordinate.

text info_txt
{
    layer = 20;
    pos_x = 0;
    pos_y = 0;
    font = _a4font;
    string = "head vertex:\nmiddle vertex:\nleg vertex:\nhit coord:";
    flags = visible;
}

panel crosshair_pan
{
    bmap = crosshair_pcx;
    layer = 20;
    pos_x = 0;
    pos_y = 0;
    flags = transparent, refresh, d3d, visible;
}

panel coords_pan
{
    layer = 20;
    pos_x = 80;
    pos_y = 0;
    digits = 0, 0, 4, _a4font, 1, vertex_coords.x;
    digits = 0, 9, 4, _a4font, 1, vertex_coords.y;
    digits = 0, 18, 4, _a4font, 1, vertex_coords.z;
    digits = 0, 27, 4, _a4font, 1, bullet_pos.z;
    flags = refresh, d3d, visible;
}

Function main disables the "D" (debug panel) key because we will use WSAD to move the player and then it loads the level:

function main()
{
    on_d = null;
    level_load (damage_wmb);
}

This is a standalone project so we need an action for the player:

action player_moves
{
    my.invisible = on; // no need to see the player because we are in 1st person mode
    crosshair_pan.pos_x = (screen_size.x - bmap_width(crosshair_pcx)) / 2;
    crosshair_pan.pos_y = (screen_size.y - bmap_height(crosshair_pcx)) / 2;
    while (1)
    {
         vec_set (camera.pos, my.pos);
         camera.tilt += 20 * mouse_force.y * time;
         camera.pan -= 20 * mouse_force.x * time;
         my.pan = camera.pan;
         my.tilt = camera.tilt;
         player_speed.x = 15 * (key_w - key_s) * time; // move forward / backward
         player_speed.y = 10 * (key_a - key_d) * time; // strafe left / right
         vec_set (temp, my.x);
         temp.z -= 1000;
         trace_mode = ignore_me + use_box;
         player_speed.z = -trace (my.x, temp);
         move_mode = ignore_you + ignore_passable;
         ent_move(player_speed, nullvector);
         if (mouse_left == 1)
         {
              ent_create (bullet_mdl, player.pos, shoot_bullet);
              snd_play (bullet_wav, 50, 0);
              while (mouse_left == 1) {wait (1);}
         }
         wait (1);
    }
}

We don't need to see the player because we are in 1st person mode; the crosshair panel is placed in the center of the screen. The camera is placed at player's position and changes its pan and tilt by moving the mouse. The player inherits its pan and tilt from the camera and moves using the keys WSAD. The movement code includes gravity; we trace 1000 quants below player's feet and then we move the player with its feet on the ground. If we press the left mouse button (LMB) we create a bullet, a sound is played and then we wait until the mouse button is released because we want to disable autofire.

Let's see the code associated to the enemy:

action enemy
{
    my.enable_impact = on;
    my.enable_shoot = on;
    my.enable_entity = on;
    my.event = compute_shot_pos;
    while (my.skill5 == 0)
    {
         ent_cycle ("idle", 0);
         vec_for_vertex(temp, my, 222); // enemy's head vertex
         my.head_pos = temp.z;
         vertex_coords.x = my.head_pos;
         vec_for_vertex(temp, my, 235); // enemy's belly vertex
         my.middle_pos = temp.z;
         vertex_coords.y = my.middle_pos;
         vec_for_vertex(temp, my, 171); // enemy's leg vertex (one of the legs)
         my.leg_pos = temp.z;
         vertex_coords.z = my.leg_pos;
         wait (1);
    }
}

The enemy is sensitive to impact, shoot or other entities; if one of these events happens, it will run function compute_shot_pos. As long as the enemy hasn't been shot (skill5 = 0), it will stand still. I have used a single frame for "idle" but you can use a real "idle" animation here. I chose 3 key vertices for head, belly and leg using Med but you can choose as many as you want; their z coords are stored in head_pos (another name for skill1), middle_pos (skill2) and leg_pos (skill3). We need to get these values in a while loop because if the enemy moves (jumps, crawls, etc) these coords will change.

Ok, let's imagine that our bullet has hit the enemy -> function compute_shot_pos() will run:

function compute_shot_pos()
{
    my.skill5 = 1;
    vec_set(bullet_pos, you.pos);
    wait (1);
    my.event = null;

Skill5 will be set to 1, stopping the while loop in action enemy. We store bullet's coords before they get lost (before the bullet disappears), we wait for a frame and then we set my.event to null.

   if (abs(bullet_pos.z - my.head_pos) < abs(bullet_pos.z - my.middle_pos)) // we've got a head shot, no need to check the legs
    {
         my.skill5 = 0;
         while (my.skill10 < 80) // skip the last frame
         {
              ent_cycle("head", my.skill10);
              my.skill10 += 4 * time;
              wait (1);
         }
    }

If the distance between the bullet and the head vertex is smaller than the distance between the bullet and the middle vertex, we have got a head shot so we play the "head" animation.

    else // middle or leg shot
    {
         if (abs(bullet_pos.z - my.middle_pos) < abs(bullet_pos.z - my.leg_pos)) // middle shot
         {
              my.skill10 = 0;
              while (my.skill10 < 80) // skip the last frame
              {
                   ent_cycle("middle", my.skill10);
                   my.skill10 += 2 * time;
                   wait (1);
              }
         }
         else // leg shot
         {
              my.skill10 = 0;
              while (my.skill10 < 80) // skip the last frame
              {
                   ent_cycle("leg", my.skill10);
                   my.skill10 += 4 * time;
                   wait (1);
              }
         }
    }
}

If the distance between the bullet and the head vertex is bigger than the distance between the bullet and the middle vertex, it could be a middle (belly) shot or a leg shot so we play the correct animation. I have animated the model so beware... you'll see some scary stuff!

The last two functions create the bullets and remove them - here's the code:

function shoot_bullet()
{
    my.enable_entity = on;
    my.enable_block = on;
    my.event = remove_bullet;
    my.passable = on;
    my.pan = player.pan;
    my.tilt = camera.tilt;

The bullet is sensitive to other entities and level blocks. When it collides with another entity (our enemy) or a level block, it will be removed after a frame, allowing us to get its coords before it disappears. The bullet has the same orientation with the player and the camera.

    my.skill20 = 0;
    my.skill1 = 300;
    my.skill2 = 0;
    my.skill3 = 0;
    my.skill1 *= time;

    while (my.skill20 < 250)
    {
         if (my.skill20 < 1)  // don't collide with the player
         {
              my.passable = on;
         }
         else
         {
              my.passable = off;
         }
         my.skill20 += 1 * time;
         ent_move (my.skill1, nullvector);
         wait (1);
    }
    remove_bullet();
}

function remove_bullet()
{
    wait (1);
    ent_remove(me);
}

We set skill20 to zero and then we increase this value inside a while loop until it reaches 250. If the bullet hasn't hit anything when skill20 >= 250, it will be removed because we don't want to waste cpu power with it. Skill1..3 are used as a local var to move the bullet. A small trick: the bullet is created inside the player model so it has to be made passable at the beginning because otherwise it would collide with the player. When skill20 >= 1 we can assume that the bullet is far enough and then we will make it impassable.

You can use more key vertices if you like; you could add another key vertex to the other leg, 2 more on the arms, etc.