4.6, new attack, enemy spawning, shaders, bunch of stuff

This commit is contained in:
Daniel Kauss Serna 2026-02-17 01:24:05 +01:00
parent e08e3ebb13
commit 19517a3176
84 changed files with 13348 additions and 91399 deletions

View file

@ -1,17 +1,7 @@
extends BaseButton
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
button_down.connect(down)
print("button")
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func down() -> void:
print("clikced!!")
SoundManager.play_sfx("click")
SoundManager.play_sfx(SoundManager.SFX_CLICK)

View file

@ -1,15 +1,5 @@
extends Node2D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"):
get_tree().quit()

View file

@ -12,17 +12,16 @@ var target_node: Node2D
func _ready() -> void:
add_to_group("enemy")
nav_agent.velocity_computed.connect(_on_velocity_computed)
target_node = get_tree().get_first_node_in_group("player")
func _physics_process(delta: float) -> void:
if not target_node: return
nav_agent.target_position = target_node.global_position
var dist = global_position.distance_to(target_node.global_position)
nav_agent.target_position = target_node.position
var dist = position.distance_to(target_node.position)
var next_path_pos = nav_agent.get_next_path_position()
var dir = global_position.direction_to(next_path_pos)
var dir = position.direction_to(next_path_pos)
if dist > approach_range:
velocity += dir * move_speed
current_charge = 0
@ -32,9 +31,9 @@ func _physics_process(delta: float) -> void:
else:
_handle_attack_charge(delta)
sprite.flip_h = target_node.global_position.x > global_position.x
sprite.flip_h = target_node.position.x > position.x
scale = Vector2.ONE * (1 + (current_charge * 0.2))
super._physics_process(delta)
func _handle_attack_charge(delta):
@ -47,6 +46,10 @@ func _handle_attack_charge(delta):
func _on_velocity_computed(safe_vel: Vector2):
velocity = safe_vel
func damage(amount: int = 1) -> void:
super.damage(amount)
SoundManager.play_sfx(SoundManager.SFX_DEATH)
func knockback():
velocity += global_position.direction_to(nav_agent.target_position).normalized() * -2000

View file

@ -7,6 +7,7 @@ signal died
@export var max_health: int = 3
@export var move_speed: float = 50.0
@export var friction: float = 0.7
@export var main_visual : Node2D
@export var death_scene : PackedScene
@onready var health: int = max_health
@ -24,6 +25,7 @@ func damage(amount: int = 1) -> void:
health_changed.emit(health)
_play_hit_flash()
EventBus.screenshake.emit(5)
if health <= 0:
die()
@ -36,15 +38,18 @@ func die():
died.emit()
func _apply_movement(_delta: float) -> void:
func _apply_movement() -> void:
velocity *= friction
move_and_slide()
func _process(delta: float) -> void:
mask_use_cd -= delta
func _physics_process(delta: float) -> void:
mask_use_cd -= delta
_apply_movement(delta)
move_and_slide()
_apply_movement()
func _play_hit_flash():
var tween = create_tween()
tween.tween_property(self, "modulate", Color.CRIMSON, 0.1)
tween.tween_property(self, "modulate", Color.WHITE, 0.3)
if main_visual:
tween.tween_property(main_visual, "modulate", Color.CRIMSON, 0.1)
tween.tween_property(main_visual, "modulate", Color.WHITE, 0.3)

View file

@ -5,9 +5,7 @@ var time = 0.0
@export var float_time = 2.0
@export var float_offset = -12.0
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
time += delta
# Calculate the new Y position using a sine wave
position.y = sin(time * float_time) * float_str + float_offset
queue_redraw()

View file

@ -21,6 +21,7 @@ class_name GrassMultiMesh extends MultiMeshInstance2D
@export var brush_radius : float = 50.0
@export var brush_density : int = 5
# TODO: save to file, enabnle filesytem warning
@export_storage var grass_data : Array[Dictionary] = []
enum GrassState {NORMAL = 0, CUT = 1}

View file

@ -8,7 +8,7 @@ func _physics_process(delta: float) -> void:
func set_from_player(val):
$Hitbox.from_player = val
func _on_hitbox_onhit() -> void:
func _on_hitbox_collided() -> void:
$Hitbox.queue_free()
$Explosion.emitting = true
emitting = false

