Untraceable "Uncaught TypeError: can't access property"

Hello. I am making a simple isometric game and at certain collisions the whole game freezes mid attacking animation and the following “Uncaught TypeError: can’t access property “x”, h.positionImpulse is undefined” happens. It does not show an error line for my source code, only for Phaser’s source code. The image bellow illustrates.

I use a complex set of sensors/hitboxes for collisions:

Some things I observed:

  • It only happens when my “pointerdown” attack event is triggered
  • It seems to happen more often when clicking profusely

Below is my code, any help is appreciated!

const CWALK = 0x0001;

const CBODY = 0x0002;

class Player extends Phaser.GameObjects.Sprite {

constructor(scene, physics, x, y, texture, frame) {

    super(scene, x, y, texture, frame);



    // Add this Game Object to the Scene's display list and update list

    scene.add.existing(this);



    // Enable physics for this sprite if needed

    this.physics = physics;

    this.physics.add.gameObject(this, {mass:0, label:'player'});



    // Set custom properties or methods

    this.textureName = texture;

    this.setScale(1);

    this.setSensor(true);

    this.setFixedRotation();

    this.speed = 5;

    this.health = 100;

    this.weaponName = 'longsword';

    this.lastDir = 'S';

    this.mouseDir = 'S';

    this.isAttacking = false;

    this.isKnockedBack = false;

    this.depth = this.y+this.height/2;

    this.anims.play(this.textureName+'standN', true);



    // Weapon setup

    this.weapon = scene.add.sprite(x, y, this.weaponName);

    this.physics.add.gameObject(this.weapon, {mass:0, label:'player_weapon'});

    this.weapon.setScale(1);

    this.weapon.anims.play(this.weaponName+'standN', true);

    this.weapon.setSensor(true);



    // Add a body collider for damage

    this.bodyCollider = this.physics.add.rectangle(this.x, this.y, 10, 20,

        {isSensor: false, density:0.01, mass:1, restitution: 0, frictionAir: 1, label:'player_body',

        friction: 0, frictionStatic: 0, slop: 1, fixedRotation: true, collisionFilter:{category:CWALK, mask:CWALK, group:CWALK}});



    // Add a circle collider to player for walking

    this.walkingCollider = this.physics.add.circle(this.x, this.y, 10,

        {isSensor: false, density:0.01, mass:20, restitution: 0, frictionAir: 1, label:'player_walker',

        friction: 0, frictionStatic: 0, slop: 1, fixedRotation: true, collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});



    // Add sensors for attacking

    this.attackWBox = this.physics.add.rectangle(this.x, this.y, 30, 10, {isSensor: true, fixedRotation: true, label:'player_attack_W',

        collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackEBox = this.physics.add.rectangle(this.x, this.y, 30, 10, {isSensor: true, fixedRotation: true, label:'player_attack_E',

        collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackNBox = this.physics.add.rectangle(this.x, this.y, 10, 15, {isSensor: true, fixedRotation: true, label:'player_attack_N',

        collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackSBox = this.physics.add.rectangle(this.x, this.y, 10, 15, {isSensor: true, fixedRotation: true, label:'player_attack_S',

        collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackNWBox = this.physics.add.rectangle(this.x, this.y, 10, 20, {isSensor: true, fixedRotation: true, label:'player_attack_NW',

        angle: -45, collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackNEBox = this.physics.add.rectangle(this.x, this.y, 10, 20, {isSensor: true, fixedRotation: true, label:'player_attack_NE',

        angle: 45, collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackSWBox = this.physics.add.rectangle(this.x, this.y, 10, 40, {isSensor: true, fixedRotation: true, label:'player_attack_SW',

        angle: 95, collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackSEBox = this.physics.add.rectangle(this.x, this.y, 10, 40, {isSensor: true, fixedRotation: true, label:'player_attack_SE',

        angle: -95, collisionFilter:{category:CBODY, mask:CBODY, group:CBODY}});

    this.attackWBox.parent = this;

    this.attackEBox.parent = this;

    this.attackNBox.parent = this;

    this.attackSBox.parent = this;

    this.attackNWBox.parent = this;

    this.attackNEBox.parent = this;

    this.attackSWBox.parent = this;

    this.attackSEBox.parent = this;



    this.sensorMap = {

        'W': this.attackWBox,

        'E': this.attackEBox,

        'N': this.attackNBox,

        'S': this.attackSBox,

        'NW': this.attackNWBox,

        'NE': this.attackNEBox,

        'SW': this.attackSWBox,

        'SE': this.attackSEBox

    };



    //Join player and weapon in a constraint

    this.physics.add.constraint(this, this.weapon, 0, 1, {pointA: {x: 0, y: 0},pointB: {x: 0, y: 0}});

    

    //Join player and body collider in a constraint

    this.physics.add.constraint(this.bodyCollider, this, 0, 1, {pointA: {x: 0, y: 0},pointB: {x: 0, y: +3}});



    //Join player and walking collider in a constraint

    this.physics.add.constraint(this.walkingCollider, this, 0, 1, {pointA: {x: 0, y: 0},pointB: {x: 0, y: +30}});



    //Join player and attack sensors in constraints

    this.physics.add.constraint(this.attackWBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: -15, y: 0 }});

    this.physics.add.constraint(this.attackEBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: +15, y: 0 }});

    this.physics.add.constraint(this.attackNBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: 0, y: -10 }});

    this.physics.add.constraint(this.attackSBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: 0, y: +10 }});

    this.physics.add.constraint(this.attackNWBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: -15, y: -10 }});

    this.physics.add.constraint(this.attackNEBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: +15, y: -10 }});

    this.physics.add.constraint(this.attackSWBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: -10, y: +10 }});

    this.physics.add.constraint(this.attackSEBox, this, 0, 1, {pointA: { x: 0, y: 0 }, pointB: { x: +10, y: +10 }});




    // Setup movement behavior

    this.on('animationcomplete', this.controlEndofAnimation, this);

}



activateAttackSensor(dir) {

    let sensor = this.sensorMap\[dir\];

    sensor.isSensor = false;

}



deactivateAttackSensor(dir) {

    let sensor = this.sensorMap\[dir\];

    sensor.isSensor = true;

}



movePlayer(action, bdir, mdir, reverse=false){

    let speedMults = this.speedMultipliers\[bdir\];

    if (action === 'stand'){

        this.setVelocity(0,0);

    }else{

        this.setVelocity(this.speed\*speedMults\[0\],this.speed\*speedMults\[1\]);

    }

    if (reverse){

        this.playReverse(this.textureName+action+mdir, true);

        this.weapon.playReverse(this.weaponName+action+mdir, true);

    } else{

        this.play(this.textureName+action+mdir, true);

        this.weapon.play(this.weaponName+action+mdir, true);

    }

}



controlEndofAnimation()

{

    let animKey = this.anims.currentAnim.key;

    if (animKey.includes('attack')) {

        this.isAttacking = false;

        this.deactivateAttackSensor(this.mouseDir);

    }

    /\* else if (animKey.includes('hit')) {

        this.isKnockedBack = false;

    } \*/

}

}

