Skip to content

3D_for_GML:_Bullet_paths

hugh greene edited this page Jun 20, 2022 · 1 revision

22.1 Animating explosions

When you create a 2D game, GM makes it really easy to create aiming and shooting because directions and collisions are implemented, even in the standard drag and drop library. When switching to 3D games, it’s not that easy. Let’s walk through each script of the gm6 that comes with this text. First in the list is the script ani_explosion, an animation script for an explosion, holding this code:

ani*=1.2;
if ani>32 then instance_destroy();

A value called ani is increased and when it reaches a certain value the instance is destroyed. This script is used to show an explosionthat grows in size and then disappears.

22.2 Activating zoom with mouse button

The next script in line is ‘draw_camera’, a drawing script for the camera object, holding the following code:

//DRAW WHAT CAMERA SEES
xf=obj_Character.x;       //x to look from
yf=obj_Character.y;       //y to look from
zf=obj_Character.z+16;    //z to look from
xt=xf+cos(degtorad(direction));         //x to look to (with direction)
yt=yf-sin(degtorad(direction));         //y to look to (with direction)
zt=zf-sin(degtorad(zdirection));     //z to look to (with z direction)
d3d_set_projection_ext(xf,yf,zf,    xt,yt,zt,   0,0,1,  70-mouse_check_button(mb_right)*60,1,1,1024);     //look from & to

This script will look familiar if you have studied previous tutorials. The last line is interesting though as it checks the right mouse button. If the right mouse button is pressed, the camera zooms in. In other words, holding the right mouse button activates sniper zoom mode.

22.3 Drawing an explosion

The next script is called draw_explosion, which draws the explosion. Here’s the code that is used:

draw_set_alpha(32/ani);
d3d_draw_ellipsoid(x-ani, y-ani, z-ani, x+ani, y+ani, z+ani, background_get_texture(bk_explosion), ani/16, ani/16, 4+ani/2);
draw_set_alpha(1);

As you can see, the alpha value is reduced as the value ani is increased. This means the explosion will gradually dissolve. Other effects are realized through creative use of the value ani. The alpha value is reset at the end.

22.4 Drawing patches of floor

In the next script, draw_floor, you can see that each one of the patches that make up the floor is drawn at an appropriate depth. For the aiming and bullets this is not very relevant so let us move on.

//draw patch of floor
scr_draw_depth();
d3d_draw_floor(x-32, y+32, 0, x+32, y-32, 0, background_get_texture(bk_floor), 1, 1);

22.5 Targeting crosshairs

The next script, ‘draw_overlay’, draws an overlay (or HUD, if you like) of crosshairs, two black lines crossing each other in the center of the screen:

d3d_set_projection_ortho(0, 0, 640, 480, 0);
draw_set_color(c_black);
draw_line(320-32, 240, 320+32, 240);
draw_line(320, 240-32, 320, 240+32);
draw_set_color(c_white);

This is probably the easiest and fastest way to create a targeting cross.

22.6 Marking the path

The next script, ‘draw_path_marker’, is a drawing script for the marker of the bullet path. You don’t have to draw the path of the bullet in the game but it helps in testing or understanding how things are actually done. This isthe code used:

d3d_draw_ellipsoid(x-1,y-1,z-1, x+1,y+1,z+1,
background_get_texture(bk_path_marker),1,1,12);

You can check the Visible checkbox in the Object Properties if you want to display the path markers. A path marker is used to mark the 3D path of the bullet and check for collisions. More about that later.

22.7 Drawing a sky dome

The next script, draw_sky, guess what? Yes, it draws the sky. Let’s move on because there is not much to see here.

//draw the sky bubble
d3d_set_culling(false);
d3d_draw_ellipsoid(-1024,-1024,-1024-64,   1024,1024,1024-64,
background_get_texture(bk_sky),2,1,32);
d3d_set_culling(true);

22.8 Drawing facing walls

