强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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);

线条样式详解

属性类型默认值说明
widthnumber1线宽(像素)
colornumber0xffffff线条颜色
alphanumber1透明度
capstring‘butt’线端形状
joinstring‘miter’拐角形状
alignmentnumber0.5描边对齐(0-1)
miterLimitnumber10尖角限制

贝塞尔曲线

二次贝塞尔曲线

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矢量绘制,无需图片资源
大量相同图形SpriteGPU 纹理批处理更高效
频繁变化的图形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()

💡 进阶提示

  1. Graphics 缓存为纹理:

    const complexGraphics = new Graphics();
    // ... 复杂绘制 ...
    const texture = app.renderer.generateTexture(complexGraphics);
    const cachedSprite = new Sprite(texture);
    // 用 sprite 替代 graphics,性能更优
    
  2. 只描边不填充:

    const outline = new Graphics();
    outline.circle(0, 0, 50);
    outline.stroke({ width: 2, color: 0xffffff });
    // 不调用 fill() 即可
    
  3. 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);
      }
    }
    

扩展阅读


上一章:04 - 容器与场景图 下一章:06 - 文本渲染(Text / BitmapText)