Pixi.js 游戏开发教程 / Sprite 与纹理
Sprite 与纹理
概述
Sprite(精灵)是 2D 游戏和图形应用中最基本的显示对象。它本质上是一张带有变换属性(位置、旋转、缩放等)的纹理矩形。理解 Sprite 和 Texture 是构建任何 Pixi.js 项目的基础。
Sprite 创建方式
方式一:使用 Assets 加载(推荐)
import { Application, Assets, Sprite } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600, background: '#1a1a2e' });
document.body.appendChild(app.canvas);
// 异步加载纹理并创建精灵
const texture = await Assets.load('/image/hero.png');
const hero = new Sprite(texture);
hero.anchor.set(0.5); // 锚点居中
hero.position.set(400, 300);
app.stage.addChild(hero);
方式二:Texture.from(同步,依赖缓存)
import { Texture, Sprite } from 'pixi.js';
// 如果纹理已缓存,直接创建;否则返回空白纹理(异步加载后自动替换)
const texture = Texture.from('/image/enemy.png');
const enemy = new Sprite(texture);
enemy.position.set(600, 300);
app.stage.addChild(enemy);
方式三:从已有纹理裁剪
import { Texture, Rectangle, Sprite } from 'pixi.js';
const baseTexture = await Assets.load('/image/spritesheet.png');
// 裁剪出特定区域
const frameRect = new Rectangle(0, 0, 64, 64);
const frameTexture = new Texture({
source: baseTexture.source,
frame: frameRect,
});
const sprite = new Sprite(frameTexture);
app.stage.addChild(sprite);
纹理(Texture)详解
Texture 与 BaseTexture 的关系
BaseTexture(基础纹理)
│
├── 存储原始图像数据(Image/Canvas/Video)
├── 管理 GPU 纹理资源
└── 可被多个 Texture 共享
│
Texture(纹理)
├── 引用一个 BaseTexture
├── 定义裁剪区域(frame)
├── 可设置旋转、修剪(trim)
└── 作为 Sprite 的渲染数据源
| 类 | 作用 | 说明 |
|---|---|---|
BaseTexture | 存储原始图像 | 一份图像数据对应一个 |
Texture | 定义图像的可视区域 | 一个 BaseTexture 可派生多个 |
纹理缓存
Pixi.js 自动缓存已加载的纹理,避免重复加载:
// 首次加载 —— 从网络/磁盘读取
const texture1 = await Assets.load('/image/hero.png');
// 再次获取 —— 从缓存返回(O(1))
const texture2 = Assets.get('/image/hero.png');
console.log(texture1 === texture2); // true
// 手动移除缓存
Assets.unload('/image/hero.png');
⚠️ 注意: 纹理缓存会占用显存。不再使用的纹理应及时调用 Assets.unload() 或 texture.destroy() 释放。
Sprite 属性
基础变换属性
const sprite = new Sprite(texture);
// 位置
sprite.x = 100;
sprite.y = 200;
// 或
sprite.position.set(100, 200);
// 缩放
sprite.scale.set(2, 2); // 2 倍放大
sprite.scale.x = 0.5; // 水平缩小一半
// 旋转(弧度,顺时针)
sprite.rotation = Math.PI / 4; // 45 度
// 锚点(0-1,相对于自身尺寸)
sprite.anchor.set(0.5, 0.5); // 中心点
// anchor(0,0) = 左上角, anchor(1,1) = 右下角
// 透明度
sprite.alpha = 0.5;
// 色调叠加
sprite.tint = 0xff0000; // 红色叠加
// 可见性
sprite.visible = false; // 完全不渲染
// 是否可交互
sprite.eventMode = 'static'; // 启用事件
sprite.cursor = 'pointer'; // 鼠标样式
属性速查表
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
position | ObservablePoint | {x:0, y:0} | 位置(像素) |
scale | ObservablePoint | {x:1, y:1} | 缩放倍数 |
rotation | number | 0 | 旋转(弧度) |
anchor | ObservablePoint | {x:0, y:0} | 锚点(0-1) |
alpha | number | 1 | 透明度(0-1) |
tint | number | 0xFFFFFF | 色调叠加 |
visible | boolean | true | 是否可见 |
width | number | 纹理宽度 | 实际渲染宽度(受 scale 影响) |
height | number | 纹理高度 | 实际渲染高度 |
Sprite 变换矩阵
Pixi.js 内部使用 3×3 仿射变换矩阵处理所有变换:
| a b tx | | scaleX * cos(θ) -sin(θ) x |
| c d ty | = | sin(θ) scaleY * cos(θ) y |
| 0 0 1 | | 0 0 1 |
// 直接操作矩阵(高级用法)
import { Matrix } from 'pixi.js';
const matrix = new Matrix();
matrix.translate(100, 200);
matrix.rotate(Math.PI / 4);
matrix.scale(2, 2);
sprite.setFromMatrix(matrix);
💡 提示: 日常开发中直接使用 position、rotation、scale 即可,无需直接操作矩阵。矩阵主要用于底层渲染和高级特效。
纹理区域(Rectangle)
import { Rectangle, Texture } from 'pixi.js';
const baseTexture = await Assets.load('/image/atlas.png');
// 定义裁剪区域
const frame1 = new Rectangle(0, 0, 32, 32);
const frame2 = new Rectangle(32, 0, 32, 32);
const frame3 = new Rectangle(64, 0, 32, 32);
// 创建裁剪纹理
const tex1 = new Texture({ source: baseTexture.source, frame: frame1 });
const tex2 = new Texture({ source: baseTexture.source, frame: frame2 });
const tex3 = new Texture({ source: baseTexture.source, frame: frame3 });
// 创建精灵并排列
[tex1, tex2, tex3].forEach((tex, i) => {
const sprite = new Sprite(tex);
sprite.x = i * 40;
sprite.y = 100;
app.stage.addChild(sprite);
});
精灵批处理原理
Pixi.js 的 WebGL 渲染器会自动将使用相同纹理的连续精灵合并为一次 draw call。
渲染流程:
Sprite A (tex1) ─┐
Sprite B (tex1) ├── 同一批次 → 1 次 draw call
Sprite C (tex1) ─┘
Sprite D (tex2) ─────→ 1 次 draw call
Sprite E (tex1) ─────→ 1 次 draw call(纹理切换,新批次)
优化建议
| 策略 | 说明 |
|---|---|
| 使用纹理图集(Atlas) | 将多个小图合并到一张大图,减少纹理切换 |
| 相同纹理的精灵连续添加 | 避免交叉排列不同纹理的精灵 |
| 使用 ParticleContainer | 大量相同纹理精灵时使用粒子容器 |
import { ParticleContainer } from 'pixi.js';
// 粒子容器 —— 专为大量同类精灵优化
const particles = new ParticleContainer(10000, {
position: true,
rotation: true,
scale: true,
tint: true,
});
for (let i = 0; i < 10000; i++) {
const p = new Sprite(commonTexture);
p.x = Math.random() * 800;
p.y = Math.random() * 600;
particles.addChild(p);
}
app.stage.addChild(particles);
⚠️ 注意: ParticleContainer 功能受限,不支持嵌套容器、滤镜、遮罩等。仅用于大量简单精灵的高性能渲染。
纹理集(TextureAtlas)
纹理集将多个小图打包到一张大图,并附带 JSON 描述文件。
JSON Atlas 格式示例
{
"frames": {
"walk_01.png": {
"frame": { "x": 0, "y": 0, "w": 64, "h": 64 },
"rotated": false,
"sourceSize": { "w": 64, "h": 64 }
},
"walk_02.png": {
"frame": { "x": 64, "y": 0, "w": 64, "h": 64 },
"rotated": false,
"sourceSize": { "w": 64, "h": 64 }
}
},
"meta": {
"image": "spritesheet.png",
"size": { "w": 256, "h": 256 }
}
}
使用 Spritesheet 加载
import { Assets, Spritesheet } from 'pixi.js';
const atlasData = await fetch('/image/atlas.json').then(r => r.json());
const atlasTexture = await Assets.load('/image/atlas.png');
const spritesheet = new Spritesheet(atlasTexture, atlasData);
await spritesheet.parse();
// 通过名称获取纹理
const walkFrame1 = spritesheet.animations['walk'][0];
const sprite = new Sprite(walkFrame1);
app.stage.addChild(sprite);
游戏开发场景
场景:角色精灵管理器
import { Application, Assets, Sprite, Texture } from 'pixi.js';
interface CharacterConfig {
name: string;
texturePath: string;
speed: number;
}
class Character {
sprite: Sprite;
speed: number;
name: string;
constructor(config: CharacterConfig, texture: Texture) {
this.name = config.name;
this.speed = config.speed;
this.sprite = new Sprite(texture);
this.sprite.anchor.set(0.5);
}
moveTo(targetX: number, targetY: number, delta: number) {
const dx = targetX - this.sprite.x;
const dy = targetY - this.sprite.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 1) {
this.sprite.x += (dx / dist) * this.speed * delta;
this.sprite.y += (dy / dist) * this.speed * delta;
this.sprite.rotation = Math.atan2(dy, dx);
}
}
}
// 使用示例
const app = new Application();
await app.init({ width: 800, height: 600, background: '#2c3e50' });
document.body.appendChild(app.canvas);
const heroTexture = await Assets.load('/image/hero.png');
const hero = new Character(
{ name: 'Hero', texturePath: '/image/hero.png', speed: 200 },
heroTexture
);
hero.sprite.position.set(400, 300);
app.stage.addChild(hero.sprite);
// 点击移动
app.stage.eventMode = 'static';
app.stage.hitArea = app.screen;
app.stage.on('pointerdown', (e) => {
const target = e.global;
app.ticker.add(function move(ticker) {
hero.moveTo(target.x, target.y, ticker.deltaTime);
const dx = target.x - hero.sprite.x;
const dy = target.y - hero.sprite.y;
if (Math.sqrt(dx * dx + dy * dy) < 2) {
app.ticker.remove(move);
}
});
});
⚠️ 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 精灵不显示 | 纹理未加载完成就创建 Sprite | 使用 await Assets.load() |
| 精灵位置偏移 | anchor 设置不正确 | 检查 anchor 设置 |
| 内存持续增长 | 纹理未释放 | 不用的纹理调用 .destroy() |
| 精灵模糊 | 纹理尺寸与显示尺寸不匹配 | 使用合适分辨率的图片 |
| tint 不生效 | 纹理本身为白色 | tint 对非白色纹理叠加效果明显 |
💡 进阶提示
纹理预乘 Alpha: PNG 带透明通道时,Pixi.js 默认预乘处理。如出现边缘黑边:
texture.source.alphaMode = 'no-premultiply-alpha';精灵池(对象复用):
class SpritePool { private pool: Sprite[] = []; private texture: Texture; constructor(texture: Texture) { this.texture = texture; } get(): Sprite { return this.pool.pop() || new Sprite(this.texture); } release(sprite: Sprite) { sprite.visible = false; this.pool.push(sprite); } }批量设置属性: Pixi.js v8 支持构造时传入配置:
const sprite = new Sprite({ texture, x: 100, y: 200, scale: 2, anchor: 0.5, });
扩展阅读
上一章:02 - Application 与渲染流程 下一章:04 - 容器与场景图