Skip to content

第15章:高级特性与扩展

15.1 章节概述

本章将探索 PixiJS 的高级特性和扩展能力,包括自定义渲染、WebGPU 支持、插件开发等内容。

本章将深入讲解:

  • 自定义渲染:Mesh、自定义着色器
  • RenderTexture:离屏渲染、后处理
  • WebGPU 支持:新一代图形 API
  • 插件开发:扩展 PixiJS 功能
  • 与其他库集成:物理引擎、UI 框架
  • 实战案例:完整项目示例

15.2 自定义渲染

15.2.1 Mesh 网格

typescript
/**
 * Mesh 网格
 * 用于自定义几何形状
 */

// 简单四边形 Mesh
const geometry = new PIXI.Geometry()
    .addAttribute('aVertexPosition', [
        0, 0,      // 左上
        100, 0,    // 右上
        100, 100,  // 右下
        0, 100     // 左下
    ], 2)
    .addAttribute('aTextureCoord', [
        0, 0,
        1, 0,
        1, 1,
        0, 1
    ], 2)
    .addIndex([0, 1, 2, 0, 2, 3]);

const shader = PIXI.Shader.from(`
    attribute vec2 aVertexPosition;
    attribute vec2 aTextureCoord;
    
    uniform mat3 projectionMatrix;
    
    varying vec2 vTextureCoord;
    
    void main() {
        gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
        vTextureCoord = aTextureCoord;
    }
`, `
    varying vec2 vTextureCoord;
    
    uniform sampler2D uSampler;
    
    void main() {
        gl_FragColor = texture2D(uSampler, vTextureCoord);
    }
`, {
    uSampler: texture
});

const mesh = new PIXI.Mesh(geometry, shader);
app.stage.addChild(mesh);

15.2.2 SimpleMesh

typescript
/**
 * SimpleMesh - 简化的 Mesh
 */

// 创建简单网格
const vertices = new Float32Array([
    0, 0,
    100, 0,
    100, 100,
    0, 100
]);

const uvs = new Float32Array([
    0, 0,
    1, 0,
    1, 1,
    0, 1
]);

const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);

const mesh = new PIXI.SimpleMesh(texture, vertices, uvs, indices);
app.stage.addChild(mesh);


// 动态修改顶点
app.ticker.add((delta) => {
    const time = performance.now() / 1000;
    
    // 波浪效果
    for (let i = 0; i < vertices.length; i += 2) {
        const x = i / 2;
        vertices[i + 1] = Math.sin(time + x * 0.5) * 10;
    }
    
    mesh.geometry.getBuffer('aVertexPosition').update();
});

15.2.3 自定义着色器

typescript
/**
 * 自定义着色器
 */

// 顶点着色器
const vertexShader = `
    attribute vec2 aVertexPosition;
    attribute vec2 aTextureCoord;
    
    uniform mat3 projectionMatrix;
    uniform mat3 translationMatrix;
    
    varying vec2 vTextureCoord;
    
    void main() {
        gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
        vTextureCoord = aTextureCoord;
    }
`;

// 片段着色器 - 水波效果
const fragmentShader = `
    precision mediump float;
    
    varying vec2 vTextureCoord;
    
    uniform sampler2D uSampler;
    uniform float uTime;
    uniform float uAmplitude;
    uniform float uFrequency;
    
    void main() {
        vec2 uv = vTextureCoord;
        
        // 水波变形
        uv.x += sin(uv.y * uFrequency + uTime) * uAmplitude;
        uv.y += cos(uv.x * uFrequency + uTime) * uAmplitude;
        
        vec4 color = texture2D(uSampler, uv);
        
        // 添加蓝色调
        color.b += 0.1;
        
        gl_FragColor = color;
    }
`;

// 创建着色器
const shader = PIXI.Shader.from(vertexShader, fragmentShader, {
    uSampler: texture,
    uTime: 0,
    uAmplitude: 0.02,
    uFrequency: 10.0
});

