Adds lightning projectile
Implements a lightning projectile with visual effects. The lightning is created using a series of bolt components that dynamically adjust their shape. Also refactors the projectile system to use a base class. Removes unused modifiers from player script.
This commit is contained in:
parent
ff62d67f54
commit
d0c2a7b3c8
12 changed files with 239 additions and 103 deletions
|
|
@ -1,27 +1,5 @@
|
|||
class_name Projectile extends Area2D
|
||||
|
||||
signal on_hit(projectile, target)
|
||||
signal on_spawned(projectile)
|
||||
signal on_destroyed(projectile)
|
||||
|
||||
@export var speed: float = 500.0
|
||||
@export var damage: float = 10.0
|
||||
@export var lifetime: float = 5.0
|
||||
@export var direction: Vector2 = Vector2.RIGHT
|
||||
@export var is_friendly: bool = true
|
||||
|
||||
# Modifier-related properties
|
||||
var pierce_count: int = 0
|
||||
var has_explosive_impact: bool = true
|
||||
var explosion_projectile_count: int = 2
|
||||
var explosion_projectile_damage_mult: float = 0.5
|
||||
var explosion_projectile_speed: float = 300.0
|
||||
var explosion_spread_angle: float = 360.0 # Full circle by default
|
||||
# References
|
||||
var source_weapon: RangedWeapon # Reference to the weapon that fired this
|
||||
var lifetime_timer: Timer
|
||||
# Add a variable to track the entity that triggered the explosion
|
||||
var ignore_target = null
|
||||
class_name Projectile
|
||||
extends ProjectileBase
|
||||
|
||||
func _ready():
|
||||
lifetime_timer = Timer.new()
|
||||
|
|
@ -37,74 +15,5 @@ func _ready():
|
|||
func _physics_process(delta):
|
||||
position += direction * speed * delta
|
||||
|
||||
func _on_body_entered(body):
|
||||
# Check if this is a body we should ignore
|
||||
if body == ignore_target:
|
||||
return
|
||||
|
||||
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 = body
|
||||
_trigger_explosion()
|
||||
|
||||
# Destroy the projectile
|
||||
destroy()
|
||||
|
||||
func _trigger_explosion():
|
||||
# Create the explosion VFX
|
||||
# var explosion = preload("res://scenes/explosion_effect.tscn").instantiate()
|
||||
# explosion.global_position = global_position
|
||||
# get_tree().root.add_child(explosion)
|
||||
# Spawn the additional projectiles
|
||||
if explosion_projectile_count > 0:
|
||||
_spawn_explosion_projectiles()
|
||||
|
||||
func _spawn_explosion_projectiles():
|
||||
# Calculate even angle distribution
|
||||
var angle_step = explosion_spread_angle / explosion_projectile_count
|
||||
var start_angle = - explosion_spread_angle / 2
|
||||
|
||||
for i in range(explosion_projectile_count):
|
||||
# Create a new projectile
|
||||
var new_proj = duplicate()
|
||||
new_proj.global_position = global_position
|
||||
|
||||
# Calculate new direction based on spread
|
||||
var random_angle = randf_range(0, 2 * PI)
|
||||
var new_dir = Vector2.RIGHT.rotated(random_angle)
|
||||
|
||||
# Set properties for the new projectile
|
||||
new_proj.direction = new_dir
|
||||
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
|
||||
|
||||
# Add to scene tree
|
||||
get_tree().root.add_child(new_proj)
|
||||
|
||||
func destroy():
|
||||
emit_signal("on_destroyed", self)
|
||||
queue_free()
|
||||
|
||||
func _on_lifetime_timeout():
|
||||
destroy()
|
||||
super._on_lifetime_timeout()
|
||||
|
|
|
|||
95
player/weapons/projectile_base.gd
Normal file
95
player/weapons/projectile_base.gd
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
class_name ProjectileBase
|
||||
extends Area2D
|
||||
|
||||
signal on_hit(projectile, target)
|
||||
signal on_spawned(projectile)
|
||||
signal on_destroyed(projectile)
|
||||
|
||||
@export var speed: float = 500.0
|
||||
@export var damage: float = 10.0
|
||||
@export var lifetime: float = 5.0
|
||||
@export var direction: Vector2 = Vector2.RIGHT
|
||||
@export var target_position: Vector2
|
||||
@export var is_friendly: bool = true
|
||||
|
||||
# Modifier-related properties
|
||||
var pierce_count: int = 0
|
||||
var has_explosive_impact: bool = true
|
||||
var explosion_projectile_count: int = 2
|
||||
var explosion_projectile_damage_mult: float = 0.5
|
||||
var explosion_projectile_speed: float = 300.0
|
||||
var explosion_spread_angle: float = 360.0 # Full circle by default
|
||||
|
||||
# References
|
||||
var source_weapon: RangedWeapon # Reference to the weapon that fired this
|
||||
var lifetime_timer: Timer
|
||||
# Add a variable to track the entity that triggered the explosion
|
||||
var ignore_target = null
|
||||
|
||||
func _on_body_entered(body):
|
||||
# Check if this is a body we should ignore
|
||||
if body == ignore_target:
|
||||
return
|
||||
|
||||
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 = body
|
||||
_trigger_explosion()
|
||||
|
||||
# Destroy the projectile
|
||||
destroy()
|
||||
|
||||
func _trigger_explosion():
|
||||
# Create the explosion VFX
|
||||
# var explosion = preload("res://scenes/explosion_effect.tscn").instantiate()
|
||||
# explosion.global_position = global_position
|
||||
# get_tree().root.add_child(explosion)
|
||||
# Spawn the additional projectiles
|
||||
if explosion_projectile_count > 0:
|
||||
_spawn_explosion_projectiles()
|
||||
|
||||
func _spawn_explosion_projectiles():
|
||||
for i in range(explosion_projectile_count):
|
||||
# Create a new projectile
|
||||
var new_proj = duplicate()
|
||||
new_proj.global_position = global_position
|
||||
|
||||
# Calculate new direction based on spread
|
||||
var random_angle = randf_range(0, 2 * PI)
|
||||
var new_dir = Vector2.RIGHT.rotated(random_angle)
|
||||
|
||||
# Set properties for the new projectile
|
||||
new_proj.direction = new_dir
|
||||
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
|
||||
|
||||
# Add to scene tree
|
||||
get_tree().root.call_deferred("add_child", new_proj)
|
||||
|
||||
func destroy():
|
||||
emit_signal("on_destroyed", self)
|
||||
queue_free()
|
||||
|
||||
func _on_lifetime_timeout():
|
||||
destroy()
|
||||
1
player/weapons/projectile_base.gd.uid
Normal file
1
player/weapons/projectile_base.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c4bk8ac2ok2fx
|
||||
30
player/weapons/projectile_lightning.gd
Normal file
30
player/weapons/projectile_lightning.gd
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
class_name ProjectileLightning
|
||||
extends ProjectileBase
|
||||
|
||||
@export var line_width: float = 1
|
||||
@onready var lightning: Array = get_children()
|
||||
|
||||
func _init() -> void:
|
||||
#super._init()
|
||||
Log.pr("ProjectileLightning _init")
|
||||
|
||||
func _ready():
|
||||
lifetime_timer = Timer.new()
|
||||
add_child(lifetime_timer)
|
||||
lifetime_timer.one_shot = true
|
||||
lifetime_timer.wait_time = lifetime
|
||||
lifetime_timer.connect("timeout", _on_lifetime_timeout)
|
||||
lifetime_timer.start()
|
||||
|
||||
emit_signal("on_spawned", self)
|
||||
connect("body_entered", _on_body_entered)
|
||||
|
||||
func _process(_delta):
|
||||
for child in lightning:
|
||||
child.goal_point = target_position
|
||||
|
||||
func _physics_process(delta):
|
||||
position += direction * speed * delta
|
||||
|
||||
func _on_lifetime_timeout():
|
||||
super._on_lifetime_timeout()
|
||||
1
player/weapons/projectile_lightning.gd.uid
Normal file
1
player/weapons/projectile_lightning.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cb7fwabidqyj8
|
||||
|
|
@ -31,7 +31,7 @@ func _init() -> void:
|
|||
add_child(fire_timer)
|
||||
fire_timer.one_shot = true
|
||||
|
||||
projectile_scene = preload("res://assets/projectiles/basic_projectile.tscn")
|
||||
projectile_scene = preload("res://assets/projectiles/projectile_lightning.tscn")
|
||||
|
||||
func _ready():
|
||||
stats.connect("stats_updated", _on_stats_updated)
|
||||
|
|
@ -40,16 +40,16 @@ func _ready():
|
|||
# Initial update
|
||||
_on_stats_updated()
|
||||
|
||||
func fire(direction: Vector2):
|
||||
func fire(direction: Vector2, target_position: Vector2):
|
||||
if !can_fire:
|
||||
return
|
||||
|
||||
_spawn_projectile(global_position, direction)
|
||||
_spawn_projectile(global_position, direction, target_position)
|
||||
|
||||
can_fire = false
|
||||
fire_timer.start(1.0 / stats.get_stat("fire_rate"))
|
||||
|
||||
func _spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2):
|
||||
func _spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2, target_position: Vector2):
|
||||
# Get projectile quantity and spread from stats
|
||||
var quantity = stats.get_stat("projectile_quantity")
|
||||
var spread_angle = stats.get_stat("projectile_spread")
|
||||
|
|
@ -66,6 +66,7 @@ func _spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2):
|
|||
for i in range(quantity):
|
||||
var projectile = projectile_scene.instantiate()
|
||||
projectile.global_position = spawn_position
|
||||
projectile.target_position = target_position
|
||||
|
||||
# Calculate the direction with spread
|
||||
var direction = spawn_direction
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue