Robert Smith
Robert Smith

Reputation: 733

How to prevent a character to jump in midair with Matter Physics in Phaser 3?

I'm creating a game in JavaScript using the Phaser 3 framework. I'm using the Matter physics engine, and I only want the player to be able to jump (or ollie, as it is in the game) if they are touching the ground. I'm trying to use the collision detection within Matter physics, but I can't seem to get anything to work.

The error in this current code is "Uncaught TypeError: this.matter.world is not a function", although I have tried other ways of implementing the collision detection used here.

I expect the player to only be able to jump when the up arrow key is hit AND they are touching the ground.

//Configurations for the physics engine
var physicsConfig = {
    default: 'matter',
    matter : {
        gravity: {
            x: 0,
            y: 2.5, // <--This is the only way I could get the skater to roll up the ramp.
        },
        debug: false //CHANGE THIS TO TRUE TO SEE LINES
    }   
}

//Variables for height and width
var gameHeight = 750;
var gameWidth = 3000;

/* Declare variable to decide whether the player can ollie or not
Player should be touching the ground or a ramp to be able to ollie */
var canOllie;

//Game configurations
var config = {
    type: Phaser.AUTO,
    width: 1500, //<-- this is the width of what we will see at one time
    height: gameHeight,
    physics: physicsConfig,
    scene: {
        preload: preload,
        create: create,
        update: update
    }   
}

//Start the game
var game = new Phaser.Game(config);

//Declare variables so we can access them in all functions
var skater;

var ground;

//Declare variable for the sky background
var sky;

function preload() {
    //Images
    this.load.image('sky', 'archery_assets/images/sky.png');

    //Load sprites from TexturePacker
    this.load.atlas('sheet', 'skate_assets/sprites.png', 'skate_assets/sprites.json');
    //Load body shapes from PhysicsEditor
    this.load.json('shapes', 'skate_assets/spritesPE.json');
}

function create() {

    //Background
    sky = this.add.image(1500, 325,'sky')
    //Scale the image
    sky.setDisplaySize(gameWidth, gameHeight);

    //Get the hitboxes
    var shapes = this.cache.json.get('shapes');

    //Set world bounds    
    this.matter.world.setBounds(0, 0, gameWidth, gameHeight);

    //Place ground object
    ground = this.matter.add.sprite(0, 0, 'sheet', 'ground', {shape: shapes.ground});
    //Ground is 600x600, so double the x pixels and we get screen width
    ground.setScale(5, 1);
    ground.setPosition(1500, 650);
    //Let the ground detect collisions 
    ground.isSensor(true);

    //Place the ramp
    var ramp = this.matter.add.sprite(0, 0, 'sheet', 'ramp', {shape: shapes.ramp});
    ramp.setPosition(550 + ramp.centerOfMass.x, 250  + ramp.centerOfMass.y);

    //Create the skater
    skater = this.matter.add.sprite(0, 0, 'sheet', 'roll/0001', {shape: shapes.s0001});
    skater.setPosition(100 + skater.centerOfMass.x, 200 + skater.centerOfMass.y);

    //Collision filtering
    var staticCategory = this.matter.world.nextCategory();
    ramp.setCollisionCategory(staticCategory);
    ground.setCollisionCategory(staticCategory);

    var skaterCategory = this.matter.world.nextCategory();
    skater.setCollisionCategory(skaterCategory);

    //Roll animation
    //Generate the frame names
    var rollFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 1, end: 4, zeroPad: 4,
        prefix: 'roll/'}
    );
    //Create the animation
    this.anims.create({
        key: 'roll', frames: rollFrameNames, frameRate: 16, repeat: -1
    });

    //Push animation
    var pushFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 5, end: 8, zeroPad: 4,
        prefix: 'push/'}
    );
    this.anims.create({
        key: 'push', frames: pushFrameNames, frameRate: 16, repeat: 0 
    });

    //Shuvit animation
    var shuvFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 9, end: 12, zeroPad: 4,
        prefix: 'shuv/'}
    );
    this.anims.create({
        key: 'shuv', frames: shuvFrameNames, frameRate: 32, repeat: 0 
    });

    //Ollie animation
    var ollieFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 13, end: 20, zeroPad: 4,
        prefix: 'ollie/'}
    );
    this.anims.create({
        key: 'ollie', frames: ollieFrameNames, frameRate: 24, repeat: 0
    });

    //Kickflip animation
    var kfFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 21, end: 33, zeroPad: 4,
        prefix: 'kickflip/'}
    );
    this.anims.create({
        key: 'kickflip', frames: kfFrameNames, frameRate: 24, repeat: 0
    });

    //This keeps the rolling animation going once the push animation is done
    skater.on('animationcomplete', () => {
        skater.anims.play('roll');
    });

    //Input for arrowkeys
    this.arrowKeys = this.input.keyboard.addKeys({
        up: 'up',
        down: 'down',
        left: 'left',
        right: 'right'
    }); 

    //Input for WASD keys
    this.WASDkeys = this.input.keyboard.addKeys({
        W: 'W',
        A: 'A',
        S: 'S',
        D: 'D'
    });

    //Spacebar
    this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);

    //Camera to follow the skater
    this.cameras.main.setBounds(0, 0, 3000, gameHeight);
    this.cameras.main.startFollow(skater);


    //Detect collision with ground
    this.matter.world('collisionactive', (skater, ground) => {
        if (this) {
            canOllie = true;
        }
        else {
            canOllie = false;
        }
    });

}

function update() {
    //Set variable for player movement
    var pushSpeed = 0;
    var ollie = 0;

    //Push
    if (this.spacebar.isDown && skater.angle > -60 && skater.angle < 60) {
        //Increase speed
        pushSpeed = 10;

        //Move player
        skater.setVelocityX(pushSpeed);

        //Play push animation
        skater.anims.play('push');
    }

    //Ollie
    if (Phaser.Input.Keyboard.JustDown(this.arrowKeys.up) && canOllie == true) {
        //Set ollie power
        ollie = -12;

        //Set skate velocity
        skater.setVelocityY(ollie);

        //Play the ollie animation
        skater.anims.play('ollie');

    }

    //Shuvit
    if (this.arrowKeys.down.isDown) {
        //Play the shuvit animation
        skater.anims.play('shuv');
    }

    //Kickflip
    if (this.WASDkeys.W.isDown) {
        //Set jump height
        ollie = -8

        //Move the player
        skater.setVelocityY(ollie);

        //Play animation
        skater.anims.play('kickflip');
    }

    //Tilting backwards in the air
    if (this.arrowKeys.left.isDown && skater.y < 470) {
        //Be able to turn backwards so you don't flip
        skater.angle -= 3 ;
    }
    //Tilting forwards in the air
    if (this.arrowKeys.right.isDown && skater.y < 470) {
        //Be able to turn forwards so you don't flip
        skater.angle += 3 ;
    }


}   

Upvotes: 1

Views: 2175

Answers (2)

Prateek p
Prateek p

Reputation: 135

Manuel Abascal's answer is missing a few thing. If you make skaterTouchingGround true, then you also need to make it false. What's more, you also need to check which objects are being collided. So,

this.matter.world.on("collisionactive", (e,o1, o2) => {
    skaterTouchingGround = true;
});
this.matter.world.on("collisionend", (e,o1, o2) => {
    skaterTouchingGround = false;
})

Where e is the event, o1 is the object 1(player) and o2 is the object 2(platform). To check which items they are, you need to label them. After you create your player, add the lines:

player.body.label = 'myLabel'

Then, in your platforms the same thing:

platform.body.label = 'myPlatform'

After that, in your collisionactive function, check the labels like so:

this.matter.world.on("collisionactive", (e,o1, o2) => {
    if(o1.label == 'myLabel' && o2.label == 'myPlatform'){
        skaterTouchingGround = true;
    }
});
this.matter.world.on("collisionend", (e,o1, o2) => {
    if(o1.label == 'myLabel' && o2.label == 'myPlatform'){
        skaterTouchingGround = false;
    }
})

