Improves lightning projectile behavior
Refactors the lightning projectile to handle collisions more effectively. It now stops upon hitting an enemy or object, triggers an explosion, and spawns child projectiles from the collision point. The lightning bolt's collision shape is dynamically updated to match the remaining distance to the target or the collision point. Also adds more test enemies to the map for testing purposes.
This commit is contained in:
parent
d0c2a7b3c8
commit
1a959fbc0c
7 changed files with 232 additions and 41 deletions
|
|
@ -1,14 +1,14 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
|
class_name LightningBolt
|
||||||
|
|
||||||
var goal_point: Vector2 = Vector2(100, 100)
|
var goal_point: Vector2 = Vector2(100, 100)
|
||||||
var min_segment_size: float = 2
|
var min_segment_size: float = 2
|
||||||
var max_segment_size: float = 10
|
var max_segment_size: float = 10
|
||||||
var points: Array = []
|
var points: Array = []
|
||||||
var emitting = true
|
var emitting = true
|
||||||
var final_goal: Vector2
|
var final_goal: Vector2
|
||||||
|
|
||||||
@export var angle_var: float = 15
|
@export var angle_var: float = 15
|
||||||
|
|
||||||
@onready var line: Line2D = $Line2D
|
@onready var line: Line2D = $Line2D
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
|
@ -18,7 +18,6 @@ func _ready():
|
||||||
$Timer.start(randf_range(0.1, 0.5))
|
$Timer.start(randf_range(0.1, 0.5))
|
||||||
|
|
||||||
func _on_timer_timeout():
|
func _on_timer_timeout():
|
||||||
Log.pr("Timer timeout")
|
|
||||||
if (points.size() > 0):
|
if (points.size() > 0):
|
||||||
points.pop_front()
|
points.pop_front()
|
||||||
line.points = points
|
line.points = points
|
||||||
|
|
@ -35,9 +34,13 @@ func update_points():
|
||||||
var curr_line_len = 0
|
var curr_line_len = 0
|
||||||
points = [Vector2()]
|
points = [Vector2()]
|
||||||
var start_point = Vector2()
|
var start_point = Vector2()
|
||||||
min_segment_size = max(Vector2().distance_to(final_goal) / 40, 1)
|
|
||||||
max_segment_size = min(Vector2().distance_to(final_goal) / 20, 10)
|
# Adjust segment size based on the distance to goal
|
||||||
while (curr_line_len < Vector2().distance_to(final_goal)):
|
var distance_to_goal = Vector2().distance_to(final_goal)
|
||||||
|
min_segment_size = max(distance_to_goal / 40, 1)
|
||||||
|
max_segment_size = min(distance_to_goal / 20, 10)
|
||||||
|
|
||||||
|
while (curr_line_len < distance_to_goal):
|
||||||
var move_vector = start_point.direction_to(final_goal) * randf_range(min_segment_size, max_segment_size)
|
var move_vector = start_point.direction_to(final_goal) * randf_range(min_segment_size, max_segment_size)
|
||||||
var new_point = start_point + move_vector
|
var new_point = start_point + move_vector
|
||||||
var new_point_rotated = start_point + move_vector.rotated(deg_to_rad(randf_range(-angle_var, angle_var)))
|
var new_point_rotated = start_point + move_vector.rotated(deg_to_rad(randf_range(-angle_var, angle_var)))
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,23 @@
|
||||||
[ext_resource type="PackedScene" uid="uid://cafaf3en63bp6" path="res://assets/projectiles/components/bolt.tscn" id="2_gc60m"]
|
[ext_resource type="PackedScene" uid="uid://cafaf3en63bp6" path="res://assets/projectiles/components/bolt.tscn" id="2_gc60m"]
|
||||||
|
|
||||||
[node name="ProjectileLightning" type="Area2D"]
|
[node name="ProjectileLightning" type="Area2D"]
|
||||||
|
collision_layer = 16
|
||||||
|
collision_mask = 12
|
||||||
script = ExtResource("1_2ex40")
|
script = ExtResource("1_2ex40")
|
||||||
|
|
||||||
[node name="Bolt" parent="." instance=ExtResource("2_gc60m")]
|
[node name="Bolt" parent="." instance=ExtResource("2_gc60m")]
|
||||||
modulate = Color(0.161899, 0.42984, 0.777414, 1)
|
modulate = Color(0.161899, 0.42984, 0.777414, 1)
|
||||||
rotation = 0.872665
|
|
||||||
|
|
||||||
[node name="Bolt2" parent="." instance=ExtResource("2_gc60m")]
|
[node name="Bolt2" parent="." instance=ExtResource("2_gc60m")]
|
||||||
modulate = Color(0.443066, 0.672326, 1, 1)
|
modulate = Color(0.443066, 0.672326, 1, 1)
|
||||||
rotation = 0.872665
|
|
||||||
|
|
||||||
[node name="Bolt3" parent="." instance=ExtResource("2_gc60m")]
|
[node name="Bolt3" parent="." instance=ExtResource("2_gc60m")]
|
||||||
modulate = Color(0.205858, 0.510414, 0.999999, 1)
|
modulate = Color(0.205858, 0.510414, 0.999999, 1)
|
||||||
rotation = 0.349066
|
|
||||||
|
|
||||||
[node name="Bolt4" parent="." instance=ExtResource("2_gc60m")]
|
[node name="Bolt4" parent="." instance=ExtResource("2_gc60m")]
|
||||||
|
modulate = Color(1, 1, 1, 0.596078)
|
||||||
|
|
||||||
[node name="Bolt5" parent="." instance=ExtResource("2_gc60m")]
|
[node name="Bolt5" parent="." instance=ExtResource("2_gc60m")]
|
||||||
|
modulate = Color(1, 1, 1, 0.603922)
|
||||||
|
|
||||||
[node name="Bolt6" parent="." instance=ExtResource("2_gc60m")]
|
[node name="Bolt6" parent="." instance=ExtResource("2_gc60m")]
|
||||||
|
|
|
||||||
12
map/Map.tscn
12
map/Map.tscn
|
|
@ -22,3 +22,15 @@ position = Vector2(20, 20)
|
||||||
|
|
||||||
[node name="TestEnemy" parent="." instance=ExtResource("4_raxr4")]
|
[node name="TestEnemy" parent="." instance=ExtResource("4_raxr4")]
|
||||||
position = Vector2(87, 72)
|
position = Vector2(87, 72)
|
||||||
|
|
||||||
|
[node name="TestEnemy2" parent="." instance=ExtResource("4_raxr4")]
|
||||||
|
position = Vector2(171, 38)
|
||||||
|
|
||||||
|
[node name="TestEnemy3" parent="." instance=ExtResource("4_raxr4")]
|
||||||
|
position = Vector2(132, 150)
|
||||||
|
|
||||||
|
[node name="TestEnemy4" parent="." instance=ExtResource("4_raxr4")]
|
||||||
|
position = Vector2(199, 107)
|
||||||
|
|
||||||
|
[node name="TestEnemy5" parent="." instance=ExtResource("4_raxr4")]
|
||||||
|
position = Vector2(272, 64)
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,12 @@ var player: CharacterBody2D
|
||||||
var animated_sprite: AnimatedSprite2D
|
var animated_sprite: AnimatedSprite2D
|
||||||
|
|
||||||
func process(_delta):
|
func process(_delta):
|
||||||
# Get mouse position in global coordinates
|
if Input.is_action_pressed("fire"):
|
||||||
var mouse_position = player.get_global_mouse_position()
|
var mouse_position = player.get_global_mouse_position()
|
||||||
|
|
||||||
# Get player position (assuming this script is attached to the player)
|
|
||||||
var player_position = player.global_position
|
var player_position = player.global_position
|
||||||
|
|
||||||
# Calculate direction vector from player to mouse
|
|
||||||
var direction = mouse_position - player_position
|
var direction = mouse_position - player_position
|
||||||
|
|
||||||
# You can normalize this vector if you want a unit vector (length of 1)
|
|
||||||
# This is useful if you only care about direction, not distance
|
|
||||||
var normalized_direction = direction.normalized()
|
var normalized_direction = direction.normalized()
|
||||||
|
|
||||||
if Input.is_action_pressed("fire"):
|
|
||||||
player.weapon.fire(normalized_direction, mouse_position)
|
player.weapon.fire(normalized_direction, mouse_position)
|
||||||
# Update animation
|
# Update animation
|
||||||
#update_animation()
|
#update_animation()
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ signal on_destroyed(projectile)
|
||||||
# Modifier-related properties
|
# Modifier-related properties
|
||||||
var pierce_count: int = 0
|
var pierce_count: int = 0
|
||||||
var has_explosive_impact: bool = true
|
var has_explosive_impact: bool = true
|
||||||
var explosion_projectile_count: int = 2
|
var explosion_projectile_count: int = 3
|
||||||
var explosion_projectile_damage_mult: float = 0.5
|
var explosion_projectile_damage_mult: float = 0.5
|
||||||
var explosion_projectile_speed: float = 300.0
|
var explosion_projectile_speed: float = 300.0
|
||||||
var explosion_spread_angle: float = 360.0 # Full circle by default
|
var explosion_spread_angle: float = 360.0 # Full circle by default
|
||||||
|
|
@ -24,11 +24,11 @@ var explosion_spread_angle: float = 360.0 # Full circle by default
|
||||||
var source_weapon: RangedWeapon # Reference to the weapon that fired this
|
var source_weapon: RangedWeapon # Reference to the weapon that fired this
|
||||||
var lifetime_timer: Timer
|
var lifetime_timer: Timer
|
||||||
# Add a variable to track the entity that triggered the explosion
|
# Add a variable to track the entity that triggered the explosion
|
||||||
var ignore_target = null
|
var ignore_target = []
|
||||||
|
|
||||||
func _on_body_entered(body):
|
func _on_body_entered(body):
|
||||||
# Check if this is a body we should ignore
|
if ignore_target.has(body):
|
||||||
if body == ignore_target:
|
Log.pr("Ignoring body: ", body.name)
|
||||||
return
|
return
|
||||||
|
|
||||||
if body.is_in_group("enemies") and is_friendly:
|
if body.is_in_group("enemies") and is_friendly:
|
||||||
|
|
@ -47,7 +47,7 @@ func _on_body_entered(body):
|
||||||
# Handle explosive impact
|
# Handle explosive impact
|
||||||
if has_explosive_impact:
|
if has_explosive_impact:
|
||||||
# Store the target that triggered the explosion
|
# Store the target that triggered the explosion
|
||||||
ignore_target = body
|
ignore_target.append(body)
|
||||||
_trigger_explosion()
|
_trigger_explosion()
|
||||||
|
|
||||||
# Destroy the projectile
|
# Destroy the projectile
|
||||||
|
|
@ -65,7 +65,9 @@ func _trigger_explosion():
|
||||||
func _spawn_explosion_projectiles():
|
func _spawn_explosion_projectiles():
|
||||||
for i in range(explosion_projectile_count):
|
for i in range(explosion_projectile_count):
|
||||||
# Create a new projectile
|
# Create a new projectile
|
||||||
|
Log.pr("Spawning explosion projectile")
|
||||||
var new_proj = duplicate()
|
var new_proj = duplicate()
|
||||||
|
Log.pr("New projectile: ", new_proj)
|
||||||
new_proj.global_position = global_position
|
new_proj.global_position = global_position
|
||||||
|
|
||||||
# Calculate new direction based on spread
|
# Calculate new direction based on spread
|
||||||
|
|
@ -87,6 +89,10 @@ func _spawn_explosion_projectiles():
|
||||||
# Add to scene tree
|
# Add to scene tree
|
||||||
get_tree().root.call_deferred("add_child", new_proj)
|
get_tree().root.call_deferred("add_child", new_proj)
|
||||||
|
|
||||||
|
func set_projectile_scale(new_scale: Vector2):
|
||||||
|
# Set the scale of the projectile
|
||||||
|
self.scale = new_scale
|
||||||
|
|
||||||
func destroy():
|
func destroy():
|
||||||
emit_signal("on_destroyed", self)
|
emit_signal("on_destroyed", self)
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
class_name ProjectileLightning
|
class_name ProjectileLightning
|
||||||
extends ProjectileBase
|
extends ProjectileBase
|
||||||
|
|
||||||
@export var line_width: float = 1
|
@export var line_width: float = 2
|
||||||
@onready var lightning: Array = get_children()
|
@onready var lightning: Array = get_children()
|
||||||
|
|
||||||
|
# Add variables to track collision state
|
||||||
|
var has_collided: bool = false
|
||||||
|
var collision_point: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
func _init() -> void:
|
func _init() -> void:
|
||||||
#super._init()
|
#super._init()
|
||||||
Log.pr("ProjectileLightning _init")
|
Log.pr("ProjectileLightning _init")
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
|
Log.pr(ignore_target)
|
||||||
|
|
||||||
lifetime_timer = Timer.new()
|
lifetime_timer = Timer.new()
|
||||||
add_child(lifetime_timer)
|
add_child(lifetime_timer)
|
||||||
lifetime_timer.one_shot = true
|
lifetime_timer.one_shot = true
|
||||||
|
|
@ -16,15 +22,186 @@ func _ready():
|
||||||
lifetime_timer.connect("timeout", _on_lifetime_timeout)
|
lifetime_timer.connect("timeout", _on_lifetime_timeout)
|
||||||
lifetime_timer.start()
|
lifetime_timer.start()
|
||||||
|
|
||||||
|
# Make sure we have a collision shape
|
||||||
|
ensure_collision_shape()
|
||||||
|
|
||||||
|
for child in lightning:
|
||||||
|
if child.has_method("set_line_width"):
|
||||||
|
child.set_line_width(line_width)
|
||||||
|
|
||||||
emit_signal("on_spawned", self)
|
emit_signal("on_spawned", self)
|
||||||
connect("body_entered", _on_body_entered)
|
connect("body_entered", _on_body_entered)
|
||||||
|
|
||||||
func _process(_delta):
|
# Add a method to ensure we have a collision shape
|
||||||
for child in lightning:
|
func ensure_collision_shape():
|
||||||
child.goal_point = target_position
|
# Check if we already have a collision shape
|
||||||
|
var has_collision_shape = false
|
||||||
|
var existing_shape = null
|
||||||
|
|
||||||
func _physics_process(delta):
|
for child in get_children():
|
||||||
position += direction * speed * delta
|
if child is CollisionShape2D:
|
||||||
|
has_collision_shape = true
|
||||||
|
existing_shape = child
|
||||||
|
break
|
||||||
|
|
||||||
|
# If no collision shape exists, create one
|
||||||
|
if not has_collision_shape:
|
||||||
|
var collision_shape = CollisionShape2D.new()
|
||||||
|
var capsule_shape = CapsuleShape2D.new()
|
||||||
|
|
||||||
|
# Set the shape properties
|
||||||
|
capsule_shape.radius = line_width / 2
|
||||||
|
collision_shape.shape = capsule_shape
|
||||||
|
|
||||||
|
add_child(collision_shape)
|
||||||
|
existing_shape = collision_shape
|
||||||
|
|
||||||
|
# Make sure the lightning area is set to detect the right objects
|
||||||
|
set_collision_mask_value(2, true) # Assuming layer 2 is for enemies
|
||||||
|
set_collision_mask_value(3, true) # Assuming layer 3 is for objects
|
||||||
|
|
||||||
|
# Update the collision shape dimensions and position
|
||||||
|
update_collision_shape(existing_shape)
|
||||||
|
|
||||||
|
# Combined method to update collision shape based on current state
|
||||||
|
func update_collision_shape(collision_shape = null):
|
||||||
|
if collision_shape == null:
|
||||||
|
# Find the collision shape if not provided
|
||||||
|
for child in get_children():
|
||||||
|
if child is CollisionShape2D:
|
||||||
|
collision_shape = child
|
||||||
|
break
|
||||||
|
|
||||||
|
if collision_shape:
|
||||||
|
var capsule = collision_shape.shape as CapsuleShape2D
|
||||||
|
if capsule:
|
||||||
|
var target = collision_point if has_collided else target_position
|
||||||
|
var distance = global_position.distance_to(target)
|
||||||
|
var dir = (target - global_position).normalized()
|
||||||
|
|
||||||
|
# Update capsule height to match exact distance
|
||||||
|
capsule.height = distance
|
||||||
|
|
||||||
|
# Fix rotation based on current direction
|
||||||
|
collision_shape.rotation = dir.angle() + PI / 2
|
||||||
|
|
||||||
|
# Fix position: Move the shape so it starts at origin
|
||||||
|
collision_shape.position = dir * (distance / 2)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
# Update target positions for lightning bolts
|
||||||
|
for child in lightning:
|
||||||
|
if child is LightningBolt:
|
||||||
|
child.goal_point = collision_point if has_collided else target_position
|
||||||
|
|
||||||
|
# Update collision shape if it exists
|
||||||
|
update_collision_shape()
|
||||||
|
|
||||||
|
# Implement the body_entered signal handler
|
||||||
|
func _on_body_entered(body):
|
||||||
|
if ignore_target.has(body):
|
||||||
|
Log.pr("Ignoring body: ", body.name)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if the colliding body is an enemy or object
|
||||||
|
if body.is_in_group("enemies") or body.is_in_group("objects"):
|
||||||
|
if not has_collided: # Only process the first collision
|
||||||
|
# Set collision state and point
|
||||||
|
has_collided = true
|
||||||
|
|
||||||
|
# Calculate the collision point
|
||||||
|
var direction_to_body = (body.global_position - global_position).normalized()
|
||||||
|
var body_radius = 10.0 # Adjust for your game
|
||||||
|
collision_point = body.global_position - (direction_to_body * body_radius)
|
||||||
|
|
||||||
|
# Debug output
|
||||||
|
Log.pr("Lightning hit: " + body.name + " at point: " + str(collision_point))
|
||||||
|
|
||||||
|
# IMPORTANT: Immediately update the collision shape to stop at collision point
|
||||||
|
update_collision_shape()
|
||||||
|
|
||||||
|
_trigger_explosion()
|
||||||
|
|
||||||
|
#super._on_body_entered(body)
|
||||||
|
|
||||||
|
|
||||||
|
if body.is_in_group("enemies") and is_friendly:
|
||||||
|
Log.pr("Hit enemy: ", body.name)
|
||||||
|
# Deal damage to enemy
|
||||||
|
if body.has_method("take_damage"):
|
||||||
|
body.take_damage(damage)
|
||||||
|
|
||||||
|
# Emit signal for modifiers to react to
|
||||||
|
emit_signal("on_hit", self, body)
|
||||||
|
|
||||||
|
# Handle piercing
|
||||||
|
if pierce_count > 0:
|
||||||
|
pierce_count -= 1
|
||||||
|
else:
|
||||||
|
# Handle explosive impact
|
||||||
|
if has_explosive_impact:
|
||||||
|
# Store the target that triggered the explosion
|
||||||
|
ignore_target.append(body)
|
||||||
|
_trigger_explosion()
|
||||||
|
|
||||||
|
func _spawn_explosion_projectiles():
|
||||||
|
for i in range(explosion_projectile_count):
|
||||||
|
# Create a new projectile
|
||||||
|
Log.pr("Spawning explosion projectile")
|
||||||
|
var new_proj = (load(scene_file_path) as PackedScene).instantiate()
|
||||||
|
Log.pr("New projectile: ", new_proj)
|
||||||
|
new_proj.global_position = collision_point
|
||||||
|
|
||||||
|
var min_distance = 50
|
||||||
|
var max_distance = 125
|
||||||
|
|
||||||
|
# Generate a random angle
|
||||||
|
var max_target_distance = 200 # Maximum distance to look for enemies
|
||||||
|
|
||||||
|
# Get all enemies in the scene
|
||||||
|
var potential_targets = get_tree().get_nodes_in_group("enemies")
|
||||||
|
var valid_targets = []
|
||||||
|
|
||||||
|
# Filter enemies to only include ones within range
|
||||||
|
for enemy in potential_targets:
|
||||||
|
var distance = enemy.global_position.distance_to(collision_point)
|
||||||
|
if distance <= max_target_distance:
|
||||||
|
valid_targets.append(enemy)
|
||||||
|
|
||||||
|
var random_point = Vector2.ZERO
|
||||||
|
|
||||||
|
# Check if we have any valid targets
|
||||||
|
if valid_targets.size() > 0:
|
||||||
|
# Pick a random enemy from valid targets
|
||||||
|
var random_enemy = valid_targets[randi() % valid_targets.size()]
|
||||||
|
random_point = random_enemy.global_position
|
||||||
|
else:
|
||||||
|
# Fallback if no enemies in range - use the original random point logic
|
||||||
|
var random_angle = randf_range(0, TAU)
|
||||||
|
var random_distance = randf_range(50, max_target_distance)
|
||||||
|
random_point = collision_point + Vector2(
|
||||||
|
cos(random_angle) * random_distance,
|
||||||
|
sin(random_angle) * random_distance
|
||||||
|
)
|
||||||
|
|
||||||
|
new_proj.target_position = random_point
|
||||||
|
|
||||||
|
new_proj.damage = damage * explosion_projectile_damage_mult
|
||||||
|
new_proj.speed = explosion_projectile_speed
|
||||||
|
|
||||||
|
# Clear explosive properties so we don't get infinite loops
|
||||||
|
new_proj.has_explosive_impact = true
|
||||||
|
new_proj.explosion_projectile_count = 1
|
||||||
|
|
||||||
|
# Pass the ignore_target to the new projectiles
|
||||||
|
new_proj.ignore_target = ignore_target
|
||||||
|
|
||||||
|
Log.pr("New projectile: ", new_proj)
|
||||||
|
|
||||||
|
# Add to scene tree
|
||||||
|
get_tree().root.call_deferred("add_child", new_proj)
|
||||||
|
|
||||||
func _on_lifetime_timeout():
|
func _on_lifetime_timeout():
|
||||||
|
Log.pr("ProjectileLightning _on_lifetime_timeout")
|
||||||
super._on_lifetime_timeout()
|
super._on_lifetime_timeout()
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ var base_stats = {
|
||||||
"fire_rate": 2.0,
|
"fire_rate": 2.0,
|
||||||
"projectile_speed": 500.0,
|
"projectile_speed": 500.0,
|
||||||
"projectile_size": 1.0,
|
"projectile_size": 1.0,
|
||||||
"projectile_lifetime": 5.0,
|
"projectile_lifetime": 1.0,
|
||||||
"projectile_quantity": 1,
|
"projectile_quantity": 1,
|
||||||
"projectile_spread": 33,
|
"projectile_spread": 33,
|
||||||
"max_pierce": 0
|
"max_pierce": 0
|
||||||
|
|
@ -84,7 +84,7 @@ func _spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2, target
|
||||||
|
|
||||||
# Set base size
|
# Set base size
|
||||||
var size = stats.get_stat("projectile_size")
|
var size = stats.get_stat("projectile_size")
|
||||||
projectile.scale = Vector2(size, size)
|
projectile.set_projectile_scale(Vector2(size, size))
|
||||||
|
|
||||||
# Allow modifiers to directly modify the projectile
|
# Allow modifiers to directly modify the projectile
|
||||||
for modifier in stats.modifiers:
|
for modifier in stats.modifiers:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue