Phaser 精灵系统 #

精灵概述 #

精灵(Sprite)是游戏中最常用的游戏对象,用于显示带有纹理的 2D 图像,并支持动画播放。

text
┌─────────────────────────────────────────────────────────────┐
│                    精灵系统架构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Sprite                                                     │
│  ├── 纹理(Texture)                                        │
│  ├── 帧(Frame)                                            │
│  ├── 动画(Animation)                                      │
│  ├── 物理体(Body)                                         │
│  └── 输入交互(Input)                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

创建精灵 #

基本创建 #

javascript
const sprite = this.add.sprite(x, y, 'textureKey');

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

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

const sprite = this.add.sprite(100, 100, 'spritesheet', 0);

物理精灵 #

javascript
const sprite = this.physics.add.sprite(x, y, 'textureKey');

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

sprite.setCollideWorldBounds(true);
sprite.setBounce(0.2);
sprite.setGravityY(300);

批量创建 #

javascript
const sprites = [];

for (let i = 0; i < 10; i++) {
  const sprite = this.add.sprite(
    Phaser.Math.Between(50, 750),
    Phaser.Math.Between(50, 550),
    'enemy'
  );
  sprites.push(sprite);
}

精灵表(Spritesheet) #

加载精灵表 #

javascript
preload() {
  this.load.spritesheet('key', 'path', {
    frameWidth: 32,
    frameHeight: 48
  });
  
  this.load.spritesheet('dude', 'assets/dude.png', {
    frameWidth: 32,
    frameHeight: 48
  });
  
  this.load.spritesheet('explosion', 'assets/explosion.png', {
    frameWidth: 64,
    frameHeight: 64,
    endFrame: 16
  });
  
  this.load.spritesheet('button', 'assets/button.png', {
    frameWidth: 200,
    frameHeight: 50,
    startFrame: 0,
    endFrame: 2,
    margin: 0,
    spacing: 0
  });
}

精灵表结构 #

text
┌─────────────────────────────────────────────────────────────┐
│                    精灵表布局                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  frameWidth: 32, frameHeight: 48                            │
│                                                             │
│  ┌────┬────┬────┬────┬────┬────┬────┬────┬────┐           │
│  │ 0  │ 1  │ 2  │ 3  │ 4  │ 5  │ 6  │ 7  │ 8  │           │
│  │    │    │    │    │    │    │    │    │    │           │
│  └────┴────┴────┴────┴────┴────┴────┴────┴────┘           │
│                                                             │
│  帧索引从左到右,从上到下编号                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

设置帧 #

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

sprite.setFrame(0);

sprite.setFrame(4);

sprite.setFrame('walk_01');

精灵动画 #

创建动画 #

javascript
create() {
  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
  });
}

动画配置 #

javascript
this.anims.create({
  key: 'walk',
  frames: this.anims.generateFrameNumbers('player', { start: 0, end: 7 }),
  frameRate: 12,
  repeat: -1,
  repeatDelay: 0,
  yoyo: false,
  delay: 0,
  duration: null,
  showOnStart: false,
  hideOnComplete: false,
  skipMissedFrames: true
});

生成帧 #

javascript
const frames = this.anims.generateFrameNumbers('dude', {
  start: 0,
  end: 7
});

const frames = this.anims.generateFrameNumbers('dude', {
  start: 0,
  end: 7,
  first: 4
});

const frames = this.anims.generateFrameNumbers('dude', {
  frames: [0, 1, 2, 3, 4, 3, 2, 1, 0]
});

const frames = this.anims.generateFrameNames('atlas', {
  prefix: 'walk_',
  start: 0,
  end: 7,
  suffix: '.png'
});

const frames = this.anims.generateFrameNames('atlas', {
  prefix: 'player_',
  suffix: '.png',
  zeroPad: 2,
  start: 1,
  end: 10
});

播放动画 #

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

sprite.anims.play('left');

sprite.anims.play('left', true);

sprite.anims.playAfterDelay('right', 2000);

sprite.anims.playAfterRepeat('turn', 3);

sprite.anims.chain(['walk', 'run', 'jump']);

sprite.anims.stop();

sprite.anims.stopOnFrame(sprite.anims.currentAnim.frames[3]);

sprite.anims.pause();
sprite.anims.resume();

动画事件 #

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

sprite.on('animationstart', (animation, frame) => {
  console.log('Animation started:', animation.key);
});

sprite.on('animationcomplete', (animation, frame) => {
  console.log('Animation completed:', animation.key);
});

sprite.on('animationrepeat', (animation, frame) => {
  console.log('Animation repeated:', animation.key);
});

sprite.on('animationupdate', (animation, frame, gameObject) => {
  console.log('Frame:', frame.textureFrame);
});

动画管理 #

javascript
const anim = this.anims.get('walk');

