0 votes

CURRENT BEST SOLUTION:
Make a RayCast node the child of the camera and set its position and rotation to (0,0,0). Check the "enabled" and "exclude parent" boxes and make sure to use "cast to" to set the length of the raycast rather than using "scale." Make sure to use forceraycastupdate() in your method, or else the raycast can return null references. I had one other point of confusion, which was that my crosshair was actually slightly off-center, which I had to fix. The following code has no issues:

func use_weapon():
$RayCast.force_raycast_update()

if !$RayCast.is_colliding():
    return

var collider = $RayCast.get_collider()
if collider.has_method("damage"):
    collider.damage(10)

There's only one problem left, which is that the raycast collides with enemy vision cones. I'll have to set the collision masks to fix that I think.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
QUESTION:
tl;dr: Does anyone know how to do FPS-style hitscan shooting correctly in Godot? Not just a workaround or a hack, but the most sensible and efficient way? Or is there a good tutorial or code example that shows how to do hitscan shooting in an FPS?

I want to cast a ray from the center of my camera to a point some distance away (say, 250 units from the camera, in the direction the camera is looking), preferably with the ability to slightly angle it randomly for bullet scatter. So far I've tried two different methods but they're both pretty broken the way I'm trying it:

var space_state = get_world().direct_space_state
var aiming = camera.global_transform.basis
var ray = space_state.intersect_ray(camera.global_transform.origin, aiming.xform(Vector3(0,0,-100)), [player, self])

And then getting the collisions, checking methods, etc. I'm using -100 for the z axis because it's flipped for some reason. It detects things maybe 5 or ten units in front of me, but definitely not 100 units. And I'm also trying the raycast node:

if !$RayCast.is_colliding():
        return
    var collider = $RayCast.get_collider()
    var ref = weakref(collider)
    if (!ref.get_ref()):
        return
    if ref.get_ref().has_method("damage"):
        collider.damage(10)

This has a couple problems. One is that it also doesn't detect things very far, even though I've made its length 300, and the other is that it keeps trying to call methods on null references, and even with the weakref checking, it gets a nil reference error (after correctly killing the enemy, if I hit the fire button too soon).
Personally, I'm not a fan of using the raycast node rather than the space state intersect_ray, since it seems inelegant anyhow.

asked May 9, 2018 in Engine by godotshill (15 points)
edited May 10, 2018 by godotshill

1 Answer

+1 vote
Best answer

Did you know that there is now an FPS tutorial on Godot docs? One of its parts talk about making bullets with rayCasts(hitscan). Hope you find what your looking for.

answered May 9, 2018 by SIsilicon (3,493 points)
selected May 9, 2018 by godotshill

I think it's in part two somewhere.

This example solves one problem I've been having, which is inconsistent hit detection.

    $RayCast.force_raycast_update()

Does the job.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.