如何让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物理对象,工作量过大且超出当前技术范围
相关截图:

相关代码
关卡文件(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物理本身不支持多边形瓦片碰撞,可针对斜向瓦片单独设置自定义碰撞形状:
- 在Tiled编辑器中,选中斜向瓦片,编辑自定义碰撞多边形(匹配瓦片的对角线形状)
- 在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,按以下步骤最小化改动:
- 修改游戏配置:设置默认物理为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 ] };
- 修改玩家类:替换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 });
- 调整玩家移动逻辑:适配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); }
- 修改关卡碰撞逻辑:使用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);
- 清理冗余代码:删除场景构造函数中的Arcade物理配置,以及所有Arcade相关的碰撞代码。
内容的提问来源于stack exchange,提问作者Kenzie Sanderson
相关产品推荐
相关产品推荐

