Code snippets

Top  Previous  Next

Steroidz

Mike Bravo 23 calling base! There's a giant asteroid coming towards us! No, there are hundreds of them and they're all coming towards our ship!

If this isn't scary stuff then in must be Steroidz! Let's take a look at function main:

function main()
{
   fps_max = 40;
   level_load (steroidz_wmb); // dummy level
   wait (2);
   score = 0; // reset score
   asteroid_generator();
   display_lives();
}
 
We lock the frame rate to 40 and then we load the level (a huge mdl sphere and a tiny cube, otherwise it won't build!). When the level is loaded we reset the score and start generating asteroids and displaying the number of lives. We'll talk about these two functions a little later; let's take a look at the action that moves player's ship:

action players_ship
{
   my.dead = 0;
   my.metal = on;
   my.skill22 = 0; // reset the number of hits
   my.enable_impact = on;
   my.event = player_dies;
   player = me;
   while (camera == null) {wait (1);}
   while (my.dead == 0) // as long as the player isn't dead
   {
       player.tilt += 0.5 * (key_cuu - key_cud) * time;
       player.pan += 2 * (key_cul - key_cur) * time;
       ship_speed.x = 0.6 * key_space * time + max ((1 - time * 0.05), 0) * ship_speed.x;
       ship_speed.y = 0;
       ship_speed.z = 0;
       ent_move(ship_speed, nullvector);
 
       vec_set (temp, nullvector);
       vec_sub (temp, camera_distance);
       vec_to_angle (camera_angle, temp);
       camera_angle.roll = 0;
 
       vec_set (camera.x, camera_distance);
       vec_rotate (camera.x, my.pan);
       vec_add (camera.x, my.x);
 
       vec_set (camera.pan, my.pan);
       ang_rotate (camera.pan, camera_angle);
 
       vec_for_vertex(particles_left, my, 89);
       vec_for_vertex(particles_right, my, 83);
 
       if (key_space == 1)
       {
           effect (ship_particles, 2, particles_left, normal);
           effect (ship_particles, 2, particles_right, normal);
           if (shipsound_handle == 0)
           {
               snd_loop (ship_snd, 50, 0);
               shipsound_handle = result;
           }
       }
       else
       {
           snd_stop (shipsound_handle);
           shipsound_handle = 0;
       }
 
       if (key_ctrl == 1) // press ctrl to fire
       {
           if (my.skill35 == 0)
           {
               my.skill35 = 1; // disable autofire
               ent_create (rocket_mdl, player.pos, ship_rocket);
               snd_play (firerocket_snd, 80, 0);
           }
       }
       else
       {
           my.skill35 = 0; // enable fire again
       }
 
       wait (1);
   }
}
 
I have defined dead as skill20 so my.dead = 0 means my.skill20 = 0; skill20 will be set to 1 when the player is dead. The player is sensitive to impact and it will die after 5 hits; the number of hits is stored in skill22. I'm using the arrow keys (cursor up and down to tilt the ship, cursor left and right to pan it), space for thrust (includes inertia) and ctrl to fire.

We are rotating the camera towards the ship, adding the proper distance (specified in var camera_distance) and then we are rotating the camera using ang_rotate, capable of rotating an object (the camera in Steroidz) depending on the ship direction / angles.

If we press space, the engines generate particles and plays a sound in a loop. The proper engine vertices are those with numbers 89 and 83 (get these numbers in Med); the two vec_for_vertex lines set the two vars (particle_left and particle_right) to these vertex coordinates.

If we press Ctrl, we create a rocket at player's position; the function that runs the rocket is:

function ship_rocket()
{
   wait (1);
   my.ambient = 100;
   my.skill30 = 0;
   my.enable_entity = on;
   my.enable_block = on;
   my.event = remove_me;
   my.passable = on;
   my.pan = player.pan;
   my.tilt = player.tilt;
 
   my.skill12 = 100 * time;
   my.skill13 = 0;
   my.skill14 = 0;
 
   while (my != null) // as long as the rocket hasn't exploded
   {
       my.skill12 = 100 * time;
       my.skill30 += 0.1 * time;
       if (vec_dist (my.x, player.x) < 100) {my.passable = on;}
       // don't collide with the player
       else {my.passable = off;}
       if (my.skill30 < 100)
       {
           ent_move (my.skill12, nullvector);
       }
       else
       {
           ent_remove (me);
       }
       wait (1);
   }
}
 
The code is similar to the one used in the bow & arrow code. The rocket will move until it hits something (an asteroid, of course) but if it hasn't hit anything it will be removed after a certain period of time.

I have used a simple trick to create an infinite "playground" for our ship: I have placed a huge sphere in the level and I'm using a simple action to keep the player in the centre of the sphere (so when the player moves, the sphere moves with him in the same direction):

action sky_follows_player
{
   my.passable = on;
   while (player == null) {wait (1);}
   while (1)
   {
       vec_set (my.pos, player.pos);
       wait (1);
   }
}
 
The sky must be passable because the asteroids have to pass through it and move towards the player. Let's get back to function asteroid_generator:

function asteroid_generator()
{
   while (1)
   {
       if (random(1) > 0.97)
       {
           asteroid_angle = player.pan + 30 - random(60); // -30...+30 degrees
           asteroid_pos.x = player.x + (5000 + random(5000)) * cos (asteroid_angle);
           asteroid_pos.y = player.y + (5000 + random(5000)) * sin (asteroid_angle);
           asteroid_pos.z = player.z + 300 - random(600);
           ent_create (asteroid_mdl, asteroid_pos, move_asteroid);
       }
       waitt (3);
   }
}
 
function move_asteroid()
{
   my.ambient = -50;
   my.metal = on;
   my.enable_entity = on;
   my.event = destroy_asteroid;
   vec_set (temp.x, player.x);
   vec_sub (temp.x, my.x);
   vec_to_angle (my.pan, temp); // turn towards the player
   my.pan += 3 - random(6); // miss the player from time to time
   my.tilt += 1 - random(2);
   while (my != null)
   {
       my.roll += 5 * time;
       my.skill3 = 20 * time;
       my.skill4 = 0;
       my.skill5 = 0;
       ent_move(my.skill3, nullvector);
       wait (1);
   }
}

The asteroids are generated from time to time, when random(1) > 0.97, on a torus (a ring) that surrounds the player, like in the picture below.

It wouldn't be fair to hit the player from behind, so the asteroids are generated only in front of the player (player.pan + 30 - random(60) degrees). Function move_asteroid will rotate the asteroid towards the player, but we change the direction a little by adding small random values to the pan and tilt angles.

function destroy_asteroid()
{
   my.ambient = 100;
   my.passable = on;
   waitt (2);
   ent_remove (me);
   snd_play (explode_snd, 70, 0);
}

If the asteroid is hit by a rocket or the player, its ambient increases for 0.125 seconds and then the asteroid is removed. If the player is hit by an asteroid, it loses a life; the function that displays the number of lives sets the visible flag to on (or off) depending on skill22 (number of hits).

function display_lives()
{
   while (1)
   {
       if (player.skill22 == 0)
       {
           first_pan.visible = on;
           second_pan.visible = on;
           third_pan.visible = on;
           fourth_pan.visible = on;
           fifth_pan.visible = on;
       }
       if (player.skill22 == 1)
       {
           first_pan.visible = on;
           second_pan.visible = on;
           third_pan.visible = on;
           fourth_pan.visible = on;
           fifth_pan.visible = off;
       }
       if (player.skill22 == 2)
       {
           first_pan.visible = on;
           second_pan.visible = on;
           third_pan.visible = on;
           fourth_pan.visible = off;
           fifth_pan.visible = off;
       }
       if (player.skill22 == 3)
       {
           first_pan.visible = on;
           second_pan.visible = on;
           third_pan.visible = off;
           fourth_pan.visible = off;
           fifth_pan.visible = off;
       }
       if (player.skill22 == 4)
       {
           first_pan.visible = on;
           second_pan.visible = off;
           third_pan.visible = off;
           fourth_pan.visible = off;
           fifth_pan.visible = off;
       }
       if (player.skill22 == 5)
       {
           first_pan.visible = off;
           second_pan.visible = off;
           third_pan.visible = off;
           fourth_pan.visible = off;
           fifth_pan.visible = off;
       }
       wait (1);
   }
}

The functions that are being used to display the particles for the engines are:

 
function ship_particles()
{
   temp.x = random(2) - 1;
   temp.y = random(10) + 2;
   temp.z = random(2) - 1;
   vec_add (my.vel_x, temp);
   my.alpha = 30 + random(50);
   my.bmap = particle_map;
   my.size = 7 + random(4);
   my.flare = on;
   my.bright = on;
   my.lifespan = 20;
   my.function = fade_particle;
}
 
function fade_particle()
{
   my.alpha -= 20 * time;
   if (my.alpha < 0) {my.lifespan = 0;}
}

There isn't anything special with these functions; temp.y sets the correct trail direction and my.alpha -= 20 * time sets its length.

After 5 hits, the player loses its lives and has to restart the game by pressing R, which simply calls function main again.

New car

I don't remember how many of you have requested this piece of code at the forum but it worked - I have decided to create the wdl for it. What I'm trying to do here is: move the player in 1st person mode -> touch a car model -> get in the car and drive it in 1st person -> press space to get out of the car and continue to walk. If I approach the car again, I'll start driving it again and so on.

This might look like a difficult task, but it isn't; all I have to do is to switch the functions player_to_car and car_to_player. We place a car model in the level and we attach it the following action:

action my_car
{
   my.enable_impact = on;
   my.event = player_to_car;
}
 
You can see that the car will react if I touch it; let's see what happens:

function player_to_car()
{
   wait (1);
   ent_remove (me);
   player.shadow = off;
   player._MOVEMODE = _MODE_DRIVING;
   player._FORCE = 1.5;
   player._BANKING = 0.1;
   player.__SLOPES = on;
   player.__WHEELS = on; // rotate only when moving
   player.__JUMP = off; // the car can't jump
   player.__STRAFE = off; // can't strafe
   player.__TRIGGER = on;
   astrength.pan = 1.5; // decrease rotation speed (pan)
   car_entity.visible = on;
   while (key_space == 0) {wait (1);} // press space to switch from car to player
   temp.x = player.x - 100 * sin(player.pan); // check to see if there is an empty space (near the car door) to deploy the player
   temp.y = player.y + 100 * cos(player.pan) ;
   temp.z = player.z;
   ent_create (car_mdl, player.pos, create_car);
   vec_set (player.pos, temp);
   car_entity.visible = off;
   car_to_player();
}

entity car_entity
{
   type = <rallycar.mdl>;
   layer = 10;
   view = camera;
   x = 20;
   y = 0;
   z = -60;
}

The car is removed when the player impacts with it but the player is transformed into a car (you'll see most of these parameters in action player_drive). I have defined a car_entity that looks exactly like the car that has disappeared and I make it visible; now the player can drive as long as he wants to. If we press space, the player is moved near the door and the car model is created again:

function create_car()
{
   wait (1);
   vec_set(temp, my.x);
   my.pan = player.pan;
   temp.z -= 2000; // trace 2000 quants below the car
   trace_mode = ignore_me + ignore_passable + ignore_models + ignore_sprites;
   my.z -= trace (my.x, temp); // place the car at ground level
   my_car(); // start the action again

I'm using "trace" to make sure that the car is placed at the ground level; action my_car is called again so the things start over.

A minor detail: we teach the player to behave like a human, not like a car; this is done in:

function car_to_player()
{
   player._MOVEMODE = _MODE_WALKING;
   player._FORCE = 0.75;
   player._BANKING = -0.1;
   player.__SLOPES = off;
   player.__WHEELS = off;
   player.__JUMP = on;
   player.__DUCK = on;
   player.__STRAFE = on;
   player.__BOB = on;
   player.__TRIGGER = on;
   astrength.pan = 7; // restore the original value in move.wdl
}

This snippet does the job but of course that it can be improved. How about checking the content of the space near the door to make sure that the player can get out and won't get stuck in a wall?