Pixi.js 游戏开发教程 / 图形绘制(Graphics)
图形绘制(Graphics)
概述
Graphics 是 Pixi.js 中用于程序化绘制矢量图形的显示对象。它可以绘制矩形、圆形、多边形、线条、贝塞尔曲线等,支持填充和描边。Graphics 常用于绘制 UI 元素(血条、按钮)、调试辅助线、粒子效果、以及原型阶段的游戏对象。
基本图形
矩形
import { Application, Graphics } from 'pixi.js';
const app = new Application();
await app.init({ width: 800, height: 600, background: '#1a1a2e' });
document.body.appendChild(app.canvas);
const rect = new Graphics();
rect.rect(0, 0, 120, 80); // x, y, width, height
rect.fill(0x3498db); // 填充蓝色
rect.stroke({ width: 2, color: 0xffffff }); // 描边白色
rect.position.set(100, 100);
app.stage.addChild(rect);
圆角矩形
const roundedRect = new Graphics();
roundedRect.roundRect(0, 0, 200, 100, 16); // 最后一个参数是圆角半径
roundedRect.fill(0x2ecc71);
roundedRect.position.set(100, 200);
app.stage.addChild(roundedRect);
圆形
const circle = new Graphics();
circle.circle(0, 0, 50); // cx, cy, radius
circle.fill(0xe74c3c);
circle.stroke({ width: 3, color: 0xc0392b });
circle.position.set(400, 150);
app.stage.addChild(circle);
椭圆
const ellipse = new Graphics();
ellipse.ellipse(0, 0, 80, 40); // cx, cy, halfWidth, halfHeight
ellipse.fill(0xf39c12);
ellipse.position.set(400, 300);
app.stage.addChild(ellipse);
线条
const line = new Graphics();
line.moveTo(50, 50);
line.lineTo(200, 150);
line.lineTo(350, 80);
line.stroke({ width: 3, color: 0xecf0f1 });
line.position.set(100, 350);
app.stage.addChild(line);
多边形
const polygon = new Graphics();
// 三角形
polygon.poly([0, -50, -43, 25, 43, 25], true); // 闭合
polygon.fill(0x9b59b6);
polygon.position.set(600, 150);
app.stage.addChild(polygon);
填充与描边
填充样式
const g = new Graphics();
// 纯色填充
g.rect(0, 0, 100, 100);
g.fill(0xff0000); // 红色
// 带透明度
g.rect(120, 0, 100, 100);
g.fill({ color: 0x00ff00, alpha: 0.5 });
// 渐变填充(线性渐变)
g.rect(240, 0, 100, 100);
g.fill({
fill: {
type: 'linear',
stops: [
{ offset: 0, color: 0xff0000 },
{ offset: 1, color: 0x0000ff },
],
},
});
app.stage.addChild(g);
描边样式
const g = new Graphics();
// 基本描边
g.circle(0, 0, 50);
g.stroke({ width: 2, color: 0xffffff });
// 完整描边配置
g.circle(120, 0, 50);
g.stroke({
width: 4,
color: 0xf1c40f,
alpha: 0.8,
cap: 'round', // 线端样式: 'butt' | 'round' | 'square'
join: 'round', // 连接样式: 'miter' | 'round' | 'bevel'
alignment: 0.5, // 对齐: 0=内侧, 1=外侧, 0.5=居中
});
app.stage.addChild(g);
线条样式详解
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width | number | 1 | 线宽(像素) |
color | number | 0xffffff | 线条颜色 |
alpha | number | 1 | 透明度 |
cap | string | ‘butt’ | 线端形状 |
join | string | ‘miter’ | 拐角形状 |
alignment | number | 0.5 | 描边对齐(0-1) |
miterLimit | number | 10 | 尖角限制 |
贝塞尔曲线
二次贝塞尔曲线
const quad = new Graphics();
quad.moveTo(0, 100);
quad.quadraticCurveTo(100, 0, 200, 100); // cpX, cpY, toX, toY
quad.stroke({ width: 2, color: 0xe67e22 });
quad.position.set(100, 400);
app.stage.addChild(quad);
三次贝塞尔曲线
const cubic = new Graphics();
cubic.moveTo(0, 100);
cubic.bezierCurveTo(50, 0, 150, 200, 200, 100); // cp1X, cp1Y, cp2X, cp2Y, toX, toY
cubic.stroke({ width: 2, color: 0x1abc9c });
cubic.position.set(350, 400);
app.stage.addChild(cubic);
圆弧与椭圆弧
const arc = new Graphics();
arc.arc(0, 0, 80, 0, Math.PI * 1.5, false); // cx, cy, r, startAngle, endAngle, anticlockwise
arc.stroke({ width: 3, color: 0x8e44ad });
arc.position.set(600, 400);
app.stage.addChild(arc);
绘制路径(Path)
const path = new Graphics();
// 手动构建路径
path.moveTo(0, 0);
path.lineTo(50, -30);
path.lineTo(100, 0);
path.lineTo(100, 50);
path.lineTo(50, 80);
path.lineTo(0, 50);
path.closePath();
path.fill(0x34495e);
path.stroke({ width: 2, color: 0xecf0f1 });
path.position.set(400, 300);
app.stage.addChild(path);
多段绘制
一个 Graphics 对象可以包含多个独立的绘图命令:
const multi = new Graphics();
// 第一段 —— 红色矩形
multi.rect(0, 0, 80, 80);
multi.fill(0xe74c3c);
// 第二段 —— 蓝色圆形(独立区域)
multi.circle(200, 40, 40);
multi.fill(0x3498db);
// 第三段 —— 绿色线条
multi.moveTo(300, 0);
multi.lineTo(400, 80);
multi.stroke({ width: 3, color: 0x2ecc71 });
multi.position.set(50, 450);
app.stage.addChild(multi);
💡 提示: 多段绘制共享同一个 Graphics 对象,减少 draw call。适合需要批量绘制静态图形的场景。
动态绘图
const dynamicGraphics = new Graphics();
app.stage.addChild(dynamicGraphics);
let angle = 0;
app.ticker.add((ticker) => {
angle += 0.02 * ticker.deltaTime;
// 每帧清除并重绘
dynamicGraphics.clear();
// 动态波浪线
dynamicGraphics.moveTo(0, 300);
for (let x = 0; x <= 800; x += 5) {
const y = 300 + Math.sin(x * 0.02 + angle) * 50;
dynamicGraphics.lineTo(x, y);
}
dynamicGraphics.stroke({ width: 2, color: 0x00ffff });
});
⚠️ 注意: 频繁调用 clear() 并重绘会影响性能。对于需要动态变化的图形,考虑限制重绘频率或使用 renderTexture。
Graphics 与 Sprite 性能对比
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 静态几何图形 | Graphics | 矢量绘制,无需图片资源 |
| 大量相同图形 | Sprite | GPU 纹理批处理更高效 |
| 频繁变化的图形 | Graphics | 直接修改顶点数据 |
| 复杂纹理对象 | Sprite | 纹理已预渲染,开销固定 |
| UI 元素(血条等) | Graphics | 程序化绘制,可动态变化 |
| 粒子效果(>1000) | Sprite+Pool | 精灵批处理性能远超 Graphics |
渐变填充
线性渐变
const gradRect = new Graphics();
gradRect.rect(0, 0, 300, 100);
gradRect.fill({
fill: {
type: 'linear',
stops: [
{ offset: 0, color: '#ff0000' },
{ offset: 0.5, color: '#00ff00' },
{ offset: 1, color: '#0000ff' },
],
},
});
gradRect.position.set(100, 100);
app.stage.addChild(gradRect);
径向渐变
const gradCircle = new Graphics();
gradCircle.circle(0, 0, 80);
gradCircle.fill({
fill: {
type: 'radial',
stops: [
{ offset: 0, color: '#ffff00' },
{ offset: 1, color: '#ff0000' },
],
},
});
gradCircle.position.set(400, 150);
app.stage.addChild(gradCircle);
游戏开发场景
场景:血条系统
class HealthBar {
container: Container;
bg: Graphics;
bar: Graphics;
maxWidth: number;
constructor(width: number, height: number, color: number) {
this.maxWidth = width;
this.container = new Container();
// 背景
this.bg = new Graphics();
this.bg.rect(0, 0, width, height);
this.bg.fill(0x333333);
this.bg.stroke({ width: 1, color: 0x666666 });
this.container.addChild(this.bg);
// 血条
this.bar = new Graphics();
this.bar.rect(0, 0, width, height);
this.bar.fill(color);
this.container.addChild(this.bar);
}
update(percent: number) {
const clamped = Math.max(0, Math.min(1, percent));
this.bar.clear();
this.bar.rect(0, 0, this.maxWidth * clamped, this.bar.height || 20);
this.bar.fill(clamped > 0.3 ? 0x2ecc71 : 0xe74c3c);
}
}
// 使用
const hpBar = new HealthBar(200, 20, 0x2ecc71);
hpBar.container.position.set(20, 20);
app.stage.addChild(hpBar.container);
let hp = 100;
app.ticker.add(() => {
hp -= 0.1;
hpBar.update(hp / 100);
});
场景:调试碰撞框
function drawDebugHitbox(sprite: Sprite, color: number = 0xff0000) {
const bounds = sprite.getBounds();
const debug = new Graphics();
debug.rect(bounds.x, bounds.y, bounds.width, bounds.height);
debug.stroke({ width: 1, color, alpha: 0.5 });
debug.fill({ color, alpha: 0.1 });
app.stage.addChild(debug);
return debug;
}
⚠️ 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Graphics 不显示 | 未设置 fill 或 stroke | 至少调用一次 fill() 或 stroke() |
| 线条有锯齿 | 分辨率低或未抗锯齿 | 启用 antialias: true |
| 动态绘图卡顿 | 每帧 clear + 重绘太复杂 | 降低重绘频率,或缓存为纹理 |
| 渐变不生效 | Pixi.js 版本问题 | 确认 v8+,旧版本渐变 API 不同 |
| fill 和 stroke 顺序 | 必须先定义路径再调用 | 先 rect()/circle(),再 fill()/stroke() |
💡 进阶提示
Graphics 缓存为纹理:
const complexGraphics = new Graphics(); // ... 复杂绘制 ... const texture = app.renderer.generateTexture(complexGraphics); const cachedSprite = new Sprite(texture); // 用 sprite 替代 graphics,性能更优只描边不填充:
const outline = new Graphics(); outline.circle(0, 0, 50); outline.stroke({ width: 2, color: 0xffffff }); // 不调用 fill() 即可Graphics 池化:
class GraphicsPool { private pool: Graphics[] = []; get(): Graphics { const g = this.pool.pop() || new Graphics(); g.clear(); g.visible = true; return g; } release(g: Graphics) { g.clear(); g.visible = false; g.removeFromParent(); this.pool.push(g); } }