You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Arcade与Matter物理引擎在Phaser 3中实现交互?

问题:Phaser 3.60.0中Arcade物理与斜向瓦片碰撞的解决方案

我使用Phaser 3.60.0开发俯视角游戏,全程基于Arcade物理引擎,仅需借助Matter物理引擎处理斜向瓦片(对角线墙体)的碰撞。目前遇到以下问题:

  • 使用this.physics.add.collider(this.player.sprite, groundLayer)时,斜向瓦片会被识别为矩形,碰撞逻辑错误
  • Matter物理默认的碰撞仅触发事件,无法实现实体碰撞阻挡效果
  • 不想将玩家精灵转换为Matter物理对象,工作量过大且超出当前技术范围

相关截图:
Arcade精灵与Tiled地图上的Matter碰撞区域
玩家精灵穿过碰撞区域


相关代码

关卡文件(Field01.js)

import Player from './Player.js';
export default class Field01 extends Phaser.Scene {
  constructor () {
    super({
      key: 'field01',
      physics: {
        arcade: {
          debug: true,
          gravity: { y: 0 }
        },
        matter: {
          debug: true,
          gravity: { y: 0 },
          debugShowBody: true,
          debugBodyColor: 0x0000ff
        }
      }
    });
  }
preload() 
  {
    this.load.image('tiles', 'assets/tilemaps/tiles/overworld-ground.png');
    this.load.tilemapTiledJSON('map', 'assets/tilemaps/maps/Field01.json');
    this.load.image('ground', 'assets/platform.png');
    this.load.spritesheet('dude', 'assets/sprites/characters/link.png', { frameWidth: 16, frameHeight: 24 });
  }
create() 
  {
    var platforms;
    var player;
    this.map = this.make.tilemap({ key: 'map' });
    this.map.landscape = this.map.addTilesetImage('fieldfare', 'tiles');
    const groundLayer = this.map.createLayer("ground", [this.map.landscape], 0, 0);
    this.map.setCollisionBetween(2,9);
    this.cameras.main.setSize(256,224);
    this.cameras.main.setBounds(0, 0, this.map.widthInPixels, this.map.heightInPixels);
    this.player = new Player(this, 352, 48);
    this.add.existing(Player);
    this.cameras.main.startFollow(this.player.sprite);
    this.cameras.main.setDeadzone(4,4);
    platforms = this.physics.add.staticGroup();
    platforms.create(64, 64, 'ground');
    groundLayer.setCollisionByProperty({ collides: true });
    this.matter.world.convertTilemapLayer(groundLayer);
    this.physics.add.collider(this.player.sprite, groundLayer);
    this.physics.add.collider(this.player.sprite, platforms, function(player, platforms) { this.scene.switch('Level1'), player.y += 48}, null, this);
  }
update() {
  this.player.update();
  }
}

玩家文件(Player.js)

export default class Player {
  constructor(scene, x, y) {
    this.scene = scene;
    const anims = scene.anims;
        anims.create({
            key: 'turn1',
            frames: [ { key: 'dude', frame: 32 } ],
            frameRate: 20
        });
        anims.create({
            key: 'rdown',
            frames: anims.generateFrameNumbers('dude', { start: 0, end: 7 }),
            frameRate: 16,
            repeat: -1
        });
        anims.create({
            key: 'rright',
            frames: anims.generateFrameNumbers('dude', { frames: [ 8, 9, 10, 11, 12, 13, 14, 15 ] }),
            frameRate: 16,
            repeat: -1
        });
        anims.create({
            key: 'rup',
            frames: anims.generateFrameNumbers('dude', { frames: [ 16, 17, 18, 19, 20, 21, 21, 23 ] }),
            frameRate: 16,
            repeat: -1
        });
        anims.create({
            key: 'rleft',
            frames: anims.generateFrameNumbers('dude', { frames: [ 24, 25, 26, 27, 28, 29, 30, 31 ] }),
            frameRate: 16,
            repeat: -1
        });
    this.sprite = scene.physics.add.sprite(x, y, "dude", 0).setSize(16, 16).setOffset(0, 8);
    this.sprite.anims.play("rdown");
    this.gamepad = scene.input.gamepad.once('down', function (pad, button, index) {
        this.gamepad = pad;
    }, this);
  }

  update() {
    const gamepad = this.gamepad;
    const sprite = this.sprite;
    const speed = 90;
    const prevVelocity = sprite.body.velocity.clone();
    sprite.body.setVelocity(0);
    if (gamepad.right && gamepad.up) {
      sprite.body.setVelocityX(speed);
      sprite.body.setVelocityY(-speed);
    } else if (gamepad.right && gamepad.down) {
      sprite.body.setVelocityX(speed);
      sprite.body.setVelocityY(speed);
    } else if (gamepad.left && gamepad.up) {
      sprite.body.setVelocityX(-speed);
      sprite.body.setVelocityY(-speed);
    } else if (gamepad.left && gamepad.down) {
      sprite.body.setVelocityX(-speed);
      sprite.body.setVelocityY(speed);
    } else if (gamepad.left) {
      sprite.body.setVelocityX(-speed);
    } else if (gamepad.right) {
      sprite.body.setVelocityX(speed);
    } else if (gamepad.up) {
      sprite.body.setVelocityY(-speed);
    } else if (gamepad.down) {
      sprite.body.setVelocityY(speed);
    }
    sprite.body.velocity.normalize().scale(speed);
    if (gamepad.left) {
      sprite.anims.play("rleft", true);
    } else if (gamepad.left && gamepad.down) {
      sprite.anims.play("rleft", true);
    } else if (gamepad.left && gamepad.up) {
      sprite.anims.play("rleft", true);
    } else if (gamepad.right && gamepad.down) {
      sprite.anims.play("rright", true);
    } else if (gamepad.right && gamepad.up) {
      sprite.anims.play("rright", true);
    } else if (gamepad.up) {
      sprite.anims.play("rup", true);
    } else if (gamepad.right) {
      sprite.anims.play("rright", true);
    } else if (gamepad.down) {
      sprite.anims.play("rdown", true);
    } else {
      sprite.stopOnFrame(sprite.anims.currentAnim.getFrameAt(0))
    }
  }
}