// 创建 Mesh
const geometry = new PIXI.Geometry()
    .addAttribute('aVertexPosition', [0, 0, 200, 0, 200, 200, 0, 200], 2)
    .addAttribute('aTextureCoord', [0, 0, 1, 0, 1, 1, 0, 1], 2)
    .addIndex([0, 1, 2, 0, 2, 3]);

const mesh = new PIXI.Mesh(geometry, shader);

// 更新 uniform
app.ticker.add((delta) => {
    shader.uniforms.uTime += delta * 0.05;
});

15.3 RenderTexture

15.3.1 基础使用

typescript
/**
 * RenderTexture 基础使用
 */

// 创建 RenderTexture
const renderTexture = PIXI.RenderTexture.create({
    width: 256,
    height: 256,
    resolution: window.devicePixelRatio
});

// 渲染到 RenderTexture
app.renderer.render(container, { renderTexture });

// 使用 RenderTexture
const sprite = new PIXI.Sprite(renderTexture);
app.stage.addChild(sprite);


// 清除 RenderTexture
app.renderer.render(new PIXI.Container(), {
    renderTexture,
    clear: true
});

// 销毁
renderTexture.destroy(true);

15.3.2 后处理效果

typescript
/**
 * 后处理效果
 */

class PostProcessing {
    private renderTexture: PIXI.RenderTexture;
    private sprite: PIXI.Sprite;
    private filters: PIXI.Filter[];
    
    constructor(width: number, height: number) {
        this.renderTexture = PIXI.RenderTexture.create({
            width,
            height,
            resolution: window.devicePixelRatio
        });
        
        this.sprite = new PIXI.Sprite(this.renderTexture);
        this.filters = [];
    }
    
    addFilter(filter: PIXI.Filter) {
        this.filters.push(filter);
        this.sprite.filters = this.filters;
    }
    
    render(renderer: PIXI.Renderer, scene: PIXI.Container) {
        // 渲染场景到 RenderTexture
        renderer.render(scene, { renderTexture: this.renderTexture });
    }
    
    getOutput(): PIXI.Sprite {
        return this.sprite;
    }
    
    resize(width: number, height: number) {
        this.renderTexture.resize(width, height);
    }
    
    destroy() {
        this.renderTexture.destroy(true);
        this.sprite.destroy();
    }
}

// 使用
const postProcess = new PostProcessing(800, 600);
postProcess.addFilter(new PIXI.BlurFilter(2));
postProcess.addFilter(new filters.BloomFilter());

// 渲染循环
app.ticker.add(() => {
    postProcess.render(app.renderer, gameScene);
});

app.stage.addChild(postProcess.getOutput());

15.3.3 截图功能

typescript
/**
 * 截图功能
 */

class Screenshot {
    static capture(
        renderer: PIXI.Renderer,
        target: PIXI.DisplayObject,
        options: {
            width?: number;
            height?: number;
            resolution?: number;
        } = {}
    ): HTMLCanvasElement {
        const bounds = target.getBounds();
        const width = options.width || bounds.width;
        const height = options.height || bounds.height;
        const resolution = options.resolution || 1;
        
        const renderTexture = PIXI.RenderTexture.create({
            width,
            height,
            resolution
        });
        
        // 调整位置以捕获完整内容
        const originalTransform = target.transform.localTransform.clone();
        target.transform.localTransform.translate(-bounds.x, -bounds.y);
        
        renderer.render(target, { renderTexture });
        
        // 恢复位置
        target.transform.localTransform.copyFrom(originalTransform);
        
        // 提取为 Canvas
        const canvas = renderer.extract.canvas(renderTexture);
        
        renderTexture.destroy(true);
        
        return canvas;
    }
    
    static async toBlob(
        renderer: PIXI.Renderer,
        target: PIXI.DisplayObject,
        type: string = 'image/png',
        quality?: number
    ): Promise<Blob> {
        const canvas = this.capture(renderer, target);
        
        return new Promise((resolve, reject) => {
            canvas.toBlob(
                (blob) => {
                    if (blob) resolve(blob);
                    else reject(new Error('Failed to create blob'));
                },
                type,
                quality
            );
        });
    }
    