class Example extends Phaser.Scene

{

constructor ()

{

    super();

}



preload ()

{

    this.load.spritesheet('warrior', 'assets/isometric_hero/main_male.png', { frameWidth: 128, frameHeight: 128 });

    this.load.spritesheet('longsword', 'assets/isometric_hero/longsword.png', { frameWidth: 128, frameHeight: 128 });

    this.load.image('follower', 'assets/bomb.png');

}



getButtonDir(player, cursors) {

    //let dir = this.directionMap\[\[this.cursors.left.isDown,this.cursors.right.isDown,this.cursors.up.isDown,this.cursors.down.isDown\]\];

    let dir = this.directionMap\[\[this.AKey.isDown,this.DKey.isDown,this.WKey.isDown,this.SKey.isDown\]\];

    if (dir === undefined) {

        return \[this.player.lastDir, 'stand'\]

    }else{

        this.player.lastDir = dir;

        return \[dir, 'run'\];

    }

}



getMouseDir() {

    let playerMidX = this.player.x;

    let playerMidY = this.player.y;

    let angle = Phaser.Math.Angle.Between(playerMidX, playerMidY, this.pointer.x, this.pointer.y);

    let deg = Phaser.Math.RadToDeg(angle);

    if (deg < 0) { deg = 360 + deg; }

    if (deg >= 337.5 || deg < 22.5) { return 'E'; }

    if (deg >= 22.5 && deg < 67.5) { return 'SE'; }

    if (deg >= 67.5 && deg < 112.5) { return 'S'; }

    if (deg >= 112.5 && deg < 157.5) { return 'SW'; }

    if (deg >= 157.5 && deg < 202.5) { return 'W'; }

    if (deg >= 202.5 && deg < 247.5) { return 'NW'; }

    if (deg >= 247.5 && deg < 292.5) { return 'N'; }

    if (deg >= 292.5 && deg < 337.5) { return 'NE'; }

}



getStance() {

    //console.log(this.pointer.leftButtonDown(), this.pointer.rightButtonDown());

    if (this.pointer.leftButtonDown()) {

        return 'attack';

    }else if (this.pointer.rightButtonDown()) {

        return 'bow';

    } else {

        return 'peace';

    }

}



update ()

{

    let \[bdir, action\] = this.getButtonDir();

    let stc = this.getStance();

    this.player.mouseDir = this.getMouseDir();



    //Only move if not attacking or being knocked back

    if (!this.player.isAttacking || this.player.isKnockedBack) {

        if (this.oppositesReverseMap\[bdir\]\[this.player.mouseDir\]) {

            this.movePlayer(action, bdir, this.player.mouseDir, true);

        } else {

            this.movePlayer(action, bdir, this.player.mouseDir, false);

        }



    }



    //TESTING DEPTH SORTING

    /\* this.playerContainer.depth = this.hitbox.body.y;//+(this.player.height\*this.player.scale);

    this.blockp.x = this.hitbox.x;

    this.blockp.y = this.playerContainer.depth;

    console.log('Hitbox: ', this.blockp.x, this.blockp.y);

    this.blockp.depth = this.playerContainer.depth + 100; //Make sure follower is always on top \*/

    //



    /\* this.block.depth = this.block.body.y+(this.block.height\*this.block.scale);

    this.blockf.x = this.block.x;

    this.blockf.y = this.block.depth;

    this.blockf.depth = this.block.depth + 1; //Make sure follower is always on top \*/

    

    //console.log('Depth: ', this.player.depth, this.block.depth);

}



movePlayer(action, bdir, mdir, reverse=false){

    let speedMults = this.speedMultipliers\[bdir\];

    if (action === 'stand'){

        this.player.setVelocity(0,0);

    }else{

        this.player.setVelocity(this.player.speed\*speedMults\[0\],this.player.speed\*speedMults\[1\]);

    }

    if (reverse){

        this.player.playReverse(this.player.textureName+action+mdir, true);

        this.player.weapon.playReverse(this.player.weaponName+action+mdir, true);

    } else{

        this.player.play(this.player.textureName+action+mdir, true);

        this.player.weapon.play(this.player.weaponName+action+mdir, true);

    }

}



getSpeedMultipliers() {

    let sm = {};

    sm\['N'\] = \[0, -1\];

    sm\['S'\] = \[0, 1\];

    sm\['E'\] = \[1, 0\];

    sm\['W'\] = \[-1, 0\];

    sm\['NE'\] = \[0.707, -0.707\];

    sm\['NW'\] = \[-0.707, -0.707\];

    sm\['SE'\] = \[0.707, 0.707\];

    sm\['SW'\] = \[-0.707, 0.707\];

    return sm;

}



loadAnims(sheet){

    let compass = \['W', 'NW', 'N', 'NE', 'E', 'SE', 'S', 'SW'\];



    //Load all 8 directions of standing animations

    this.standAnims = \[\];

    let prefix = sheet+'stand';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32;

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: \[offset, offset+1, offset+2, offset+3 \] }),

            frameRate: 18,

            repeat: -1,

            yoyo: true

        });

        this.standAnims.push(prefix+compass\[i\]);

    }

    //Load all 8 directions of running animations

    this.runAnims = \[\];

    prefix = sheet+'run';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32+4;

        let frameseq = \[offset, offset+1, offset+2, offset+3, offset+4, offset+5, offset+6, offset+7 \];

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: frameseq }),

            frameRate: 20,

            repeat: -1,

            yoyo: false

        });

        this.runAnims.push(prefix+compass\[i\]);

    }

    //Load all 8 directions of attacking animations

    this.attackAnims = \[\];

    prefix = sheet+'attack';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32+12;

        let frameseq = \[offset, offset+1, offset+2, offset+3 \];

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: frameseq }),

            frameRate: 20,

            repeat: 0,

            yoyo: false

        });

        this.attackAnims.push(prefix+compass\[i\]);

    }

    //Load all 8 directions of defend animations

    this.dieAnims = \[\];

    prefix = sheet+'defend';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32+16; //i = line, 32 = frames per line, +16 = starting frame for die animations

        let frameseq = \[offset, offset+1\];;

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: frameseq }),

            frameRate: 20,

            repeat: 0,

            yoyo: false

        });

        this.dieAnims.push(prefix+compass\[i\]);

    }

    //Load all 8 directions of dying animations

    this.dieAnims = \[\];

    prefix = sheet+'die';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32+18; //i = line, 32 = frames per line, +16 = starting frame for die animations

        let frameseq = \[offset, offset+1, offset+2, offset+3, offset+4, offset+5\];;

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: frameseq }),

            frameRate: 20,

            repeat: 0,

            yoyo: false

        });

        this.dieAnims.push(prefix+compass\[i\]);

    }

    //Load all 8 directions of getting hit animations

    this.hitAnims = \[\];

    prefix = sheet+'hit';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32+24;

        let frameseq = \[offset, offset+1, offset+2, offset+3\];;

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: frameseq }),

            frameRate: 20,

            repeat: 0,

            yoyo: false

        });

        this.hitAnims.push(prefix+compass\[i\]);

    }

    //Load all 8 directions of using bow animations

    this.bowAnims = \[\];

    prefix = sheet+'bow';

    for (let i = 0; i < compass.length; i++) {

        let offset = i\*32+28;

        let frameseq = \[ offset, offset+1, offset+2, offset+3\];;

        this.anims.create({

            key: prefix+compass\[i\],

            frames: this.anims.generateFrameNumbers(sheet, { frames: frameseq }),

            frameRate: 20,

            repeat: 0,

            yoyo: false

        });

        this.bowAnims.push(prefix+compass\[i\]);

    }

}