游戏配置文件(game.js)

import Field01 from "./Field01.js";

var config = {
  type: Phaser.AUTO,
  width: 256,
  height: 224,
  pixelArt: true,
  input: {
    gamepad: true
  },
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 0 },
      debug: true
    }
  },
  scene: [ Field01 ]
  }

var game = new Phaser.Game(config);

索引文件(index.html)

<!DOCTYPE html>
<html>
  <head>
    <script src="phaser.js"></script>
    <script src="matter.js"></script>
  </head>
  <body>
    <script type="module" src="game.js"></script>
  </body>
</html>

解决方案

方案1:不切换到Matter的临时解决思路

Arcade物理本身不支持多边形瓦片碰撞,可针对斜向瓦片单独设置自定义碰撞形状:

  1. 在Tiled编辑器中,选中斜向瓦片,编辑自定义碰撞多边形(匹配瓦片的对角线形状)
  2. 在Phaser中遍历瓦片,手动创建Arcade静态碰撞体:
// 替换Field01.js中create函数的碰撞代码
groundLayer.forEachTile(tile => {
  if (tile.properties.collides && tile.index === 你的斜向瓦片ID) {
    // 根据Tiled中的多边形数据创建静态碰撞体
    const collisionPoints = tile.collisionShapes[0].points.map(point => ({
      x: point.x + tile.pixelX,
      y: point.y + tile.pixelY
    }));
    const staticBody = this.physics.add.staticGroup();
    staticBody.create(tile.pixelX, tile.pixelY).setPolygon(collisionPoints);
    // 关闭原瓦片的矩形碰撞
    groundLayer.setCollision(tile.index, false);
  }
});
this.physics.add.collider(this.player.sprite, groundLayer);
this.physics.add.collider(this.player.sprite, this.physics.staticGroup.getChildren());

注意:该方法仅适合少量斜向瓦片,大量使用会影响性能。

方案2:切换到Matter物理引擎的完整步骤

如果必须迁移到Matter,按以下步骤最小化改动:

  1. 修改游戏配置:设置默认物理为Matter
var config = {
  type: Phaser.AUTO,
  width: 256,
  height: 224,
  pixelArt: true,
  input: {
    gamepad: true
  },
  physics: {
    default: 'matter',
    matter: {
      gravity: { y: 0 },
      debug: true
    }
  },
  scene: [ Field01 ]
};
  1. 修改玩家类:替换Arcade精灵为Matter精灵
// Player.js中替换sprite创建代码
this.sprite = scene.matter.add.sprite(x, y, "dude", 0).setSize(16, 16).setOffset(0, 8);
// 关闭旋转(俯视角不需要)
this.sprite.setFixedRotation();
// 设置矩形碰撞体(与原Arcade配置一致)
this.sprite.setBody({ type: 'rectangle', width: 16, height: 16 }, { x: 0, y: 8 });
  1. 调整玩家移动逻辑:适配Matter的速度控制
// Player.js的update函数中替换速度设置部分
sprite.setVelocity(0);
if (gamepad.right && gamepad.up) {
  sprite.setVelocity(speed, -speed);
} else if (gamepad.right && gamepad.down) {
  sprite.setVelocity(speed, speed);
} else if (gamepad.left && gamepad.up) {
  sprite.setVelocity(-speed, -speed);
} else if (gamepad.left && gamepad.down) {
  sprite.setVelocity(-speed, speed);
} else if (gamepad.left) {
  sprite.setVelocity(-speed, 0);
} else if (gamepad.right) {
  sprite.setVelocity(speed, 0);
} else if (gamepad.up) {
  sprite.setVelocity(0, -speed);
} else if (gamepad.down) {
  sprite.setVelocity(0, speed);
}
// 归一化速度保持最大移动速度
const velocity = sprite.body.velocity;
const speedRatio = speed / Math.hypot(velocity.x, velocity.y);
if (!isNaN(speedRatio)) {
  sprite.setVelocity(velocity.x * speedRatio, velocity.y * speedRatio);
}
  1. 修改关卡碰撞逻辑:使用Matter的碰撞处理
// Field01.js的create函数中,移除Arcade相关碰撞代码,保留Matter转换
// 移除 this.physics.add.collider(this.player.sprite, groundLayer);
this.matter.world.convertTilemapLayer(groundLayer);

// 平台组改为Matter静态组
platforms = this.matter.add.staticGroup();
platforms.create(64, 64, 'ground');

// Matter碰撞回调
this.matter.add.collision(this.player.sprite, platforms, (player, platform) => {
  this.scene.switch('Level1');
  player.y += 48;
}, null, this);
  1. 清理冗余代码:删除场景构造函数中的Arcade物理配置,以及所有Arcade相关的碰撞代码。

内容的提问来源于stack exchange,提问作者Kenzie Sanderson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 22:07:02