Weapons - part 1

Top  Previous  Next

This month we will start discussing about various types of weapons that can be implemented in our games. Now that I think about it, we can (and we will!) create several weapon types:

- c_trace based weapons, which use invisible c_trace rays to detect if the enemy was hit or not. This type of weapon is ideal for pistols, shotguns, machine guns, sniper guns, etc because the player doesn't get to see the bullets anyway.

- weapons that fire entity-based, visible bullets. We can create rocket launchers, grenade launchers, etc using this approach.

- c_scan based weapons, a new idea for me. We are going to create a weapon that kills everyone in the area, no matter if they can see the player or not. Think of it as being a weapon that fires small atomic bombs, if you want.

 

Now don't get me wrong; you can definitely code a shotgun that uses entity-based bullets instead of using an invisible c_trace ray for it. I'm just trying to break down the methods that can be used to create weapons with Gamestudio, so feel free to play with them until you get what you're looking for.

 

OK, so the first thing we're going to need is a player movement snippet. Frankly, the one I've designed for the shooter template is the best one I've ever created, so I'll use the most up to date version of it here, including it in the main.c script. Move the player using the WSAD keys, jump with "space" and run using the left "shift" key.

 

Unlike the template weapons, which are very complex because they are highly configurable, my goal is to make the code for these weapons as simple as possible, and yet keep it fully functional, thus helping the beginners learn how to code their own weapons without putting in too much effort. Let's start by examining the main1.c file:

 

#include <acknex.h>

#include <default.c>

 

#include "player.c" // include player's movement / camera code

#include "enemies.c" // dummy enemies, used for testing

#include "weapons1.c" // c_trace based weapons

 

ENTITY* sky =

{

       type = "skycube+6.tga";

       flags2 = SKY | CUBE | SHOW;

}

 

function main()

{

       camera.ambient = 100;

       camera.arc = 90; // set camera.arc to a proper value for shooter games

       fps_max = 75; // limit the frame rate to 75 fps (not really needed)

       video_screen = 1; // start the game in full screen mode

       video_mode = 8; // run at a 1024x768 pixels resolution on monitors with a 4/3 aspect ratio

       level_load ("weapons1.wmb");

}

 

As you can see, it's a simple script that includes the needed files, creates a sky entity, and then sets some common sense values inside function main( ). Let's take a quick look inside the enemies.c script:

 

action dummy_enemies() // attach this action to your helpless enemies

{

       var anim_percentage = 0;

       my.skill99 = 30; // smaller than 50 to ensure that the enemy dies at the first shot

       while (my.skill99 > 0)

       {

               ent_animate(my, "stand", anim_percentage, ANM_CYCLE); // play the "stand" animation in a loop

               anim_percentage += 3 * time_step; // "3" controls the "stand" animation speed

               wait (1);

       }

       // the enemy is dead here

       anim_percentage = 0;

       while (anim_percentage < 100)

       {

               ent_animate(my, "death", anim_percentage, NULL); // play the "death" animation

               anim_percentage += 4 * time_step; // "4" controls the "death" animation speed

               wait (1);

       }

       set (my, PASSABLE); // allow the player to pass through the corpse now

}

 

Yes, it's that simple! Our enemies store the health inside their own skill99; our weapons will make use of this information. The enemies play their "stand" animation until their health reaches zero, and then they play their "death" animation. Doesn't get simpler than that!

 

OK, now we're ready to see the weapons1.c source code:

 

ENTITY* pistol_weapon;

 

SOUND* gotweapon_wav = "gotweapon.wav";

SOUND* pistolfired_wav = "pistolfired.wav";

 

action my_pistol() // attach this action to your pistol model

{

       set (my, PASSABLE);

       while (!player) {wait (1);}

       while (vec_dist(player.x, my.x) > 100) // wait until the player has come close to the pistol

       {

               my.pan += 3 * time_step; // rotate until the player picks up the pistol

               wait (1);

       }

       snd_play(gotweapon_wav, 80, 0); // play the "got the weapon" sound

       pistol_weapon = ent_createlayer("pistol.mdl", 0, 20); // create the pistol entity on the 20th view layer

       pistol_weapon.flags2 |= SHOW; // show the pistol entity                

       pistol_weapon.x = 55; // 55 quants ahead of the view, play with this value

       pistol_weapon.y = -25; // 25 quants towards the right side of the screen, play with this value

       pistol_weapon.z = -30; // 30 quants below, play with this value

       pistol_weapon.pan = 5; // pistol's pan angle (set its tilt, roll, etc if you want to)

       wait (1);

       ent_remove (my); // remove the pistol from the ground

}

 

Truth be told, I was expecting the c_trace weapon code to be much bigger, so I was very happy to see that I've managed to squeeze all the important stuff in about 30 lines of code. First of all, please note that I am using a "real" entity that is placed in the level for the weapon, and then, as soon as the player picks it up (comes closer than 100 quants to it) I remove it, replacing it with a "view" entity which has the great feature of not existing as a "real" entity in the level, thus allowing me to create a weapon that doesn't penetrate the level walls no matter how close I come to them.

 

The code inside action my_pistol( ) take care of all of that, also setting the x, y, z and pan angle for my "view" pistol entity.

 

function fire_bullets() // this function is triggered by any left mouse button push

{

       if (!pistol_weapon) return; // don't fire bullets if the player didn't pick up the pistol yet

       VECTOR trace_coords;

       vec_set(trace_coords.x, vector(2000, 0, 0)); // the pistol gun has a firing range of 2,000 quants

       vec_rotate(trace_coords.x, camera.pan);

       vec_add(trace_coords.x, camera.x);

       c_trace(camera.x, trace_coords.x, IGNORE_ME | IGNORE_PASSABLE | SCAN_TEXTURE | ACTIVATE_SHOOT); // trace 2,000 quants in front of the player

       if (you) // hit one of the entities in the level?

       {

               you.skill99 -= 50; // 50 = damage done by the pistol to the enemy, provided that it stores its health inside skill99

       }                                

       snd_play(pistolfired_wav, 100, 0); // bullet fired sound effect

}

 

function gun_startup()

{

       on_mouse_left = fire_bullets; // call this function when the left mouse button is pressed        

}

 

The function that takes care of the firing part is even smaller; it creates a ray that starts from the center of the screen (camera.x) and moves forward 2,000 quants, tracing along this ray. If the "you" pointer isn't NULL (the tracing ray has hit an entity), the "you" entity will decrease its skill99 by 50 units. And if you look a few lines above, you will remember that our dummy enemies store the health inside their own skill99.

 

Finally, the tiny gun_startup( ) function will run at game start automatically, instructing the engine to execute the fire_bullets( ) function each and every time when the player presses the left mouse button.

 

Time to run our main1.c script! Just do it (hope I don't run into a copyright issue here ;) and if everything works as expected, you will see something like this:

 

aum103_workshop1

 

Move close to the rotating pistol in order to pick it up, and then fire a few bullets at our dummy enemies.

 

aum103_workshop2

 

You now have access to a simple, and yet 100% functional c_trace based weapon snippet that can serve as a base for many weapon types. Actually, only 2 important things that are missing from this piece of code:

a) A crosshair that allows you to shoot precisely where you want to;

b) Some code to turn this weapon into an auto-fire gun, for example a machine gun.

 

If these things got you worried, please fear not, for the code inside the weapons2 folder addresses exactly these issues.

 

aum103_workshop3

 

Actually, I seized the opportunity to add one more thing: gun recoil code, which is done by simply displacing the weapon 1 quant backwards, and then moving it back - see for yourself.

 

ENTITY* mgun_weapon;

 

SOUND* gotweapon_wav = "gotweapon.wav";

SOUND* mgunfired_wav = "mgunfired.wav";

 

BMAP* crosshair_tga = "crosshair.tga";

 

PANEL* crosshair_pan =

{

       layer = 15;

       bmap = crosshair_tga;

       flags = SHOW;

}

 

action my_machinegun() // attach this action to your machine gun model

{

       set (my, PASSABLE);

       while (!player) {wait (1);}

       while (vec_dist(player.x, my.x) > 100) // wait until the player has come close to the machine gun

       {

               my.pan += 3 * time_step; // rotate until the player picks up the machine gun

               wait (1);

       }

       snd_play(gotweapon_wav, 80, 0); // play the "got the weapon" sound

       mgun_weapon = ent_createlayer("machinegun.mdl", 0, 20); // create the machine gun entity on the 20th view layer

       mgun_weapon.flags2 |= SHOW; // show the machine gun entity                

       mgun_weapon.x = 55; // 55 quants ahead of the view, play with this value

       mgun_weapon.y = -25; // 25 quants towards the right side of the screen, play with this value

       mgun_weapon.z = -30; // 30 quants below, play with this value

       mgun_weapon.pan = 5; // machine gun's pan angle (set its tilt, roll, etc if you want to)

       wait (1);

       ent_remove (my); // remove the machine gun from the ground

}

 

function fire_bullets() // this function is triggered by any left mouse button push

{

       if (!mgun_weapon) return; // don't fire bullets if the player didn't pick up the machine gun yet

       VECTOR trace_coords;

       while (mouse_left)

       {

               mgun_weapon.x -= 1;

               vec_set(trace_coords.x, vector(5000, 0, 0)); // the machine gun gun has a firing range of 5,000 quants

               vec_rotate(trace_coords.x, camera.pan);

               vec_add(trace_coords.x, camera.x);

               c_trace(camera.x, trace_coords.x, IGNORE_ME | IGNORE_PASSABLE | SCAN_TEXTURE | ACTIVATE_SHOOT); // trace 5,000 quants in front of the player

               if (you) // hit one of the entities in the level?

               {

                       you.skill99 -= 10; // 10 = damage done by the machine gun to the enemy, provided that it stores its health inside skill99

               }                                

               snd_play(mgunfired_wav, 100, 0); // bullet fired sound effect

               wait (-0.08);

               mgun_weapon.x += 1;                

               wait (-0.08);

       }

}

 

function gun_startup()

{

       on_mouse_left = fire_bullets; // call this function when the left mouse button is pressed        

       crosshair_pan.pos_x = (screen_size.x - bmap_width(crosshair_tga)) / 2; // these two lines keep the crosshair panel in the center of the screen

       crosshair_pan.pos_y = (screen_size.y - bmap_height(crosshair_tga)) / 2; // regardless of the screen resolution

}

 

As you can see, most of the code is similar with the one that was used for the pistol. The important changes are the addition of the crosshair panel, together with the code that keeps it in the center of the screen (the last 2 lines inside function gun_startup) and the auto-fire, which was done by adding a while(mouse_left) loop inside function fire_bullets. The gun recoil was added by using two "wait (-0.08);" instructions, subtracting and then adding 1 quant to the weapon's x coordinate after each "wait" instruction - this tells the weapon to move closer to the the camera or away from it.

 

What other weapons could we build using c_trace? A sniper gun could easily be created by adding a simple sniper scope panel to our pistol code and zooming in (setting camera.arc to a small value) whenever the player presses the zoom-in button - there's a fully functional example in the shooter template code, so there has to be something else that we can do for our third example... I know! Let's create a c_trace gun that uses a visible tracing ray, one of those red laser lights that would render our crosshair useless! Hold on a bit...

 

aum103_workshop4

 

ENTITY* sniper_weapon;

 

SOUND* gotweapon_wav = "gotweapon.wav";

SOUND* sniperfired_wav = "sniperfired.wav";

 

action my_snipergun() // attach this action to your sniper gun model

{

       set (my, PASSABLE);

       while (!player) {wait (1);}

       while (vec_dist(player.x, my.x) > 100) // wait until the player has come close to the sniper gun

       {

               my.pan += 3 * time_step; // rotate until the player picks up the sniper gun

               wait (1);

       }

       snd_play(gotweapon_wav, 80, 0); // play the "got the weapon" sound

       sniper_weapon = ent_createlayer("snipergun.mdl", 0, 20); // create the sniper gun entity on the 20th view layer

       sniper_weapon.flags2 |= SHOW; // show the sniper gun entity                

       sniper_weapon.x = 55; // 55 quants ahead of the view, play with this value

       sniper_weapon.y = -25; // 25 quants towards the right side of the screen, play with this value

       sniper_weapon.z = -30; // 30 quants below, play with this value

       sniper_weapon.pan = 5; // sniper gun's pan angle (set its tilt, roll, etc if you want to)

       wait (1);

       ent_remove (my); // remove the sniper gun from the ground

}

 

function fire_bullets() // this function is triggered by any left mouse button push

{

       if (!sniper_weapon) return; // don't fire bullets if the player didn't pick up the sniper gun yet

       VECTOR trace_coords;

       vec_set(trace_coords.x, vector(10000, 0, 0)); // the sniper gun has a firing range of 10,000 quants

       vec_rotate(trace_coords.x, camera.pan);

       vec_add(trace_coords.x, camera.x);

       c_trace(camera.x, trace_coords.x, IGNORE_ME | IGNORE_PASSABLE | SCAN_TEXTURE | ACTIVATE_SHOOT); // trace 10,000 quants in front of the player

       if (you) // hit one of the entities in the level?

       {

               you.skill99 -= 100; // 100 = damage done by the sniper gun to the enemy, provided that it stores its health inside skill99

       }                                

       snd_play(sniperfired_wav, 100, 0); // bullet fired sound effect

}

 

function gun_startup()

{

       VECTOR laser_origin, laser_end;

       on_mouse_left = fire_bullets; // call this function when the left mouse button is pressed        

       while (!sniper_weapon) {wait (1);}

       while (1)

       {

               vec_set(laser_origin.x, vector(20, -10, -5)); // the origin of the red laser ray is offset by 20, -10 and 5 quants in relation to camera.x

               vec_rotate(laser_origin.x, camera.pan);

               vec_add(laser_origin.x, camera.x);                                

 

               vec_set(laser_end.x, vector(10000, 0, 0)); // the red laser pointer has a firing range of 10,000 quants (use smaller values here if needed)

               vec_rotate(laser_end.x, camera.pan);

               vec_add(laser_end.x, camera.x);

 

               draw_line3d(laser_origin.x, NULL, 100); // draw a red line

               draw_line3d(laser_end.x, vector(0, 0, 255), 100); // that starts at laser_origin and ends at laser_end

               wait (1);

       }

}

 

This is the content of the 3rd weapon snippet; I used the pistol code as a base, creating a sniper weapon out of it, so all the interesting stuff happens inside function gun_startup( ). The red laser targeting system sets the laser_origin vector to a good position in relation with the sniper gun model, while the laser_end vector is set 10,000 quants away from the camera. The last few lines of code inside the function do the actual drawing between laser_origin and laser_end.

 

Since the red laser ray is initiated from a sideway position (and not from the center of the screen), you will discover that the precision of the laser pointer is much bigger at greater distances, as its angle with the view normal is closer and closer to zero. Well, we're dealing with a sniper gun here, so having greater precision at greater distance is exactly what we need.

 

Now that I think about it, you could also use particles to create a visible tracing ray. The good news is that the new laser weapon from the updated shooter template does just that, so you'll also have access to a fully functional weapon that uses this method. I'll see you all next month!