const exists = this.anims.exists('walk');

this.anims.remove('walk');

const keys = this.anims.getAll();

this.anims.pauseAll();
this.anims.resumeAll();

精灵属性 #

位置与变换 #

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

sprite.x = 200;
sprite.y = 300;
sprite.setPosition(200, 300);

sprite.scaleX = 2;
sprite.scaleY = 2;
sprite.setScale(2, 2);
sprite.setScale(2);

sprite.rotation = Math.PI / 4;
sprite.angle = 45;
sprite.setRotation(Math.PI / 4);

sprite.setOrigin(0.5, 0.5);
sprite.setOrigin(0, 0);
sprite.setOrigin(1, 1);

显示属性 #

javascript
sprite.alpha = 0.5;
sprite.setAlpha(0.5);

sprite.visible = true;
sprite.setVisible(true);

sprite.depth = 10;
sprite.setDepth(10);

sprite.setTint(0xff0000);
sprite.setTint(0xff0000, 0x00ff00, 0x0000ff, 0xffff00);
sprite.clearTint();

sprite.setFlipX(true);
sprite.setFlipY(true);
sprite.setFlip(true, true);
sprite.resetFlip();

尺寸属性 #

javascript
sprite.width = 64;
sprite.height = 64;
sprite.setSize(64, 64);
sprite.setDisplaySize(64, 64);

sprite.scaleX = sprite.width / originalWidth;
sprite.scaleY = sprite.height / originalHeight;

精灵交互 #

启用交互 #

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

sprite.setInteractive();

sprite.setInteractive({
  useHandCursor: true
});

sprite.setInteractive(new Phaser.Geom.Rectangle(0, 0, 100, 50), Phaser.Geom.Rectangle.Contains);

交互事件 #

javascript
sprite.on('pointerdown', (pointer, localX, localY, event) => {
  console.log('Clicked!');
});

sprite.on('pointerup', (pointer, localX, localY, event) => {
  console.log('Released!');
});

sprite.on('pointerover', (pointer, localX, localY, event) => {
  sprite.setTint(0xcccccc);
});

sprite.on('pointerout', (pointer, event) => {
  sprite.clearTint();
});

sprite.on('pointermove', (pointer, localX, localY, event) => {
  console.log('Moving:', localX, localY);
});

sprite.on('wheel', (pointer, deltaX, deltaY, deltaZ, event) => {
  console.log('Wheel:', deltaY);
});

拖拽功能 #

javascript
const sprite = this.add.sprite(100, 100, 'draggable')
  .setInteractive({ draggable: true });

this.input.setDraggable(sprite);

this.input.on('dragstart', (pointer, gameObject) => {
  gameObject.setTint(0xff0000);
});

this.input.on('drag', (pointer, gameObject, dragX, dragY) => {
  gameObject.x = dragX;
  gameObject.y = dragY;
});

this.input.on('dragend', (pointer, gameObject) => {
  gameObject.clearTint();
});

sprite.setInteractive({ draggable: true, dragAngle: 0 });

this.input.setDraggable(sprite, true);

物理精灵 #

Arcade 物理精灵 #

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

sprite.setVelocity(100, 200);
sprite.setVelocityX(100);
sprite.setVelocityY(200);

sprite.setAcceleration(10, 20);

sprite.setBounce(0.5);
sprite.setBounce(0.5, 0.8);

sprite.setCollideWorldBounds(true);

sprite.setGravity(0, 300);
sprite.setGravityY(300);

sprite.setDrag(100);
sprite.setAngularDrag(100);

sprite.setAngularVelocity(45);

sprite.setImmovable(true);

sprite.body.allowGravity = false;

sprite.setMass(10);

sprite.setOffset(10, 10);
sprite.setSize(32, 48);
sprite.setCircle(16);

Matter.js 物理精灵 #

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

sprite.setFriction(0.1);
sprite.setBounce(0.5);
sprite.setStatic(true);
sprite.setSensor(true);
sprite.setDensity(0.01);

精灵池 #

创建精灵池 #

javascript
class Bullet extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y, 'bullet');
  }
  
  fire(x, y, velocityX, velocityY) {
    this.setPosition(x, y);
    this.setActive(true);
    this.setVisible(true);
    this.setVelocity(velocityX, velocityY);
  }
  
  deactivate() {
    this.setActive(false);
    this.setVisible(false);
    this.setVelocity(0, 0);
  }
}

class GameScene extends Phaser.Scene {
  create() {
    this.bulletPool = this.physics.add.group({
      classType: Bullet,
      maxSize: 30,
      runChildUpdate: true
    });
  }
  
  fireBullet(x, y) {
    const bullet = this.bulletPool.get();
    if (bullet) {
      bullet.fire(x, y, 0, -300);
    }
  }
}

使用 Group 管理精灵 #

javascript
this.enemies = this.add.group({
  key: 'enemy',
  repeat: 10,
  setXY: { x: 100, y: 100, stepX: 70 }
});

this.enemies.children.iterate((enemy) => {
  enemy.setTint(0xff0000);
});

this.enemies.add(sprite);

this.enemies.remove(sprite, true, true);

this.enemies.clear(true, true);

精灵纹理 #

动态纹理 #

javascript
const texture = this.textures.createCanvas('dynamic', 64, 64);
const ctx = texture.getContext();
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, 64, 64);
texture.refresh();

const sprite = this.add.sprite(100, 100, 'dynamic');

更换纹理 #

javascript
sprite.setTexture('newTexture');

sprite.setTexture('atlas', 'newFrame');

const texture = this.textures.get('player');
sprite.setTexture(texture);

纹理裁剪 #

javascript
sprite.setCrop(0, 0, 32, 48);

sprite.setCrop();

const rect = sprite.getCrop();

自定义精灵类 #

创建自定义精灵 #

javascript
class Player extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y, 'player');
    
    scene.add.existing(this);
    scene.physics.add.existing(this);
    
    this.speed = 200;
    this.health = 100;
    
    this.setCollideWorldBounds(true);
    this.setBounce(0.2);
  }
  
  moveLeft() {
    this.setVelocityX(-this.speed);
    this.anims.play('left', true);
  }
  
  moveRight() {
    this.setVelocityX(this.speed);
    this.anims.play('right', true);
  }
  
  stop() {
    this.setVelocityX(0);
    this.anims.play('turn');
  }
  
  jump() {
    if (this.body.touching.down) {
      this.setVelocityY(-330);
    }
  }
  
  takeDamage(amount) {
    this.health -= amount;
    if (this.health <= 0) {
      this.die();
    }
  }
  
  die() {
    this.setActive(false);
    this.setVisible(false);
  }
}

class GameScene extends Phaser.Scene {
  create() {
    this.player = new Player(this, 100, 450);
  }
  
  update() {
    if (this.cursors.left.isDown) {
      this.player.moveLeft();
    } else if (this.cursors.right.isDown) {
      this.player.moveRight();
    } else {
      this.player.stop();
    }
    
    if (this.cursors.up.isDown) {
      this.player.jump();
    }
  }
}

精灵预制体 #

javascript
class Enemy extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y, type) {
    super(scene, x, y, `enemy-${type}`);
    
    scene.add.existing(this);
    scene.physics.add.existing(this);
    
    this.type = type;
    this.health = this.getHealthByType(type);
    this.speed = this.getSpeedByType(type);
  }
  
  getHealthByType(type) {
    const healthMap = {
      'small': 50,
      'medium': 100,
      'large': 200
    };
    return healthMap[type] || 100;
  }
  
  getSpeedByType(type) {
    const speedMap = {
      'small': 150,
      'medium': 100,
      'large': 50
    };
    return speedMap[type] || 100;
  }
  
  update() {
    if (this.active) {
      this.scene.physics.moveToObject(this, this.scene.player, this.speed);
    }
  }
}

完整示例 #

玩家精灵 #

javascript
class Player extends Phaser.Physics.Arcade.Sprite {
  constructor(scene, x, y) {
    super(scene, x, y, 'dude');
    
    scene.add.existing(this);
    scene.physics.add.existing(this);
    
    this.setBounce(0.2);
    this.setCollideWorldBounds(true);
    
    this.score = 0;
    this.lives = 3;
    this.isInvincible = false;
  }
  
  move(cursors) {
    if (cursors.left.isDown) {
      this.setVelocityX(-160);
      this.anims.play('left', true);
    } else if (cursors.right.isDown) {
      this.setVelocityX(160);
      this.anims.play('right', true);
    } else {
      this.setVelocityX(0);
      this.anims.play('turn');
    }
    
    if (cursors.up.isDown && this.body.touching.down) {
      this.setVelocityY(-330);
    }
  }
  
  collectItem(points) {
    this.score += points;
    this.scene.events.emit('scoreUpdate', this.score);
  }
  
  takeDamage() {
    if (this.isInvincible) return;
    
    this.lives--;
    this.scene.events.emit('livesUpdate', this.lives);
    
    if (this.lives <= 0) {
      this.die();
      return;
    }
    
    this.isInvincible = true;
    this.setTint(0xff0000);
    
    this.scene.time.delayedCall(2000, () => {
      this.isInvincible = false;
      this.clearTint();
    });
  }
  
  die() {
    this.scene.events.emit('gameOver', { score: this.score });
  }
}

下一步 #

现在你已经掌握了精灵系统,接下来学习 输入处理,了解如何处理键盘、鼠标和触摸输入!

最后更新:2026-03-29