extends Node2D
# Declare a variable 'hitbox_component' that references the HitBoxComponent node.
# The @onready keyword ensures 'hitbox_component' is only assigned once the node is ready.
@onready var hitbox_component: HitBoxComponent = $HitBoxComponentitbox_component: HitBoxComponent = $HitBoxComponent
extends Node
# Exported variable for the SwordAbility scene.
@export var sword_ability: PackedScene
# Constants
const MAX_RANGE = 150
# Variables
var base_damage = 5
var additional_damage_percent = 1
var base_wait_time
func _ready():
# Initialize base_wait_time with the initial wait time of the Timer node.
base_wait_time = $Timer.wait_time
# Connect the timeout signal of the Timer node to the on_timer_timeout method.
$Timer.timeout.connect(on_timer_timeout)
# Connect to the GameEvent signal for ability upgrades being added.
GameEvent.ability_upgrade_added.connect(on_ability_upgrade_added)
func on_timer_timeout():
# Get the player node.
var player = get_tree().get_first_node_in_group("player") as Node2D
# If player is not found, return early.
if player == null:
return
# Get all nodes in the "enemy" group within MAX_RANGE.
var enemies = get_tree().get_nodes_in_group("enemy")
enemies = enemies.filter(func(enemy: Node2D):
# Filter enemies within the maximum range using squared distance.
return enemy.global_position.distance_squared_to(player.global_position) < pow(MAX_RANGE, 2)
)
# If no enemies are within range, return early.
if enemies.size() == 0:
return
# Sort enemies by distance to the player.
enemies.sort_custom(func(a: Node2D, b: Node2D):
var a_distance = a.global_position.distance_squared_to(player.global_position)
var b_distance = b.global_position.distance_squared_to(player.global_position)
return a_distance < b_distance
)
# Instantiate a SwordAbility instance from sword_ability scene.
var sword_instance = sword_ability.instantiate() as SwordAbility
# Get the foreground layer node and add the sword instance as a child.
var foreground_layer = get_tree().get_first_node_in_group("foreground_layer")
foreground_layer.add_child(sword_instance)
# Set the damage of the sword instance's hitbox component.
sword_instance.hitbox_component.damage = base_damage * additional_damage_percent
# Position the sword instance near the closest enemy.
sword_instance.global_position = enemies[0].global_position
sword_instance.global_position += Vector2.RIGHT.rotated(randf_range(0, TAU)) * 4
# Rotate the sword instance towards the closest enemy.
var enemy_direction = enemies[0].global_position - sword_instance.global_position
sword_instance.rotation = enemy_direction.angle()
func on_ability_upgrade_added(upgrade: AbilityUpgrade, current_upgrades: Dictionary):
# Handle ability upgrades added via GameEvent signal.
if upgrade.id == "sword_rate":
# Adjust the Timer's wait time based on the "sword_rate" upgrade.
var percent_reduction = current_upgrades["sword_rate"]["quantity"] * .1
$Timer.wait_time = base_wait_time + (1 - percent_reduction)
$Timer.start()
elif upgrade.id == "sword_damage":
# Adjust additional damage percent based on the "sword_damage" upgrade.
additional_damage_percent = 1 + (current_upgrades["sword_damage"]["quantity"] * .15)
extends Node2D
# Constants
const MAX_RADIUS = 100
# Declare a variable 'hitbox_component' that references the HitBoxComponent node.
# The @onready keyword ensures 'hitbox_component' is only assigned once the node is ready.
@onready var hitbox_component = $HitBoxComponent
# Variable to store the base rotation direction.
var base_rotation = Vector2.RIGHT
func _ready():
# Initialize base_rotation with a random rotation direction.
base_rotation = Vector2.RIGHT.rotated(randf_range(0, TAU))
# Create and configure a tween for animation.
var tween = create_tween()
tween.tween_method(tween_method, 0.0, 2.0, 3) # Start tweening from 0.0 to 2.0 seconds over 3 seconds.
tween.tween_callback(queue_free) # Call queue_free() after tween completes.
func tween_method(rotations: float):
# Calculate the current rotation based on tween progress.
var percent = rotations / 2
var current_radius = percent * MAX_RADIUS
var current_direction = Vector2.RIGHT.rotated(rotations * TAU)
# Get the player node.
var player = get_tree().get_first_node_in_group("player")
# If player is not found, return early.
if player == null:
return
# Set the global position based on player's position and calculated direction.
global_position = player.global_position + (current_direction * current_radius)
extends Node
# Exported variable for the AxeAbility scene.
@export var axe_ability_scene: PackedScene
# Variables to store base damage and additional damage percent.
var base_damage = 10
var additional_damage_percent = 1
func _ready():
# Connect the timeout signal of the Timer node to the on_timer_timeout method.
$Timer.timeout.connect(on_timer_timeout)
# Connect to the GameEvent signal for ability upgrades being added.
GameEvent.ability_upgrade_added.connect(on_ability_upgrade_added)
func on_timer_timeout():
# Get the player node.
var player = get_tree().get_first_node_in_group("player") as Node2D
# If player is not found, return early.
if player == null:
return
# Get the foreground layer node.
var foreground = get_tree().get_first_node_in_group("foreground_layer") as Node2D
# If foreground layer is not found, return early.
if foreground == null:
return
# Instantiate an AxeAbility instance from axe_ability_scene.
var axe_instance = axe_ability_scene.instance() as Node2D
# Add the axe instance as a child of the foreground layer.
foreground.add_child(axe_instance)
# Set the global position of the axe instance to the player's position.
axe_instance.global_position = player.global_position
# Set the damage of the axe instance's hitbox component.
axe_instance.hitbox_component.damage = base_damage * additional_damage_percent
func on_ability_upgrade_added(upgrade: AbilityUpgrade, current_upgrades: Dictionary):
# Handle ability upgrades added via GameEvent signal.
if upgrade.id == "axe_damage":
# Adjust additional damage percent based on the "axe_damage" upgrade.
additional_damage_percent = 1 + (current_upgrades["axe_damage"]["quantity"] * 0.15)
extends Node2d
# Declare a variable 'hit_box_component' that references the HitBoxComponent node.
# The @onready keyword ensures 'hit_box_component' is only assigned once the node is ready.
@onready var hit_box_component = $HitBoxComponent
extends Node
# Constants
const BASE_RANGE = 100
const BASE_DAMAGE = 15
# Exported variable for the EMP ability scene.
@export var EMP_ability_scene: PackedScene
# Variable to store the current EMP count.
var EMP_count = 0
func _ready():
# Connect the timeout signal of the Timer node to the on_timer_timeout method.
$Timer.timeout.connect(on_timer_timeout)
# Connect to the GameEvent signal for ability upgrades being added.
GameEvent.ability_upgrade_added.connect(on_ability_upgrade_added)
func on_timer_timeout():
# Get the player node.
var player = get_tree().get_first_node_in_group("player") as Node2D
# If player is not found, return early.
if player == null:
return
# Define the direction for EMP spawns.
var direction = Vector2.RIGHT.rotated(randf_range(0, TAU))
# Calculate additional rotation degrees for multiple EMP spawns.
var additional_rotation_degrees = 360.0 / (EMP_count + 1)
# Calculate EMP distance within the base range.
for i in range(EMP_count + 1):
var adjusted_direction = direction.rotated(deg2rad(i * additional_rotation_degrees))
var EMP_distance = randf_range(0, BASE_RANGE)
var spawn_position = player.global_position + (adjusted_direction * EMP_distance)
# Perform raycast to adjust spawn position based on collisions.
var query_parameters = PhysicsRayQueryParameters2D.new(player.global_position, spawn_position, 1)
var result = get_tree().root.world_2d.direct_space_state.intersect_ray(query_parameters)
if !result.empty():
spawn_position = result.position
# Instantiate EMP ability and set its properties.
var EMP_ability = EMP_ability_scene.instance()
get_tree().get_first_node_in_group("foreground_layer").add_child(EMP_ability)
EMP_ability.global_position = spawn_position
EMP_ability.hit_box_component.damage = BASE_DAMAGE
func on_ability_upgrade_added(upgrade: AbilityUpgrade, current_upgrades: Dictionary):
# Handle ability upgrades added via GameEvent signal.
if upgrade.id == "EMP_count":
EMP_count = current_upgrades["EMP_count"]["quantity"]
extends Node
# Define the game_events class that extends Node.
class_name GameEvents
# Define signals for game events.
signal ability_upgrade_added(upgrade: AbilityUpgrade, current_upgrades: Dictionary)
signal exp_vial_collected(number: float)
signal player_damaged
# Emit signal when an experience vial is collected.
func emit_exp_vial_collected(number: float):
exp_vial_collected.emit(number)
# Emit signal when an ability upgrade is added.
func emit_ability_upgrade_added(upgrade: AbilityUpgrade, current_upgrades: Dictionary):
ability_upgrade_added.emit(upgrade, current_upgrades)
# Emit signal when player is damaged.
func emit_player_damaged():
player_damaged.emit()
extends Node
# Define the path where the save file will be stored
const SAVE_FILE_PATH = "user://game.save"
# Initialize a dictionary to hold save data with default values
var save_data: Dictionary = {
"meta_upgrade_currency": 0, # The amount of currency collected for meta upgrades
"meta_upgrades": {} # A dictionary that stores data about collected upgrades
}
# Called when the node is added to the scene for the first time
func _ready():
# Connects the 'exp_vial_collected' signal to the 'on_exp_collected' function
GameEvent.exp_vial_collected.connect(on_exp_collected)
# Load the save file data if it exists
load_save_file()
# Loads the save file data if the file exists
func load_save_file():
# Check if the save file exists
if not FileAccess.file_exists(SAVE_FILE_PATH):
return # If it doesn't exist, return without doing anything
# Open the file in read mode
var file = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
# Load the saved data into the 'save_data' dictionary
save_data = file.get_var()
# Saves the current state of 'save_data' to a file
func save():
# Open the file in write mode (this will overwrite existing data)
var file = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
# Store the current 'save_data' dictionary in the file
file.store_var(save_data)
# Adds a meta upgrade to the 'save_data' and increases its quantity
func add_meta_upgrade(upgrade: MetaUpgrade):
# Check if the upgrade ID does not already exist in 'meta_upgrades'
if !save_data["meta_upgrades"].has(upgrade.id):
# If it doesn't exist, initialize its data with a quantity of 0
save_data["meta_upgrades"][upgrade.id] = {
"quantity": 0
}
# Increase the quantity of the specified upgrade by 1
save_data["meta_upgrades"][upgrade.id]["quantity"] += 1
# Save the updated 'save_data' to the file
save()
# Returns the number of collected upgrades for a given upgrade ID
func get_upgrade_count(upgrade_id: String):
# Check if the specified upgrade ID exists in 'meta_upgrades'
if save_data["meta_upgrades"].has(upgrade_id):
# Return the quantity of the specified upgrade
return save_data["meta_upgrades"][upgrade_id]["quantity"]
# If the upgrade ID does not exist, return 0
return 0
# Called when experience points (EXP) are collected
func on_exp_collected(number: float):
# Add the collected amount to the 'meta_upgrade_currency'
save_data["meta_upgrade_currency"] += number
# Save the updated currency value to the file
save()
extends AudioStreamPlayer
# Exported array variable to hold multiple AudioStream objects.
@export var streams: Array[AudioStream]
func _ready():
# Connect the finished signal to the on_finished method to handle when the current stream finishes playing.
finished.connect(on_finished)
# Connect the timeout signal of the Timer node to the on_timer_timeout method to manage music transitions.
$Timer.timeout.connect(on_timer_timeout)
# This function is triggered when the audio stream finishes playing.
func on_finished():
# Start the timer after the stream finishes playing.
$Timer.start()
# This function handles the timeout event, switching to a random music stream from the array.
func on_timer_timeout():
# Return early if the 'streams' array is empty or null.
if streams == null or streams.size() == 0:
return
# Pick a random stream from the 'streams' array.
stream = streams.pick_random()
# Play the selected stream.
play()
extends CanvasLayer
# Signal to notify when the screen transition is halfway complete.
signal transistioned_halfway
# Variable to control whether the transition halfway event is emitted or skipped.
var skip_emit = false
# Function to handle the basic screen transition animation.
func transistion():
# Play the default transition animation using the AnimationPlayer.
$AnimationPlayer.play("default")
# Wait for the halfway point of the transition to be reached.
await transistioned_halfway
# Play the animation in reverse (to hide the transition effect).
$AnimationPlayer.play_backwards("default")
# Function to transition to a new scene with a screen animation.
func transistion_to_scene(scene_path: String):
# Call the screen transition animation (this plays the transition animation).
ScreenTransition.transistion()
# Wait for the transition animation to reach the halfway point.
await transistioned_halfway
# Change the current scene to the specified scene.
get_tree().change_scene_to_file(scene_path)
# Function to emit the transistioned_halfway signal.
# This signal triggers when the transition animation is halfway through.
func emit_transistioned_halfway():
# If skip_emit is true, prevent emitting the signal and reset skip_emit to false.
if skip_emit:
skip_emit = false
return
# Emit the transistioned_halfway signal to notify other parts of the game.
transistioned_halfway.emit()
extends Node2D
# Exported variables to reference health component and sprite for visual effects.
@export var health_component: Node
@export var sprite: Sprite2D
# This function is called when the node is ready (on initialization).
func _ready():
# Set the texture of the GPU particles to match the sprite's texture.
$GPUParticles2D.texture = sprite.texture
# Connect the 'died' signal from the health component to the 'on_died' function.
health_component.died.connect(on_died)
# This function is triggered when the entity dies (via the 'died' signal).
func on_died():
# Check if the owner is valid and is a Node2D.
if owner == null or not owner is Node2D:
return
# Get the position of the entity's owner.
var spawn_position = owner.global_position
# Get the "entities_layer" where the entity will be moved.
var entities = get_tree().get_first_node_in_group("entities_layer")
# Remove the entity from its parent (i.e., the current parent node).
get_parent().remove_child(self)
# Add the entity to the "entities_layer" group.
entities.add_child(self)
# Set the entity's position to its spawn position.
global_position = spawn_position
# Play the default animation to signify the death process.
$AnimationPlayer.play("Default")
# Play a random audio sound associated with the death process.
$HitRandomAudioPlayerComponent.play_random()
extends Node
# Define the health component class.
class_name HealthComponent
# Signals to notify when health changes, decreases, or when the entity dies.
signal died
signal health_changed
signal health_decreased
# Exported variable to define the maximum health value.
@export var max_health: float = 10
# Variable to store the current health of the entity.
var current_health
# This function is called when the node is ready (on initialization).
func _ready():
# Set the current health to the maximum health at the start.
current_health = max_health
# Function to apply damage to the entity.
func damage(damage_amount: float):
# Decrease the current health by the damage amount, ensuring it stays within the valid range.
current_health = clamp(current_health - damage_amount, 0, max_health)
# Emit the health_changed signal to notify listeners.
health_changed.emit()
# If the damage is positive (meaning the entity took damage), emit health_decreased signal.
if damage_amount > 0:
health_decreased.emit()
# Call the check_death function to determine if the entity has died, but defer it to avoid immediate execution.
Callable(check_death).call_deferred()
# Function to heal the entity by a certain amount.
func heal(heal_amount):
# Heal the entity by applying negative damage.
damage(-1)
# Function to return the entity's health as a percentage of maximum health.
func get_health_percent():
# If max_health is 0, return 0 to avoid division by zero.
if max_health <= 0:
return 0
# Return the current health as a fraction of max_health, clamped to a value between 0 and 1.
return min(current_health / max_health, 1)
# Function to check if the entity has died (i.e., health reaches 0).
func check_death():
# If the current health is 0, emit the died signal and free the entity.
if current_health == 0:
died.emit()
owner.queue_free()
extends Area2D
# Define the hit_box_component class that extends Area2D.
class_name HitBoxComponent
# Variable to store the damage dealt by this hitbox.
var damage = 0
extends Node
# Define the hurt_box_component class that extends Area2D.
class_name HurtBoxComponent
# Signal to notify when the entity is hit by a hitbox.
signal hit
# Exported variable to reference the health component.
@export var health_component: Node
# Preload the scene for displaying floating damage text.
var floating_text_scene = preload("res://scenes/UI/floating_text.tscn")
# This function is called when the node is ready (on initialization).
func _ready():
# Connect the 'area_entered' signal to the 'on_area_entered' function.
area_entered.connect(on_area_entered)
# This function is triggered when the hurt box collides with another area.
func on_area_entered(other_area: Area2D):
# Check if the other area is a HitBoxComponent, if not return early.
if not other_area is HitBoxComponent:
return
# If the health_component is not defined, return early.
if health_component == null:
return
# Cast the other area to a HitBoxComponent to access its properties.
var hitbox_component = other_area as HitBoxComponent
# Apply damage to the health component based on the hitbox's damage.
health_component.damage(hitbox_component.damage)
# Instantiate the floating damage text and add it to the foreground layer.
var floating_text = floating_text_scene.instantiate() as Node2D
get_tree().get_first_node_in_group("foreground_layer").add_child(floating_text)
# Position the floating text above the hurt box.
floating_text.global_position = global_position + (Vector2.UP * 8)
# Format the floating text based on the damage value (integer or float).
var format_string = "%0.1f"
if round(hitbox_component.damage) == hitbox_component.damage:
format_string = "%0.0f"
# Start the floating text animation with the damage value.
floating_text.start(format_string % hitbox_component.damage)
# Emit the hit signal to notify other components that the hurt box was hit.
hit.emit()
extends AudioStreamPlayer
# Exported variables for the list of audio streams, pitch randomization, and pitch range.
@export var streams: Array[AudioStream]
@export var randomize_pitch = true
@export var min_pitch = 0.9
@export var max_pich = 1.1
# Function to play a random audio stream.
func play_random():
# If there are no audio streams, return early.
if streams == null or streams.size() == 0:
return
# If pitch randomization is enabled, set the pitch scale to a random value between min_pitch and max_pitch.
if randomize_pitch:
pitch_scale = randf_range(min_pitch, max_pich)
# Otherwise, set the pitch scale to 1 (default).
else:
pitch_scale = 1
# Pick a random audio stream from the list of streams and set it as the current stream.
stream = streams.pick_random()
# Play the selected audio stream.
play()
extends Node
# Exported variables for max speed and acceleration values.
@export var max_speed: int = 40
@export var acceleration: float = 5
# Variable to store the current velocity of the entity.
var velocity = Vector2.ZERO
# Function to accelerate the entity towards the player.
func accelerate_to_player():
# Cast the owner to Node2D to ensure it is the correct type.
var owner_node2d = owner as Node2D
if owner_node2d == null:
return
# Get the player node from the scene tree.
var player = get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return
# Calculate the direction vector towards the player, normalized to a unit vector.
var direction = (player.global_position - owner_node2d.global_position).normalized()
# Call the function to accelerate the entity in the calculated direction.
accelerate_in_direction(direction)
# Function to accelerate the entity in a given direction.
func accelerate_in_direction(direction: Vector2):
# Calculate the desired velocity based on the direction and max speed.
var desired_velocity = direction * max_speed
# Smoothly lerp the current velocity towards the desired velocity, using an exponential decay.
velocity = velocity.lerp(desired_velocity, 1 - exp(-acceleration * get_process_delta_time()))
# Function to decelerate the entity (stop movement).
func decelerate():
# Call accelerate_in_direction with a zero vector to gradually stop the entity's movement.
accelerate_in_direction(Vector2.ZERO)
# Function to move the entity based on the calculated velocity.
func move(character_body: CharacterBody2D):
# Set the character's velocity to the calculated velocity.
character_body.velocity = velocity
# Move the character body using the move_and_slide method (to handle collisions).
character_body.move_and_slide()
# Update the internal velocity to match the character's current velocity.
velocity = character_body.velocity
extends Node
# Exported variables to control drop chance, reference to health component, and vial scene.
@export_range(0,1) var drop_percent: float = 0.5 # The base chance for the vial to drop.
@export var health_component: Node # Reference to the health component of the entity.
@export var vial_scene : PackedScene # The scene for the vial to instantiate.
# This function is called when the node is ready (on initialization).
func _ready():
# Connect the 'died' signal of the health component to the 'on_died' function.
(health_component as HealthComponent).died.connect(on_died)
# This function is triggered when the entity dies.
func on_died():
# Start by using the base drop percent.
var adjusted_drop_percent = drop_percent
# Check if the player has an "exp_gain" upgrade and adjust the drop percent if so.
var exp_gain_upgrade_count = MetaProgression.get_upgrade_count("exp_gain")
if exp_gain_upgrade_count > 0:
adjusted_drop_percent += .1 # Increase the drop chance if the upgrade is present.
# If a random value is greater than the adjusted drop chance, return early (no drop).
if randf() > adjusted_drop_percent:
return
# If no vial scene is defined, return early (nothing to drop).
if vial_scene == null:
return
# Ensure the owner of the component is a Node2D (for positioning).
if not owner is Node2D:
return
# Get the spawn position from the owner's global position.
var spawn_position = (owner as Node2D).global_position
# Instantiate the vial from the vial_scene.
var vial_instance = vial_scene.instantiate() as Node2D
# Get the entities_layer to add the vial as a child node.
var entities_layer = get_tree().get_first_node_in_group("entities_layer")
entities_layer.add_child(vial_instance)
# Set the vial's position to the spawn position.
vial_instance.global_position = spawn_positionlobal_position = spawn_position
extends Node
# Exported variables to reference the health component, sprite, and hit flash material.
@export var health_component: Node
@export var sprite: Sprite2D
@export var hit_flash_material: ShaderMaterial
# Variable to store the Tween instance used for animating the hit flash.
var hit_flash_tween: Tween
# This function is called when the node is ready (on initialization).
func _ready():
# Connect the 'health_decreased' signal from the health component to the 'on_health_decreased' function.
health_component.health_decreased.connect(on_health_decreased)
# Set the material of the sprite to the specified hit_flash_material.
sprite.material = hit_flash_material
# This function is triggered when health decreases.
func on_health_decreased():
# If a valid tween exists, stop it before starting a new one.
if hit_flash_tween != null and hit_flash_tween.is_valid():
hit_flash_tween.kill()
# Set the shader parameter "lerp_percent" to 1 to initiate the hit flash effect.
(sprite.material as ShaderMaterial).set_shader_parameter("lerp_percent", 1.0)
# Create a new tween for animating the hit flash effect.
hit_flash_tween = create_tween()
# Animate the "lerp_percent" shader parameter to 0 over 0.2 seconds, reducing the flash intensity.
hit_flash_tween.tween_property(sprite.material, "shader_parameter/lerp_percent", 0.0, .2)
extends CharacterBody2D
# Accessing the VelocityComponent node for controlling movement.
@onready var velocity_componenet = $VelocityComponent
# Accessing the Visuals node, which might be used for animations or sprites.
@onready var visuals = $Visuals
# Variable to track if the enemy is moving.
var is_moving = false
# Called when the node is added to the scene.
func _ready():
# Connects the `hit` signal from the HurtBoxComponent to the `on_hit` function.
$HurtBoxComponent.hit.connect(on_hit)
# Called every frame. 'delta' is the time elapsed since the previous frame.
func _process(delta):
# If the enemy is set to move, accelerate towards the player.
if is_moving:
velocity_componenet.accelerate_to_player()
# If not moving, decelerate to stop gradually.
else:
velocity_componenet.decelerate()
# Move the enemy based on the current velocity.
velocity_componenet.move(self)
# Get the direction of movement. Positive is right, negative is left.
var move_sign = sign(velocity.x)
# If the enemy is moving, adjust the scale to flip the sprite direction.
if move_sign != 0:
visuals.scale = Vector2(move_sign, 1)
# Sets whether the enemy should be moving or not.
func set_is_moving(moving: bool):
is_moving = moving
# Called when the enemy gets hit.
func on_hit():
# Play a random hit sound using the HitRandomAudioPlayerComponent.
$HitRandomAudioPlayerComponent.play_random()
extends CharacterBody2D
# Accessing the VelocityComponent node for controlling movement.
@onready var velocity_componenet = $VelocityComponent
# Accessing the Visuals node, likely used for animations or sprites.
@onready var visuals = $Visuals
# Variable to track if the enemy is moving.
var is_moving = false
# Called when the node is added to the scene.
func _ready():
# Connects the `hit` signal from the HurtBoxComponent to the `on_hit` function.
$HurtBoxComponent.hit.connect(on_hit)
# Called every frame. 'delta' is the time elapsed since the previous frame.
func _process(delta):
# The drone always accelerates towards the player, making it aggressive.
velocity_componenet.accelerate_to_player()
# Move the drone based on the current velocity.
velocity_componenet.move(self)
# Get the direction of movement. Positive is right, negative is left.
var move_sign = sign(velocity.x)
# If the drone is moving, adjust the scale to flip the sprite direction.
if move_sign != 0:
visuals.scale = Vector2(move_sign, 1)
# Called when the drone gets hit.
func on_hit():
# Play a random hit sound using the HitRandomAudioPlayerComponent.
$HitRandomAudioPlayerComponent.play_random()
extends CharacterBody2D
# Constant defining the maximum speed the grunt can reach.
const MAX_SPEED = 40
# Accessing the Visuals node, likely used for animations or sprites.
@onready var visuals = $Visuals
# Accessing the VelocityComponent node for controlling movement.
@onready var velocity_component = $VelocityComponent
# Called when the node is added to the scene.
func _ready():
# Connects the `hit` signal from the HurtBoxComponent to the `on_hit` function.
$HurtBoxComponent.hit.connect(on_hit)
# Called every frame. 'delta' is the time elapsed since the previous frame.
func _process(delta):
# The grunt accelerates towards the player, adjusting its speed.
velocity_component.accelerate_to_player()
# Move the grunt based on the current velocity.
velocity_component.move(self)
# Get the direction of movement. Positive is right, negative is left.
var move_sign = sign(velocity.x)
# If the grunt is moving, adjust the scale to flip the sprite direction,
# facing the opposite direction of movement (hence the negative sign).
if move_sign != 0:
visuals.scale = Vector2(-move_sign, 1)
# Called when the grunt gets hit.
func on_hit():
# Play a random hit sound using the HitRandomAudioPlayerComponent.
$HitRandomAudioPlayerComponent.play_random()
extends CharacterBody2D
# Reference to the arena time manager for handling difficulty changes.
@export var arena_time_manager: Node
# Accessing various components of the player for functionality.
@onready var damage_interval_timer = $DamageIntervalTimer
@onready var health_component = $HealthComponent
@onready var health_bar = $HealthBar
@onready var abilities = $Abilities
@onready var animation_player = $AnimationPlayer
@onready var visuals = $Visuals
@onready var velocity_component = $VelocityComponent
# Variable to keep track of the number of bodies colliding with the player.
var number_colliding_bodies = 0
# Stores the base speed of the player.
var base_speed = 0
# Called when the player is added to the scene.
func _ready():
# Connects the arena's difficulty increase signal to the respective handler.
arena_time_manager.arena_difficulty_increased.connect(on_arena_difficulty_increased)
# Store the base speed from the velocity component.
base_speed = velocity_component.max_speed
# Connects collision signals to respective handlers.
$CollisionArea2D.body_entered.connect(on_body_entered)
$CollisionArea2D.body_exited.connect(on_body_exited)
# Connect the damage timer's timeout signal to the handler.
damage_interval_timer.timeout.connect(on_damage_interval_timer_timeout)
# Connect health change signals to update display and handle damage events.
health_component.health_decreased.connect(on_health_decreased)
health_component.health_changed.connect(on_health_changed)
# Listen for ability upgrades.
GameEvent.ability_upgrade_added.connect(on_ability_upgrade_added)
# Update the health bar display to reflect current health.
update_health_display()
# Called every frame. 'delta' is the time elapsed since the previous frame.
func _process(delta):
# Get the player's movement vector based on input.
var movement_vector = get_movement_vector()
# Normalize the movement vector to ensure consistent speed in all directions.
var direction = movement_vector.normalized()
# Accelerate the player in the input direction.
velocity_component.accelerate_in_direction(direction)
# Move the player based on the current velocity.
velocity_component.move(self)
# If the player is moving, play the walking animation.
if movement_vector.x != 0 or movement_vector.y != 0:
animation_player.play("Walk_Right")
# Otherwise, reset the animation to the idle state.
else:
animation_player.play("RESET")
# Adjust the player's visuals to face the direction of movement.
var move_sign = sign(movement_vector.x)
if move_sign != 0:
visuals.scale = Vector2(move_sign, 1)
# Get the movement vector based on player input for different directions.
func get_movement_vector():
var x_movement = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
var y_movement = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
return Vector2(x_movement, y_movement)
# Check if the player can deal damage when colliding with other objects.
func check_deal_damage():
# Only deal damage if there are colliding bodies and the damage timer isn't running.
if number_colliding_bodies == 0 or !damage_interval_timer.is_stopped():
return
# Inflict damage on the player and start the damage interval timer.
health_component.damage(1)
damage_interval_timer.start()
# Update the health bar to reflect the player's current health.
func update_health_display():
health_bar.value = health_component.get_health_percent()
# Called when another body enters the collision area.
func on_body_entered(other_body: Node2D):
number_colliding_bodies += 1
check_deal_damage()
# Called when another body exits the collision area.
func on_body_exited(other_body: Node2D):
number_colliding_bodies -= 1
# Called when the damage interval timer completes, allowing damage to be dealt again.
func on_damage_interval_timer_timeout():
check_deal_damage()
# Called when the player's health decreases.
func on_health_decreased():
# Emit a game event indicating the player took damage.
GameEvent.emit_player_damaged()
# Play a random sound to indicate the player was hit.
$HitRandomStreamPlayer.play_random()
# Called when the player's health changes (increased or decreased).
func on_health_changed():
update_health_display()
# Called when an ability upgrade is added.
func on_ability_upgrade_added(ability_upgrade: AbilityUpgrade, current_upgrades: Dictionary):
# If the upgrade is an ability, instantiate and add it to the abilities node.
if ability_upgrade is Ability:
var ability = ability_upgrade as Ability
abilities.add_child(ability.ability_controller_scene.instantiate())
# If the upgrade increases player speed, adjust the max speed.
elif ability_upgrade.id == "player_speed":
velocity_component.max_speed = base_speed + (base_speed * current_upgrades["player_speed"]["quantity"] * 0.1)
# Called when the arena difficulty increases.
func on_arena_difficulty_increased(difficulty: int):
# Check for health regeneration upgrades.
var health_regeneration_quantity = MetaProgression.get_upgrade_count("health_regeneration")
# If the player has health regeneration upgrades and it's a 30-second interval.
if health_regeneration_quantity > 0:
var is_thirty_second_interval = (difficulty % 6) == 0
# Heal the player by 1 point if the conditions are met.
if is_thirty_second_interval:
health_component.heal(1)
extends Camera2D
# Stores the position the camera should move towards (usually the player's position).
var target_position = Vector2.ZERO
# Called when the node enters the scene tree for the first time.
func _ready():
# Sets this camera as the current one in the scene.
make_current()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
# Updates the target position to follow the player.
acquire_target()
# Smoothly move the camera's position towards the target position.
# The 'lerp' function interpolates between the current position and the target.
# 'exp' is used for smooth damping, where a higher value (20) means faster following.
global_position = global_position.lerp(target_position, 1.0 - exp(-delta * 20))
# Find the player's position and set it as the target.
func acquire_target():
# Get all nodes that are in the "player" group.
var player_nodes = get_tree().get_nodes_in_group("player")
# If there's at least one player node found.
if player_nodes.size() > 0:
# Cast the first player node to a Node2D (assuming there is only one player).
var player = player_nodes[0] as Node2D
# Set the target position to the player's global position.
target_position = player.global_position
extends Node2D
# Accessing the Sprite2D node for displaying the experience drop.
@onready var sprite = $Sprite2D
# Called when the node enters the scene tree for the first time.
func _ready():
# Connects the `area_entered` signal from Area2D to the `on_area_entered` function.
$Area2D.area_entered.connect(on_area_entered)
# Handles the movement of the experience drop towards the player.
# 'percent' controls how far the movement progresses, and 'start_position' is where it starts from.
func tween_collect(percent: float, start_position: Vector2):
# Get the player node from the "player" group.
var player = get_tree().get_first_node_in_group("player")
# If no player is found, return early.
if player == null:
return
# Interpolate the position between the starting point and the player's position.
global_position = start_position.lerp(player.global_position, percent)
# Calculate the direction from the start position to the current position.
var direction_from_start = global_position - start_position
# Calculate the target rotation to face the player as it moves.
var target_rotation = direction_from_start.angle() + deg_to_rad(90)
# Smoothly rotate towards the target rotation using exponential damping.
rotation = lerp(rotation, target_rotation, 1 - exp(-get_process_delta_time()))
# Handles the collection of the experience drop.
func collect():
# Emit a game event that one experience vial has been collected.
GameEvent.emit_exp_vial_collected(1)
# Free the node from the scene to remove it after being collected.
queue_free()
# Called when another area enters the experience drop's Area2D.
func on_area_entered(other_area: Area2D):
# Create a new tween for animating the collection.
var tween = create_tween()
# Set the tween to run animations in parallel (simultaneously).
tween.set_parallel()
# Animate the movement of the experience drop towards the player.
# This will run from 0.0 (start) to 1.0 (end) over 0.5 seconds.
tween.tween_method(tween_collect.bind(global_position), 0.0, 1.0, 0.5)
.set_ease(Tween.EASE_IN)
.set_trans(Tween.TRANS_EXPO)
# Shrink the sprite to zero scale over 0.15 seconds, with a delay of 0.35 seconds.
tween.tween_property(sprite, "scale", Vector2.ZERO, 0.15).set_delay(0.35)
# Chain the next tween, which will call the `collect` function when done.
tween.chain()
tween.tween_callback(collect)
# Play a random sound using the RandomStreamPlayer2DComponent when collected.
$RandomStreamPlayer2DComponent.play_random()
extends Node
# Exported variable for the end screen scene (used when the player dies).
@export var end_screen_scene: PackedScene
# Preload the pause menu scene to be used when the game is paused.
var pause_menu_scene = preload("res://scenes/UI/pause_menu.tscn")
# This function is called when the node is ready (on initialization).
func _ready():
# Connect the player's health component 'died' signal to the 'on_player_died' function.
$%Player.health_component.died.connect(on_player_died)
# This function handles unhandled input events such as pausing the game.
func _unhandled_input(event):
# If the "pause" action is pressed, instantiate and add the pause menu scene to the tree.
if event.is_action_pressed("pause"):
add_child(pause_menu_scene.instantiate())
# Mark the input as handled to prevent further processing by other nodes.
get_tree().root.set_input_as_handled()
# This function is triggered when the player dies.
func on_player_died():
# Instantiate the end screen from the provided end_screen_scene.
var end_screen_insitance = end_screen_scene.instantiate()
# Add the end screen as a child of the current node.
add_child(end_screen_insitance)
# Set the end screen to show the defeat state.
end_screen_insitance.set_defeat()
# Save the game progress using the MetaProgression class.
MetaProgression.save()MetaProgression.save()
extends Node
# Signal emitted when the arena difficulty increases, with the new difficulty level.
signal arena_difficulty_increased(arena_difficulty: int)
# The interval used to increase the difficulty level.
const DIFFICULTY_INTERVAL = 5
# Accessing the Timer node for tracking time.
@onready var timer = $Timer
# Reference to the end screen scene that appears when the timer ends.
@export var end_screen_scene: PackedScene
# Variable to keep track of the current arena difficulty.
var arena_difficulty = 0
# Called when the node is added to the scene.
func _ready():
# Connect the timer's `timeout` signal to the `on_timer_timeout` function.
timer.timeout.connect(on_timer_timeout)
# Called every frame. 'delta' is the time elapsed since the previous frame.
func _process(delta):
# Calculate the next time target based on the current difficulty level.
# As difficulty increases, the interval between difficulty increases shortens.
var next_time_target = timer.wait_time - ((arena_difficulty + 1) * DIFFICULTY_INTERVAL)
# If the time left on the timer reaches or drops below the target time.
if timer.time_left <= next_time_target:
# Increase the arena difficulty level.
arena_difficulty += 1
# Emit the `arena_difficulty_increased` signal with the new difficulty.
arena_difficulty_increased.emit(arena_difficulty)
# Returns the time that has elapsed since the timer started.
func get_time_elapsed():
return timer.wait_time - timer.time_left
# Called when the timer completes its countdown.
func on_timer_timeout():
# Instantiate the end screen scene when the timer runs out.
var end_screen_instance = end_screen_scene.instantiate()
# Add the end screen to the scene tree, displaying it to the player.
add_child(end_screen_instance)
# Play the jingle or sound associated with the end screen.
end_screen_instance.play_jingle()
# Save any progression or state through the MetaProgression system.
MetaProgression.save()
extends Node
# The radius around the player within which enemies will spawn.
const SPAWN_RADIUS = 350
# References to different enemy scenes to be spawned.
@export var basic_enemy_scene: PackedScene
@export var wizard_enemy_scene: PackedScene
@export var bat_enemy_scene: PackedScene
@export var arena_time_manager: Node
# Accessing the Timer node for managing spawn intervals.
@onready var timer = $Timer
# Variables for controlling spawn times and enemy spawns.
var base_spawn_time = 0
var enemy_table = WeightedTable.new() # Weighted table for random enemy selection.
var number_to_spawn = 1 # Number of enemies to spawn each time.
# Called when the node enters the scene tree for the first time.
func _ready():
# Add the basic enemy scene to the weighted table with an initial weight.
enemy_table.add_item(basic_enemy_scene, 10)
# Set the base spawn time to the timer's wait time.
base_spawn_time = timer.wait_time
# Connect the timer's `timeout` signal to the `on_timer_timeout` function.
timer.timeout.connect(on_timer_timeout)
# Connect the difficulty increase signal to adjust enemy spawning with increasing difficulty.
arena_time_manager.arena_difficulty_increased.connect(on_arena_difficulty_increased)
# Function to get a valid spawn position around the player.
func get_spawn_position():
# Get the player node from the "player" group.
var player = get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return Vector2.ZERO
# Initialize spawn position and direction.
var spawn_position = Vector2.ZERO
var random_direction = Vector2.RIGHT.rotated(randf_range(0, TAU)) # Random direction for spawning.
# Try up to 4 times to find a clear position.
for i in 4:
spawn_position = player.global_position + (random_direction * SPAWN_RADIUS)
# Add a small offset to avoid spawning right at the edge of an obstacle.
var additional_check_offset = random_direction * 20
# Set up a raycast to ensure the spawn position is clear.
var query_parameters = PhysicsRayQueryParameters2D.create(player.global_position, spawn_position + additional_check_offset, 1)
var result = get_tree().root.world_2d.direct_space_state.intersect_ray(query_parameters)
# If the raycast result is empty, it means the spawn area is clear.
if result.is_empty():
break
else:
# If not clear, rotate the direction and try again.
random_direction = random_direction.rotated(deg_to_rad(90))
return spawn_position
# Called when the timer times out, spawning enemies.
func on_timer_timeout():
# Restart the timer for the next spawn interval.
timer.start()
# Get the player node; if not present, stop the spawning process.
var player = get_tree().get_first_node_in_group("player") as Node2D
if player == null:
return
# Spawn the specified number of enemies.
for i in number_to_spawn:
# Pick a random enemy scene from the weighted table.
var enemy_scene = enemy_table.pick_item()
# Create an instance of the selected enemy scene.
var enemy = enemy_scene.instantiate() as Node2D
# Find the node that manages the entities layer and add the new enemy.
var entities_layer = get_tree().get_first_node_in_group("entities_layer")
entities_layer.add_child(enemy)
# Set the enemy's position to a valid spawn position.
enemy.global_position = get_spawn_position()
# Called when the arena difficulty increases.
func on_arena_difficulty_increased(arena_difficulty: int):
# Reduce the timer's wait time as difficulty increases, making enemies spawn faster.
var time_off = (.1 / 18) * arena_difficulty
time_off = min(time_off, .7) # Cap the reduction to a maximum of 0.7 seconds.
timer.wait_time = base_spawn_time - time_off
# Add different types of enemies based on difficulty level.
if arena_difficulty == 12:
enemy_table.add_item(wizard_enemy_scene, 15) # Add wizards with higher weight.
elif arena_difficulty == 18:
enemy_table.add_item(bat_enemy_scene, 8) # Add bats with a certain weight.
# Increase the number of enemies spawned every 6 difficulty levels.
if (arena_difficulty % 6) == 0:
number_to_spawn += 1
extends Node
# Signal emitted when the experience points (EXP) are updated, carrying the current and target EXP values.
signal exp_updated(current_exp: float, target_exp: float)
# Signal emitted when the player levels up, providing the new level.
signal level_up(new_level: int)
# Constant representing the growth in target experience needed for the next level.
const TARGET_EXP_GROWTH = 5
# Variable to track the current amount of experience points.
var current_exp = 0
# Variable to track the player's current level.
var current_level = 1
# Variable to hold the experience points required to reach the next level.
var target_exp = 2
# Called when the node is added to the scene.
func _ready():
# Connect the experience vial collection event to the corresponding handler function.
GameEvent.exp_vial_collected.connect(on_exp_vial_collected)
# Function to increment the current experience by a specified amount.
func increment_exp(number: float):
# Update current experience, ensuring it does not exceed the target experience.
current_exp = min(current_exp + number, target_exp)
# Emit the updated EXP values.
exp_updated.emit(current_exp, target_exp)
# Check if the current experience has reached the target experience.
if current_exp == target_exp:
# Level up the player.
current_level += 1
# Increase the target experience for the next level.
target_exp += TARGET_EXP_GROWTH
# Reset current experience to zero after leveling up.
current_exp = 0
# Emit the updated EXP values again after leveling up.
exp_updated.emit(current_exp, target_exp)
# Emit the level up signal with the new level.
level_up.emit(current_level)
# Function called when an experience vial is collected, triggering the EXP increment.
func on_exp_vial_collected(number: float):
increment_exp(number)t_exp(number)
extends Node
# References to the experience manager and the upgrade screen scene.
@export var exp_manager: Node
@export var upgrade_screen_scene: PackedScene
# Dictionary to track the current upgrades applied to the player.
var current_upgrades = {}
# Weighted table to manage the pool of available upgrades.
var upgrade_pool: WeightedTable = WeightedTable.new()
# Preloading upgrade resources for easy access.
var upgrade_axe = preload("res://resources/upgrades/axe.tres")
var upgrade_axe_damage = preload("res://resources/upgrades/axe_damage.tres")
var upgrade_sword_rate = preload("res://resources/upgrades/sword_rate.tres")
var upgrade_sword_damage = preload("res://resources/upgrades/sword_damage.tres")
var upgrade_player_speed = preload("res://resources/upgrades/player_speed.tres")
var upgrade_anvil = preload("res://resources/upgrades/anvil.tres")
var upgrade_anvil_count = preload("res://resources/upgrades/anvil_count.tres")
# Called when the node enters the scene tree for the first time.
func _ready():
# Add various upgrades to the weighted pool with specific weights.
upgrade_pool.add_item(upgrade_axe, 7)
upgrade_pool.add_item(upgrade_anvil, 1000)
upgrade_pool.add_item(upgrade_sword_rate, 10)
upgrade_pool.add_item(upgrade_sword_damage, 8)
upgrade_pool.add_item(upgrade_player_speed, 5)
# Connect the level up event from the experience manager to the handler function.
exp_manager.level_up.connect(on_level_up)
# Function to apply a selected upgrade to the player.
func apply_upgrade(upgrade: AbilityUpgrade):
# Check if the upgrade has already been applied.
var has_upgrade = current_upgrades.has(upgrade.id)
if not has_upgrade:
# If not, add it to the current upgrades.
current_upgrades[upgrade.id] = {
"resource" : upgrade,
"quantity" : 1
}
else:
# If it already exists, just increment its quantity.
current_upgrades[upgrade.id]["quantity"] += 1
# If the upgrade has a maximum quantity, check if it has been reached.
if upgrade.max_quantity > 0:
var current_quantity = current_upgrades[upgrade.id]["quantity"]
if current_quantity == upgrade.max_quantity:
# If max quantity is reached, remove it from the upgrade pool.
upgrade_pool.remove_item(upgrade)
# Update the upgrade pool based on the chosen upgrade.
update_upgrade_pool(upgrade)
# Emit an event indicating that an ability upgrade has been added.
GameEvent.emit_ability_upgrade_added(upgrade, current_upgrades)
# Function to update the upgrade pool based on the selected upgrade.
func update_upgrade_pool(chosen_upgrade: AbilityUpgrade):
if chosen_upgrade.id == upgrade_axe.id:
# If the axe upgrade is chosen, add axe damage upgrade to the pool.
upgrade_pool.add_item(upgrade_axe_damage, 10)
elif chosen_upgrade.id == upgrade_anvil.id:
# If the anvil upgrade is chosen, add anvil count upgrade to the pool.
upgrade_pool.add_item(upgrade_anvil_count, 5)
# Function to pick upgrades from the upgrade pool.
func pick_upgrades():
var chosen_upgrades: Array[AbilityUpgrade] = [] # Array to store the selected upgrades.
for i in 2: # Attempt to choose 2 unique upgrades.
if upgrade_pool.items.size() == chosen_upgrades.size():
break # Stop if all items in the pool have been chosen.
while true:
# Randomly pick an upgrade from the pool.
var chosen_upgrade = upgrade_pool.pick_item()
if chosen_upgrade not in chosen_upgrades:
# If it's unique, add it to the chosen upgrades.
chosen_upgrades.append(chosen_upgrade)
break
return chosen_upgrades # Return the array of chosen upgrades.
# Function called when an upgrade is selected from the upgrade screen.
func on_upgrade_selected(upgrade: AbilityUpgrade):
apply_upgrade(upgrade) # Apply the selected upgrade.
# Function called when the player levels up.
func on_level_up(current_level: int):
var upgrade_screen_instance = upgrade_screen_scene.instantiate() # Create an instance of the upgrade screen.
add_child(upgrade_screen_instance) # Add the upgrade screen to the scene.
var chosen_upgrades = pick_upgrades() # Pick available upgrades to present.
# Set the upgrades to the upgrade screen instance.
upgrade_screen_instance.set_ability_upgrades(chosen_upgrades as Array[AbilityUpgrade])
# Connect the upgrade selection event to its handler function.
upgrade_screen_instance.upgrade_selected.connect(on_upgrade_selected)
# Class representing a weighted table for item selection based on their assigned weights.
class_name WeightedTable
# Array to store items along with their associated weights.
var items: Array[Dictionary] = []
# Total sum of weights to help with item selection.
var weight_sum = 0
# Function to add an item to the weighted table with a specified weight.
func add_item(item, weight: int):
# Append the item and its weight as a dictionary to the items array.
items.append({"item": item, "weight": weight})
# Update the total weight sum.
weight_sum += weight
# Function to remove an item from the weighted table.
func remove_item(item_to_remove):
# Filter out the item to remove from the items array.
items = items.filter(func(item): return item["item"] != item_to_remove)
# Reset the weight sum and recalculate it based on remaining items.
weight_sum = 0
for item in items:
weight_sum += item["weight"]
# Function to randomly pick an item from the weighted table, optionally excluding certain items.
func pick_item(exclude: Array = []):
var adjusted_items: Array[Dictionary] = items # Start with all items.
var adjusted_weight_sum = weight_sum # Start with the total weight.
# If there are items to exclude, create an adjusted list of items.
if exclude.size() > 0:
adjusted_items = [] # Initialize adjusted items array.
adjusted_weight_sum = 0 # Reset the weight sum for adjusted items.
for item in items:
if item["item"] in exclude:
# Skip any items that are in the exclude list.
continue
# Add the item to the adjusted list and update the adjusted weight sum.
adjusted_items.append(item)
adjusted_weight_sum += item["weight"]
# Randomly select a weight within the range of the adjusted weight sum.
var chosen_weight = randi_range(1, adjusted_weight_sum)
var iteration_sum = 0 # Initialize a running total for the weighted selection.
# Iterate through the adjusted items to find one based on the chosen weight.
for item in adjusted_items:
iteration_sum += item["weight"] # Accumulate the weight.
if chosen_weight <= iteration_sum:
# If the chosen weight is less than or equal to the accumulated weight, return the item.
return item["item"]
# Class representing a card for an ability upgrade in the UI.
extends PanelContainer
signal selected # Signal emitted when the card is selected.
@onready var name_label = %NameLabel # Label for the upgrade name.
@onready var description_label = %DescriptionLabel # Label for the upgrade description.
var disabled = false # Flag to indicate if the card is disabled.
# Called when the node enters the scene tree for the first time.
func _ready():
gui_input.connect(on_gui_input) # Connects to GUI input events.
mouse_entered.connect(on_mouse_entered) # Connects mouse enter event.
mouse_exited.connect(on_mouse_exited) # Connects mouse exit event.
# Plays the card "in" animation after a specified delay.
func play_in(delay: float = 0):
modulate = Color.TRANSPARENT # Set the initial color to transparent.
await get_tree().create_timer(delay).timeout # Wait for the delay.
$AnimationPlayer.play("in") # Play the "in" animation.
# Sets the ability upgrade information on the card.
func set_ability_upgrade(upgrade: AbilityUpgrade):
name_label.text = upgrade.name # Set the name label.
description_label.text = upgrade.description # Set the description label.
# Plays the discard animation.
func play_discard():
$AnimationPlayer.play("Discard")
# Handles the selection of the card.
func select_card():
disabled = true # Disable the card to prevent re-selection.
$AnimationPlayer.play("Clicked") # Play the clicked animation.
# Play discard animations for other cards in the same group.
for other_card in get_tree().get_nodes_in_group("upgrade_card"):
if other_card == self:
continue # Skip the current card.
other_card.play_discard()
await $AnimationPlayer.animation_finished # Wait for the animation to finish.
selected.emit() # Emit the selected signal.
# Handles input events for the card.
func on_gui_input(event: InputEvent):
if disabled:
return # Ignore input if the card is disabled.
if event.is_action_pressed("left_click"): # Check for left click action.
select_card() # Select the card.
# Called when the mouse enters the card area.
func on_mouse_entered():
if disabled:
return # Ignore if disabled.
$HoverAnimationPlayer.play("Hover") # Play hover animation.
# Called when the mouse exits the card area.
func on_mouse_exited():
if disabled:
return # Ignore if disabled.
$HoverAnimationPlayer.play("RESET") # Reset hover animation.
# Class representing the UI that displays the elapsed time in the arena.
extends CanvasLayer
@export var arena_time_manager: Node # Reference to the node managing arena time.
@onready var label = %Label # Reference to the label that displays the time.
# Called every frame; updates the label with the elapsed time.
func _process(_delta):
if arena_time_manager == null:
return # If the time manager is not assigned, do nothing.
# Get the elapsed time from the arena time manager.
var time_elapsed = arena_time_manager.get_time_elapsed()
# Format the elapsed time and update the label.
label.text = format_seconds_to_string(time_elapsed)
# Formats the elapsed time in seconds to a string "MM:SS".
func format_seconds_to_string(seconds: float):
var minutes = floor(seconds / 60) # Calculate the total minutes.
var remaining_seconds = seconds - (minutes * 60) # Calculate the remaining seconds.
return str(minutes) + ":" + ("%02d" % floor(remaining_seconds)) # Return formatted string.
# Class representing the end screen that appears when the game ends.
extends CanvasLayer
@onready var panel_container = $%PanelContainer # Reference to the panel that contains UI elements.
# Called when the node enters the scene tree for the first time.
func _ready():
# Set the pivot offset of the panel to its center for proper scaling animations.
panel_container.pivot_offset = panel_container.size / 2
# Create a tween for animating the panel's appearance.
var tween = create_tween()
tween.tween_property(panel_container, "scale", Vector2.ZERO, 0) # Start scale at zero (invisible).
tween.tween_property(panel_container, "scale", Vector2.ONE, 0.3) # Animate to full size.
.set_ease(Tween.EASE_OUT).set_trans(tween.TRANS_BACK) # Use easing for a smooth effect.
# Pause the game when the end screen is shown.
get_tree().paused = true
# Connect button signals to their respective functions.
$%ContinueButton.pressed.connect(on_continue_button_pressed)
$%QuitButton.pressed.connect(on_quit_button_pressed)
# This function sets up the end screen for a defeat scenario.
func set_defeat():
$%TitleLabel.text = "Defeat." # Update title for defeat.
$%DescriptionLabel.text = "The Cybernetic Uprising has been \n complete" # Update description.
play_jingle(true) # Play defeat jingle.
# This function plays the appropriate jingle based on the game state.
func play_jingle(defeat: bool = false):
if defeat:
$DefeatStreamPlayer.play() # Play defeat audio.
else:
$VictoryStreamPlayer.play() # Play victory audio.
# This function handles the continue button press event.
func on_continue_button_pressed():
ScreenTransistion.transistion() # Start transition animation.
await ScreenTransistion.transistioned_halfway # Wait for halfway point of transition.
get_tree().paused = false # Unpause the game.
get_tree().change_scene_to_file("res://scenes/UI/meta_menu.tscn") # Change to meta menu scene.
# This function handles the quit button press event.
func on_quit_button_pressed():
ScreenTransistion.transistion() # Start transition animation.
await ScreenTransistion.transistioned_halfway # Wait for halfway point of transition.
get_tree().paused = false # Unpause the game.
get_tree().change_scene_to_file("res://scenes/UI/main_menu.tscn") # Change to main menu scene.
# Class representing the experience bar that displays the player's experience progress.
extends CanvasLayer
@export var exp_manager: Node # Reference to the experience manager node.
@onready var progress_bar = $MarginContainer/ProgressBar # Get the ProgressBar node within the scene.
# Called when the node enters the scene tree for the first time.
func _ready():
progress_bar.value = 0 # Initialize the progress bar value to 0 at the start.
# Connect the exp_updated signal from the experience manager to the on_exp_updated method.
exp_manager.exp_updated.connect(on_exp_updated)
# This function is called when the experience updates.
func on_exp_updated(current_exp: float, target_exp: float):
# Calculate the percentage of experience gained relative to the target.
var percent = current_exp / target_exp
progress_bar.value = percent # Update the progress bar's value to reflect the current experience.
# Class representing floating text that appears and animates in the game.
extends Node2D
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Placeholder for any initialization logic needed when the node is ready.
# Starts the floating text animation with the specified text.
func start(text: String):
$Label.text = text # Set the text of the Label node to the provided text.
# Create a tween for handling animations.
var tween = create_tween()
tween.set_parallel() # Set the tween to execute animations in parallel.
# First animation: Move the text upwards and fade out.
tween.tween_property(self, "global_position", global_position + (Vector2.UP * 10), .3) \
.set_ease(Tween.EASE_OUT) # Move up 10 units over 0.3 seconds with ease-out.
tween.chain() # Prepare for the next tween to follow.
# Second animation: Continue moving up and scale up.
tween.tween_property(self, "global_position", global_position + (Vector2.UP * 48), .5) \
.set_ease(Tween.EASE_IN) # Move up an additional 48 units over 0.5 seconds with ease-in.
tween.tween_property(self, "scale", Vector2.ONE, .5) \
.set_ease(Tween.EASE_IN) # Scale up to its original size over 0.5 seconds.
tween.chain() # Prepare for the next tween to follow.
# Set a callback to free the node once the tweening is complete.
tween.tween_callback(queue_free)
# Create a second tween for scaling effect.
var scale_tween = create_tween()
scale_tween.tween_property(self, "scale", Vector2.ONE * 1.5, .15) \
.set_ease(Tween.EASE_OUT) # Scale up to 1.5 times the original size over 0.15 seconds.
scale_tween.tween_property(self, "scale", Vector2.ONE, .15) \
.set_ease(Tween.EASE_IN) # Scale back down to original size over 0.15 seconds.
# Class representing the main menu that allows players to start the game, access options, view upgrades, or quit the game.
extends CanvasLayer
# Preloaded scenes for options menu and upgrades menu.
var options_scene = preload("res://scenes/UI/options_menu.tscn")
var upgrade_scene = preload("res://scenes/UI/meta_menu.tscn")
# Called when the node enters the scene tree for the first time.
func _ready():
# Connect button pressed signals to their respective functions.
$%PlayButton.pressed.connect(on_play) # Play button: starts the game.
$%QuitButton.pressed.connect(on_quit) # Quit button: exits the game.
$%OptionsButton.pressed.connect(on_options) # Options button: opens the options menu.
$%UpgradesButton.pressed.connect(on_upgrade) # Upgrades button: opens the upgrades menu.
# Handles the action when the play button is pressed, transitioning to the main game scene.
func on_play():
ScreenTransistion.transistion() # Start the screen transition effect.
await ScreenTransistion.transistioned_halfway # Wait until the transition is halfway done.
get_tree().change_scene_to_file("res://scenes/main/main.tscn") # Change to the main game scene.
# Opens the options menu when the options button is pressed.
func on_options():
ScreenTransistion.transistion() # Start the screen transition effect.
await ScreenTransistion.transistioned_halfway # Wait for the transition to be halfway done.
var options_instance = options_scene.instantiate() # Create an instance of the options menu scene.
add_child(options_instance) # Add the options menu to the scene tree.
options_instance.back_pressed.connect(on_options_closed.bind(options_instance)) # Connect back button signal.
# Opens the upgrades menu when the upgrades button is pressed.
func on_upgrade():
ScreenTransistion.transistion() # Start the screen transition effect.
await ScreenTransistion.transistioned_halfway # Wait until the transition is halfway done.
get_tree().change_scene_to_file("res://scenes/UI/meta_menu.tscn") # Change to the upgrades menu scene.
# Exits the game when the quit button is pressed.
func on_quit():
get_tree().quit() # Calls the quit function to close the game.
# Handles cleanup when the options menu is closed.
func on_options_closed(options_instance: Node):
options_instance.queue_free() # Free the options instance from memory when it is closed.
# Class representing the meta upgrades menu where players can view and purchase upgrades.
extends CanvasLayer
@export var upgrades: Array[MetaUpgrade] = [] # Array of available meta upgrades to display.
@onready var grid_container = $%GridContainer # The container for displaying upgrade cards.
var meta_upgrade_card_scene = preload("res://scenes/UI/meta_upgrade_card.tscn") # Preloaded scene for meta upgrade cards.
# Called when the node enters the scene tree for the first time.
func _ready():
# Connect the back button's pressed signal to the on_back function.
$%BackButton.pressed.connect(on_back)
# Clear any existing children in the grid container before adding new cards.
for child in grid_container.get_children():
child.queue_free()
# Instantiate and set up each meta upgrade card.
for upgrade in upgrades:
var meta_upgrade_card_instance = meta_upgrade_card_scene.instantiate() # Create an instance of the card scene.
grid_container.add_child(meta_upgrade_card_instance) # Add the card instance to the grid container.
meta_upgrade_card_instance.set_meta_upgrade(upgrade) # Set the upgrade data for the card.
# Handles the action when the back button is pressed, transitioning back to the main menu.
func on_back():
ScreenTransistion.transistion_to_scene("res://scenes/UI/main_menu.tscn") # Transitions to the main menu scene.
# Class representing a card for displaying and managing meta upgrades.
extends PanelContainer
# On-ready variables for UI elements.
@onready var name_label = $%NameLabel # Label for displaying the upgrade's name.
@onready var description_label = $%DescriptionLabel # Label for displaying the upgrade's description.
@onready var progress_bar = $%ProgressBar # Progress bar showing how much currency is available for the upgrade.
@onready var purchase_button = $%PurchaseButton # Button to purchase the upgrade.
@onready var progress_label = $%ProgressLabel # Label showing current currency and required cost for the upgrade.
@onready var count_label = %CountLabel # Label displaying how many upgrades have been purchased.
var upgrade: MetaUpgrade # Reference to the associated MetaUpgrade resource.
# Called when the node enters the scene tree for the first time.
func _ready():
# Connects the purchase button's pressed signal to the on_purchase function.
purchase_button.pressed.connect(on_purchase)
# Sets the associated meta upgrade and updates the display.
func set_meta_upgrade(upgrade: MetaUpgrade):
self.upgrade = upgrade # Assigns the upgrade to the local variable.
name_label.text = upgrade.title # Sets the upgrade title.
description_label.text = upgrade.description # Sets the upgrade description.
update_progress() # Updates the progress bar and button state.
# Plays the clicked animation for the card.
func select_card():
$AnimationPlayer.play("Clicked")
# Updates the progress bar and button state based on the current currency and upgrade status.
func update_progress():
var current_quantity = 0 # Initialize current quantity of upgrades.
# Check if the upgrade exists in the save data and retrieve the current quantity.
if MetaProgression.save_data["meta_upgrades"].has(upgrade.id):
current_quantity = MetaProgression.save_data["meta_upgrades"][upgrade.id]["quantity"]
# Check if the maximum quantity has been reached.
var is_maxed = current_quantity >= upgrade.max_quantity
var currency = MetaProgression.save_data["meta_upgrade_currency"] # Get the current currency available.
# Calculate the percentage of currency available for the upgrade cost.
var percent = currency / upgrade.exp_cost
percent = min(percent, 1) # Clamp the percentage to a maximum of 1.
progress_bar.value = percent # Update the progress bar value.
# Enable or disable the purchase button based on currency and max quantity.
purchase_button.disabled = percent < 1 || is_maxed
if is_maxed:
purchase_button.text = "Max" # Change button text if maxed.
# Update the progress and count labels.
progress_label.text = str(currency) + "/" + str(upgrade.exp_cost) # Show current currency and cost.
count_label.text = "x%d" % current_quantity # Show the number of purchased upgrades.
# Handles the purchase of the upgrade when the purchase button is pressed.
func on_purchase():
if upgrade == null:
return # Exit if there's no upgrade set.
MetaProgression.add_meta_upgrade(upgrade) # Adds the upgrade to the progression system.
MetaProgression.save_data["meta_upgrade_currency"] -= upgrade.exp_cost # Deducts the cost from currency.
MetaProgression.save() # Saves the updated data.
update_progress() # Updates the display after purchase.
get_tree().call_group("meta_upgrade_card", "update_progress") # Updates all cards in the group.
$AnimationPlayer.play("Clicked") # Play the click animation for feedback.
# Class representing the options menu for adjusting game settings.
extends CanvasLayer
# Signal emitted when the back button is pressed.
signal back_pressed
# On-ready variables for UI elements.
@onready var window_button: Button = $%WindowButton
@onready var sfx_slider = $%SFXSlider
@onready var music_slider = $%MusicSlider
@onready var back_button = $%BackButton
# Called when the node enters the scene tree for the first time.
func _ready():
# Connects button and slider signals to their respective functions.
back_button.pressed.connect(on_back) # Connects back button to close menu.
window_button.pressed.connect(on_window) # Connects window mode button to toggle.
sfx_slider.value_changed.connect(on_audio_slider_changed.bind("SFX")) # Connects SFX slider to volume change.
music_slider.value_changed.connect(on_audio_slider_changed.bind("Music")) # Connects Music slider to volume change.
update_display() # Initializes the display of current settings.
# Updates the display elements to reflect the current settings.
func update_display():
window_button.text = "Windowed" # Default text for window button.
if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN:
window_button.text = "Fullscreen" # Changes text based on the current window mode.
# Sets the slider values to the current audio bus volumes.
sfx_slider.value = get_bus_volume_percent("SFX") # Updates SFX slider to current volume.
music_slider.value = get_bus_volume_percent("Music") # Updates Music slider to current volume.
# Retrieves the volume percentage of the specified audio bus.
func get_bus_volume_percent(bus_name: String):
var bus_index = AudioServer.get_bus_index(bus_name) # Gets the bus index for the given name.
var volume_db = AudioServer.get_bus_volume_db(bus_index) # Gets the volume in decibels for that bus.
return db_to_linear(volume_db) # Converts volume from dB to a linear scale for the slider.
# Sets the volume percentage of the specified audio bus.
func set_bus_volume_percent(bus_name: String, percent: float):
var bus_index = AudioServer.get_bus_index(bus_name) # Gets the bus index for the given name.
var volume_db = linear_to_db(percent) # Converts linear percentage to dB.
AudioServer.set_bus_volume_db(bus_index, volume_db) # Sets the volume for the specified bus.
# Toggles between fullscreen and windowed mode when the window button is pressed.
func on_window():
var mode = DisplayServer.window_get_mode() # Gets the current window mode.
if mode != DisplayServer.WINDOW_MODE_FULLSCREEN:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) # Sets to fullscreen mode.
else:
# Sets to windowed mode, removing the borderless flag.
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
update_display() # Updates the display after changing mode.
# Handles changes to audio slider values for both SFX and Music.
func on_audio_slider_changed(value: float, bus_name: String):
set_bus_volume_percent(bus_name, value) # Sets the volume for the specified bus based on the slider value.
# Handles the back button press, emitting the back_pressed signal.
func on_back():
ScreenTransistion.transistion() # Initiates a transition effect when going back.
await ScreenTransistion.transistioned_halfway # Waits until the transition is halfway done.
back_pressed.emit() # Emits the back_pressed signal to notify listeners.
# Class representing a pause menu that appears during gameplay.
extends CanvasLayer
# Reference to the panel container that holds the menu options.
@onready var panel_container = %PanelContainer
# Preloads the options menu scene for instantiation later.
var options_menu_scene = preload("res://scenes/UI/options_menu.tscn")
var is_closing = false # Tracks whether the menu is in the process of closing.
# Called when the node enters the scene tree for the first time.
func _ready():
# Pauses the game when the pause menu is ready.
get_tree().paused = true
# Centers the pivot of the panel container for scaling animations.
panel_container.pivot_offset = panel_container.size / 2
# Connects button pressed signals to their respective functions.
$%ResumeButton.pressed.connect(on_resume)
$%OptionsButton.pressed.connect(on_options)
$%QuitButton.pressed.connect(on_quit)
# Plays the default animation for the menu.
$AnimationPlayer.play("default")
# Creates a tween animation to scale the panel container from zero to one.
var tween = create_tween()
tween.tween_property(panel_container, "scale", Vector2.ZERO, 0) # Start scale
tween.tween_property(panel_container, "scale", Vector2.ONE, .3) # End scale
.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
# Handles unhandled input events, checking if the pause action was pressed.
func _unhandled_input(event):
if event.is_action_pressed("pause"):
close() # Close the menu if the pause action is triggered.
get_tree().root.set_input_as_handled() # Marks the input as handled to prevent further processing.
# Closes the pause menu with an animation.
func close():
if is_closing: # Prevents multiple close requests.
return
is_closing = true # Sets the closing flag to true.
$AnimationPlayer.play_backwards("default") # Plays the closing animation.
# Creates a tween animation to scale the panel container down to zero.
var tween = create_tween()
tween.tween_property(panel_container, "scale", Vector2.ONE, 0) # Start scale
tween.tween_property(panel_container, "scale", Vector2.ZERO, .3) # End scale
.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK)
await tween.finished # Waits for the tween animation to finish.
get_tree().paused = false # Unpauses the game.
queue_free() # Removes the menu from the scene.
# Function to resume the game when the resume button is pressed.
func on_resume():
close() # Calls the close function to handle resuming.
# Function to open the options menu when the options button is pressed.
func on_options():
ScreenTransistion.transistion() # Initiates a screen transition effect.
await ScreenTransistion.transistioned_halfway # Waits until the transition is halfway done.
# Instantiates the options menu and adds it to the scene.
var options_menu_instance = options_menu_scene.instantiate()
add_child(options_menu_instance)
# Connects the back pressed signal of the options menu to handle back navigation.
options_menu_instance.back_pressed.connect(on_options_back_pressed.bind(options_menu_instance))
# Function to quit to the main menu when the quit button is pressed.
func on_quit():
get_tree().paused = false # Unpauses the game before changing scenes.
get_tree().change_scene_to_file("res://scenes/UI/main_menu.tscn") # Changes the scene to the main menu.
# Function to handle back navigation from the options menu.
func on_options_back_pressed(options_menu: Node):
options_menu.queue_free() # Removes the options menu from the scene.
# Class representing a button that plays a random sound when pressed.
extends Button
# Called when the node enters the scene tree for the first time.
func _ready():
# Connects the pressed signal of the button to the on_pressed function.
pressed.connect(on_pressed)
# Function that is called when the button is pressed.
func on_pressed():
# Plays a random sound using the RandomStreamPlayerComponent when the button is pressed.
$RandomStreamPlayerComponent.play_random()
# Class representing the screen that displays available upgrades for the player to select from.
extends CanvasLayer
# Signal emitted when an upgrade is selected.
signal upgrade_selected(upgrade: AbilityUpgrade)
# PackedScene reference for the upgrade card UI element.
@export var upgrade_card_scene: PackedScene
# Reference to the container that holds the upgrade cards.
@onready var card_container: HBoxContainer = %CardContainer
# Called when the node enters the scene tree for the first time.
func _ready():
# Pauses the game when the upgrade screen is opened.
get_tree().paused = true
# Sets the ability upgrades to be displayed on the upgrade screen.
func set_ability_upgrades(upgrades: Array[AbilityUpgrade]):
var delay = 0 # Initial delay for card animations
for upgrade in upgrades:
# Instantiate a new upgrade card from the upgrade_card_scene.
var card_instance = upgrade_card_scene.instantiate()
# Add the card instance to the card container.
card_container.add_child(card_instance)
# Set the ability upgrade data for the card.
card_instance.set_ability_upgrade(upgrade)
# Play the entrance animation with a delay for a staggered effect.
card_instance.play_in(delay)
# Connect the selected signal of the card to the on_upgrade_selected function, binding the current upgrade.
card_instance.selected.connect(on_upgrade_selected.bind(upgrade))
delay += 0.2 # Increment delay for the next card
# Function called when an upgrade is selected.
func on_upgrade_selected(upgrade: AbilityUpgrade):
# Emit the upgrade_selected signal with the chosen upgrade.
upgrade_selected.emit(upgrade)
# Play the exit animation for the upgrade screen.
$AnimationPlayer.play("out")
await $AnimationPlayer.animation_finished # Wait for the animation to finish
# Unpause the game after the upgrade screen closes.
get_tree().paused = false
queue_free() # Remove the upgrade screen from the scene
# Class representing a vignette effect that overlays the game screen.
extends CanvasLayer
# Called when the node enters the scene tree for the first time.
func _ready():
# Connects the player_damaged event to the on_player_damaged function.
GameEvent.player_damaged.connect(on_player_damaged)
# Function to handle player damage events.
func on_player_damaged():
# Plays the "hit" animation when the player takes damage.
$AnimationPlayer.play("hit")
# Class representing an upgrade resource with associated properties.
extends Resource
class_name MetaUpgrade
# Unique identifier for the upgrade, used for referencing.
@export var id: String
# Maximum quantity of this upgrade that can be obtained (default is 1).
@export var max_quantity: int = 1
# Experience points cost required to purchase this upgrade (default is 10).
@export var exp_cost: int = 10
# Title or name of the upgrade to be displayed in the UI.
@export var title: String
# Description of the upgrade, allowing for multiline text input.
@export_multiline var description: String
# Class representing an ability upgrade that extends from AbilityUpgrade.
extends AbilityUpgrade
class_name Ability
# Scene that controls the behavior and presentation of the ability.
@export var ability_controller_scene: PackedScene
# Class representing an upgrade for an ability, allowing enhancements or new abilities to be applied.
extends AbilityUpgrade
class_name AbilityUpgrade # Make sure this matches your intended functionality.
# Scene that controls the behavior and presentation of the ability.
@export var ability_controller_scene: PackedScene