    static async download(
        renderer: PIXI.Renderer,
        target: PIXI.DisplayObject,
        filename: string = 'screenshot.png'
    ) {
        const blob = await this.toBlob(renderer, target);
        const url = URL.createObjectURL(blob);
        
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.click();
        
        URL.revokeObjectURL(url);
    }
}

// 使用
document.addEventListener('keydown', async (e) => {
    if (e.key === 's' && e.ctrlKey) {
        e.preventDefault();
        await Screenshot.download(app.renderer, app.stage, 'game-screenshot.png');
    }
});

15.4 WebGPU 支持

15.4.1 WebGPU 简介

WebGPU vs WebGL

┌─────────────────────────────────────────────────────────────┐
│                    WebGL                                    │
├─────────────────────────────────────────────────────────────┤
│  - 基于 OpenGL ES                                           │
│  - 广泛支持                                                 │
│  - 状态机模型                                               │
│  - 单线程                                                   │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    WebGPU                                   │
├─────────────────────────────────────────────────────────────┤
│  - 新一代图形 API                                           │
│  - 更低的 CPU 开销                                          │
│  - 计算着色器支持                                           │
│  - 更好的多线程支持                                         │
│  - 更现代的 API 设计                                        │
└─────────────────────────────────────────────────────────────┘


PixiJS v8+ 支持 WebGPU

15.4.2 使用 WebGPU

typescript
/**
 * 使用 WebGPU 渲染器
 */

// 检查 WebGPU 支持
async function checkWebGPUSupport(): Promise<boolean> {
    if (!navigator.gpu) {
        return false;
    }
    
    try {
        const adapter = await navigator.gpu.requestAdapter();
        return adapter !== null;
    } catch {
        return false;
    }
}

// 创建 WebGPU 应用
async function createApp() {
    const webgpuSupported = await checkWebGPUSupport();
    
    const app = new PIXI.Application();
    
    await app.init({
        width: 800,
        height: 600,
        preference: webgpuSupported ? 'webgpu' : 'webgl',
        // 或强制使用 WebGPU
        // preference: 'webgpu',
    });
    
    console.log('Renderer type:', app.renderer.type);
    // 1 = WebGL, 2 = WebGPU
    
    return app;
}

// 使用
const app = await createApp();
document.body.appendChild(app.canvas);

15.5 插件开发

15.5.1 扩展系统

typescript
/**
 * PixiJS 扩展系统
 */

// 自定义扩展
const myExtension = {
    extension: {
        type: PIXI.ExtensionType.RendererPlugin,
        name: 'myPlugin'
    },
    
    init(renderer: PIXI.Renderer) {
        console.log('Plugin initialized');
    },
    
    destroy() {
        console.log('Plugin destroyed');
    }
};

// 注册扩展
PIXI.extensions.add(myExtension);

// 使用
// app.renderer.plugins.myPlugin

15.5.2 自定义加载器

typescript
/**
 * 自定义资源加载器
 */

const customLoader = {
    extension: {
        type: PIXI.ExtensionType.LoadParser,
        priority: PIXI.LoaderParserPriority.Normal
    },
    
    test(url: string): boolean {
        return url.endsWith('.custom');
    },
    
    async load(url: string): Promise<any> {
        const response = await fetch(url);
        const text = await response.text();
        
        // 自定义解析逻辑
        return parseCustomFormat(text);
    },
    
    unload(asset: any) {
        // 清理资源
    }
};

PIXI.extensions.add(customLoader);

// 现在可以加载 .custom 文件
const data = await PIXI.Assets.load('data.custom');

15.5.3 自定义显示对象

typescript
/**
 * 自定义显示对象
 */

class CustomSprite extends PIXI.Sprite {
    private _customProperty: number = 0;
    
    constructor(texture?: PIXI.Texture) {
        super(texture);
    }
    
    get customProperty(): number {
        return this._customProperty;
    }
    
    set customProperty(value: number) {
        this._customProperty = value;
        this.onCustomPropertyChange();
    }
    
    private onCustomPropertyChange() {
        // 响应属性变化
        this.tint = this._customProperty > 0.5 ? 0xFF0000 : 0x00FF00;
    }
    
