Code snippets

Top  Previous  Next

Radar

One good thing about the old raycasting engines was the you could easily have a radar (call it automap if you want to). This piece of code will allow you to see the whole map on a panel and more than that, what's moving on it.

First of all I have got these values (in Wed) for the level boundaries in the office.wmp level:

var level_minx = -1800;
var level_maxx = 1800;
var level_miny = -1500;
var level_maxy = 1500;

Feel free to choose approximate values because if you are using a small radar panel it won't matter than much if level_minx is -1800 or -1824.

The idea behind the radar code is pretty simple: we use a panel for the map (a screenshot of the top view in Wed, without unnecessary geometry on it) and several small panels with higher layers for the entities that move on the panel.

panel radar_pan
{
  bmap = radar_map;
  layer = 20;
  pos_x = 0;
  pos_y = 0;
  flags = overlay, refresh, d3d, visible;
}

panel entity1_pan
{
  bmap = pentity_map; // the player
  layer = 21;
  pos_x = 0;
  pos_y = 0;
  flags = transparent, overlay, refresh, d3d;
}
...............................................................................

panel entity5_pan
{
  bmap = entity_map;
  layer = 21;
  pos_x = 0;
  pos_y = 0;
  flags = transparent, overlay, refresh, d3d;
}

You can see that radar_pan (the level) has layer = 20 and the other panels (the entities) have layer = 21 so they will be displayed over radar_pan. I have only used 5 entity panels in this example, but you can add as many panels / entities as you want.

Every entity that wants to be displayed on the radar must include this line in its action / function (better make it the first line):

radarx_ptr = me; x = 1..5 in my example

The player has a radar1_ptr = me line in its player_prog action, the patrolling guard has a radar2_ptr = me line in its patrol_prog action and so on. Btw, the panel for the player (entity1_pan) uses a different bitmap (the player is blue and all the other entities are red on the radar).

We need to start the radar by running

function init_radar()
{
  while (1)
  {
     if (radar1_ptr != null)
     {
        entity1_pan.visible = on;
        entity1_pan.pos_x = scalex * (abs(radar1_ptr.x - level_maxx));
        entity1_pan.pos_y = scaley * (abs(radar1_ptr.y - level_miny));
    }
     ...........................................................................................................................

   if (radar5_ptr != null)
   {
        entity5_pan.visible = on;
        entity5_pan.pos_x = scalex * (abs(radar5_ptr.x - level_maxx));
        entity5_pan.pos_y = scaley * (abs(radar5_ptr.y - level_miny));
   }
   wait (1);
}
}

If the entity will appear on the radar (the radarx_ptr pointer to it isn't null) we make its panel visible and we calculate its position on the radar. Let's take a look at this line:

entity1_pan.pos_x = scalex * (abs(radar1_ptr.x - level_maxx)); 

You know that you can move a panel on the screen by changing its pos_x and pos_y parameters; this is what we are doing here. For radar1_ptr.x = -1800 and level_maxx = 1800:

scalex * (abs(radar1_ptr.x - level_maxx)) = length_of_the_radar_bitmap (128 pixels) -> scalex = 128 / 3600 = 0.0355

I could have measured the length of the bitmap (using bmap_width and bmap_height) and get rid of scalex and scaley buy these parameters allow you to do a "fine tuning" to the radar. I'm using a similar formula for the y axis so I won't explain it here.

Ok, I got your point but what if I have the same action for 5 monsters? I can't assign a single pointer to all of them!

I see your point but I have the answer. Check a different flag (set a different skill value) for every monster and set the pointers this way:

if (my.flag1 == 1) {radar3_ptr = me;}
if (my.flag2 == 1) {radar4_ptr = me;}

This is what I did with the two elevators that appear in the office level (and on the radar, of course). I have zipped the doors.wdl and office.wdl that include all these changes, so you have a working example.

John Wayne

Ever wanted to be like John Wayne? I'm pretty sure that he has learned to use the gun that well by shooting at small sized, cheap plates. With this standalone project you'll be able to do that too, using two pistols with stereo sound!

Function main is always simple; please note that I have disabled the debug panel because I use WSAD for movement.

function main()
{
     on_d = null;
     level_load (johnwayne_wmb);
     wait (2);
     game_init();
}

But what's with this game_init function?

function game_init()

{
     crosshair_pan.pos_x = screen_size.x / 2 - 8;
     crosshair_pan.pos_y = screen_size.y / 2 - 8;
     while (1)
     {
           if (mouse_left == 1)
           {
                 crosshair_pan.visible = on;
                 if (got_gun1 == 1 && got_gun2 == 0)
                 {
                       animate_right();
                       snd_play (shoot_snd, 70, -100);
                       ent_create (bullet_mdl, player.pos, move_bullet);
                 }
                 if (got_gun1 == 0 && got_gun2 == 1)
                 {
                       animate_left();
                       snd_play (shoot_snd, 70, 100);
                       ent_create (bullet_mdl, player.pos, move_bullet);
                 }
                 if (got_gun1 == 1 && got_gun2 == 1)
                 {
                       animate_right();
                       snd_play (shoot_snd, 70, -100); // right speaker
                       ent_create (bullet_mdl, player.pos, move_bullet);
                       waitt (3);
                       animate_left();
                       snd_play (shoot_snd, 70, 100); // left speaker
                       ent_create (bullet_mdl, player.pos, move_bullet);
                 }
                 while (mouse_left == 1) {wait (1);}
           }
           wait (1);
     }
}

First of all, game_init calculates the crosshair position; the bitmap for it has 16x16 pixels so we're substracting 8 pixels from the center of the screen. If the left mouse button (LMB) is clicked, the crosshair is made visible. I have associated 2 variables with the 2 pistols: got_gun1 = 1 means that I have got the right pistol and got_gun2 = 1 means that I have got the left pistol. It is pretty obvious what's happening when you fire one of the weapons; if you have picked up both pistols, the guns are shot using the new snd_play instruction which produces a nice stereophonic effect.

We don't want the player to be able to use autofire so we wait until the LMB is released.

This is a standalone project so I proudly present you world's shortest camera code:

action john_moves
{
     player = me;
     my.invisible = on;
     while (1)
     {
           vec_set (camera.pos, my.pos);
           camera.tilt += 20 * mouse_force.y * time;
           my.pan += 4 * (key_a - key_d) * time - 20 * mouse_force.x * time;
           camera.pan = my.pan;
           player_dist.x = 10 * (key_w - key_s) * time;
           player_dist.y = 0;
           player_dist.z = 0;
           ent_move(player_dist, nullvector);
           wait (1);
     }
}

John Wayne is played by (who else?) guard.mdl so I have decided to make it invisible. Use the keys WSAD to move the player and the mouse to look around. I know that this camera code hasn't got inertia so the movement is pretty crappy but I've told you that this is world's shortest camera code and more than that - who says that John Wayne had inertia at all?

Let's take a look at the code that is attached to the pistol models; I will explain what's happening with the pistol on the right because the code for the left pistol is the same:

action pistol1
{
     my.enable_impact = me;
     my.event = get_pistol1;
     while (my != null) // the gun wasn't picked up yet
     {
           my.pan += 3 * time;
           wait (1);
     }
}

function get_pistol1()
{
     wait (1);
     got_gun1 = 1;
     snd_play (gotgun_snd, 80, 0);
     ent_remove (me);
     right_pistol.visible = on;
}

The pistol is sensitive to impact and will rotate as long as it hasn't been picked up. When we impact with the pistol, its function get_pistol1 sets got_gun1, plays a sound, removes the pistol from the ground and enables the right_pistol entity:

entity right_pistol
{
     type = <eagled.mdl>;
     layer = 10;
     view = camera;
     x = 25;
     y = -10;
     z = -10;
}

This is a typical entity definition; left_pistol has y = 10 instead of y = -10 so it will appear on the other side of the screen. The pistols are animated using two simple while loops:

function animate_right()

{
     while (right_pistol.tilt < 5)
     {
           right_pistol.tilt += 5 * time;
           wait (1);
     }
     while (right_pistol.tilt > 0)
     {
           right_pistol.tilt -= 5 * time;
           wait (1);
     }
     right_pistol.tilt = 0;
}

When we fire one of the guns, the bullet starts from the origin of the player and moves towards the target using the move_bullet function:

function move_bullet()
{
     wait (1);
     my.invisible = on;
     my.enable_entity = on;
     my.enable_block = on;
     my.event = remove_me;
     my.passable = on;
     my.pan = camera.pan;
     my.tilt = camera.tilt;
     my.skill1 = 0;
     bullet_speed.x = 200;
     bullet_speed.y = 0;
     bullet_speed.z = 0;
     bullet_speed *= time;
     while (my.skill1 < 50)
     {
           if (my == null) {return;}
           if (my.skill1 < 0.1)  // don't collide with the player
           {
                 my.passable = on;
           }
           else
           {
                 my.passable = off;
           }
           my.skill1 += 0.1 * time;
           ent_move (bullet_speed, nullvector);
           wait (1);
     }
     remove me;
}

The bullet can collide with other entities or with the walls and it will be removed if it hits something. The bullet is passable at the moment of creation so it won't hit John's body, but after a few frames its passable flag will be reset. This baby travels with 200 quants / tick!

Finally, the code for that weird stove - the plate_generator:

action plate_generator
{
     my.passable = on;
     while (1)
     {
           if (random(1) > 0.3)
           {
                 ent_create (plate_mdl, my.pos, move_plate);
           }
           waitt (32);
     }
}

If random(1) > 0.3, a new plate is generated every two seconds. Let's take a look at the function that moves the plate:

function move_plate()
{
     wait (1);
     my.enable_impact = on;
     my.enable_block = on;
     my.event = destroy_plate;
     my.skill12 = (1 - random(2)) / 5;
     my.skill13 = (1 - random(2)) / 5;
     my.skill14 = 2 + random(1);
     plate_speed *= time;
     while (me != null)
     {
           my.roll += 1 * time; // uncomment this line to see some weird stuff
           ent_move (my.skill12, nullvector);
           my.skill14 -= 0.05 * time;
           wait (1);
     }
}

You can see that the plate will be removed when it is hit by a bullet or collides with a wall. I'm using a special technique here so please pay attention: my.skill12..14 are used as if they were a single variable with its xyz components. All you need to do is make sure that you start with the right skill (0, 3, 6, 9, 12, etc) and that you refer to the same skill when you use it as if it were a standard variable with 3 components (the way I did with the ent_move instruction). This is extremely useful because the skills are here and they'll use memory even if you don't use them. More than that, if you have 100 plates running in the level, you won't like to have 100 separate variables and 100 slightly modified actions.

function destroy_plate()

{
     wait (1);
     if (event_type == event_impact) // hit by a bullet
     {
           my.transparent = on;
           my.alpha = 100;
           while (my.roll < 1440) // 4 rotations
           {
                 my.roll += 10 * time;
                 my.alpha -= 0.7 * time;
                 wait (1);
           }
     }
     ent_remove (me); // remove the plate (without tilting) if it hit the ground (event_block)
}

If the plate hits a wall or the ground, it is simply removed. If the plate is hit by a bullet, its roll angle is increased while its transparency is decreased until the plate disappears.