Code snippets

Top  Previous  Next

Scrolling panel

Sometimes you want to be able to scroll some text printed on a panel but you can't simply change its pos_x and pos_y parameters because you don't want a huge panel to move up and down across the screen. You need something smaller, something like a window in the panel. Ok, so let's use one:

First of all we define a panel:

panel spanel_pan
{
  bmap = spanel_pcx;
  pos_x = 0;
  pos_y = 0;
  layer = 30;
  button 217, 25, arrow1_pcx, arrow1_pcx, arrow1_pcx, scroll_up, null, null;
  button 217, 45, arrow2_pcx, arrow2_pcx, arrow2_pcx, scroll_down, null, null;
  window = 10, 20, 200, 50, text_pcx, text_pos.x, text_pos.y;
  flags = overlay, d3d, refresh;
}

This is a common panel definition; we have two arrow - shaped buttons that will be used to scroll the text up and down and a window definition. Let's take a closer look at it:

window = 10, 20, 200, 50, text_pcx, text_pos.x, text_pos.y;

This instruction creates a window (a hole) in the panel. The window will be placed at (10, 20) and will have 200 pixels on x and 50 pixels on y. The text that will appear in this window is printed on text_pcx; its coordinates are given by text_pos.x and text_pos.y

function scrolling_text()
{
  mouse_toggle(); // show the cursor
  text_pos.x = 10;
  text_pos.y = 20;
  while (1)
  {
     if (key_h == 1) // press H to show the panel
     {
        while (key_h == 1) {wait (1);}
        spanel_pan.visible = (spanel_pan.visible == off);
     }
     wait (1);
  }
}

Function scrolling_text is called in main; it shows the cursor using a template function (mouse_toggle) and then sets the starting position for the text that will appear in the window. The while loop shows the panel or hides it when we press the "H" key, using a single line of code. Try to understand how this line works:

spanel_pan.visible = (spanel_pan.visible == off);

and you'll be a step closer to the yellow belt :)

The last functions are:

function scroll_up()
{
  while (mouse_left == 1)
  {
     if (text_pos.y < 350) // text height - window height
     {
        text_pos.y += 3 * time;
     }
     wait (1);
  }
}

function scroll_down()
{
  while (mouse_left == 1)
  {
     if (text_pos.y > 0)
     {
        text_pos.y -= 3 * time;
     }
     wait (1);
  }
}

These functions run when we press one of the arrows that make the text scroll. If we press one of the arrows, the text scrolls in the corresponding directions as long as the left mouse button is pressed. If the text has reached its upper / lower limit, it can't be scrolled anymore. Have fun but don't forget that any panel uses video memory; try to use a decent width / height for every panel.


3rd shooter

No, it isn't the 3rd shooter in this magazine, it's the 3rd person shooter code. The good news is that you can use the code for any arcade game. This project has its own main function:

function main()
{
  level_load (shoot3rd_wmb);
  wait (2);
  clip_size = 0;
  fps_max = 50;
  on_d = null;
}

There's nothing special in main; I have locked the frame rate to 50 fps and the "D" key won't bring the debug panel on because we need the key for player's movement (WSAD).

The action attached to the player is the biggest one so let's throw it in before you get tired:

action player_init
{
  player = me;
  player.skill20 = 100;
  my.enable_impact = on;
  my.event = player_damage;
  camera.z = my.z + 700;
  camera.tilt = -90;
  ent_create(soldiergun_mdl, nullvector, attach_soldiergun); // give him a gun

We use player's skill20 to store the health. The player is sensitive to impact; if it is hit by something its player_damage event is triggered. The camera is placed 700 quants above the player and looks down at it. The player gets a gun, but we'll talk about function attach_soldiergun a little later.

  while (my.skill20 > 0)
  {
     my.pan += 4 * (key_a - key_d) * time - 20 * mouse_force.x * time;
     player_speed.x = 10 * (key_w - key_s) * (1 - mouse_left) * time;
     player_speed.y = 0;

As long as the player is alive (skill20 > 0), it can rotate by changing its pan with "A" and "D". The player moves forward / backward with "W" and "S", but only if the left mouse button (LMB) isn't pressed. I had to do this because when the player fires, its gun must point towards the enemy and when this player model walks its weapon points to the left.

     if (mouse_left == 1)
     {
        ent_cycle ("attack", 100);
        ent_create (bullet_mdl, my.skill12, init_shot);
        snd_play (shoot1_snd, 70, 0);
        while (mouse_left == 1) {wait (1);}
     }
     else
    {
        if (player_speed.x != 0)
        {
           ent_cycle ("walk", my.skill10);
           my.skill10 += 10 * time;
           if (my.skill10 > 100) {my.skill10 = 0;}
        }
    }

If we press the LMB, the player will change its frame to the last "attack" frame and a bullet will be fired. We don't want to have autofire, so we wait until the LMB is released. If LMB isn't pressed, the player will play its "walk" animation frames in a loop.

    vec_set (temp, my.x);
    temp.z -= 3000;
    trace_mode = ignore_me + ignore_sprites + ignore_models + use_box;
    player_speed.z = -trace (my.x, temp);
    ent_move(player_speed, nullvector);

    camera.pan = my.pan;
    camera.x = my.x + 250 * cos (my.pan);
    camera.y = my.y + 250 * sin (my.pan);

    wait (1);
}
}

The player moves using gravity - you've seen similar code in stratego2. Finally, the camera is centered in a point that is placed 250 quants in front of the player.


function attach_soldiergun()
{
  proc_late();
  my.passable = on;
  my.metal = on;
  while(you != null)
  {
      vec_set(my.x,you.x);
      vec_set(my.pan,you.pan);
      my.frame = you.frame;
      my.next_frame = you.next_frame;
      vec_for_vertex(you.skill12, my, 6); // soldier's bullet position is stored in soldier's skill12
      wait(1);
  }
ent_remove(my);
}

