Phaser 物理引擎 #

物理引擎概述 #

Phaser 内置了两个物理引擎:Arcade Physics(轻量级)和 Matter.js(真实物理)。

text
┌─────────────────────────────────────────────────────────────┐
│                    物理引擎对比                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Arcade Physics                                            │
│  ├── 轻量级,高性能                                        │
│  ├── AABB 碰撞检测                                         │
│  ├── 适合简单平台游戏                                      │
│  ├── 无旋转碰撞                                            │
│  └── 学习成本低                                            │
│                                                             │
│  Matter.js                                                 │
│  ├── 真实物理模拟                                          │
│  ├── 复杂形状碰撞                                          │
│  ├── 支持旋转、约束                                        │
│  ├── 适合物理益智游戏                                      │
│  └── 功能强大                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Arcade Physics #

配置 #

javascript
const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 300 },
      debug: false
    }
  },
  scene: GameScene
};

创建物理精灵 #

javascript
create() {
  this.player = this.physics.add.sprite(100, 450, 'dude');
  
  this.player.setBounce(0.2);
  this.player.setCollideWorldBounds(true);
  
  this.player.setGravityY(300);
  
  this.player.setVelocity(100, 200);
  this.player.setVelocityX(160);
  this.player.setVelocityY(-330);
}

静态物理体 #

javascript
create() {
  this.platforms = this.physics.add.staticGroup();
  
  this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
  
  this.platforms.create(600, 400, 'ground');
  this.platforms.create(50, 250, 'ground');
  this.platforms.create(750, 220, 'ground');
}

动态物理体 #

javascript
create() {
  this.stars = this.physics.add.group({
    key: 'star',
    repeat: 11,
    setXY: { x: 12, y: 0, stepX: 70 }
  });
  
  this.stars.children.iterate((child) => {
    child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
  });
}

物理体属性 #

javascript
const sprite = this.physics.add.sprite(100, 100, 'player');

sprite.setVelocity(x, y);
sprite.setVelocityX(value);
sprite.setVelocityY(value);

sprite.setAcceleration(x, y);
sprite.setAccelerationX(value);
sprite.setAccelerationY(value);

sprite.setBounce(x, y);
sprite.setBounceX(value);
sprite.setBounceY(value);

sprite.setDrag(x, y);
sprite.setDragX(value);
sprite.setDragY(value);

sprite.setGravity(x, y);
sprite.setGravityX(value);
sprite.setGravityY(value);

sprite.setMass(value);

sprite.setAngularVelocity(value);
sprite.setAngularDrag(value);

sprite.setImmovable(true);

sprite.setCollideWorldBounds(true);

sprite.body.allowGravity = false;

sprite.setFriction(x, y);

sprite.setMaxVelocity(x, y);

sprite.setOffset(x, y);

sprite.setSize(width, height);

sprite.setCircle(radius, offsetX, offsetY);

碰撞检测 #

javascript
create() {
  this.physics.add.collider(this.player, this.platforms);
  
  this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
}

collectStar(player, star) {
  star.disableBody(true, true);
  
  this.score += 10;
  this.scoreText.setText('Score: ' + this.score);
}

碰撞回调 #

javascript
create() {
  this.physics.add.collider(this.player, this.platforms, null, this.checkCollision, this);
}

checkCollision(player, platform) {
  return player.y < platform.y;
}

分组碰撞 #

javascript
create() {
  this.enemies = this.physics.add.group();
  this.bullets = this.physics.add.group();
  
  this.physics.add.collider(this.enemies, this.platforms);
  this.physics.add.collider(this.bullets, this.platforms, this.bulletHitPlatform, null, this);
  this.physics.add.overlap(this.player, this.enemies, this.hitEnemy, null, this);
  this.physics.add.overlap(this.bullets, this.enemies, this.bulletHitEnemy, null, this);
}

物理体调试 #

javascript
const config = {
  physics: {
    default: 'arcade',
    arcade: {
      debug: true,
      debug: {
        showBody: true,
        showStaticBody: true,
        showVelocity: true,
        bodyColor: 0xff0000,
        staticBodyColor: 0x0000ff,
        velocityColor: 0x00ff00
      }
    }
  }
};

手动碰撞检测 #

javascript
update() {
  if (this.physics.world.overlap(this.player, this.enemy)) {
    this.handleCollision();
  }
  
  if (this.physics.world.collide(this.player, this.platforms)) {
    console.log('Colliding with platform');
  }
}

射线检测 #

javascript
const ray = new Phaser.Geom.Line(
  this.player.x, this.player.y,
  this.enemy.x, this.enemy.y
);