View file

@ -1,31 +1,30 @@
class_name Hitbox extends Area2D
signal collided;
var from_player = true
signal onhit;
var hitplayer = false
#func _process(delta: float) -> void:
#EventBus.cut_grass_at.emit(global_position, 14)
var enabled = true
func _ready() -> void:
collision_mask = 0xffff
body_entered.connect(_on_body_entered)
area_entered.connect(_on_area_entered)
func _on_area_entered(area : Area2D):
if area is Hitbox:
if area.from_player != from_player:
collided.emit()
func _on_body_entered(body: Node) -> void:
print(body)
if not enabled: return
if body is Enemy and from_player:
var bname := str(body.name)
body.damage()
onhit.emit()
collided.emit()
body.knockback()
EventBus.debug_print.emit("Hit: " + bname)
if body is Player and not from_player and not hitplayer:
var bname := str(body.name)
hitplayer = true
if body is Player and not from_player:
body.damage()
onhit.emit()
EventBus.debug_print.emit("Hit: " + bname)
EventBus.debug_print.emit(str(body))
if body is StaticBody2D:
onhit.emit()
collided.emit()
if body is StaticBody2D or body is Hitbox:
collided.emit()

View file

@ -17,12 +17,6 @@ var runes : Texture2D = load("res://assets/vfx/runes.png");
var rot = 0;
var time = 0;
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
queue_redraw()
@ -105,7 +99,7 @@ func _draw():
var layer_color = main_color
layer_color.a = clamp(vis_layers - i, 0, 1)
draw_set_transform(Vector2.ZERO, layer_rot, Vector2(1, squish))
draw_set_transform(Vector2.ZERO, layer_rot, Vector2.ONE)
if layer_type < 0.2:
var small_circles = rand_r(0, 5)
var small_radius = layer_radius * 0.1
@ -117,3 +111,4 @@ func _draw():
elif layer_type <= 1:
var rune_num = round(4 + layer_counter * 32)
_draw_text_circle(layer_radius, rune_num, layer_color)
scale.y = squish

25
scripts/mask_ability.gd Normal file
View file

@ -0,0 +1,25 @@
class_name MaskAbility extends Node2D
var from_player : bool = false
var target_position : Vector2
var start_position : Vector2
func mask_ready():
set_from_player(from_player)
func _get_all_children(node) -> Array:
var nodes : Array = []
for N in node.get_children():
if N.get_child_count() > 0:
nodes.append(N)
nodes.append_array(_get_all_children(N))
else:
nodes.append(N)
return nodes
func set_from_player(val):
from_player = val
var children = _get_all_children(self)
for c in children:
if c is Hitbox:
c.from_player = from_player

View file

@ -0,0 +1 @@
uid://vu72vj17tapv

View file

@ -6,7 +6,7 @@ enum MaskType { MELEE, RANGED, SPIT }
@export var mask_name: String
@export var texture: Texture2D
@export var drop_texture: Texture2D
@export var spawn_sfx : String
@export var spawn_sfx : Resource
@export_group("Combat")
@export var attack_scene: PackedScene
@ -18,22 +18,23 @@ func activate(attacker: Node2D, target_pos: Vector2) -> void:
if not attack_scene:
return
var atk = attack_scene.instantiate()
var atk : MaskAbility = attack_scene.instantiate()
if is_parented_to_attacker:
attacker.add_child(atk)
atk.position = Vector2.ZERO
else:
attacker.get_parent().add_child(atk)
atk.global_position = attacker.global_position
atk.look_at(target_pos)
if atk.get("start_position"):
atk.start_position = attacker.global_position
atk.target_position = target_pos
if atk.has_method("set_from_player"):
atk.set_from_player(attacker.is_in_group("player"))
atk.position = attacker.position
atk.start_position = attacker.position
atk.target_position = target_pos
var from_player = attacker.is_in_group("player")
atk.from_player = from_player
if not from_player:
atk.modulate = Color("#0000ff")
atk.mask_ready()
SoundManager.play_sfx(spawn_sfx)

View file

