• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

SOLVED First Person 3D gun clipping through walls and other issues...

Spam1985

Member
Game Maker Studio 1.4:
Hi folks. I made my first 3D model using Maarten Baert's Model Creator (thanks to NickKall for bringing that amazing program to my attention!)
It's a gun. It's created with a d3d_model_primitive_begin script.
Managed to get it into the game and follow the camera as the player looks around, but I am having some issues:

1) It clips through everything else in the game world.

2) It perfectly rotates with the camera horizontally, but when looking up or down, the rotation is off and becomes more extreme the higher/lower you look.

3) Should such primitives be created at the start of the game? Do they need to be created and destroyed as and when to save memory?

Thanks for reading and any help is much appreciated!

///Player's Draw Event
GML:
//camera
d3d_set_projection_ext(x, y, z + eyeLevel, xto, yto, zto,  0, 0 , 1, fov, aspectRatio, 1, 1500);

//draw gun
d3d_transform_set_identity();
d3d_transform_add_rotation_x(-zDir); //rotates the gun vertically (not working properly!)
d3d_transform_add_rotation_y(0);
d3d_transform_add_rotation_z(direction + 90); //rotates gun horizontally (works!)

d3d_transform_add_translation(x,y,z);

d3d_model_draw(global.gun,-15,5,25,background_get_texture(tex));

d3d_transform_set_identity();
///Relevant Step Event code
GML:
zDir -= (display_mouse_get_y() - display_get_height()/2) / 10;

//clamp zDir
zDir = clamp(zDir, -80, 80);

//set xto, yto, zto...
var d = degtorad(direction);
var zd = degtorad(zDir);
xto = x + cos(d) * abs(sin(zd) + sign(-zDir));
yto = y - sin(d) * abs(sin(zd) + sign(-zDir));
 
Last edited:

Joe Ellis

Member
I think rendering it to a separate surface and overlaying it ontop is good idea, that will stop the clipping, and also you won't need to rotate it with the camera, you can use a separate view & projection which stays the same all the time and renders the gun in the place you want it to be on the screen
However it could cause problems with lighting, if you're only using a directional light, you could rotate that, but with multiple lights it would be simpler to just rotate & translate the gun.

Another way to stop the clipping, if you don't want to use a separate surface, you can make a shader with a "depth bias", which reduces the gl_position.z in the vertex shader so that the pixels don't get clipped cus their depth is less than the walls. You'll have to experiment with the bias amount though until the tip of the gun no longer gets clipped, and I think dividing the gl_position.z instead of subtracting is the better, cus subtracting can make the depth become < 0(or the znear) and then would get clipped cus it's too near to the camera
 
Last edited:

Spam1985

Member
Hi Joe, thanks for your advice.

I've never used surfaces or shaders before.
From what I can tell, it looks as though shaders would not work on some systems... I'd rather play it safe and not use them.

With surfaces would I still be able to have the 3D gun animate, move and sway? My game does not currently use lighting so that isn't an issue.
 

Joe Ellis

Member
No problem, yeah the movement of the gun would be the same either way, it looks like the separate surface method is the best fit.
I'm not sure about shaders not working on some systems, but gm uses a default shader for rendering everything anyway, so I think it should be fine with simple shaders in glsl es.
Here is some code for the surface stuff:
GML:
///At the start of the game

global.gun_surface = surface_create(surface_get_width(application_surface), surface_get_height(application_surface))

///Player's Draw Event

surface_set_target(global.gun_surface)
draw_clear_alpha(c_black, 0)

//camera needs to point in the same direction as the gun model
//I'm assuming that the forwards vector is (1, 0, 0), but if the gun points along the y axis instead use (0, 1, 0)
d3d_set_projection_ext(0, 0, eyeLevel, 1, 0, eyeLevel,  0, 0, 1, fov, aspectRatio, 1, 1500);
//eye level might not be needed here, you could apply it to the gun's z position instead and have the camera z at 0

//draw gun
d3d_model_draw(global.gun,-15,5,25,background_get_texture(tex));

surface_reset_target()

///Player's Draw Gui Begin Event

draw_surface(global.gun_surface, 0, 0)
So yeah this way is good cus you don't have to do any of the code for rotating the gun, you just need to make sure the camera is looking down the barrel of it. You can either offset the gun's position like you're doing or offset the camera. You still need the code for setting xto and yto, but use this when rendering the environment
 