Then:

if(skaterTouchingGround){
    jump
}

The code given above works as of phaser 3, if you added your labels correctly. Do remember to change the variable names to those that you have used.

NOTE: Even if the objects are being grouped in a phaser group( this.add.group() ), when you create your sprite, you should add the label before adding it to the group using group.add()

Upvotes: 0

Manuel Abascal
Manuel Abascal

Reputation: 6342

I have finally found the solution for this issue:

  • First, declare a variable called skaterTouchingGround right after the config object like so:
let skaterTouchingGround;
  • Second, in the create() function add the collision detector event & set the variable skaterTouchingGround to true like so:
//Detect collision with ground
this.matter.world.on("collisionactive", (skater, ground) => {
   skaterTouchingGround = true;
});
  • Third, in the update() function & if statement, add the skaterTouchingGround variable as condition to trigger the jump & once the jump is done set the variable skaterTouchingGround to false like so:
//Ollie
if (Phaser.Input.Keyboard.JustDown(this.arrowKeys.up) && skaterTouchingGround) {
    skaterTouchingGround = false;
    //Set ollie power
    ollie = -12;

    //Set skate velocity
    skater.setVelocityY(ollie);

    //Play the ollie animation
    skater.anims.play('ollie');

}

If you follow these steps, the skater won't be able to jump midair.

EDIT:

Finally, for your reference, here is the final full working code sample:

//Configurations for the physics engine
var physicsConfig = {
    default: 'matter',
    matter : {
        gravity: {
            x: 0,
            y: 2.5, // <--This is the only way I could get the skater to roll up the ramp.
        },
        debug: false //CHANGE THIS TO TRUE TO SEE LINES
    }   
}

//Variables for height and width
var gameHeight = 750;
var gameWidth = 3000;

/* Declare variable to decide whether the player can ollie or not
Player should be touching the ground or a ramp to be able to ollie */
var canOllie;

//Game configurations
var config = {
    type: Phaser.AUTO,
    width: 1500, //<-- this is the width of what we will see at one time
    height: gameHeight,
    physics: physicsConfig,
    scene: {
        preload: preload,
        create: create,
        update: update
    }   
}

//Start the game
var game = new Phaser.Game(config);

//Declare variables so we can access them in all functions
var skater;
let skaterTouchingGround;
var ground;

//Declare variable for the sky background
var sky;

function preload() {
    //Images
    this.load.image('sky', 'archery_assets/images/sky.png');

    //Load sprites from TexturePacker
    this.load.atlas('sheet', 'skate_assets/sprites.png', 'skate_assets/sprites.json');
    //Load body shapes from PhysicsEditor
    this.load.json('shapes', 'skate_assets/spritesPE.json');
}

function create() {

    //Background
    sky = this.add.image(1500, 325,'sky')
    //Scale the image
    sky.setDisplaySize(gameWidth, gameHeight);

    //Get the hitboxes
    var shapes = this.cache.json.get('shapes');

    //Set world bounds    
    this.matter.world.setBounds(0, 0, gameWidth, gameHeight);

    //Place ground object
    ground = this.matter.add.sprite(0, 0, 'sheet', 'ground', {shape: shapes.ground});
    //Ground is 600x600, so double the x pixels and we get screen width
    ground.setScale(5, 1);
    ground.setPosition(1500, 650);
    //Let the ground detect collisions 
    ground.isSensor(true);

    //Place the ramp
    var ramp = this.matter.add.sprite(0, 0, 'sheet', 'ramp', {shape: shapes.ramp});
    ramp.setPosition(550 + ramp.centerOfMass.x, 250  + ramp.centerOfMass.y);

    //Create the skater
    skater = this.matter.add.sprite(0, 0, 'sheet', 'roll/0001', {shape: shapes.s0001});
    skater.setPosition(100 + skater.centerOfMass.x, 200 + skater.centerOfMass.y);

    //Collision filtering
    var staticCategory = this.matter.world.nextCategory();
    ramp.setCollisionCategory(staticCategory);
    ground.setCollisionCategory(staticCategory);

    var skaterCategory = this.matter.world.nextCategory();
    skater.setCollisionCategory(skaterCategory);

    //Roll animation
    //Generate the frame names
    var rollFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 1, end: 4, zeroPad: 4,
        prefix: 'roll/'}
    );
    //Create the animation
    this.anims.create({
        key: 'roll', frames: rollFrameNames, frameRate: 16, repeat: -1
    });

    //Push animation
    var pushFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 5, end: 8, zeroPad: 4,
        prefix: 'push/'}
    );
    this.anims.create({
        key: 'push', frames: pushFrameNames, frameRate: 16, repeat: 0 
    });

    //Shuvit animation
    var shuvFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 9, end: 12, zeroPad: 4,
        prefix: 'shuv/'}
    );
    this.anims.create({
        key: 'shuv', frames: shuvFrameNames, frameRate: 32, repeat: 0 
    });

    //Ollie animation
    var ollieFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 13, end: 20, zeroPad: 4,
        prefix: 'ollie/'}
    );
    this.anims.create({
        key: 'ollie', frames: ollieFrameNames, frameRate: 24, repeat: 0
    });

    //Kickflip animation
    var kfFrameNames = this.anims.generateFrameNames(
        'sheet', {start: 21, end: 33, zeroPad: 4,
        prefix: 'kickflip/'}
    );
    this.anims.create({
        key: 'kickflip', frames: kfFrameNames, frameRate: 24, repeat: 0
    });

    //This keeps the rolling animation going once the push animation is done
    skater.on('animationcomplete', () => {
        skater.anims.play('roll');
    });

    //Input for arrowkeys
    this.arrowKeys = this.input.keyboard.addKeys({
        up: 'up',
        down: 'down',
        left: 'left',
        right: 'right'
    }); 

    //Input for WASD keys
    this.WASDkeys = this.input.keyboard.addKeys({
        W: 'W',
        A: 'A',
        S: 'S',
        D: 'D'
    });

    //Spacebar
    this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);

    //Camera to follow the skater
    this.cameras.main.setBounds(0, 0, 3000, gameHeight);
    this.cameras.main.startFollow(skater);

    //Detect collision with ground
    this.matter.world.on("collisionactive", (skater, ground) => {
        skaterTouchingGround = true;
    });
}

function update() {
    //Set variable for player movement
    var pushSpeed = 0;
    var ollie = 0;

    //Push
    if (this.spacebar.isDown && skater.angle > -60 && skater.angle < 60) {
        //Increase speed
        pushSpeed = 10;

        //Move player
        skater.setVelocityX(pushSpeed);

        //Play push animation
        skater.anims.play('push');
    }

    //Ollie
    if (Phaser.Input.Keyboard.JustDown(this.arrowKeys.up) && skaterTouchingGround) {
        skaterTouchingGround = false;
        //Set ollie power
        ollie = -12;

        //Set skate velocity
        skater.setVelocityY(ollie);

        //Play the ollie animation
        skater.anims.play('ollie');

    }
    //Shuvit
    if (this.arrowKeys.down.isDown) {
        //Play the shuvit animation
        skater.anims.play('shuv');
    }

    //Kickflip
    if (this.WASDkeys.W.isDown) {
        //Set jump height
        ollie = -8

        //Move the player
        skater.setVelocityY(ollie);

        //Play animation
        skater.anims.play('kickflip');
    }

    //Tilting backwards in the air
    if (this.arrowKeys.left.isDown && skater.y < 470) {
        //Be able to turn backwards so you don't flip
        skater.angle -= 3 ;
    }
    //Tilting forwards in the air
    if (this.arrowKeys.right.isDown && skater.y < 470) {
        //Be able to turn forwards so you don't flip
        skater.angle += 3 ;
    }
}

Upvotes: 2

Related Questions