Polished Laser visuals and added perk that added retargeting on kill

main
Artur 2026-06-02 16:36:27 +02:00
parent 89446660c0
commit 85e0ca75ff
5 changed files with 132 additions and 33 deletions

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cn7yee27ivj7b"
path="res://.godot/imported/laser.wav-ff32fed33cb3ccbcd074381583780fec.sample"
[deps]
source_file="res://assets/music&sfx/sfx/laser.wav"
dest_files=["res://.godot/imported/laser.wav-ff32fed33cb3ccbcd074381583780fec.sample"]
[params]
force/8_bit=false
force/mono=false
force/max_rate=false
force/max_rate_hz=44100
edit/trim=false
edit/normalize=false
edit/loop_mode=0
edit/loop_begin=0
edit/loop_end=-1
compress/mode=2

View File

@ -1,32 +1,48 @@
extends Node2D extends Node2D
const BEAM_DURATION := 3.0 const BEAM_DURATION := 3.0
const PRIMARY_TICK_DMG := 15 const PRIMARY_TICK_DMG := 40
const SPLASH_TICK_DMG := 3 const SPLASH_TICK_DMG := 3
const TICK_INTERVAL := 0.5 const TICK_INTERVAL := 0.5
const SWEEP_RATE := 22.0
@export var segment_size := 16.0 @export var segment_size := 16.0
@export var start_offset := 24.0
@onready var perk_effects = get_node("/root/Game/PerkEffects")
var beam_seg := preload("res://scenes/beam.tscn") var beam_seg := preload("res://scenes/beam.tscn")
var target: Node2D = null var target: Node2D = null
var all_segs: Array = [] var all_segs: Array = []
var mid_segs: Array = [] var mid_segs: Array = []
var tick_timer := 0.0 var tick_timer := 0.0
var elapsed := 0.0 var elapsed := 0.0
var done := false var done := false
var current_angle := 0.0
var sweep_to := 0.0
var sweeping := false
func _ready() -> void: func _ready() -> void:
target = get_highest_hp_enemy() target = get_highest_hp_enemy()
if target == null: if target == null:
queue_free() queue_free()
return return
spawn_beam() current_angle = global_position.direction_to(target.global_position).angle()
rebuild_segments()
func _process(delta: float) -> void: func _process(delta: float) -> void:
if done: if done:
return return
elapsed += delta elapsed += delta
tick_timer += delta tick_timer += delta
if sweeping:
current_angle = lerp_angle(current_angle, sweep_to, 1.0 - exp(-SWEEP_RATE * delta))
if abs(angle_difference(current_angle, sweep_to)) < 0.005:
current_angle = sweep_to
sweeping = false
rebuild_segments()
if tick_timer >= TICK_INTERVAL: if tick_timer >= TICK_INTERVAL:
tick_timer -= TICK_INTERVAL tick_timer -= TICK_INTERVAL
do_damage_tick() do_damage_tick()
@ -34,32 +50,65 @@ func _process(delta: float) -> void:
done = true done = true
cleanup() cleanup()
func spawn_beam() -> void: func rebuild_segments() -> void:
var origin := global_position var dir := Vector2.from_angle(current_angle)
var dest := target.global_position var beam_start := global_position + dir * start_offset
var dir := origin.direction_to(dest) var dest_dist := global_position.distance_to(target.global_position)
var dist := origin.distance_to(dest) var beam_dist := dest_dist - start_offset
var angle := dir.angle() var n_mid := int(max(0.0, beam_dist - segment_size) / segment_size)
place_seg("start", origin, angle) var positions: Array = [beam_start]
var types: Array = ["start"]
var n_mid := int(max(0.0, dist - segment_size * 2.0) / segment_size)
for i in range(n_mid): for i in range(n_mid):
var pos := origin + dir * (segment_size * (float(i) + 1.0)) positions.append(beam_start + dir * (segment_size * float(i + 1)))
mid_segs.append(place_seg("middle", pos, angle)) types.append("middle")
positions.append(beam_start + dir * (segment_size * float(n_mid + 1)))
types.append("end")
place_seg("end", dest, angle) while all_segs.size() < positions.size():
var seg = beam_seg.instantiate()
get_parent().add_child(seg)
all_segs.append(seg)
while all_segs.size() > positions.size():
var seg = all_segs.pop_back()
if is_instance_valid(seg):
seg.queue_free()
func place_seg(type: String, pos: Vector2, angle: float) -> Node: mid_segs.clear()
var seg = beam_seg.instantiate() for i in range(all_segs.size()):
seg.beam_type = type var seg = all_segs[i]
seg.global_position = pos if not is_instance_valid(seg):
seg.rotation = angle continue
get_parent().add_child(seg) seg.global_position = positions[i]
all_segs.append(seg) seg.rotation = current_angle
return seg if seg.beam_type != types[i]:
seg.beam_type = types[i]
seg.shape.disabled = types[i] != "middle"
if seg.sprite.sprite_frames.has_animation(types[i]):
seg.sprite.play(types[i])
if types[i] == "middle":
mid_segs.append(seg)
if all_segs.size() > 1 and is_instance_valid(all_segs[0]):
var ref_frame: int = all_segs[0].sprite.frame
var ref_progress: float = all_segs[0].sprite.frame_progress
for i in range(1, all_segs.size()):
var seg = all_segs[i]
if is_instance_valid(seg):
seg.sprite.frame = ref_frame
seg.sprite.frame_progress = ref_progress
func retarget(new_target: Node2D) -> void:
target = new_target
sweep_to = global_position.direction_to(new_target.global_position).angle()
sweeping = true
func do_damage_tick() -> void: func do_damage_tick() -> void:
if (not is_instance_valid(target) or target.is_dying) and perk_effects.laser_retarget_enabled:
var new_target = get_highest_hp_enemy()
if new_target != null:
retarget(new_target)
if is_instance_valid(target) and not target.is_dying: if is_instance_valid(target) and not target.is_dying:
target.take_damage(PRIMARY_TICK_DMG) target.take_damage(PRIMARY_TICK_DMG)
@ -79,9 +128,9 @@ func cleanup() -> void:
seg.queue_free() seg.queue_free()
queue_free() queue_free()
func get_highest_hp_enemy() -> Node: func get_highest_hp_enemy() -> Node2D:
var best: Node = null var best: Node2D = null
var best_hp: int = -1 var best_hp: int = -1
for enemy in get_tree().get_nodes_in_group("enemies"): for enemy in get_tree().get_nodes_in_group("enemies"):
if not is_instance_valid(enemy) or enemy.is_dying: if not is_instance_valid(enemy) or enemy.is_dying:
continue continue

