[Solved. Posted with code] Enable / disable hit boxes once per animation frame?

What would the best approach for enabling and disabling hit boxes on the player’s sword once per animation frame?

Currently I’m using “this.physics.add.overlap” to check for a sword hitting an enemy, then on button press I activate the sword’s sprite body on a specific animation frame. My issue is that the overlap is detected more than once (e.g four times per animation frame, as I have very few animation frames in total).
What I’m looking for is a way to hit enemies once per sword swing upon button press.
(All the examples I found online destroy colliders permanently)

export default class Play extends Phaser.Scene {
create() {
    //Player
    this.player = new Player(this, 0, 0);
	this.container = this.add.container(100, 100);
  	this.container.setSize(16, 16);
  	this.physics.world.enable(this.container);
  	this.container.add(this.player);
  	//Weapon
	this.meleeWeapon = new MeleeWeapon(this, 0, 0);
	this.physics.world.enable(this.meleeWeapon);
	this.container.add(this.meleeWeapon); 
    //Enemy
	this.enemy = new Enemy(this, 600, 600);
	this.enemies = this.add.group();
	this.enemies.add(this.enemy)

	//Collisions
  	this.physics.add.overlap(this.meleeWeapon, this.enemies, this.onMeetEnemy, false, this);
	}
//the enemy class is in charge of subtracting damage from the enemy's health
onMeetEnemy(weapon, enemy) {
   enemy.takeDamage(weapon.damage);
}

update() {
    if(this.spaceBar.isDown) {
	  this.meleeWeapon.attack();
	}
}

The weapon class

export default class MeleeWeapon extends Phaser.Physics.Arcade.Sprite {
      constructor (scene, x, y) {
           this.body.enable = false;
           this.anims.create({
            key: 'attack',
            frames: this.anims.generateFrameNames('meleeWeapon', {prefix:'meleeWeapon-attack-', start: 1, end: 4 }),
        frameRate: 10,
        repeat: 0
    });
      }

      attack () {
           if(!this.isAttacking) {
            this.isAttacking = true;
            this.attackAnimation = this.anims.play('attack', false);
            this.attackAnimation.on('animationupdate', () => {
               
                if(this.attackAnimation.anims.currentFrame.index == 2)
                {
                    this.body.enable = true;
                } 
                else
                {
                    this.body.enable = false;
                };
            }); 

            this.attackAnimation.on('animationcomplete', () => {
                this.isAttacking = false;
            });          
        }
    }

Not sure if I understand the problem, but maybe you could do something like this:

if(!this.isAttacking) {
    if (!this.attackAnimation.anims.isPlaying) {
        this.attackAnimation = this.anims.play('attack', false);
        this.attackAnimation.on('animationupdate', () => {
           
            if(this.attackAnimation.anims.currentFrame.index == 2)
            {
                if (!this.isAttacking) {
                    this.isAttacking = true;
                    this.body.enable = true;
                }
                else this.body.enable = false;
            }
            else
            {
                this.body.enable = false;
            };
        }

        this.attackAnimation.on('animationcomplete', () => {
            this.isAttacking = false;
        });
    }
}
1 Like

Thank you for taking the time to look into this.
My issues is that if body is enabled for the duration of the animation frame, overlap is checked multiple times per update hence the enemy takes too many hits. From your code I think you understood the issue, I guess you were trying to get body enabled once and then disabled right away (and this is what I’m looking for) but your code doesn’t work as body doesn’t get enabled at all (it always returns false).

That doesn’t make sense. Obviously I haven’t tested it, but I think it should work.
Make sure to remove the first this.isAttacking = true;
Basically, just add an extra boolean for index == 2, if it happened already, set enable = false.
You can remove the outer ‘this.isAttacking’ test.

You are right, my mistake. Body.enabled is returning true, but the overlap doesn’t register anymore (in the Play scene)

Then let overlap just do its thing, and move this isAttacking check to update or onMeetEnemy.

1 Like

:wave:

Can you disable the body when the first overlap is detected? Then reenable when the next sword swing starts.

1 Like

Thanks. I’ll move it as you suggest and see if it works

:wave: Thank you. But in this case, I’ll also need to disable the body if a miss happens (say the player just swings it sword away from an enemy), that means if no overlap has been detected. I guess with a timer or something?

Well I suppose you can do both: disable the body when there’s an overlap and also when the attack animation completes.

1 Like

Thanks both to @Milton and @samme for the help. Here’s the working code:

Play.js

export default class Play extends Phaser.Scene {

create() {
    //Player
    this.player = new Player(this, 0, 0);
	this.container = this.add.container(100, 100);
  	this.container.setSize(16, 16);
  	this.physics.world.enable(this.container);
  	this.container.add(this.player);
  	//Weapon
	this.meleeWeapon = new MeleeWeapon(this, 0, 0);
	this.physics.world.enable(this.meleeWeapon);
	this.container.add(this.meleeWeapon); 
    this.meleeWeapon.setUp();
    //Enemy
	this.enemy = new Enemy(this, 600, 600);
	this.enemies = this.add.group();
	this.enemies.add(this.enemy)
	//Collisions
  	this.physics.add.overlap(this.meleeWeapon, this.enemies, this.onMeetEnemy, false, this);
	}

//the enemy class is in charge of subtracting damage from the enemy's health
onMeetEnemy(weapon, enemy) {
   enemy.takeDamage(weapon.damage);
}
if(this.spaceBar.isDown) {
   if(!this.meleeWeapon.isAttacking) {
	this.meleeWeapon.attack();
    }
   }

onHitEnemy(weapon, enemy) {
    if(weapon.isAttacking) {
        enemy.takeDamage(this.meleeWeapon.damage);
        weapon.body.enable = false;
    }
  }
}

Weapon.js

export default class MeleeWeapon extends Phaser.Physics.Arcade.Sprite
{
    constructor (scene, x, y)
    {
       //... add to scene and so on...

         this.anims.create({
            key: 'attack',
            frames: this.anims.generateFrameNames('meleeWeapon', {prefix:'meleeWeapon-attack-', start: 1, end: 4 }),
            frameRate: 10,
            repeat: 0
        });

        this.damage = 1;

        }

    setUp()
    {
        this.body.enable = false;
        this.isAttacking = false;
    }

    attack ()
    {
        if(!this.isAttacking)
        {
            this.isAttacking = true;
            this.body.enable = true;
            this.attackAnimation = this.anims.play('attack', false);
            this.attackAnimation.on('animationcomplete', () => {
                this.body.enable = false;
                this.isAttacking = false;
            }); 

        }   
    }
}