const hit = this.physics.raycast(
  this.player.x, this.player.y,
  this.enemy.x, this.enemy.y,
  this.platforms
);

移动向目标 #

javascript
this.physics.moveTo(this.player, targetX, targetY, speed);

this.physics.moveToObject(this.player, this.enemy, 200);

this.physics.moveToPointer(this.player, 200);

const velocity = this.physics.velocityFromAngle(45, 200);
this.player.setVelocity(velocity.x, velocity.y);

const angle = this.physics.angleToXY(this.player, targetX, targetY);
const velocity = this.physics.velocityFromAngle(angle * 180 / Math.PI, 200);

Matter.js #

配置 #

javascript
const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: 'matter',
    matter: {
      gravity: { y: 1 },
      debug: false
    }
  },
  scene: GameScene
};

创建物理体 #

javascript
create() {
  this.player = this.matter.add.sprite(100, 100, 'player');
  
  this.player.setFriction(0.1);
  this.player.setBounce(0.5);
  this.player.setDensity(0.01);
  
  this.matter.add.rectangle(400, 550, 800, 50, { isStatic: true });
  
  this.matter.add.circle(400, 300, 50);
  
  this.matter.add.polygon(400, 200, 6, 50);
}

物理体形状 #

javascript
this.matter.add.rectangle(x, y, width, height, options);

this.matter.add.circle(x, y, radius, options);

this.matter.add.polygon(x, y, sides, radius, options);

this.matter.add.trapezoid(x, y, width, height, slope, options);

this.matter.add.fromVertices(x, y, vertexSets, options);

this.matter.add.gameObject(sprite, {
  shape: {
    type: 'polygon',
    sides: 6,
    radius: 50
  }
});

静态物体 #

javascript
this.matter.add.rectangle(400, 550, 800, 50, {
  isStatic: true
});

this.matter.add.rectangle(400, 300, 200, 20, {
  isStatic: true,
  angle: Math.PI / 4
});

物理属性 #

javascript
const body = this.matter.add.circle(400, 300, 50);

body.setDensity(0.01);

body.setMass(10);

body.setFriction(0.1);
body.setFrictionAir(0.01);
body.setFrictionStatic(0.5);

body.setBounce(0.8);

body.setVelocity({ x: 10, y: 0 });

body.setAngularVelocity(0.1);

body.setPosition({ x: 100, y: 100 });

body.setAngle(Math.PI / 4);

body.setStatic(true);

body.setSensor(true);

body.setFixedRotation(true);

约束 #

javascript
const bodyA = this.matter.add.circle(200, 200, 20);
const bodyB = this.matter.add.circle(300, 200, 20);

this.matter.add.constraint(bodyA, bodyB, 100, 0.1);

this.matter.add.spring(bodyA, bodyB, 100, 0.1);

this.matter.add.worldConstraint(body, 200, 0.1, {
  pointA: { x: 400, y: 100 }
});

碰撞事件 #

javascript
create() {
  this.matter.world.on('collisionstart', (event) => {
    event.pairs.forEach((pair) => {
      const bodyA = pair.bodyA;
      const bodyB = pair.bodyB;
      
      console.log('Collision between:', bodyA.label, bodyB.label);
    });
  });
  
  this.matter.world.on('collisionactive', (event) => {
    
  });
  
  this.matter.world.on('collisionend', (event) => {
    
  });
}

碰撞分类 #

javascript
const CATEGORY_PLAYER = 0x0001;
const CATEGORY_ENEMY = 0x0002;
const CATEGORY_PLATFORM = 0x0004;

this.player = this.matter.add.sprite(100, 100, 'player', null, {
  collisionFilter: {
    category: CATEGORY_PLAYER,
    mask: CATEGORY_ENEMY | CATEGORY_PLATFORM
  }
});

this.enemy = this.matter.add.sprite(300, 100, 'enemy', null, {
  collisionFilter: {
    category: CATEGORY_ENEMY,
    mask: CATEGORY_PLAYER | CATEGORY_PLATFORM
  }
});

this.platform = this.matter.add.rectangle(400, 550, 800, 50, {
  isStatic: true,
  collisionFilter: {
    category: CATEGORY_PLATFORM,
    mask: CATEGORY_PLAYER | CATEGORY_ENEMY
  }
});

传感器 #

javascript
const sensor = this.matter.add.rectangle(400, 300, 100, 100, {
  isSensor: true,
  isStatic: true,
  label: 'trigger'
});

this.matter.world.on('collisionstart', (event) => {
  event.pairs.forEach((pair) => {
    if (pair.bodyA.label === 'trigger' || pair.bodyB.label === 'trigger') {
      console.log('Entered trigger zone');
    }
  });
});

复合体 #

