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