Our laser works, but it doesn’t look anything like a laser. Let’s make it glow. To do so, we’re going to use Godot’s post-processing glow effect, which works both in 2D and in 3D.
We need to:
Create a new 2D scene for your final laser beam demo. Click and drag LaserBeam.tscn
from the FileSystem tab to your scene. Add a WorldEnvironment node as a sibling of the laser and select it.
In the Inspector, click on the Environment property and create a new “Environment” resource. Expand that resource. Change its Background Mode to “Canvas” and enable the Glow property.
Expand the Glow category and change the Blend Mode property to “Additive”. Doing so adds the values generated by the glow shader to the rendered frame, making the effect look bright. The “Soft Light” blending mode is a good alternative for a more nuanced and realistic result.
Then, head back to the LaserBeam2D scene. Select the FillLine2D and change the Default Color to a lighter blue. In the Capping category, change Joint Mode, Begin Cap, and End Cap to “Round”.
Select FillLine2D, BeamParticles2D, CollisionParticles2D, and CastingParticles2D and change their Modulate color property to a tone beyond 100% white. To do so, click on the color slot to open the color picker, and turn on the “Raw” mode. The raw mode allows you to increase color values beyond 100% intensity in each channel, triggering effects such as the glow shader.
For more information, see the official documentation on Glow.
If you try the game, the beam is continuously active. Let’s add an interactive weapon fire mechanic.
Head back to the script editor and open the LaserBeam.gd
file.
In the _ready
callback, deactivate the _physics_process
callback and set the FillLine2D last point to Vector2.ZERO
. Doing so makes the line invisible at the start of the game.
func _ready() -> void: set_physics_process(false) fill.points[1] = Vector2.ZERO
Add a new property, is_casting
, to be able to toggle the beam’s cast on and off. At the top of the class, above the fill
onready variable, add a new variable named is_casting
, set it to false by default, and give it a setter function, set_is_casting
.
var is_casting := false setget set_is_casting
This setter function is going to control what happens when we start and finish casting the laser beam.
Add the set_is_casting()
setter method after cast_beam()
.
There, don’t forget first to assign the argument the function receives to the is_casting
variable. If is_casting
is true
, we are going to reset the laser beam. Set cast_to
to Vector2.ZERO
and do the same for the last point in the fill line.
func set_is_casting(cast: bool) -> void: is_casting = cast if is_casting: cast_to = Vector2.ZERO fill.points[1] = cast_to
After that, use the value of is_casting
to enable or disable the _physics_process
callback.
set_physics_process(is_casting)
You can use the _unhandled_input
callback to test the shooting mechanic by calling set_is_casting()
. Something like this:
func _unhandled_input(event: InputEvent) -> void: # Turn on casting if Enter or Space is pressed. if event.is_action_pressed("ui_accept"): set_is_casting(true) # Stop casting the beam upon releasing the key. elif event.is_action_released("ui_accept"): set_is_casting(false)
But make sure to remove that input handling function after running the test as input is not the responsibility of the LaserBeam2D.
If you test the scene now, the beam has its full width whenever you fire. We are going to animate it, so the laser grows and shrinks. That’s where the Tween node we created in the first section comes in.
We need a new exported variable to control how fast the line’s width
increases or decreases when animating the beam.
Add a new variable below max_length
and name it growth_time
.
export var growth_time := 0.1
Then, add a reference to the Tween node, under the onready var fill
statement.
onready var tween := $Tween
We need to store the FillLine2D’s initial width to animate to that value. Add a new onready var
below the tween
variable to store that value.
onready var line_width: float = fill.width
At this point, the top of your script should look like this:
extends RayCast2D export var cast_speed := 7000.0 export var max_length := 1400 export var growth_time := 0.1 onready var fill := $FillLine2D onready var tween := $Tween onready var line_width: float = fill.width
Create two methods below cast_beam()
to animate the bar’s width. Call them appear()
and disappear()
.
We are going to use Tween.interpolate_property()
to animate our beam’s width
.
When you tween properties, you might want to stop running animations before starting a new one, so they don’t conflict. That’s our case with appear()
and disappear()
You can use Tween.is_active()
and Tween.stop_all()
to do so:
func appear() -> void: if tween.is_active(): tween.stop_all() func disappear() -> void: if tween.is_active(): tween.stop_all()
Let’s focus on the appear()
method for now. We need to:
fill.width
from 0
to line_width
for a duration of growth_time
.tween
.func appear() -> void: if tween.is_active(): tween.stop_all() tween.interpolate_property(fill, "width", 0, line_width, growth_time * 2) tween.start()
Do the same in the disappear()
, but interpolate from the current fill.width
to 0
.
func disappear() -> void: if tween.is_active(): tween.stop_all() tween.interpolate_property(fill, "width", fill.width, 0, growth_time) tween.start()
Let’s call appear()
and disappear()
inside set_is_casting()
. If is_casting
is true
, call appear, otherwise, call disappear()
:
func set_is_casting(cast: bool) -> void: is_casting = cast if is_casting: cast_to = Vector2.ZERO fill.points[1] = cast_to appear() else: disappear() set_physics_process(is_casting)
If you test the scene now, with the input code set up, you should see the beam animate.