    // 重写更新方法
    updateTransform() {
        super.updateTransform();
        // 自定义更新逻辑
    }
    
    // 重写渲染方法
    render(renderer: PIXI.Renderer) {
        // 自定义渲染前逻辑
        super.render(renderer);
        // 自定义渲染后逻辑
    }
    
    // 重写销毁方法
    destroy(options?: PIXI.IDestroyOptions) {
        // 清理自定义资源
        super.destroy(options);
    }
}

15.6 与其他库集成

15.6.1 物理引擎集成

typescript
/**
 * Matter.js 物理引擎集成
 */

import Matter from 'matter-js';

class PhysicsWorld {
    private engine: Matter.Engine;
    private world: Matter.World;
    private bodies: Map<Matter.Body, PIXI.DisplayObject> = new Map();
    
    constructor() {
        this.engine = Matter.Engine.create();
        this.world = this.engine.world;
    }
    
    addBody(
        sprite: PIXI.Sprite,
        options: Matter.IBodyDefinition = {}
    ): Matter.Body {
        const body = Matter.Bodies.rectangle(
            sprite.x,
            sprite.y,
            sprite.width,
            sprite.height,
            options
        );
        
        Matter.World.add(this.world, body);
        this.bodies.set(body, sprite);
        
        return body;
    }
    
    addCircle(
        sprite: PIXI.Sprite,
        radius: number,
        options: Matter.IBodyDefinition = {}
    ): Matter.Body {
        const body = Matter.Bodies.circle(
            sprite.x,
            sprite.y,
            radius,
            options
        );
        
        Matter.World.add(this.world, body);
        this.bodies.set(body, sprite);
        
        return body;
    }
    
    update(delta: number) {
        Matter.Engine.update(this.engine, delta * 16.67);
        
        // 同步位置
        for (const [body, sprite] of this.bodies) {
            sprite.x = body.position.x;
            sprite.y = body.position.y;
            sprite.rotation = body.angle;
        }
    }
    
    removeBody(body: Matter.Body) {
        Matter.World.remove(this.world, body);
        this.bodies.delete(body);
    }
    
    setGravity(x: number, y: number) {
        this.world.gravity.x = x;
        this.world.gravity.y = y;
    }
}

// 使用
const physics = new PhysicsWorld();
physics.setGravity(0, 1);

const sprite = PIXI.Sprite.from('box.png');
sprite.anchor.set(0.5);
physics.addBody(sprite, { restitution: 0.8 });

app.ticker.add((delta) => {
    physics.update(delta);
});

15.6.2 状态管理集成

typescript
/**
 * 状态管理集成
 */

// 简单状态管理
class Store<T> {
    private state: T;
    private listeners: Set<(state: T) => void> = new Set();
    
    constructor(initialState: T) {
        this.state = initialState;
    }
    
    getState(): T {
        return this.state;
    }
    
    setState(newState: Partial<T>) {
        this.state = { ...this.state, ...newState };
        this.notify();
    }
    
    subscribe(listener: (state: T) => void) {
        this.listeners.add(listener);
        return () => this.listeners.delete(listener);
    }
    
    private notify() {
        for (const listener of this.listeners) {
            listener(this.state);
        }
    }
}

// 游戏状态
interface GameState {
    score: number;
    lives: number;
    level: number;
    paused: boolean;
}

const gameStore = new Store<GameState>({
    score: 0,
    lives: 3,
    level: 1,
    paused: false
});

// UI 组件响应状态变化
class ScoreDisplay extends PIXI.Text {
    constructor() {
        super('Score: 0', { fontSize: 24, fill: 0xFFFFFF });
        
        gameStore.subscribe((state) => {
            this.text = `Score: ${state.score}`;
        });
    }
}

// 更新状态
function addScore(points: number) {
    const state = gameStore.getState();
    gameStore.setState({ score: state.score + points });
}

15.7 实战案例

15.7.1 简单游戏框架

typescript
/**
 * 简单游戏框架
 */

// 场景接口
interface IScene {
    init(): Promise<void>;
    update(delta: number): void;
    destroy(): void;
    getContainer(): PIXI.Container;
}