@ -13,13 +13,19 @@ var mask_time_remaining := 15.0
var closest_mask_drop: MaskDrop
var last_mask_drop : MaskDrop
const INTERACT_DIST = 60.0
const INTERACT_DIST = 100.0
var dead = false
func _ready() -> void:
health_changed.connect(EventBus.health_changed.emit)
health_changed.emit(health)
equip_mask(current_mask_data)
func _process(delta: float) -> void:
super._process(delta)
if Input.is_action_pressed("attack"):
use_mask(get_global_mouse_position())
func _physics_process(delta: float) -> void:
if dead: return
@ -49,6 +55,9 @@ func revive():
dead = false
visible = true
func damage(amount: int = 1) -> void:
EventBus.player_dmg.emit()
super.damage(amount)
func start_dash(dir: Vector2):
dash_active = true
@ -60,21 +69,25 @@ func start_dash(dir: Vector2):
tween.tween_property(player_sprite, "scale:x", 1, 0.1)
func _input(event: InputEvent) -> void:
if event.is_action_pressed("attack") and current_mask_data:
use_mask(get_global_mouse_position())
#if event.is_action_pressed("attack") and current_mask_data:
#use_mask(get_global_mouse_position())
if event.is_action_pressed("interact") and closest_mask_drop:
equip_mask(closest_mask_drop)
collect_drop(closest_mask_drop)
func equip_mask(drop_node : MaskDrop):
func equip_mask(mask_data : MaskData):
current_mask_data = mask_data
mask_sprite.texture = mask_data.texture
EventBus.mask_changed.emit(mask_data.mask_name)
func collect_drop(drop_node : MaskDrop):
if drop_node.has_method("collect"):
drop_node.collect(global_position)
current_mask_data = drop_node.mask_type
mask_time_remaining = mask_start_time
mask_sprite.texture = current_mask_data.texture
EventBus.mask_changed.emit(current_mask_data.mask_name)
equip_mask(drop_node.mask_type)
func _handle_mask_durability(delta : float):
if get_tree().get_node_count_in_group("enemy") > 0:
@ -96,9 +109,10 @@ func _check_items():
if last_mask_drop and closest_mask_drop != last_mask_drop:
last_mask_drop.hide_popup()
last_mask_drop = closest_mask_drop
if closest_mask_drop:
if min_dist < INTERACT_DIST:
closest_mask_drop.show_popup()
else:
closest_mask_drop.hide_popup()
last_mask_drop = closest_mask_drop

View file

@ -1,14 +1,9 @@
extends Sprite2D
@export var nextScene : String
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
@export var nextScene : PackedScene
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
func _process(_delta: float) -> void:
var enemies = len(get_tree().get_nodes_in_group("enemy"))
var player = get_tree().get_first_node_in_group("player")

View file

@ -1,10 +1,11 @@
extends Node2D
@export var range : int = 50
@export var show_range : int = 50
func _process(_delta: float) -> void:
if has_node("%Player"):
if %Player.global_position.distance_to(global_position) < range:
var player = get_tree().get_first_node_in_group("player")
if player:
if player.global_position.distance_to(global_position) < show_range:
show_ins()
else:
hide_ins()

54
scripts/room_spawn.gd Normal file
View file

@ -0,0 +1,54 @@
class_name RoomSpawn extends Area2D
@export var waves_container: Node2D
var waves: Array[Node] = []
var current_wave_index: int = 0
var active_enemies: int = 0
var has_triggered: bool = false
func _ready() -> void:
body_entered.connect(_on_body_entered)
if not waves_container:
push_error("Waves container not assigned in RoomSpawn!")
return
waves = waves_container.get_children()
for wave in waves:
wave.hide()
wave.process_mode = Node.PROCESS_MODE_DISABLED
func _on_body_entered(body: Node2D) -> void:
if has_triggered:
return
if body.is_in_group("player"):
has_triggered = true
start_next_wave()
func start_next_wave() -> void:
if current_wave_index >= waves.size():
return
var current_wave = waves[current_wave_index]
current_wave.show()
current_wave.process_mode = Node.PROCESS_MODE_INHERIT
var enemies = current_wave.get_children()
active_enemies = enemies.size()
if active_enemies == 0:
current_wave_index += 1
start_next_wave()
return
for enemy in enemies:
enemy.tree_exited.connect(_on_enemy_died)
func _on_enemy_died() -> void:
active_enemies -= 1
if active_enemies <= 0:
current_wave_index += 1
start_next_wave()