The next script, draw_wall, draws the walls. We have four types of walls, one for each direction. As you can see, a type value is used to determine the type of wall that needs to be drawn:

//draw wall
//set depth based on camera
scr_DrawDepth();
//check wall types!
if type=0 then  //facing north
{
d3d_draw_wall(x-32,y,0, x+32,y,128, background_get_texture(bk_wall),1,1);
}
else
if type=1 then  //facing east
{
d3d_draw_wall(x,y-32,0, x,y+32,128, background_get_texture(bk_wall),1,1);
}
else
if type=2 then  //facing south
{
d3d_draw_wall(x+32,y,0, x-32,y,128, background_get_texture(bk_wall),1,1);
}
else
if type=3 then  //facing west
{
d3d_draw_wall(x,y+32,0, x,y-32,128, background_get_texture(bk_wall),1,1);
}

22.9 Setting up a bullet

In the next script, ini_Bullet, a bullet is initialised (set up). This is where the good stuff is. Look at the code that is used:

//start bullet
anglex=cos(degtorad(obj_Camera.direction));
angley=-sin(degtorad(obj_Camera.direction));
anglez=-sin(degtorad(obj_Camera.zdirection));
for (distance=2;distance<1024;distance+=8;)
{
xt=x+anglex*distance;
yt=y+angley*distance;
zt=z+14+anglez*distance;
if zt<1 then {use=instance_create(xt,yt,obj_explosion);use.z=0;exit;}
use=instance_create(xt,yt,obj_PathMarker);
use.z=zt;
}
sound_play(snd_fire);

The angle of the bullet trajectory (it's path) is calculated based on the direction and z direction of the camera object. In other words, the bullet travels in the direction you are looking in, which is what you want.

22.10 Placing markers on a bullet path

Now that we have the angle at which the bullet needs to travel, we can place the path markers at intervals. The use of path markers is not the fastest way but it is easy to understand because it uses objects. And objects is what you have been using since the day you started with 2D games in GM. We see a loop, starting with this line of code:

for (distance=2; distance<1024; distance+=8;)

This loop is used to walk along the path of the bullet, so to speak; it starts at a small distance from the player (distance=2) and ends at a large distance from the player (distance<1024), with steps of 8 units each (distance+=8). Each step, the code that follows is executed, starting with these three lines:

xt=x+anglex*distance;
yt=y+angley*distance;
zt=z+14+anglez*distance;

The three points (xt, yt, zt) determine the x, y and z of the path marker object. Each time, one of these path markers is placed at a distance from the shooter. The angle values that were determined based on the camera’s direction (remember?) are used to calculate the exact points to place the path markers. The next line makes sure that no marker is placed if the marker is too close to the ground or below the ground.

if zt<1 then {use=instance_create(xt,yt,obj_explosion);use.z=0;exit;}

No marker is placed. In stead an explosion is placed immediately, without even creating a marker at that spot. It also exits this script because the bullet has already reach its end destination. The script continues with these lines:

use=instance_create(xt,yt,obj_path_marker);
use.z=zt;

As you can see, a path marker object is placed at the location and its z value is set. This script ends with the line that plays the sound of firing: sound_play(snd_fire);

22.11 Checking for hits

The next three scripts (init_camera, init_character, and init_explosion) do not hold any surprises, especially not if you are familiar with these tutorials. In the next script, ini_hit_wall, we see what happens when a path marker collides with a wall object:

if z<128 then instance_change(obj_explosion,true);

What this does is: create an explosion when the marker is below a z value of 128 units (which is the height of the buildings).

22.12 Understanding bullet paths

The rest of the scripts should not hold any surprises. So, let’s conclude by trying to understand the general principles behind bullet paths that we have seen in this walkthrough. When the player presses the left mouse button, a script to make a bullet path is run from the Character object. The exact 3D angle is calculated and path markers placed at intervals. When a marker collides with a building block (or when a marker is below ground level), an explosion is created.

Clone this wiki locally