When designing visual effects, you always want to have the underlying mechanic or interactions in place. That’s why we’re going to prototype our laser’s fire mechanic first.
For this effect, we need:
RayCast2D
node to detect collisions and limit the length of the laser.Line2D
node to draw our laser beam, following the raycast.Particle2D
nodes. We are going to emit particles from the ship, along the beam, and where the laser hits. Name them respectively CastingParticles2D, BeamParticles2D, and CollisionParticles2D.Tween
node to animate the laser’s width, when we start or stop casting the beam.Create these nodes in a new scene, with the raycast as the scene’s root node. Rename the RayCast2D
node LaserBeam2D.
Your scene should look like that:
We are going to use the glowing_circle.png image for all the particle systems. You can find it in the FileSystem dock under the assets/ folder. Select all the Particle2D
nodes at the same time and drag the image to the Texture property in the Inspector. Changing a property with multiple nodes selected updates them all at the same time.
Save your scene as LaserBeam.tscn
Next, we have to write some code to fire the beam.
Let’s start by making the beam cast towards a direction until it reaches its maximum length. Add a new GDScript file to the LaserBeam2D.
Open the script and add two exported properties so you can configure the beam’s cast speed and its maximum length.
class_name LaserBeam2D extends RayCast2D export var cast_speed := 7000.0 export var max_length := 1400
We need to make the raycast extend up to max_length
at a speed of cast_speed
. We are then going to use it to limit the length of the Line2D node, where it intersects with other colliders.
We are going to use the _physics_process
callback for that. Inside the _physics_process
method, let’s use the cast_to
property of RayCast2D`` to extend the ray. Clamp the length to
max_length`.
func _physics_process(delta: float) -> void: cast_to = (cast_to + Vector2.RIGHT * cast_speed * delta).clamped(max_length)
The calculation above extends the laser to the right, which corresponds to an angle of 0
in Godot. Doing so ensures we won’t run into problems when we rotate the laser later.
We have the raycast set, now let’s create the laser. Back to the scene, in the FillLine2D, create 2 points. The line should:
Vector2(0, 0)
.You can use the Grid Snap to snap the points to precise locations or expand the Points property in the Inspector to set their coordinates by hand.
Back to the script, store a reference to the FillLine2D node. You can place it right below the exported variables you already have:
onready var fill := $FillLine2D
Now, we need to make the FillLine2D extend with the ray we cast in _physics_process
. For that, create a cast_beam()
method and call it in _physics_process
:
func _physics_process(delta: float) -> void: cast_to = (cast_to + Vector2.RIGHT * cast_speed * delta).clamped(max_length) cast_beam() func cast_beam() -> void: pass
In the cast_beam()
method, we are going to calculate how far the laser should extend:
In the cast_beam()
method, add a cast_point
variable and have it default to the cast_to
value. If the raycast is colliding, then assign the collision point’s coordinates to the cast_point
variable.
The physics engine updates the collision information on raycasts and other physics nodes all together at the end of the physics frame. We need to update the raycast manually to get correct collision information. Call force_raycast_update()
before checking for collisions to do so.
Finally, set the fill line’s last point to the value of cast_point
to extend the laser beam. Your cast_beam
function should look like this:
func cast_beam() -> void: var cast_point := cast_to force_raycast_update() if is_colliding(): cast_point = to_local(get_collision_point()) fill.points[1] = cast_point
Note that we need to convert the collision point’s coordinates to local ones because the function get_collision_point()
returns a global position, and our fill line uses local coordinates for drawing.
To test your changes, in _physics_process
, you can add a call to look_at(get_global_mouse_position())
. This function rotates your node towards the mouse. Be sure to remove the extra line after testing at the end of the tutorial.