2025/06/21

- Type
- Learning Resource
- Format
- Video
- Version
- Godot 4.x
- Subject Tags
- Downloadable Demo
- FAQ/Troubleshooting
- Bonus
- Created
- Updated
- 2025/05/13
- 2025/06/21
This video and guide explain how to create a laser beam in 2D using Godot. The laser is a raycast node that extends and retracts, with a line drawn on top of the invisible ray to visualize it.
Here is the complete code reference for the laser shown in the video, with just the raycast and the line. This is the foundation of the laser. The bulk of the logic is in the set_is_casting()
and _physics_process()
functions. The appear()
and disappear()
functions animate the laser's thickness with a tween.
@tool
extends RayCast2D
## Speed at which the laser extends when first fired, in pixels per second.
@export var cast_speed := 7000.0
## Maximum length of the laser in pixels.
@export var max_length := 1400.0
## Distance in pixels from the origin to start drawing and firing the laser.
@export var start_distance := 40.0
## Base duration of the tween animation in seconds.
@export var growth_time := 0.1
@export var color := Color.WHITE: set = set_color
## If `true`, the laser is firing.
@export var is_casting := false: set = set_is_casting
var tween: Tween = null
@onready var line_2d: Line2D = %Line2D
@onready var line_width := line_2d.width
func _ready() -> void:
set_color(color)
set_is_casting(is_casting)
line_2d.points[0] = Vector2.RIGHT * start_distance
line_2d.points[1] = Vector2.ZERO
line_2d.visible = false
if not Engine.is_editor_hint():
set_physics_process(false)
func _physics_process(delta: float) -> void:
target_position.x = move_toward(
target_position.x,
max_length,
cast_speed * delta
)
var laser_end_position := target_position
force_raycast_update()
if is_colliding():
laser_end_position = to_local(get_collision_point())
line_2d.points[1] = laser_end_position
func set_is_casting(new_value: bool) -> void:
if is_casting == new_value:
return
is_casting = new_value
set_physics_process(is_casting)
if not line_2d:
return
if is_casting:
var laser_start := Vector2.RIGHT * start_distance
line_2d.points[0] = laser_start
line_2d.points[1] = laser_start
appear()
else:
target_position = Vector2.ZERO
disappear()
func appear() -> void:
line_2d.visible = true
if tween and tween.is_running():
tween.kill()
tween = create_tween()
tween.tween_property(line_2d, "width", line_width, growth_time * 2.0).from(0.0)
func disappear() -> void:
if tween and tween.is_running():
tween.kill()
tween = create_tween()
tween.tween_property(line_2d, "width", 0.0, growth_time).from_current()
tween.tween_callback(line_2d.hide)
func set_color(new_color: Color) -> void:
color = new_color
if line_2d == null:
return
line_2d.modulate = new_color
The node used for the laser has multiple features you can use to change its look. Most notably, you can assign a gradient to the Gradient property to add some shade or change the tint along the length of the beam. The gradient will be applied from the start to the end of the line. Line2D
If you use very bright colors, the engine's glow post-processing effect can pick up on them and give you a nice gradient within the glow.
Here are some color gradients to try:
All three particle effects use a white color by default. I use the modulate
property to make their color match the laser color. Here's the code that updates the color of all three particle systems and the laser line. It's part of the script:
@export var color := Color.WHITE: set = set_color
@onready var line_2d: Line2D = %Line2D
@onready var casting_particles: GPUParticles2D = %CastingParticles2D
@onready var collision_particles: GPUParticles2D = %CollisionParticles2D
@onready var beam_particles: GPUParticles2D = %BeamParticles2D
func _ready() -> void:
set_color(color)
func set_color(new_color: Color) -> void:
color = new_color
if line_2d == null:
return
line_2d.modulate = new_color
casting_particles.modulate = new_color
collision_particles.modulate = new_color
beam_particles.modulate = new_color
It uses:
color
to store the color valueset_color
, attached to the color
property, to apply the color to the laser and all particle systems when changing the colorPaired with the tool annotation, the code runs in the editor and allows us to preview the color change.
The laser uses 3 particle systems to make the beam look appealing. They add energy and movement to what would otherwise be just a boring, static line:
All three particle systems have this in common:
modulate
property to change their color based on the laser colorAs often with particles, it's mostly a few settings that really differentiate them. More on that below. But first, let's talk about how I use to keep the laser and the particle color in sync.
The casting particles use spread and linear velocity to fire in a cone, paired with a relatively high emission amount. Here are the most notable properties I set on their process material:
The collision particles burst out from the point where the laser hits something. They're similar to the casting particles, but have a few differences to distinguish them. Here are the most notable properties I set on them:
The beam particles emit along the entire length of the laser beam, creating the impression of energy radiating from it. They use a high emission amount, a box emission shape that resizes with the laser beam, and a lot of tangential acceleration to create a swirling effect.
Here are the most notable properties I set on their process material:
Here's the code that resizes the beam particles emission shape to match the laser beam size. It's part of the script:
@onready var line_2d: Line2D = %Line2D
@onready var beam_particles: GPUParticles2D = %BeamParticles2D
func _physics_process(delta: float) -> void:
# ...
var laser_start_position := line_2d.points[0]
beam_particles.position = laser_start_position + (laser_end_position - laser_start_position) * 0.5
beam_particles.process_material.emission_box_extents.x = laser_end_position.distance_to(laser_start_position) * 0.5
Below you can find the complete laser code reference with the added particle effects. You can also download the project files to see the laser in action or modify it.
@tool
extends RayCast2D
## Speed at which the laser extends when first fired, in pixels per seconds.
@export var cast_speed := 7000.0
## Maximum length of the laser in pixels.
@export var max_length := 1400.0
## Distance in pixels from the origin to start drawing and firing the laser.
@export var start_distance := 40.0
## Base duration of the tween animation in seconds.
@export var growth_time := 0.1
@export var color := Color.WHITE: set = set_color
## If `true`, the laser is firing.
## It plays appearing and disappearing animations when it's not animating.
## See `appear()` and `disappear()` for more information.
@export var is_casting := false: set = set_is_casting
var tween: Tween = null
@onready var line_2d: Line2D = %Line2D
@onready var casting_particles: GPUParticles2D = %CastingParticles2D
@onready var collision_particles: GPUParticles2D = %CollisionParticles2D
@onready var beam_particles: GPUParticles2D = %BeamParticles2D
@onready var line_width := line_2d.width
func _ready() -> void:
set_color(color)
set_is_casting(is_casting)
line_2d.points[0] = Vector2.RIGHT * start_distance
line_2d.points[1] = Vector2.ZERO
line_2d.visible = false
casting_particles.position = line_2d.points[0]
if not Engine.is_editor_hint():
set_physics_process(false)
func _physics_process(delta: float) -> void:
target_position.x = move_toward(
target_position.x,
max_length,
cast_speed * delta
)
var laser_end_position := target_position
force_raycast_update()
if is_colliding():
laser_end_position = to_local(get_collision_point())
collision_particles.global_rotation = get_collision_normal().angle()
collision_particles.position = laser_end_position
line_2d.points[1] = laser_end_position
var laser_start_position := line_2d.points[0]
beam_particles.position = laser_start_position + (laser_end_position - laser_start_position) * 0.5
beam_particles.process_material.emission_box_extents.x = laser_end_position.distance_to(laser_start_position) * 0.5
collision_particles.emitting = is_colliding()
func set_is_casting(new_value: bool) -> void:
if is_casting == new_value:
return
is_casting = new_value
set_physics_process(is_casting)
if beam_particles == null:
return
beam_particles.emitting = is_casting
casting_particles.emitting = is_casting
if is_casting:
var laser_start := Vector2.RIGHT * start_distance
line_2d.points[0] = laser_start
line_2d.points[1] = laser_start
casting_particles.position = laser_start
appear()
else:
target_position = Vector2.ZERO
collision_particles.emitting = false
disappear()
func appear() -> void:
line_2d.visible = true
if tween and tween.is_running():
tween.kill()
tween = create_tween()
tween.tween_property(line_2d, "width", line_width, growth_time * 2.0).from(0.0)
func disappear() -> void:
if tween and tween.is_running():
tween.kill()
tween = create_tween()
tween.tween_property(line_2d, "width", 0.0, growth_time).from_current()
tween.tween_callback(line_2d.hide)
func set_color(new_color: Color) -> void:
color = new_color
if line_2d == null:
return
line_2d.modulate = new_color
casting_particles.modulate = new_color
collision_particles.modulate = new_color
beam_particles.modulate = new_color
To make the laser glow, I'm using the engine's built-in glow environment effect. The scene has a world environment node with the glow post-processing effect turned on. This effect blankets the entire screen and makes any pixel above a certain brightness threshold glow.
By setting the laser to a very bright value, it triggers the glow effect.
When using this post processing effect, be careful that other assets in the game are not as bright or they will glow.
If your game uses the Vulkan rendering engine, you can also turn on the HDR 2D project setting to allow picking very bright colors.
Don't stop here. Step-by-step tutorials are fun but they only take you so far.
Try one of our proven study programs to become an independent Gamedev truly capable of realizing the games you’ve always wanted to make.
Get help from peers and pros on GDQuest's Discord server!
20,000 membersJoin ServerThere are multiple ways you can join our effort to create free and open source gamedev resources that are accessible to everyone!
Sponsor this library by learning gamedev with us onGDSchool
Learn MoreImprove and build on assets or suggest edits onGithub
Contributeshare this page and talk about GDQUest onRedditYoutubeTwitter…