javascript
const car = this.matter.add.rectangle(400, 300, 100, 40);
const wheelA = this.matter.add.circle(370, 330, 15);
const wheelB = this.matter.add.circle(430, 330, 15);

const compound = this.matter.body.create({
  parts: [car, wheelA, wheelB]
});

this.matter.add.constraint(wheelA, car, 30, 0.1, {
  pointA: { x: 0, y: 0 },
  pointB: { x: -30, y: 30 }
});

Matter.js 调试 #

javascript
const config = {
  physics: {
    default: 'matter',
    matter: {
      debug: {
        showBody: true,
        showStaticBody: true,
        showInternalEdges: true,
        showVelocity: true,
        showCollisions: true,
        showAxes: true,
        showAngleIndicator: true,
        bodyColor: 0xff0000,
        staticBodyColor: 0x0000ff
      }
    }
  }
};

物理体类型 #

Arcade Physics 类型 #

text
┌─────────────────────────────────────────────────────────────┐
│                    Arcade 物理体类型                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Dynamic Body                                               │
│  ├── 受重力影响                                            │
│  ├── 受速度影响                                            │
│  ├── 可以移动                                              │
│  └── 适合玩家、敌人                                        │
│                                                             │
│  Static Body                                                │
│  ├── 不受重力影响                                          │
│  ├── 固定位置                                              │
│  ├── 高性能                                                │
│  └── 适合地面、平台                                        │
│                                                             │
│  Kinematic Body                                             │
│  ├── 不受重力影响                                          │
│  ├── 可以移动                                              │
│  ├── 不受碰撞影响                                          │
│  └── 适合移动平台                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Matter.js 类型 #

javascript
const dynamic = this.matter.add.circle(400, 100, 50);

const static = this.matter.add.rectangle(400, 550, 800, 50, {
  isStatic: true
});

const kinematic = this.matter.add.rectangle(400, 300, 100, 20, {
  isStatic: true,
  ignoreGravity: true
});

完整示例 #

平台游戏 #

javascript
class PlatformGame extends Phaser.Scene {
  constructor() {
    super({ key: 'PlatformGame' });
  }
  
  preload() {
    this.load.image('sky', 'assets/sky.png');
    this.load.image('ground', 'assets/platform.png');
    this.load.image('star', 'assets/star.png');
    this.load.image('bomb', 'assets/bomb.png');
    this.load.spritesheet('dude', 'assets/dude.png', {
      frameWidth: 32,
      frameHeight: 48
    });
  }
  
  create() {
    this.add.image(400, 300, 'sky');
    
    this.platforms = this.physics.add.staticGroup();
    this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
    this.platforms.create(600, 400, 'ground');
    this.platforms.create(50, 250, 'ground');
    this.platforms.create(750, 220, 'ground');
    
    this.player = this.physics.add.sprite(100, 450, 'dude');
    this.player.setBounce(0.2);
    this.player.setCollideWorldBounds(true);
    
    this.anims.create({
      key: 'left',
      frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
      frameRate: 10,
      repeat: -1
    });
    
    this.anims.create({
      key: 'turn',
      frames: [ { key: 'dude', frame: 4 } ],
      frameRate: 20
    });
    
    this.anims.create({
      key: 'right',
      frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
      frameRate: 10,
      repeat: -1
    });
    
    this.cursors = this.input.keyboard.createCursorKeys();
    
    this.stars = this.physics.add.group({
      key: 'star',
      repeat: 11,
      setXY: { x: 12, y: 0, stepX: 70 }
    });
    
    this.stars.children.iterate((child) => {
      child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
    });
    
    this.score = 0;
    this.scoreText = this.add.text(16, 16, 'Score: 0', {
      fontSize: '32px',
      fill: '#fff'
    });
    
    this.physics.add.collider(this.player, this.platforms);
    this.physics.add.collider(this.stars, this.platforms);
    this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
  }
  
  update() {
    if (this.cursors.left.isDown) {
      this.player.setVelocityX(-160);
      this.player.anims.play('left', true);
    } else if (this.cursors.right.isDown) {
      this.player.setVelocityX(160);
      this.player.anims.play('right', true);
    } else {
      this.player.setVelocityX(0);
      this.player.anims.play('turn');
    }
    
    if (this.cursors.up.isDown && this.player.body.touching.down) {
      this.player.setVelocityY(-330);
    }
  }
  
  collectStar(player, star) {
    star.disableBody(true, true);
    
    this.score += 10;
    this.scoreText.setText('Score: ' + this.score);
  }
}

下一步 #

现在你已经掌握了物理引擎,接下来学习 动画系统,了解如何创建流畅的游戏动画!

最后更新:2026-03-29