Spam1985

Member
Thanks Joe! I I added these checks into the draw events and now it works:

if (!surface_exists(global.gun_surface))
{
global.gun_surface = surface_create(surface_get_width(application_surface), surface_get_height(application_surface))
}

Hopefully I'm not going to get memory leaks and stuff now. I've always been scared of using surfaces!
 
Last edited:

Joe Ellis

Member
Did you put the code in to create it? If not, you could put it in player's create event.
If you did create it, it might be because the surface wasn't cleared when it was created, I remember some error happens if you try to draw it before you actually draw anything on it.

So you could in the player's create event do:
Code:
if !variable_global_exists("gun_surface")
{global.gun_surface = -1}

if global.gun_surface = -1
|| !surface_exists(global.gun_surface)
{
global.gun_surface = surface_create(surface_get_width(application_surface), surface_get_height(application_surface))
surface_set_target(global.gun_surface)
draw_clear_alpha(c_black, 0)
surface_reset_target()
}
 

Spam1985

Member
I simply added this code to both draw events and it worked. Does it look OK? Can you foresee any possible issues? (I'm not familiar with using surfaces)

GML:
///draw gun surface
if (!surface_exists(global.gun_surface))
{
global.gun_surface = surface_create(surface_get_width(application_surface), surface_get_height(application_surface))
}
Anyways, thank you so much for all your help... your solution works like a charm!
 
Last edited:

Joe Ellis

Member
Ah brilliant, I'm glad it works. No doing that should be fine, and they say you're supposed to always check a surface exists before drawing it cus it's possible that the surface can be deleted at any moment by other things going on on the pc, it's very unlikely but can still happen, also something like switching from\to fullscreen could maybe cause it on some pc's
 

Jase217

Member
I believe you can just disable z testing before drawing the gun model, then re enable it after, that should make the gun model draw on top of everything else and you won't need to mess around with surfaces.
GML:
// Disable Z Testing, making the weapon draw above everything else in the world.
gpu_set_ztestenable(false);

// --- Draw gun here ---

// Re enable Z Testing
gpu_set_ztestenable(true);
EDIT*: I just realised you're using d3d functions, I'm not sure if there is a ztestenable function using d3d but there is the function d3d_set_zwriteenable. That may work too. You would have to place the draw code after drawing everything else in the world.

EDIT**: There's also a bit of a catch! Once z testing or writing is disabled I believe the vertices will need to be setup in order from furthest away from the camera to the closest.
 
Last edited:

Joe Ellis

Member
I believe you can just disable z testing before drawing the gun model, then re enable it after, that should make the gun model draw on top of everything else and you won't need to mess around with surfaces.
GML:
// Disable Z Testing, making the weapon draw above everything else in the world.
gpu_set_ztestenable(false);

// --- Draw gun here ---

// Re enable Z Testing
gpu_set_ztestenable(true);
EDIT*: I just realised you're using d3d functions, I'm not sure if there is a ztestenable function using d3d but there is the function d3d_set_zwriteenable. That may work too. You would have to place the draw code after drawing everything else in the world.

EDIT**: There's also a bit of a catch! Once z testing or writing is disabled I believe the vertices will need to be setup in order from furthest away from the camera to the closest.
That's what I first thought of doing, but the problem with parts of the gun mesh overlapping makes it quite tricky. You could sort the vertices by furthest to nearest though, and this would be the most efficient option.
The function in 1.4 is d3d_set_hidden btw
Turning zwriteenable off would still have the clipping cus it still checks the depth, but it's useful for making things with transparency overlap, while still being clipped if they're behind something opaque, and also for rendering sky
 
What I don't understand is why we can't have more direct access over the depth buffer. Why can't there just be a "clear" command? It isn't clear to me when gamemaker is creating and or destroying depth buffers. Does it create a new one every time you create or start drawing to a surface??
 

Joe Ellis

Member
It seems that each surface has it's own depth buffer, and draw_clear also clears it's depth buffer. That made me wonder if you can turn color_writeenable off for rgba and it will still clear the depth buffer, but color_writeenable doesn't have any effect on draw clear :(
 

Spam1985

Member
Thank you all so much for your responses!
Joe, your surface method is working wonderfully, I wonder though if you could give me an example of how I might rotate the gun model?

The way the guns follow the view is brilliant but at this point they might as well be sprites since they dont act 3d.
Thinking they should have some slight lag as you move the view around.

Would I use the d3d_transform_set_identity(); and add_rotation and all that jazz?
 

Joe Ellis

Member
Yeah you can use those, or matrix_build
I prefer to use matrix_build for most things cus it's all done in one line. It works fine if you only rotate Y & Z (pitch & yaw) but if you use X\Roll, the matrix_build function works in YXZ order, which makes the X\Roll affect the Y rotation in a way that's confusing,
in that case, I build 2 matrices and multiply them together, one with the Y & Z rotation and the translation, and the other just has the X rotation, essentially so the order is XYZ

GML:
///instance_get_matrix()

if roll = 0
{matrix = matrix_build(x, y, z, 0, pitch, yaw, scale, scale, scale)}
else
{matrix = matrix_multiply(matrix_build(0, 0, 0, 0, 0, roll, 1, 1, 1), matrix_build(x, y, z, 0, pitch, yaw, scale, scale, scale))}
Then when drawing the model, you simply do matrix_set(matrix_world, matrix), and d3d_transform_set_identity() afterwards.

For the gun actually moving when you shoot and walk, I've never actually done this so I can't really give any good advice at this point, but I am actually working on this exact thing at the moment with my engine, so I'll keep you updated when I do some stuff and share it with you.
One thing I'm planning on doing is when you shoot, the gun has a knockback kind of thing, where it moves\snaps backwards slightly, and then the few frames after this it gradually moves back into it's normal position:

GML:
//When fired

gun_x = gun_knockback_amount


///Step Event

if gun_x != gun_rest_x
{

//I'm going to test these 3 methods and see which one looks the best:

//lerp
gun_x = lerp(gun_x, gun_rest_x, gun_return_speed)

//basic
gun_x += gun_return_speed
if gun_x > gun_rest_x
{gun_x = gun_rest_x}

//acceleration & speed
gun_xspeed += gun_return_speed
gun_x += gun_xspeed
if gun_x >= gun_rest_x
{
gun_x = gun_rest_x
gun_xspeed = 0
}

}
The pitch\Y rotation could also be affected in the same way as this
 
Last edited:

Spam1985

Member
Thanks Joe.

That matrix stuff looks completely alien to me, reading that code I haven't the foggiest idea what is happening lol.
I would prefer to use add_rotation and stuff but if I could get anything to work at this point I'd be happy.

Let's say I have a "recoil" variable that is supposed to make the gun barrel pitch upwards when firing. I add something like this in but the gun just disappears completely.

GML:
///Draw gun
if (!surface_exists(global.gun_surface))
{
    global.gun_surface = surface_create(surface_get_width(application_surface), surface_get_height(application_surface))
}
surface_set_target(global.gun_surface);
draw_clear_alpha(c_black, 0);

d3d_set_projection_ext(0, 0, eyeLevel, 0, 1, eyeLevel,  0, 0, 1, 40, aspectRatio, 1, 1500);

//this transform stuff ain't workin'!
d3d_transform_set_identity();
d3d_transform_add_rotation_x(recoil); //add recoil rotation
d3d_transform_add_rotation_y(0);
d3d_transform_add_rotation_z(0);
d3d_transform_add_translation(x,y,z);

d3d_model_draw(global.model_uziBody,35,110,0,background_get_texture(tex_uzi));

d3d_transform_set_identity();

surface_reset_target()
 

Joe Ellis

Member
Hmm, I think it's cus you added translation, remember it's drawing it at(near) zero so it's always right next to the camera.
You could use it for making the gun move a bit when shooting though.
Also, I just realized that the rotations won't work properly due to offsetting the gun in d3d_model_draw(35,110,0)
Cus rotations work around 0, 0, 0, so you'll get alot of unwanted translation cus of it, I drew this to explain what happens:



So you want to offset the gun with d3d_add_translation(35,110,0) instead, after the rotations.
You also need to take into account the zero\origin in the model itself, to make it rotate in the most desirable way, you probably want to have the origin point where the player's wrist would be, as that's the thing that's rotating and making the gun rotate really.
I think once you get that sorted you should be all set.
 

Spam1985

Member
Thanks Joe! Gun rotation works now thanks to your fantastic explanation and diagram (I bloody love diagrams!)
Got what I needed now - gun following the camera, no clipping issues and some rotation added. Lovely!

You've been a huge help. Thanks again!
 
Top