View file

@ -0,0 +1 @@
uid://dr2icqss6nekh

View file

@ -1,8 +1,8 @@
extends Node2D
extends MaskAbility
func set_from_player(val):
$Anchor/Scithe/Hitbox.from_player = val
func mask_ready():
super.mask_ready()
look_at(target_position)
func _on_animation_player_animation_finished(_anim_name: StringName) -> void:
queue_free()

View file

@ -13,7 +13,8 @@ func _update_settings():
volume_slider.value = Settings.master_volume
fullscreen_check.button_pressed = Settings.fullscreen
vsync_check.button_pressed = Settings.vsync
glow_check.button_pressed = Settings.glow
func _input(event: InputEvent) -> void:
_update_settings()
if (event.is_action_pressed("ui_cancel")):
@ -51,4 +52,5 @@ func _on_continue_button_pressed() -> void:
func _on_glow_toggled(enabled: bool) -> void:
EventBus.change_glow.emit(enabled)
Settings.glow = enabled
Settings.apply_glow()

View file

@ -2,7 +2,7 @@ extends Node2D
var debug_enabled = false
func _process(delta: float) -> void:
func _process(_delta: float) -> void:
if debug_enabled:
var player = get_tree().get_first_node_in_group("player")
if player:

View file

@ -1,3 +1,4 @@
@warning_ignore_start("unused_signal")
extends Node
signal dialogue_requested(text: String)

View file

@ -10,7 +10,7 @@ func _ready():
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
func change_scene(target_path: String, is_error: bool = false):
func change_scene(target_path: PackedScene, is_error: bool = false):
rect.mouse_filter = Control.MOUSE_FILTER_STOP
if is_error:
@ -26,9 +26,9 @@ func change_scene(target_path: String, is_error: bool = false):
display_log_file(log_file)
await get_tree().create_timer(0.5).timeout
var error = get_tree().change_scene_to_file(target_path)
var error = get_tree().change_scene_to_packed(target_path)
if error != OK:
push_error("Failed to load scene: " + target_path)
push_error("Failed to load scene")
log_display.visible = false
log_display.text = ""

View file

@ -4,7 +4,7 @@ var master_volume: float = 1.0
var fullscreen: bool = false
var vsync : bool = true
var max_fps: int = 60
var glow = false
var glow = true
func apply_volume():
var bus_index := AudioServer.get_bus_index("Master")
@ -13,7 +13,7 @@ func apply_volume():
linear_to_db(master_volume)
)
func apply_bloom():
func apply_glow():
EventBus.change_glow.emit(glow)
func apply_fullscreen():

View file

@ -1,16 +1,13 @@
extends Node
const SFX_DEATH = preload("res://assets/sfx/death.mp3")
const SFX_HEART = preload("res://assets/sfx/heart.mp3")
const SFX_SWING = preload("res://assets/sfx/swing.mp3")
const SFX_CLICK = preload("res://assets/sfx/click.mp3")
const SFX_DEATH : Resource = preload("res://assets/sfx/death.mp3")
const SFX_HEART : Resource = preload("res://assets/sfx/heart.mp3")
const SFX_SWING : Resource = preload("res://assets/sfx/swing.mp3")
const SFX_CLICK : Resource = preload("res://assets/sfx/click.mp3")
const MUSIC_TITLE = preload("res://assets/music/title.wav")
const MUSIC_TITLE : Resource = preload("res://assets/music/title.wav")
# Configuration
var pool_size = 10
var sfx_dict = {}
var music_dict = {}
var sfx_pool = []
var current_pool_index = 0
@ -19,17 +16,6 @@ var current_pool_index = 0
func _ready():
process_mode = Node.PROCESS_MODE_ALWAYS
sfx_dict = {
"death": SFX_DEATH,
"heart": SFX_HEART,
"swing": SFX_SWING,
"click": SFX_CLICK
}
music_dict = {
"title": MUSIC_TITLE
}
for i in range(pool_size):
var asp = AudioStreamPlayer.new()
@ -43,17 +29,15 @@ func _ready():
music_player_2.bus = "Music"
func play_sfx(sound_name: String):
if sfx_dict.has(sound_name):
var asp : AudioStreamPlayer = sfx_pool[current_pool_index]
asp.stream = sfx_dict[sound_name]
asp.play()
current_pool_index = (current_pool_index + 1) % pool_size
func play_music(music_name: String, fade_duration: float = 1.0):
if not music_dict.has(music_name): return
var next_track = music_dict[music_name]
func play_sfx(sound_resource: Resource):
var asp : AudioStreamPlayer = sfx_pool[current_pool_index]
asp.stream = sound_resource
asp.pitch_scale = randf() + 0.5
asp.play()
current_pool_index = (current_pool_index + 1) % pool_size
func play_music(music_res: Resource, fade_duration: float = 1.0):
var next_track = music_res
var active = music_player_1 if music_player_1.playing else music_player_2
var idle = music_player_2 if music_player_1.playing else music_player_1