The function above gives player a gun that will exist as long as the player isn't removed from the level. The gun was animated together with the player and then it was saved as a separate model so we only need to set its x, pan and frame to be the equal with player's x, pan and frame. We store the bullet vertex coords in player's skill12 (you.skill12 = player.skill12 because the player has created the gun).

If the player or the enemy shoot, function init_shot runs:

function init_shot()
{
  my.pan = you.pan;
  my.tilt = you.tilt;
  my.skill10 = 1;
  my.enable_entity = on;
  my.enable_block = on;
  my.event = remove_bullet;
  my.ambient = 100;
  my.skill1 = 0;
  bullet1_speed.x = 10;
  bullet1_speed.y = 0;
  bullet1_speed.z = 0;
  bullet1_speed *= time;
  while ((my != null) && (my.skill1 < 500)) // 500 = max distance
  {
     my.skill1 += 1 * time;
     move_mode = ignore_you + ignore_passents;
     ent_move (bullet1_speed, nullvector);
     wait (1);
  }
  ent_remove (me);
}

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

The bullet has the same pan and tilt with its creator; it can collide with entites and level blocks. The bullet will exist (as long as it hasn't collided with anything) for a limited period of time - until its skill1 reaches 500. The bullet ignores its creator or any passable entity so it can't collide with the gun that has fired it, with water, etc. If the bullet hits something it is removed after 1 frame.

If the player is hit by an enemy bullet, its player_damage function will be triggered:

function player_damage()
{
  if (you.skill10 != 1)
  {
     return;
  }
  else
  {
     my.skill20 -= 10;
     if (my.skill20 <= 0)
     {
        my.enable_impact = off;
        my.event = null;
        my.skill10 = 0;
        my.skill30 = 1; // I'm dead
        while (my.skill10 < 67)
        {
           ent_cycle("death", my.skill10);
           my.skill10 += 6 * time;

            if (camera.z > player.z + 300)
            {
               camera.z -= 10 * time;
            }
            wait (1);
        }
     }
  }
}

The player could be hit by a wall, by an enemy or by an enemy bullet. This is why we have to check skill10 at the beginning of the function to make sure that the player is hit by a bullet (we've set it to 1 in function init_shot, remember?) Every bullet will take 10 health points and if player's health goes below 0, the player will play its "death" animation. The camera will zoom in a little by decreasing its height in order to show a bigger player corpse.

Time to move on to the enemy! First of all, the action that creates the enemies:

action generate_enemy
{
  my.scale_x *= 0.7;
  my.scale_y = my.scale_x;
  my.passable = on;
  my.oriented = on;
  my.bright = on;
  my.flare = on;
  while (player == null) {wait (1);}
  while (vec_dist (my.x, player.x) < 500) {wait (1);}
  waitt (48 + random (64));
  vec_set (temp, my.pos);
  temp.z += 50;
  ent_create (enemy_mdl, my.pos, move_enemy);
}

These 3rd person games allow us to create the enemies on the fly; I have used a few oriented sprites as enemy generators. If the player comes closer to them, they will start creating enemies. This way you can spread enemy generators throught player's path.

The generators will until the player is created; if it comes closer than 500 quants, the enemy is created after 3..7 seconds, 50 quants above the sprite. The enemy moves using the function below:

function move_enemy
{
  my.enable_impact = on;
  my.event = enemy_damage;
  ent_create(enemygun_mdl, nullvector, attach_enemygun); // give him a gun

The enemy is sensitive to impact too; it gets a gun using a similar function.

  while (vec_dist(my.x, player.x) > (300 + random(100)))
  {
     enemy_speed.x = 10 * time;
     enemy_speed.y = 0;
     enemy_speed.z = 0;

     vec_set (temp.x, player.x);
     vec_sub (temp.x, my.x);
     vec_to_angle (my.pan, temp);

     move_mode = ignore_you + ignore_passents;
     ent_move (enemy_speed, nullvector);
     ent_cycle ("walk", my.skill10);
     my.skill10 += 10 * time;
     if (my.skill10 > 100) {my.skill10 = 0;}
     my.tilt = 0;
     wait (1);
  }

As long as the player is farther than 300..400 quants, the enemy will move towards it, playing its "walk" animation in a loop.

  ent_cycle("attack", 100);
  waitt (2);
  while (my.skill30 == 0 && player.skill20 > 0)
  {
     vec_set (temp.x, player.x);
     vec_sub (temp.x, my.skill12);
     vec_to_angle (my.pan, temp);
     ent_create (bullet_mdl, my.skill12, init_shot);
     my.tilt = 0;
     snd_play (shoot1_snd, 70, 0);
     waitt (32);
  }
}

When the enemy has come close enough, it changes to "attack" and starts firing at the player if the player and the enemy are alive. The enemy rotates towards the player and fires a bullet every 2 seconds.

If the enemy is hit by a bullet, its enemy_function runs. The good news is that the enemies can shoot each other :)

function enemy_damage()
{
  if (you.skill10 != 1) {return;}
  wait (1);
  my.enable_impact = off;
  my.event = null;
  my.skill10 = 0; // start with the first death frame
  my.skill30 = 1; // I'm dead
  while (my.skill10 < 67)
  {
     ent_cycle("death", my.skill10); // play death animation
     my.skill10 += 6 * time;
     wait (1);
  }
  my.passable = on; // can pass through the corpse
}

First of all we check if the enemy has impacted with a bullet. If this is true we play its "death" animation and then we make its corpse passable because the player should be able to pass through it.

This was all for today. I'll see you in a few weeks with a fresh standalone project!