This tutorial came about as a result of a question on my YouTube channel, although it is one of those things that you are going to need to know before long in 3D game programming, so I have made the answer part of the official Godot Engine tutorial series. The question was:
I am making a 3d game and I want to use a sort [of] top down style camera.
I want it so that if an object gets between the player and the camera, that same object will become transparent. For that ( I think ) I can use the “Use Alpha” function in the fixed material settings.
So I think I need a raycast shooting rays ( between the player and the camera ) and if it hits something it needs to “find’ which object was hit en change the material settings so it becomes transparent.
Now, the problem is that I don’t know how to set this up;
-how can I get which object is hit by the ray and how to change the material?
-do I need to create a separate layer for this?
This is one of those things even the most simple 3D game is going to encounter, but the solution isn’t obvious, although the questioner was extremely close in his guess. You can solve this using Ray casting, it’s probably just the how of it all that is difficult to figure out.
As always there is a video version of this tutorial available here.
If you have a situation like the above, where you want to determine if something is in the way of your camera, it’s exactly RayCasting that you want. Let’s take a look at how.
Setting Up The Scene
First you need your scene to be set up correctly. This means that each entity in your scene that can be collided with has to be a physics object of some kind. I created the following scene arrangement:
As you can see, I have a camera and two Rigid Body objects, each with a CollisionShape (Cube) and a Test Cube child. Additionally I created a RayCast object as a child of my camera. Be sure to create the RayCast child before you transform your camera, so it is positioned properly. For my RayCast I use the following settings:
My scene ends up looking like this:
As you can see, the Ray extends from my camera and through both objects in the scene. In this example we want to see if we hit the first cube (“RigidBody 2”) and if we do, we want to hide it. Time to look at some code, the following script was attached to the camera:
extends Camera var ray = null func _ready(): ray = get_node("RayCast") set_process(true) func _process(delta): if(Input.is_key_pressed(KEY_UP)): self.translate(Vector3(0,0.1,0)) if(Input.is_key_pressed(KEY_DOWN)): self.translate(Vector3(0,-0.1,0)) if(ray.is_enabled() and ray.is_colliding()): var collidedWithObject = ray.get_collider() if(collidedWithObject.get_name() == "RigidBody 2"): collidedWithObject.hide() else: get_parent().get_node("RigidBody 2").show()
… and that’s it. We check to see that our ray is enabled and if it is currently colliding with something. If it is, the first collided object is returned by the call get_collider(). In a bit of a hard coded hack, we check the name of that object, and if its our second cube, we hide the entire hierarchy. In the event that no collision occurs, we make sure that RIgid Body 2 is visible with a call to Show(). You will notice that there is code in there to move the camera on press of the Up and Down arrow keys. As you will see, as you move the camera, when the ray isn’t colliding, the second cube is shown, otherwise it is, like so:
It is also possible to create the ray programmatically using the PhysicsServer, like so:
extends Camera func _ready(): set_fixed_process(true) func _fixed_process(delta): if(Input.is_key_pressed(KEY_UP)): self.translate(Vector3(0,0.1,0)) if(Input.is_key_pressed(KEY_DOWN)): self.translate(Vector3(0,-0.1,0)) var directState = PhysicsServer.space_get_direct_state(self.get_world().get_space()) var collisions = directState.intersect_ray(get_translation(),Vector3(0,0,0)) if(collisions.size() and collisions["collider"].get_name() == "RigidBody 2"): collisions["collider"].hide() else: get_parent().get_node("RigidBody 2").show()
Note that _process what changed to _fixed_process(). This is because space_get_direct_state() is only valid during _fixed_process or _integrate_forces() calls. Otherwise we crate an intersect_ray() between the camera position and the origin, which in turn returns if there is a collision between those two points. If there is, we hide the collided objects, otherwise we call show().