21
scripts/star.gd Normal file
View file

@ -0,0 +1,21 @@
class_name MaskProjectile extends MaskAbility
@export var speed := 500.
@export var spawn_sfx : AudioStream
var dir : Vector2 = Vector2.RIGHT
var rot_speed = 30
func mask_ready():
super.mask_ready()
dir = to_local(target_position).normalized()
SoundManager.play_sfx(spawn_sfx)
get_tree().create_timer(20).timeout.connect(queue_free)
create_tween().tween_property(self, "rot_speed", 3, 0.3)
func _physics_process(delta: float) -> void:
rotation += rot_speed * delta
position += dir * speed * delta
func _on_hitbox_collided() -> void:
queue_free()

1
scripts/star.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://cemrdyqfubw7h

View file

@ -1,6 +1,7 @@
extends Enemy
func _process(delta: float) -> void:
super._process(delta)
queue_redraw()
func _draw():

18
scripts/stars.gd Normal file
View file

@ -0,0 +1,18 @@
extends MaskAbility
@export var star_scene : PackedScene
@export var count = 3
func mask_ready():
var children := get_children()
for c in children:
var star : MaskProjectile = star_scene.instantiate()
star.position = c.global_position
star.from_player = from_player
star.target_position = target_position
if not from_player:
star.modulate = Color.BLACK
get_tree().current_scene.add_child(star)
star.mask_ready()
await get_tree().create_timer(0.05).timeout
get_tree().create_timer(20).timeout.connect(queue_free)

1
scripts/stars.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ct4pupgvmtpgg

View file

@ -4,7 +4,7 @@ extends Node2D
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
SoundManager.play_music("title")
SoundManager.play_music(SoundManager.MUSIC_TITLE)
# Called every frame. 'delta' is the elapsed time since the previous frame.
@ -15,9 +15,9 @@ func _process(delta: float) -> void:
func _on_start_button_pressed() -> void:
if !WorldState.done_tuto:
WorldState.done_tuto = true
SceneTransition.change_scene("res://scenes/tutorial.tscn")
SceneTransition.change_scene(load("res://scenes/tutorial.tscn"))
else:
SceneTransition.change_scene("res://scenes/stage1.tscn")
SceneTransition.change_scene(load("res://scenes/stage1.tscn"))
func _on_settings_pressed() -> void:

20
scripts/trail.gd Normal file
View file

@ -0,0 +1,20 @@
@tool
class_name Trail extends Line2D
@export var max_points = 10
var trail_points : PackedVector2Array = []
func _ready():
points = []
func _physics_process(_delta):
trail_points.append(global_position)
if trail_points.size() > max_points:
trail_points.remove_at(0)
var local_points : PackedVector2Array = []
for p in trail_points:
local_points.append(to_local(p))
points = local_points

1
scripts/trail.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bxqbkdomwqbtc

View file

@ -1,12 +1,7 @@
extends Node2D
@onready var player : Player = get_tree().get_first_node_in_group("player")
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if player:
if player.global_position.distance_to(global_position) > 600: