Godot Version
Godot 4.2.2 Stable
Question
Greetings! I have made a finall boss character to the my top down 2d game but it acting weird. First problem im facing with is the boss is not processing ranged attacks properly, i mean, it only plays the animation, not spawning the projectiles. The other problem is lil bro refuses to die i mean, , it supposed to be call a death function when its hp reaches to 0 or below 0 but i think i made something wrong with the handling phase states, it's only doing ranged attacks and when the player's body collides with the boss's body, the player gets instantly one shot. I will drop the whole boss's code below for inspecting the code. Any suggestions/constructive criticism warmly welcomed, thanks in advance!
extends CharacterBody2D
# Constants for magic numbers
const MELEE_DISTANCE = 30.0
const SHOOTING_DISTANCE = 180
const INITIAL_HP = 2000.0
const BASE_ARMOR = 0
const PHASE_2_ARMOR = 2
const PHASE_DAMAGE_INCREMENT = 20
@export var movement_speed = 20.0
@export var hp = INITIAL_HP
@export var knockback_recovery = 3.5
@export var exp = 1
@export var enemy_damage = 1
var knockback = Vector2.ZERO
@onready var player = get_tree().get_first_node_in_group("player")
@onready var loot_base = get_tree().get_first_node_in_group("loot")
@onready var sprite = $AnimatedSprite2D
@onready var snd_hit = $golem_boss_death
@onready var hitBox = $HitBox
@onready var attack_timer = $PlayerDetectionArea/AttackCooldown
@onready var player_detection_area = $PlayerDetectionArea
@onready var ImmuneTimer = $ImmuneTimer
var golem_hand_scene = preload("res://scenes/golem_hand.tscn")
var golem_hand_glowing_scene = preload("res://scenes/golem_hand_glowing.tscn")
var exp_orb = preload("res://scenes/exp_orb.tscn")
signal remove_from_array(object)
# Phase management
var current_phase: int = 1
var armor: int = BASE_ARMOR
# HP thresholds for phases
var phase_thresholds = {
1: [1500.0, 2000.0],
2: [1000.0, 1500.0],
3: [500.0, 1000.0],
4: [0.0, 500.0]
}
# State management
enum BossState {
MOVING,
ATTACKING,
IMMUNE
}
var current_state: BossState = BossState.MOVING
var is_attacking: bool = false
func _ready():
hitBox.damage = enemy_damage
player_detection_area.body_entered.connect(_on_player_detected)
player_detection_area.body_exited.connect(_on_player_lost)
player_detection_area.monitoring = false # Disable monitoring in phases 1 and 2
sprite.connect("animation_finished", Callable(self, "_on_animation_finished"))
func _physics_process(delta):
if not is_instance_valid(player):
return
var direction = global_position.direction_to(player.global_position)
var distance_to_player = global_position.distance_to(player.global_position)
# Knockback gradually resets
knockback = knockback.lerp(Vector2.ZERO, knockback_recovery * delta)
# Handle immune state
if current_state == BossState.IMMUNE:
armor = BASE_ARMOR # Reset armor after immune state
return
match current_state:
BossState.MOVING:
if current_phase < 2 and distance_to_player <= MELEE_DISTANCE:
if attack_timer.is_stopped() and not is_attacking:
velocity = Vector2.ZERO
current_state = BossState.ATTACKING
play_animation("melee_attack")
handle_attack()
elif current_phase >= 3 and distance_to_player <= SHOOTING_DISTANCE:
if attack_timer.is_stopped() and not is_attacking:
velocity = Vector2.ZERO
current_state = BossState.ATTACKING
play_animation("ranged_attack")
handle_attack()
else:
velocity = direction * movement_speed + knockback
play_animation("walk" if current_phase < 3 else "glow_move")
is_attacking = false
BossState.ATTACKING:
if sprite.animation_finished:
current_state = BossState.MOVING
# Directly set velocity, so call move_and_slide()
move_and_slide()
sprite.flip_h = direction.x < -0.1
func _on_hurt_box_hurt(damage, angle, knockback_amount):
if current_state == BossState.IMMUNE:
return # Ignore damage while in immune state
else:
damage = max(damage - armor, 0) # Armor calculation
hp -= damage
knockback = angle * knockback_amount
if hp <= 0:
death()
else:
snd_hit.play()
update_phase()
func attack():
handle_attack()
func melee_attack():
if player.global_position.distance_to(global_position) <= MELEE_DISTANCE:
player.call_deferred("_on_hurt_box_hurt", enemy_damage, Vector2.ZERO, 0)
play_animation("melee_attack")
func ranged_attack():
if attack_timer.is_stopped():
var projectile_scene = golem_hand_scene if current_phase < 4 else golem_hand_glowing_scene
var projectile = projectile_scene.instantiate()
projectile.global_position = global_position
var direction = (player.global_position - global_position).normalized()
projectile.velocity = direction
get_parent().add_child(projectile)
attack_timer.start() # Start the attack cooldown timer
# Ensure the animation plays only once per attack
play_animation("ranged_attack") # Play the ranged attack animation
func handle_attack():
if current_phase < 3:
melee_attack() # Allow melee attack in phases 1 and 2
elif current_phase >= 3 and player_detection_area.monitoring:
ranged_attack() # Only allow ranged attack if monitoring is active
func _on_animation_finished():
# Reset the attacking state when the animation finishes
is_attacking = false
func determine_phase(hp: float) -> int:
for phase in phase_thresholds.keys():
var min_hp = phase_thresholds[phase][0]
var max_hp = phase_thresholds[phase][1]
if hp > min_hp and hp <= max_hp:
return phase
return 0 # Default for invalid values
func update_phase():
var phase = determine_phase(hp)
if phase != current_phase: # Only update phase if it has changed
set_phase(phase)
func set_phase(phase: int):
if phase == current_phase:
return
current_phase = phase
match phase:
1:
armor = BASE_ARMOR
2:
armor = PHASE_2_ARMOR
play_animation("armor_up")
await sprite.animation_finished
3:
armor += 1
enemy_damage += PHASE_DAMAGE_INCREMENT
play_animation("glow") # Transition animation
await sprite.animation_finished
player_detection_area.monitoring = true # Activate area in phase 3
4:
armor += 1
enemy_damage += PHASE_DAMAGE_INCREMENT
play_animation("immune")
current_state = BossState.IMMUNE # Change state to immune
ImmuneTimer.start()
await sprite.animation_finished
play_animation("glow_move")
func death():
emit_signal("remove_from_array", self)
play_animation("death")
await sprite.animation_finished
var new_exp = exp_orb.instantiate()
new_exp.global_position = global_position
new_exp.exp = exp
loot_base.call_deferred("add_child", new_exp)
queue_free()
func play_animation(animation_name: String):
if sprite.animation != animation_name: # Prevent re-triggering the same animation
sprite.play(animation_name)
func _on_attack_cooldown_timeout():
attack()
func _on_player_detected(body: Node) -> void:
if body.is_in_group("player"):
attack_timer.start()
func _on_player_lost(body: Node) -> void:
if body.is_in_group("player"):
attack_timer.stop()
func _on_immune_timer_timeout():
var distance_to_player = global_position.distance_to(player.global_position)
if distance_to_player >= SHOOTING_DISTANCE and not is_attacking:
velocity = Vector2.ZERO
current_state = BossState.ATTACKING # Change state to immune
ImmuneTimer.stop()