A fun addition to our lightning effect is having it bounce between objects.
The first thing we need to do is define a jump range. Open the LightingBeam scene and add an Area2D as a child. As a child of that, add a CollisionShape2D.
Over in the inspector, we add a new CircleShape2D and set the radius. I used 128
.
This means if there’s a body within 128 pixels of the end point, we can jump to it.
In LightningBeam.gd
, we define the maximum times the lightning can bounce and cache JumpArea.
#... export (int, 0, 10) var bounces_max := 3 #... onready var jump_area := $JumpArea
We make sure the jump area’s position is always the same as the target point, as that’s where we’re jumping from.
func _physics_process(delta): #... jump_area.global_position = target_point
The shoot()
function receives the most changes. We store all static bodies from jump_area
in _secondary_bodies
, but remove _primary_body
because we don’t want the lightning to jump to the first target. In this case, it wouldn’t jump at all!
If the ray cast has a collision, we update the target point to its position.
func shoot() -> void: var _target_point = target_point var _primary_body = get_collider() var _secondary_bodies = jump_area.get_overlapping_bodies() if _primary_body: _secondary_bodies.erase(_primary_body) _target_point = _primary_body.global_position #...
We’re going to handle jumping jolts a little differently. Rather than having them start at the end of the last jolt, we’ll have them originate from the previous body’s position. I chose to do it this way as it looks like the body is taking in the lightning before expelling it to the next target.
First, we introduce the _start
variable, which will change as we instance jolts.
func shoot() -> void: #... for flash in range(flashes): var _start = global_position var jolt = lightning_jolt.instance() add_child(jolt) jolt.create(_start, target_point)
Under this code, we have the following loop.
This loop works much like how our lighting jolt is shaped: we grab a body from secondary bodies and instance a jolt to its position before updating the start point again and moving on.
func shoot() -> void: #... for flash in range(flashes): #... # reset the starting point _start = _target_point # loops for bounces_max, or number of bodies; whichever is smaller for _i in range(min(bounces_max, _secondary_bodies.size())): var _body = _secondary_bodies[_i] jolt = lightning_jolt.instance() add_child(jolt) jolt.create(_start, _body.global_position) _start = _body.global_position yield(get_tree().create_timer(flash_time), "timeout")
We have a slight issue, however. If you run a scene with multiple static bodies, you may notice the sparks are sent to the center of the secondary bodies. We want our sparks to appear at the point of collision. To solve this, we’ll add a RayCast2D to our LightningJolt scene so it can work out the collision point itself.
Add a RayCast2D to the LightningJolt scene.
We cache our RayCast2D.
extends Line2D #... onready var ray_cast := $RayCast2D
Then, we update the ray cast in the create()
function.
func create(start: Vector2, end: Vector2) -> void: ray_cast.global_position = start ray_cast.cast_to = end - start ray_cast.force_raycast_update() if ray_cast.is_colliding(): end = ray_cast.get_collision_point() #...
And there we have it: lightning that bounces between static bodies!
extends RayCast2D export (int, 1, 10) var flashes := 3 export (float, 0.0, 3.0) var flash_time := 0.1 export (int, 0, 10) var bounces_max := 3 export var lightning_jolt: PackedScene = preload("res://LightningBeam/LightningJolt.tscn") var target_point := Vector2.ZERO onready var jump_area := $JumpArea func _physics_process(delta) -> void: target_point = to_global(cast_to) if is_colliding(): target_point = get_collision_point() jump_area.global_position = target_point func shoot() -> void: var _target_point = target_point var _primary_body = get_collider() var _secondary_bodies = jump_area.get_overlapping_bodies() if _primary_body: _secondary_bodies.erase(_primary_body) _target_point = _primary_body.global_position for flash in range(flashes): var _start = global_position var jolt = lightning_jolt.instance() add_child(jolt) jolt.create(_start, target_point) _start = _target_point for _i in range(min(bounces_max, _secondary_bodies.size())): var _body = _secondary_bodies[_i] jolt = lightning_jolt.instance() add_child(jolt) jolt.create(_start, _body.global_position) _start = _body.global_position yield(get_tree().create_timer(flash_time), "timeout")
extends Line2D export (float, 0.5, 3.0) var spread_angle := PI/4.0 export (int, 1, 36) var segments := 12 onready var sparks := $Sparks onready var ray_cast := $RayCast2D func _ready() -> void: set_as_toplevel(true) func create(start: Vector2, end: Vector2) -> void: ray_cast.global_position = start ray_cast.cast_to = end - start ray_cast.force_raycast_update() if ray_cast.is_colliding(): end = ray_cast.get_collision_point() var _points := [] var _current := start var _segment_length := start.distance_to(end) / segments _points.append(start) for segment in range(segments): var _rotation := rand_range(-spread_angle / 2, spread_angle / 2) var _new := _current + (_current.direction_to(end) * _segment_length).rotated(_rotation) _points.append(_new) _current = _new _points.append(end) points = _points sparks.global_position = end