extends CharacterBody2D
const SPEED = 300.0
var last_direction: Vector2 = Vector2.RIGHT
var is_attacking: bool = false
var use_second_attack: bool = false # Toggle for the attack combo
var hitbox_offset: Vector2
# --- NODES ---
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var swing_sword: AudioStreamPlayer2D = $SwingSword
@onready var hitbox: Area2D = $Hitbox
func _ready() -> void:
# Initialize hitbox offset
hitbox_offset = hitbox.position
# --- SOUNDS ---
const SLASH_1 = preload("uid://dajfxthjtexbv")
const SLASH_2 = preload("uid://dicth32opmm58")
const SLASH_3 = preload("uid://52tgn8a60r1p")
# Put the sounds in an array so we can easily pick a random one later
var slash_sounds: Array = [SLASH_1, SLASH_2, SLASH_3]
#------------------------
# MOVEMENT AND ANIMATION
#-------------------------
func _physics_process(_delta: float) -> void:
if Input.is_action_just_pressed("attack") and not is_attacking:
attack()
# Check this EVERY frame, not just when the button is clicked
if is_attacking:
velocity = Vector2.ZERO
else:
# Only process movement and animation if we are NOT attacking
process_movement()
process_animation()
# move_and_slide should always run at the very end so physics update properly
move_and_slide()
func process_movement() -> void:
var direction := Input.get_vector("left", "right","up","down")
if direction != Vector2.ZERO:
velocity = direction * SPEED
last_direction = direction
update_hitbox_offset()
else:
velocity = Vector2.ZERO
func process_animation() -> void:
if is_attacking:
return
if velocity != Vector2.ZERO:
play_animation("run", last_direction)
else:
play_animation("idle",last_direction)
func play_animation(prefix: String, dir: Vector2) -> void:
if dir.x > 0:
animated_sprite_2d.play(prefix + "_right")
elif dir.y < 0:
animated_sprite_2d.play(prefix + "_up")
elif dir.y > 0:
animated_sprite_2d.play(prefix + "_down")
elif dir.x < 0:
animated_sprite_2d.play(prefix + "_left")
#-------------------
# ATTACK
#-------------------
func attack() -> void:
# Ignore input if we are already in the middle of an attack animation
if is_attacking:
return
is_attacking = true
# Load a random sound from our array and play it using the video's variable name
swing_sword.stream = slash_sounds.pick_random()
swing_sword.play()
# Alternate between 'attack' and 'attack2' animations
if use_second_attack:
play_animation("attack2", last_direction)
else:
play_animation("attack", last_direction)
# Flip the boolean so the next click plays the other animation
use_second_attack = !use_second_attack
func _on_animated_sprite_2d_animation_finished() -> void:
if is_attacking:
is_attacking = false
#---------
# HITBOX
#---------
func update_hitbox_offset() -> void:
# Calculate the total distance of the original offset you set in the editor
var distance := hitbox_offset.length()
# This perfectly matches how play_animation() chooses directions!
if last_direction.x > 0:
hitbox.position = Vector2(distance, 0)
elif last_direction.y < 0:
hitbox.position = Vector2(0, -distance)
elif last_direction.y > 0:
hitbox.position = Vector2(0, distance)
elif last_direction.x < 0:
hitbox.position = Vector2(-distance, 0)
func _on_hitbox_body_shape_entered(body_rid: RID, body: Node2D, body_shape_index: int, local_shape_index: int) -> void:
pass # Replace with function body.
What else could cause an AnimatedSprite2D to be invisible in-game while its CollisionShape2D is still visible? Thanks.
Hello! The debugger warning was caused by the Area2D hitbox signal that I connected. It was pointing to this function:
func _on_hitbox_body_entered(body: Node2D) -> void:
pass # Replace with function body.
I have already implemented the function, so that warning is now resolved.
Here’s my player.gd script currently:
extends CharacterBody2D
const SPEED = 300.0
var last_direction: Vector2 = Vector2.RIGHT
var is_attacking: bool = false
var use_second_attack: bool = false # Toggle for the attack combo
var hitbox_offset: Vector2
# --- NODES ---
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var swing_sword: AudioStreamPlayer2D = $SwingSword
@onready var hitbox: Area2D = $Hitbox
func _ready() -> void:
# Initialize hitbox offset
hitbox_offset = hitbox.position
# --- SOUNDS ---
const SLASH_1 = preload("uid://dajfxthjtexbv")
const SLASH_2 = preload("uid://dicth32opmm58")
const SLASH_3 = preload("uid://52tgn8a60r1p")
# Put the sounds in an array so we can easily pick a random one later
var slash_sounds: Array = [SLASH_1, SLASH_2, SLASH_3]
#------------------------
# MOVEMENT AND ANIMATION
#-------------------------
func _physics_process(_delta: float) -> void:
# Disable Hitbox until Attacking
hitbox.monitoring = false
if Input.is_action_just_pressed("attack") and not is_attacking:
attack()
# Check this EVERY frame, not just when the button is clicked
if is_attacking:
velocity = Vector2.ZERO
else:
# Only process movement and animation if we are NOT attacking
process_movement()
process_animation()
# move_and_slide should always run at the very end so physics update properly
move_and_slide()
func process_movement() -> void:
var direction := Input.get_vector("left", "right","up","down")
if direction != Vector2.ZERO:
velocity = direction * SPEED
last_direction = direction
update_hitbox_offset()
else:
velocity = Vector2.ZERO
func process_animation() -> void:
if is_attacking:
return
if velocity != Vector2.ZERO:
play_animation("run", last_direction)
else:
play_animation("idle",last_direction)
func play_animation(prefix: String, dir: Vector2) -> void:
if dir.x > 0:
animated_sprite_2d.play(prefix + "_right")
elif dir.y < 0:
animated_sprite_2d.play(prefix + "_up")
elif dir.y > 0:
animated_sprite_2d.play(prefix + "_down")
elif dir.x < 0:
animated_sprite_2d.play(prefix + "_left")
#-------------------
# ATTACK
#-------------------
func attack() -> void:
# Ignore input if we are already in the middle of an attack animation
if is_attacking:
return
is_attacking = true
hitbox.monitoring = true
# Load a random sound from our array and play it using the video's variable name
swing_sword.stream = slash_sounds.pick_random()
swing_sword.play()
# Alternate between 'attack' and 'attack2' animations
if use_second_attack:
play_animation("attack2", last_direction)
else:
play_animation("attack", last_direction)
# Flip the boolean so the next click plays the other animation
use_second_attack = !use_second_attack
func _on_animated_sprite_2d_animation_finished() -> void:
if is_attacking:
is_attacking = false
#---------
# HITBOX
#---------
func update_hitbox_offset() -> void:
# Calculate the total distance of the original offset you set in the editor
var distance := hitbox_offset.length()
# This perfectly matches how play_animation() chooses directions!
if last_direction.x > 0:
hitbox.position = Vector2(distance, 0)
elif last_direction.y < 0:
hitbox.position = Vector2(0, -distance)
elif last_direction.y > 0:
hitbox.position = Vector2(0, distance)
elif last_direction.x < 0:
hitbox.position = Vector2(-distance, 0)
func _on_hitbox_body_entered(body: Node2D) -> void:
if is_attacking:
print("hit")
pass # Replace with function body.
As for the AnimatedSprite2D not showing, I managed to fix it, although I’m not sure why it works. I had to run my scenes in this order:
After doing that, both the player and the golem appeared correctly in the main scene. I’m not sure why this sequence fixes it, but that’s what solved the issue for me. Thanks for your help!