Logo 3.5Cats_AndHalfAFish


Interacting with game items in 3d.

January 30, 2020 Shade, Godot : ray casting
Mouse picking in Godot 3d.

Note : Godot 3.2 beta 4.

'Finding Fifi' : interaction. Most 3d games have the player collide with (run into) an item to interact with it. And there are many tutorials available to show you how to accomplish this.
That technique however isn't suitable for a puzzle point&click game. As the name suggests, a player is expected to point the mouse at an item and use the left mouse button to interact with it.
So, first order of business is to figure out how to detect items under the mouse pointer in Godot 3d.

Ray Casting (and how -not- to use it).

I spent quite some time reading up on this, and it seemed like ray casting would be the way to go.
The code is not difficult to write :

Code for ray casting.
Code used in ray casting.

It is important that this code is run from _physics_process() because the space that is queried might be locked at other times (you can't query it).
'my_spatial' is the node that this script is attached to. It has to be a Spatial node.
The method intersect_ray() has a quite a number of parameters you can set. An important one is the exlude array, where you can put references to nodes that the ray should ignore. The type of items you put in that array depends on the settings of the 2 'collide_with' params. The default is 'collide_with_bodies = true' and 'collide_with_areas = false'. In this case, the exclude array only needs to mention StaticBody, KinematicBody, and RigidBody nodes. In the other case, the array should only include Area nodes.
Another important param for point&click games is the lenght of the ray, which can be set through the 'ray_to' param. One way to get an idea of what length to use, is to give the player node a RayCast child node. I've been using ray cast lengths between 10 and 50, but it will be easier to pick an appropriate RAY_LENGTH once I'm using the game assets.

RayCast child node.
RayCast child node to get an idea of ray cast length.

I should also mention that the items that you want to detect with ray casting should be physics bodies or areas, and that you should adjust the params in intersect_ray() accordingly.
Most tutorials also mention that you should set 'input ray pickable = on' for the bodies or areas. This is incorrect (see issue report 28261). Input Rays are cast by the engine itself ; the rays created in code are -not- input rays, so they are not influenced by this setting !

After a couple of hours of playing with ray casting, I was forced to admit defeat (here comes the 'how not to use it' part).
In my game, I want to have some kind of mouse_over effect to let the player know what items he can interact with. So, the idea was to use the RAY_LENGTH from the intersect_ray() method to limit the mouse_over effect to items that are 'close enough' to the player. So, at the time of mouse_over (of a nearby item), I checked the objs_under_mouse array to see if the ray could detect that item.

The results were ... strange. Sometimes, the array would be empty when I expected the item to be in there. At other times, the array would contain a completely different object (like a wall). I just couldn't figure it out.

So, I did what I always do in case like that : I searched the web.
Nobody else seemed to be complaining about ray casting in Godot, and I was pretty sure my code was correct. So, what was the problem ?

I spent some more time tweaking the code, looking at the results, and thinking about it ... And then it finally hit me : the timing was the culprit !
I had been using the -mouse_over- event to check the objs_under_mouse array ! And that array would of course still be empty at the time of the mouse_over : the ray hasn't detected the item yet. It would only contain the item right -after- the mouse_over event !
It's pretty easy to test this by changing the mouse_over to mouse_out ...
And yup ! Now the objs_under_mouse array consistently contains the item under the mouse pointer. He, he. Glad I understand what's going on ...

Note : Don't use ray casting with mouse_over, only with mouse click or mouse_out !!!

Alright. Time for a new way to go about this ... (I just remember something from an interview with Rand Miller I watched yesterday : "If you don't like solving problems, you really shouldn't be trying to create games ..." (or something to that order)).

Distance between 2 spatial nodes.

This idea is very simple : when an item is detecting mouse_over, calculate its distance from the player. If that distance is less than the interaction_distance, the player can interact with that object.
The code for this is very simple :

Code for mouse_over distance calculation.
Code for mouse_over distance calculation.

For instance, we can change the mouse cursor when we're close enough to an object to interact with it :

Code for mouse_over and mouse_out effects.
Code for mouse_over and mouse_out effects.

The final step is then, to set/clear an item_selected variable (references the node under the mouse pointer) in the on_mouse_over()/on_mouse_out() functions. The function on_mouse_click() can use that information to do something with that item (pick it up, unlock something, ...). But that's a post for another time ...

Code for mouse_click on nearby item.
Code for mouse_click on nearby item.

Of course, you shoudn't forget to register the event handlers for the items you want to interact with. 'body' refers to for instance the StaticBody node that registers the mouse events. You can also do this manually, in the 'node' panel of the editor.

Code to register the event handlers.
Code to register the event handlers.

So. Back to working on my game now ...