View File

@ -7,6 +7,7 @@ var throwing_knife = preload("res://scenes/throwing_knive.tscn")
var cauldron var cauldron
var available_perks: Array[Perk] = [] var available_perks: Array[Perk] = []
var fireball_aoe_enabled = false var fireball_aoe_enabled = false
var laser_retarget_enabled = false
var throwing_knife_enabled = false var throwing_knife_enabled = false
var throwing_knife_cooldown: float = 2.0 var throwing_knife_cooldown: float = 2.0
var throwing_knife_count: int = 1 var throwing_knife_count: int = 1
@ -21,6 +22,7 @@ var _spellbook_angle: float = 0.0
var _spellbooks: Array = [] var _spellbooks: Array = []
var _icon_knife = preload("res://assets/weapons/knvie.png") var _icon_knife = preload("res://assets/weapons/knvie.png")
var _icon_laser: AtlasTexture
var _icon_book = preload("res://assets/books_set_2/books_pentagram.png") var _icon_book = preload("res://assets/books_set_2/books_pentagram.png")
var _icon_brew = preload("res://assets/books_set_2/books_health_potion.png") var _icon_brew = preload("res://assets/books_set_2/books_health_potion.png")
var _icon_shuriken: AtlasTexture var _icon_shuriken: AtlasTexture
@ -51,6 +53,19 @@ func _ready() -> void:
_icon_player.atlas = preload("res://assets/Swordsman_lvl1/Without_shadow/Swordsman_lvl1_Idle_without_shadow.png") _icon_player.atlas = preload("res://assets/Swordsman_lvl1/Without_shadow/Swordsman_lvl1_Idle_without_shadow.png")
_icon_player.region = Rect2(0, 0, 64, 64) _icon_player.region = Rect2(0, 0, 64, 64)
_icon_laser = AtlasTexture.new()
_icon_laser.atlas = preload("res://assets/Fire Pixel Bullet 16x16/All_Fire_Bullet_Pixel_16x16_05.png")
_icon_laser.region = Rect2(96, 16, 16, 16)
var lrt = Perk.new()
lrt.name = "Laser Lock-On"
lrt.description = "Laser retargets to the next highest health enemy on kill"
lrt.stats = _stat_toggle("Retarget")
lrt.spell = SpellLibrary.LASER
lrt.icon = _icon_laser
lrt.effect = laser_retarget
available_perks.append(lrt)
var dsh = Perk.new() var dsh = Perk.new()
dsh.name = "Double Shuriken" dsh.name = "Double Shuriken"
dsh.description = "Fire two shurikens at once" dsh.description = "Fire two shurikens at once"
@ -135,6 +150,9 @@ func _process(delta: float) -> void:
var offset = (TAU / _spellbooks.size()) * i var offset = (TAU / _spellbooks.size()) * i
_spellbooks[i].global_position = witch.global_position + Vector2(cos(_spellbook_angle + offset), sin(_spellbook_angle + offset)) * SPELLBOOK_RADIUS _spellbooks[i].global_position = witch.global_position + Vector2(cos(_spellbook_angle + offset), sin(_spellbook_angle + offset)) * SPELLBOOK_RADIUS
func laser_retarget():
laser_retarget_enabled = true
func double_shuriken(): func double_shuriken():
witch.shuriken_count = 2 witch.shuriken_count = 2

View File

@ -10,7 +10,8 @@ var fire_swirl = preload("res://scenes/fire_swirl.tscn")
var tornado = preload("res://scenes/tornado.tscn") var tornado = preload("res://scenes/tornado.tscn")
var laser = preload("res://scenes/laser.tscn") var laser = preload("res://scenes/laser.tscn")
var shuriken_count = 1 var shuriken_count = 1
var _fire_sfx = preload("res://assets/music&sfx/sfx/fire.wav") var _fire_sfx = preload("res://assets/music&sfx/sfx/fire.wav")
var _laser_sfx = preload("res://assets/music&sfx/sfx/laser.wav")
var max_hp: int = 100 var max_hp: int = 100
var current_hp: int = 100 var current_hp: int = 100
@ -106,6 +107,13 @@ func shoot_laser():
ls.global_position = global_position ls.global_position = global_position
get_parent().add_child(ls) get_parent().add_child(ls)
camera.shake(0.4, 1.2) camera.shake(0.4, 1.2)
var asp = AudioStreamPlayer.new()
asp.stream = _laser_sfx
asp.volume_db = 6
asp.bus = "SFX"
get_parent().add_child(asp)
asp.play()
asp.finished.connect(asp.queue_free)
func take_damage(amount: int) -> void: func take_damage(amount: int) -> void:
if is_invincible: if is_invincible: