如何防止角色在 Phaser 3 中使用 Matter Physics 在半空中跳跃?

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

我正在 JavaScript 使用 Phaser 3 框架创建游戏。我正在使用 Matter 物理引擎,我只希望玩家在接触地面时能够跳跃(或 ollie,就像在游戏中一样)。我正在尝试使用物质物理学中的碰撞检测,但似乎无法正常工作。

当前代码中的错误是“Uncaught TypeError: this.matter.world is not a function”,尽管我尝试了其他方法来实现此处使用的碰撞检测。

我希望玩家只有在按下向上箭头键并且接触地面时才能跳跃。

//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 ;
    }


}   

我终于找到了这个问题的解决方案:

  • 首先,在config[之后声明一个名为skaterTouchingGround的变量=52=] 像这样的对象:
let skaterTouchingGround;
  • Second,在create()函数中添加碰撞检测事件&设置变量skaterTouchingGroundtrue 像这样:
//Detect collision with ground
this.matter.world.on("collisionactive", (skater, ground) => {
   skaterTouchingGround = true;
});
  • 第三个,在update()函数&if 语句,添加 skaterTouchingGround 变量作为触发跳转的条件 & 一旦跳转完成,将变量 skaterTouchingGround 设置为 false 像这样:
//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');

}

如果按照这些步骤操作,滑冰者将无法在空中跳跃。

编辑:

最后,供您参考,这里是最终的完整工作代码示例:

//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 ;
    }
}

Manuel Abascal 的回答遗漏了一些东西。如果您将 skakerTouchingGround 设置为 true,那么您还需要将其设置为 false。更重要的是,您还需要检查正在碰撞的对象。所以,

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

其中e是事件,o1是对象1(玩家),o2是对象2(平台)。要检查它们是哪些项目,您需要给它们贴上标签。创建播放器后,添加以下行:

player.body.label = 'myLabel'

然后,在您的平台上同样如此:

platform.body.label = 'myPlatform'

之后,在你的 collisionactive 函数中,像这样检查标签:

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;
    }
})

然后:

if(skaterTouchingGround){
    jump
}

如果您正确添加了标签,上面给出的代码可以从 Phaser 3 开始使用。请记住将变量名称更改为您使用过的名称。

注意:即使对象被分组在移相器组(this.add.group())中,当您创建精灵时,您也应该在使用 group.add()[将其添加到组之前添加标签=17=]