How to Use AnimationPlayer to instance a Hurtbox to a Specific Attack Frame

Godot Version

4.3

Question

` I’m trying to have my character attack with a hurtbox in my platformer
game. The problem is in trying to have the hurtbox become active only when
I’m attacking and also having the hurtbox active only during a frame or two during the attack. So far no luck. I’ve tried adding a Call Back track in the animation player and then use the hitbox itself as the method, but I can’t make the thing work for some reaon. Does anyone have a simple and effective solution for this problem? I’ll share my scripts here:

Hitbox:

class_name HitBox
extends Area3D

@export var _damage: int = 1

func _ready():
	set_hitbox_enabled(false)

func set_hitbox_enabled(value: bool):
	monitoring = value  # Enable or disable detection
	for child in get_children():
		if child is CollisionShape3D:
			child.disabled = !value  # Enable/disable collision shape  # Hitbox disabled by default

func  set_damage(value: int):
	_damage = value

func get_damage() -> int:
	return _damage

Character controller:

class_name PlayerEntity
extends CharacterBody3D

@export var SPEED = 3.0
@export var JUMP_VELOCITY = 4.5
@export var acceleration = 12.0
@export var deceleration = 10.0
@export var gravity_multiplier = 1.0  # Multiplier for gravity

@export var attacking = false


@onready var animation:  = $AnimationPlayer
@onready var hitbox = $HitBox/CollisionShape3D
@onready var sprite_3d = $Sprite3D

# Connect the animation_finished signal to reset 'attacking' flag when attack animation finishes

func _process(_delta: float) -> void:
	if Input.is_action_just_pressed("attack"):
		attack()

func _physics_process(delta: float) -> void:
	# Modify gravity multiplier based on player state
	if not is_on_floor():
		if velocity.y > 0:  # Ascending (jumping up)
			gravity_multiplier = 1.0  # Normal gravity
		else:  # Descending (falling down)
			gravity_multiplier = 2.0  # Increased gravity

		velocity += get_gravity() * gravity_multiplier * delta

	else:
		gravity_multiplier = 1.0  # Reset gravity when on the floor
	
	# Handle jump release for short hop
	if Input.is_action_just_released("jump") and velocity.y > 0:
		velocity.y *= 0.5
		
	
			
	# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	# Get input for horizontal movement
	var input_dir = Input.get_vector("left","right", "ui_up", "ui_down")
	
	#Constrain the movement to X and Y vectors
	var move_dir = Vector3(input_dir.x, 0, input_dir.y)
	if attacking and is_on_floor(): #stops the player from moving when is attacking
		velocity = Vector3.ZERO
		
	else:
		velocity.x = move_toward(velocity.x, move_dir.x * SPEED, acceleration * delta)

	
	if input_dir.x > 0:
		$Sprite3D.scale.x = 1
	elif input_dir.x < 0:
		$Sprite3D.scale.x = -1
		
	
	#Decelerate when no input is given
	if move_dir == Vector3.ZERO:
		velocity.x = move_toward(velocity.x, input_dir.x * SPEED, acceleration * delta)
			

	

	# Move the character
	move_and_slide()

	# Handle animations
	update_animation(Vector3(input_dir.x, 0, 0))

func attack():
	attacking = true
	animation.play("attack_1")
	


func update_animation(_direction: Vector3) -> void:
	if !attacking:
		if velocity.y > 0:  # Ascending
			animation.play("jump")
		elif velocity.y < 0 and not is_on_floor():  # Descending
			animation.play("Fall")
		if velocity.x != 0 and is_on_floor():
			animation.play("run")  # Optional fall animation
		elif velocity.x == 0 and is_on_floor():
			animation.play("idle")	
	else:  # Idle
		animation.play("attack_1")

Any help is greatly appreciated! `

-Chris

In my game, I have a class called HitboxShapeCast which is a ShapeCast2D that moves with the player or enemies. What I do is when it’s time to attack (for example, your animation player reached a certain point), I have a function inside my class that will basically get the Colliders when it’s called, and go through them each, and if one of those colliders is the Enemy (or player) class, then call their “Hurt” function. This assumes that you use proper Object Oriented design, and both your Enemies and your player derives from a base class OR you have some kind of composition set up where an entity can receive a hit.
I use C# but I’ll show you my code here anyway to hopefully set you on the right path!

public partial class HitboxShapeCast : ShapeCast2D
{
    public event EventHandler<HitRegisteredEventArgs> HitRegistered;

    /// <summary>
    ///     Deals damage to the entities that intersect with the player's
    ///     <see cref="ShapeCast2D" />.
    /// </summary>
    /// <param name="damage">How much damage to deal</param>
    /// <param name="type">Type of damage, see: <see cref="PlayerAttackTypes" /></param>
    /// <param name="counterAttackOnly">Process only counter-attacks when calling this function.</param>
    public void DealDamageToEntities(float damage, PlayerAttackTypes type, bool counterAttackOnly = false)
    {
        int count = GetCollisionCount();
        for (int i = 0; i < count; i++)
        {
            if (counterAttackOnly)
            {
                if (GetCollider(i) is BeerCan canOutsideAttackRange)
                {
                    bool canBeKicked = canOutsideAttackRange.Kick();
                    if (canBeKicked)
                    {
                        GameController.Instance.SlowdownTime(alsoZoomIn: true, onObject: canOutsideAttackRange);
                    }
                }

                continue;
            }
            
            if (GetCollider(i) is Breakable breakable)
            {
                breakable.Hit();
                continue;
            }

            if (GetCollider(i) is not PredationCharacterEntity body)
            {
                continue;
            }

            if (GetCollider(i) is BeerCan can)
            {
                can.Kick();
            }

            if (body.IsDead)
            {
                continue;
            }

            body.Hurt(damage);
            HitRegistered?.Invoke(this, new HitRegisteredEventArgs(damage, type));
            

            if (!UserPreferences.Instance.UserSettings.DamageNumbers)
            {
                continue;
            }

            DamageNumbers instance = NodeHelpers.InstantiateNodeOfType<DamageNumbers>
                ("res://Objects/Combat/DamageNumbers.tscn");

            instance.SetDamageNumber(damage.ToString(CultureInfo.InvariantCulture));
            body.AddChild(instance);
        }
    }
}