// 游戏类
class Game {
    private app: PIXI.Application;
    private currentScene: IScene | null = null;
    private scenes: Map<string, new (game: Game) => IScene> = new Map();
    
    constructor() {
        this.app = new PIXI.Application();
    }
    
    async init() {
        await this.app.init({
            width: 800,
            height: 600,
            backgroundColor: 0x1a1a2e
        });
        
        document.body.appendChild(this.app.canvas);
        
        this.app.ticker.add(this.update, this);
    }
    
    registerScene(name: string, SceneClass: new (game: Game) => IScene) {
        this.scenes.set(name, SceneClass);
    }
    
    async switchScene(name: string) {
        // 销毁当前场景
        if (this.currentScene) {
            this.currentScene.destroy();
            this.app.stage.removeChild(this.currentScene.getContainer());
        }
        
        // 创建新场景
        const SceneClass = this.scenes.get(name);
        if (!SceneClass) {
            throw new Error(`Scene "${name}" not found`);
        }
        
        this.currentScene = new SceneClass(this);
        await this.currentScene.init();
        this.app.stage.addChild(this.currentScene.getContainer());
    }
    
    private update(delta: number) {
        this.currentScene?.update(delta);
    }
    
    getApp(): PIXI.Application {
        return this.app;
    }
}

// 菜单场景
class MenuScene implements IScene {
    private game: Game;
    private container: PIXI.Container;
    
    constructor(game: Game) {
        this.game = game;
        this.container = new PIXI.Container();
    }
    
    async init() {
        const title = new PIXI.Text('My Game', {
            fontSize: 48,
            fill: 0xFFFFFF
        });
        title.anchor.set(0.5);
        title.position.set(400, 200);
        
        const startButton = this.createButton('Start', 400, 350);
        startButton.on('pointerdown', () => {
            this.game.switchScene('game');
        });
        
        this.container.addChild(title, startButton);
    }
    
    private createButton(text: string, x: number, y: number): PIXI.Container {
        const button = new PIXI.Container();
        
        const bg = new PIXI.Graphics();
        bg.beginFill(0x4CAF50);
        bg.drawRoundedRect(-75, -25, 150, 50, 10);
        bg.endFill();
        
        const label = new PIXI.Text(text, {
            fontSize: 20,
            fill: 0xFFFFFF
        });
        label.anchor.set(0.5);
        
        button.addChild(bg, label);
        button.position.set(x, y);
        button.eventMode = 'static';
        button.cursor = 'pointer';
        
        return button;
    }
    
    update(delta: number) {
        // 菜单动画
    }
    
    destroy() {
        this.container.destroy({ children: true });
    }
    
    getContainer(): PIXI.Container {
        return this.container;
    }
}

// 游戏场景
class GameScene implements IScene {
    private game: Game;
    private container: PIXI.Container;
    private player: PIXI.Sprite | null = null;
    
    constructor(game: Game) {
        this.game = game;
        this.container = new PIXI.Container();
    }
    
    async init() {
        // 加载资源
        const texture = await PIXI.Assets.load('player.png');
        
        this.player = new PIXI.Sprite(texture);
        this.player.anchor.set(0.5);
        this.player.position.set(400, 300);
        
        this.container.addChild(this.player);
        
        // 键盘控制
        this.setupControls();
    }
    
    private setupControls() {
        const keys: Set<string> = new Set();
        
        window.addEventListener('keydown', (e) => keys.add(e.key));
        window.addEventListener('keyup', (e) => keys.delete(e.key));
        
        this.game.getApp().ticker.add(() => {
            if (!this.player) return;
            
            const speed = 5;
            if (keys.has('ArrowLeft')) this.player.x -= speed;
            if (keys.has('ArrowRight')) this.player.x += speed;
            if (keys.has('ArrowUp')) this.player.y -= speed;
            if (keys.has('ArrowDown')) this.player.y += speed;
        });
    }
    
    update(delta: number) {
        // 游戏逻辑
    }
    
    destroy() {
        this.container.destroy({ children: true });
    }
    
    getContainer(): PIXI.Container {
        return this.container;
    }
}

// 启动游戏
async function main() {
    const game = new Game();
    await game.init();
    
    game.registerScene('menu', MenuScene);
    game.registerScene('game', GameScene);
    
    await game.switchScene('menu');
}

main();

15.8 总结与展望

15.8.1 PixiJS 核心要点

PixiJS 核心要点总结

1. 渲染系统
   - Application 是入口点
   - Renderer 处理 WebGL/WebGPU 渲染
   - 批处理优化 Draw Call

2. 显示对象
   - DisplayObject 是基类
   - Container 组织场景图
   - Sprite 显示图像
   - Graphics 绘制矢量图形
   - Text 渲染文本

3. 变换系统
   - position, scale, rotation
   - 本地坐标 vs 世界坐标
   - 变换矩阵

4. 交互系统
   - eventMode 控制交互
   - 指针事件统一处理
   - 事件冒泡和捕获

5. 资源管理
   - Assets 统一加载
   - 纹理图集优化
   - 缓存和卸载

6. 特效系统
   - 滤镜(Filter)
   - 遮罩(Mask)
   - 混合模式(BlendMode)

7. 动画系统
   - Ticker 帧循环
   - 补间动画
   - 粒子系统

8. 性能优化
   - 批处理
   - 对象池
   - 视口剔除
   - 内存管理

15.8.2 学习路径

PixiJS 学习路径

初级:
1. 基础概念(Application, Sprite, Container)
2. 基本交互(事件系统)
3. 简单动画(Ticker)

中级:
4. 纹理管理(Assets, Spritesheet)
5. 图形绘制(Graphics)
6. 文本渲染(Text, BitmapText)
7. 滤镜和遮罩

高级:
8. 自定义渲染(Mesh, Shader)
9. 性能优化
10. 插件开发
11. 与其他库集成

实战:
12. 完整项目开发
13. 游戏开发
14. 数据可视化
15. 交互式应用

15.8.3 资源推荐

学习资源

官方资源:
- PixiJS 官网: https://pixijs.com
- PixiJS 文档: https://pixijs.download/release/docs/
- PixiJS GitHub: https://github.com/pixijs/pixijs
- PixiJS Examples: https://pixijs.io/examples/

社区资源:
- PixiJS Discord
- Stack Overflow
- GitHub Discussions

工具:
- TexturePacker(纹理图集)
- Spine(骨骼动画)
- DragonBones(骨骼动画)
- PixiJS DevTools(调试)

扩展库:
- pixi-filters(滤镜)
- pixi-spine(Spine 动画)
- pixi-sound(音频)
- pixi-particles(粒子)

15.9 本章小结

核心概念

概念说明
Mesh自定义几何网格
RenderTexture离屏渲染目标
WebGPU新一代图形 API
Extension扩展系统
自定义着色器GLSL 着色器

关键代码

typescript
// 自定义 Mesh
const geometry = new PIXI.Geometry()
    .addAttribute('aVertexPosition', vertices, 2)
    .addIndex(indices);

const mesh = new PIXI.Mesh(geometry, shader);

// RenderTexture
const rt = PIXI.RenderTexture.create({ width: 256, height: 256 });
renderer.render(container, { renderTexture: rt });

// WebGPU
await app.init({ preference: 'webgpu' });

// 扩展
PIXI.extensions.add(myExtension);

15.10 练习题

基础练习

  1. 创建一个自定义 Mesh

  2. 使用 RenderTexture 实现截图功能

  3. 创建一个自定义显示对象

进阶练习

  1. 实现一个简单的物理引擎集成

  2. 创建一个自定义滤镜

挑战练习

  1. 开发一个完整的小游戏,包含菜单、游戏、结算场景

恭喜完成 PixiJS 实战系列!

通过本系列的学习,你应该已经掌握了:

  • PixiJS 的核心概念和 API
  • 各种显示对象的使用方法
  • 交互和动画系统
  • 性能优化技巧
  • 高级特性和扩展开发

继续实践,构建更多有趣的项目!


文档版本:v1.0
字数统计:约 11,000 字
代码示例:40+ 个

如有转载或 CV 的请标注本站原文地址