getDirectionMaps() {

    //Data: this.cursors.left.isDown+" "+this.cursors.right.isDown+" "+this.cursors.up.isDown+" "+this.cursors.down.isDown

    let d = {};

    d\[\[false,false,false,true\]\] = 'S';

    d\[\[false,false,true,false\]\] = 'N';

    d\[\[false,true,false,false\]\] = 'E';

    d\[\[true,false,false,false\]\] = 'W';

    d\[\[false,true,true,false\]\] = 'NE';

    d\[\[true,false,true,false\]\] = 'NW';

    d\[\[false,true,false,true\]\] = 'SE';

    d\[\[true,false,false,true\]\] = 'SW';



    //Opposites map for reversing animations

    let opr = {};

    opr\['N'\] = {'S':true, 'E':false, 'W':false, 'NE':false, 'NW':false, 'SE':true, 'SW':true};

    opr\['S'\] = {'N':true, 'E':false, 'W':false, 'NE':true, 'NW':true, 'SE':false, 'SW':false};

    opr\['E'\] = {'W':true, 'N':false, 'S':false, 'NE':false, 'NW':true, 'SE':false, 'SW':true};

    opr\['W'\] = {'E':true, 'N':false, 'S':false, 'NE':true, 'NW':false, 'SE':true, 'SW':false};

    opr\['NE'\] = {'SW':true, 'N':false, 'E':false, 'W':true, 'S':true, 'NW':false, 'SE':false};

    opr\['NW'\] = {'SE':true, 'N':false, 'E':true, 'W':false, 'S':true, 'NE':false, 'SW':false};

    opr\['SE'\] = {'NW':true, 'N':true, 'E':false, 'W':true, 'S':false, 'NE':false, 'SW':false};

    opr\['SW'\] = {'NE':true, 'N':true, 'E':true, 'W':false, 'S':false, 'NW':false, 'SE':false};



    //Simple direct opposites map

    let ops = {};

    ops\['N'\] = 'S';

    ops\['S'\] = 'N';

    ops\['E'\] = 'W';

    ops\['W'\] = 'E';

    ops\['NE'\] = 'SW';

    ops\['NW'\] = 'SE';

    ops\['SE'\] = 'NW';

    ops\['SW'\] = 'NE';

    return \[d, opr, ops\];

}



create ()

{

    // Animation set

    this.loadAnims('warrior');

    this.loadAnims('longsword');



    // Player setup

    let startX = this.sys.game.canvas.width/2;

    let startY = this.sys.game.canvas.height/2;



    this.player = new Player(this, this.matter, startX, startY, 'warrior');

    this.player.setSensor(true);

    this.player.anims.play('warriorstandN', true);



    // Block to hit

    this.enemy = new Player(this, this.matter, startX-100, startY-100, 'warrior');



    // Input Events

    this.cursors = this.input.keyboard.createCursorKeys();

    this.WKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W);

    this.AKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);

    this.SKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S);

    this.DKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);



    // Mouse setup

    this.pointer = this.input.activePointer;

    //this.pointer = this.input.activePointer;



    // On pointer down, attack

    this.input.on('pointerdown', this.playerAttack, this);



    // Directional setup

    \[this.directionMap, this.oppositesReverseMap, this.oppositesSimpleMap\] = this.getDirectionMaps();

    this.speedMultipliers = this.getSpeedMultipliers();



    this.matter.world.on('collisionactive', (event) => this.handleActiveCollisions(event));

    //this.matter.world.on('collisionactive', (event, bodyA, bodyB) => this.handleCollisions(event, bodyA, bodyB));



    /\* this.matter.world.on('collisionactive', (event, bodyA, bodyB) => {

        if (bodyA === this.player.collider && bodyB === this.enemy.collider) {

            console.log("Collision active:");

        }

    }); \*/

}



handleActiveCollisions(event) {

    event.pairs.forEach(pair => {

        const { bodyA, bodyB } = pair;

        if (bodyA.label.includes("attack") && bodyB.label.includes("attack")) {

            let player1 = bodyA.parent;

            let player2 = bodyB.parent;

            if (player1!==player2){

                if (player1.isAttacking && player2.isAttacking) {

                    //player1.isKnockedBack = true;

                    //player2.isKnockedBack = true;

                    //player1.play(bodyA.parent.textureName+'hit'+bodyA.parent.mouseDir, true);

                    //player2.play(bodyB.parent.textureName+'hit'+bodyB.parent.mouseDir, true);

                    if (player1.mouseDir === this.oppositesSimpleMap\[player2.mouseDir\]) {

                        player1.setVelocity(-this.speedMultipliers\[player1.mouseDir\]\[0\]\*bodyA.parent.speed\*2,

                                            -this.speedMultipliers\[player1.mouseDir\]\[1\]\*bodyA.parent.speed\*2);

                        player2.setVelocity(-this.speedMultipliers\[player2.mouseDir\]\[0\]\*bodyB.parent.speed\*2,

                                            -this.speedMultipliers\[player2.mouseDir\]\[1\]\*bodyB.parent.speed\*2);

                    }

                }

            }

        }

    });

}



//Handle all collisions in the game

handleCollisions(event, bodyA, bodyB){

        console.log("Collision active between two attack sensors:", bodyA.label, bodyB.label);

    /\* if (bodyA.label.includes("attack") && bodyB.label.includes("attack")) {

        let player1 = bodyA.parent;

        let player2 = bodyB.parent;

        if (player1!==player2){

            if (player1.isAttacking && player2.isAttacking) {

                player1.isAttacking = false;

                player2.isAttacking = false;

                console.log("yo: ", bodyA.label, bodyB.label);

                player1.isKnockedBack = true;

                player2.isKnockedBack = true;

                player1.play(bodyA.parent.textureName+'hit'+bodyA.parent.mouseDir, true);

                player2.play(bodyB.parent.textureName+'hit'+bodyB.parent.mouseDir, true);

                player1.setVelocity(-bodyA.parent.speed\*2, -bodyA.parent.speed\*2);

                player2.setVelocity(-bodyB.parent.speed\*2, -bodyB.parent.speed\*2);

            }

        }

    } \*/




    /\* event.pairs.forEach(pair => {

        const { bodyA, bodyB } = pair;

        console.log("This is a collision: ", bodyA.label, bodyB.label);

    }); \*/

}



playerAttack()

{

    // If left button is down, attack

    if (this.pointer.leftButtonDown()) {

        this.player.activateAttackSensor(this.player.mouseDir);

        this.player.isAttacking = true;

        this.player.play(this.player.textureName+'attack'+this.player.mouseDir, true);

        this.player.weapon.play(this.player.weaponName+'attack'+this.player.mouseDir, true);



        //Make enemy attack in the opposite direction

        this.enemy.isAttacking = true;

        this.enemy.mouseDir = this.oppositesSimpleMap\[this.player.mouseDir\];

        this.enemy.play(this.enemy.textureName+'attack'+this.oppositesSimpleMap\[this.player.mouseDir\], true);

        this.enemy.weapon.play(this.player.weaponName+'attack'+this.oppositesSimpleMap\[this.player.mouseDir\], true);

    }

    

}

}

const config = {

type: Phaser.AUTO,

parent: 'phaser-example',

backgroundColor: '#999999',

// pixelArt: true,

scene: \[ Example \],

scale: {

mode: Phaser.Scale.ENVELOP,

autoCenter: Phaser.Scale.CENTER_BOTH,

width: 1920,

height: 1080,

},

physics: {

    default: 'matter',

    matter: {

        gravity: { y: 0 },

        debug: false

    }

}

};

const game = new Phaser.Game(config);

Guys I kinda solved it by mistake? The problem was activating/deactivating the sensors in the attack animation.