关于 phaser-fx
phaser-fx 是面向 Phaser 4 开发者的参考技能,涵盖滤镜、遮罩、光照、着色和 Beam 渲染器。它说明 Phaser 3 到 Phaser 4 的 API 变化,并介绍效果实现与性能取舍。
主要功能
- 01Phaser 4 滤镜目录
- 02内部与外部滤镜用法
- 03遮罩与模板技术
- 04着色模式与颜色效果
- 05光照与锥形光
- 06Phaser 3 迁移说明
适用场景
- 01实现游戏对象与摄像机效果
- 02替换 Phaser 3 遮罩和特效
- 03制作光照与视野锥效果
- 04评估滤镜的性能取舍
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yakoub-ai/ phaser4-gamedev --skill phaser-fx安装后在 Claude Code 中输入/phaser-fx确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/filters-api.mdlighting.mdrenderer-and-shaders.md详细介绍
Phaser 4 滤镜、遮罩、光照与 Beam 渲染器
Phaser 4 全面替换了 v3 渲染器。FX 和遮罩在 v3 中是两个彼此独立且互不兼容的系统,如今合并为一个名为 Filters 的系统,可用于所有游戏对象和摄像机,没有例外。
如果你正在转换 v3 代码,请从底部的转换表开始。如果你正在编写新代码,请从这里开始。
一条能避免所有人踩坑的规则
游戏对象必须先调用 enableFilters()。摄像机则不需要。
// Game object — filters is null until you enable it
sprite.enableFilters();
sprite.filters!.internal.addGlow(0xff00ff, 4, 0, 1);
// Camera — filters is always there
this.cameras.main.filters.external.addVignette(0.5, 0.5, 0.5, 0.5);
对一个刚创建的精灵调用 sprite.filters.internal.addBlur() 会抛出异常,因为 filters 为 null。这是最常见的 Phaser 4 特效错误,而错误消息(Cannot read properties of null)并没有指出原因。
内部与外部
每个滤镜列表都有两部分,选错部分是第二常见的错误。
filters.internal | filters.external | |
|---|---|---|
| 空间 | 对象自身的空间 | 渲染上下文,通常是全屏 |
| 是否随对象移动 | 是 | 否 |
| 分辨率 | 对象的尺寸 | 整个画布 |
| 开销 | 较低 | 较高 |
| 用途 | 精灵发光、面板遮罩 | 全屏色彩分级、暗角 |
// A glow that follows the enemy: internal.
enemy.enableFilters();
enemy.filters!.internal.addGlow(0xff0000, 8, 0, 1);
// A full-screen red wash when the player is hurt: external, on the camera.
this.cameras.main.filters.external.addColorMatrix();
有些对象无法定义内部空间,会自动回退到外部空间:没有宽度或高度的对象,以及描边超出其报告边界的 Shape 对象。如果形状上的内部滤镜表现得像屏幕特效,原因就在这里。
滤镜目录
每个滤镜都是对 filters.internal 或 filters.external 调用一个 add* 方法:
| 滤镜 | 功能 |
|---|---|
addBlur(quality, x, y, strength) | 高斯模糊 |
addGlow(color, outerStrength, innerStrength, scale) | 轮廓发光 |
addShadow(x, y, decay, power, color, samples, intensity) | 投影 |
addBokeh(radius, amount, contrast) | 景深风格模糊 |
addPixelate(amount) | 像素化 |
addBlocky(amount) | 块状量化 |
addVignette(x, y, radius, strength) | 边缘变暗 |
addBarrel(amount) | 镜头挤压/扩张畸变 |
addDisplacement(texture, x, y) | 使用纹理进行位移——热浪、水面效果 |
addColorMatrix() | 色彩分级(见下文) |
addCombineColorMatrix() | 混合两个色彩矩阵 |
addGradientMap(...) | 通过渐变重新映射亮度 |
addThreshold(...) | Alpha/颜色截断 |
addQuantize(...) | 减少颜色数量 |
addMask(source, invert, viewCamera, viewTransform, scaleFactor) | 遮罩 |
addKey(...) | 色度键控 |
addBlend(...) | WebGL 四种原生混合模式之外的混合模式 |
addTiltShift(...) | 移轴效果 |
addPanoramaBlur(...) | 全景模糊 |
addSampler(...) | 从其他纹理取样 |
addImageLight(...) | 基于图像的光照 |
addNormalTools(...) | 法线贴图处理 |
addParallelFilters(...) | 运行两条滤镜链并合并结果 |
addCustom(...) | 自定义着色器 |
ColorMatrix 有一个嵌套属性
颜色相关方法位于 colorMatrix 子属性上,而不在滤镜本身:
const cm = sprite.filters!.internal.addColorMatrix();
cm.colorMatrix.sepia(); // correct
cm.colorMatrix.grayscale();
cm.colorMatrix.night(0.3);
// cm.sepia(); // wrong — this was the v3 shape
复合特效位于 Actions 中
一些 v3 FX 被重建为 Actions,因为它们需要多个滤镜协同工作:
Phaser.Actions.AddEffectBloom(target); // was the Bloom FX
Phaser.Actions.AddEffectShine(target); // was the Shine FX
Phaser.Actions.AddMaskShape(target); // was the Circle FX
遮罩
BitmapMask 已不存在。createGeometryMask() 和 createBitmapMask() 不是游戏对象的方法。GeometryMask 仍作为类保留,但只有已弃用的 Canvas 渲染器会使用它——在 Beam 下完全不起作用。
有两种替代方案,选择取决于形状:
矩形裁剪 → 摄像机视口
滚动面板、小地图、分屏。比滤镜开销更低,而且像素精确。
const panelCam = this.cameras.add(x, y, width, height);
panelCam.setScroll(scrollX, scrollY);
panelCam.ignore(this.children.list.filter(c => c !== panelContent));
this.cameras.main.ignore(panelContent);
Phaser 4 中没有
camera.setScissor()。如果你在指南中看到这个调用,该方法并不存在——请使用setViewport()。
其他任意形状 → Mask 滤镜
const shape = this.add.graphics();
shape.fillStyle(0xffffff).fillCircle(64, 64, 64);
content.enableFilters();
content.filters!.internal.addMask(shape); // GameObject or texture key
content.filters!.internal.addMask(shape, true); // inverted: hide inside, show outside
Mask 源可以是纹理键或游戏对象。如果传入对象,Phaser 会将其渲染到 DynamicTexture,然后使用该纹理——因此动画遮罩和由滤镜输出生成的遮罩都可用。scaleFactor 用于在遮罩精度和内存占用之间进行权衡;如果设置了它,请相应缩放遮罩源。
Stencil——持久遮罩(4.2.0+)
对于需要在多个对象和帧之间保持不变的遮罩,使用 Stencil 比反复运行 Mask 滤镜开销更低。与 v3 的 Stencil 遮罩不同,这些遮罩通用且持久,可接受任何能绘制像素的源。
const stencil = this.add.stencil();
stencil.add(this.add.circle(400, 300, 120, 0xffffff));
模式:addLayer、subtractLayer、clear、clearRegion,其中 add 和 subtract 均可反转。this.add.stencilreference() 会使用不同设置重新渲染现有的 Stencil,可用于复用或移除 Stencil 几何体。
Stencil 内部的常规 alpha 不起作用,因此 alpha 由 stencilAlphaStrategy 控制(见下文)。如果 stencilInvert 看起来没有效果,请使用 4.2.1 或更高版本——它在 4.2.0 中因 alpha 错误而无法正常工作。
着色
Phaser 4 将 tint 颜色 与 tint 模式 分开了。在 v3 中,你可以设置 tintFill = true;该属性现在已不存在。
sprite.setTint(0xff0000);
sprite.setTintMode(Phaser.TintModes.FILL); // was tintFill = true
模式:MULTIPLY(默认)、FILL、ADD、SCREEN、OVERLAY、HARD_LIGHT、MULTIPLY_TWO。
MULTIPLY_TWO(4.2.0+)会为每个角使用第二种 tint 颜色,因此无需着色器即可实现渐变 tint——火焰、冰霜、反色:
sprite.setTint(0xff2200).setTint2(0xffee00).setTintMode(Phaser.TintModes.MULTIPLY_TWO);
Mesh2D 和 Tile 支持恒定的 tint2。TilemapGPULayer 完全不支持对图块进行 tint。
形状完全没有 tint。
Rectangle、Arc、Triangle和其他Shape对象未实现 Tint 组件——它们没有setTint()和setTintMode()方法,编译器也会提示这一点。请改用fillColor:rect.fillColor = 0xff0000。这个问题最常见于原型开发:先用彩色矩形进行测试,之后才发现伤害闪烁模式无法通过编译。
伤害闪烁是每款动作游戏都需要的效果:
hit() {
this.sprite.setTint(0xffffff).setTintMode(Phaser.TintModes.FILL);
this.scene.time.delayedCall(80, () => this.sprite.clearTint());
}
优先使用这种方式,而不是白色闪烁滤镜——它只需一次绘制调用,也不需要帧缓冲区。
光照
this.lights.enable().setAmbientColor(0x333333);
const torch = this.lights.addLight(x, y, 200, 0xffaa33, 2);
对象必须选择启用光照;组件和法线贴图设置请参阅 references/lighting.md。
锥形光源(4.2.0+)
手电筒、视野锥、车头灯、探照灯。它们使用同一光照着色器运行——无需遮罩、第二个摄像机或将场景渲染两次:
const beam = this.lights.addConeLight(
x, y,
300, // radius
0xffffff, // colour
2, // intensity
rotation, // radians — the direction the cone points
0.2, // inner angle, radians
0.6, // outer angle, radians
);
beam.setConeRotation(Phaser.Math.Angle.Between(px, py, mx, my));
beam.setConeAngles(0.15, 0.5);
beam.disableCone(); // back to a normal point light
任何已启用 Phaser 光照的对象都可以受到锥形光源照射。因此,与 v3 的实现方式相比,潜行视野锥和火把机制的开销低得多。
性能
滤镜是帧缓冲区操作。每个滤镜都是一个渲染通道,而消耗就在这些通道上。
- 闪光和颜色变化优先使用色调,而非滤镜。 色调属于批处理的一部分;滤镜则是单独的通道。
- 优先使用一个摄像机滤镜,而非 N 个对象滤镜。 如果 20 个敌人各自都使用发光效果,就会多出 20 个通道。如果它们可以共用一个屏幕空间效果,就这样做。
- 两种方式都可行时,优先使用内部滤镜——内部滤镜按对象尺寸渲染,外部滤镜则按完整画布渲染。
- 滤镜渲染时绝不会使用 mipmap。 不要指望滤镜输出会通过 mipmap 变得柔和(4.1.0 修复了相反的问题:滤镜之前会错误地使用 mipmap)。
- 只启用一次滤镜,不要每帧都启用。 在
create()中调用enableFilters(),然后在update()中调整控制器的属性。每帧调用add*会不断叠加滤镜,直到耗尽帧预算——这是很容易意外造成的泄漏,而且看起来像性能问题,而非逻辑问题。 - Alpha 策略使用
discard, 这是设计使然,效率不高。dither和threshold用于特效和模板,不是性能优化手段。
请使用 playtest 测试工具验证以上内容,而不是凭肉眼判断——负载下的帧率骤降会体现滤镜的开销,单次读数无法捕捉到这一点,而 sample 步骤可以:
{ action: 'sample', expression: `game.loop.actualFps`,
duration: 3000, interval: 100, expect: { stat: 'min', atLeast: 50 } }
读取无头模式下的 FPS 时,只能用于比较,不能当作绝对数值。 测试工具通过 SwiftShader 运行 WebGL,而滤镜恰恰是软件渲染最不擅长的部分:包含发光、暗角、颜色矩阵和受光粒子发射器的场景,在无头模式下测得的帧率约为 12–20fps,而在任何真实 GPU 上都能以 60fps 运行。请在同一台机器上将一次运行与之前的运行进行比较;不要为滤镜密集型场景设置绝对阈值,也不要将无头模式下的数值报告为游戏性能。
从 Phaser 3 迁移
| v3 | v4 |
|---|---|
sprite.preFX.addGlow() | sprite.enableFilters(); sprite.filters!.internal.addGlow() |
sprite.postFX.addBlur() | sprite.enableFilters(); sprite.filters!.external.addBlur() |
camera.postFX.addVignette() | camera.filters.external.addVignette()(无需启用步骤) |
new BitmapMask(...)、setMask() | filters.internal.addMask(source) |
createGeometryMask() | 摄像机视口或 addMask() |
sprite.tintFill = true | sprite.setTintMode(Phaser.TintModes.FILL) |
setPipeline('X') | 渲染节点——customRenderNodes、RenderConfig#renderNodes |
| Bloom FX | Phaser.Actions.AddEffectBloom() |
| Shine FX | Phaser.Actions.AddEffectShine() |
| Circle FX | Phaser.Actions.AddMaskShape() |
| Gradient FX | Gradient 游戏对象 |
colorMatrixFilter.sepia() | filter.colorMatrix.sepia() |
Mesh / Plane | Mesh2D(4.2.0+) |
在确认代码没有问题前,先运行迁移 grep:
grep -rn "preFX\|postFX\|BitmapMask\|createGeometryMask\|tintFill\|setPipeline\|setScissor" src/
附加资源
参考文件
references/filters-api.md— 所有滤镜的完整参数列表、详细示例,以及每种效果的内部/外部选择建议。references/lighting.md— 光照模型、法线贴图、锥形光、Lighting组件和性能范围。references/renderer-and-shaders.md— Beam、渲染节点、自定义着色器、Mesh2D、SpriteGPULayer、Stencil、CustomContext和 Alpha 策略。
相关内容
skills/phaser-migrate/— 完整的 v3→v4 变更列表和发行说明skills/phaser-particles/— 粒子效果,通常会与这些内容搭配使用skills/phaser-analyze/references/performance-playbook.md— 特效何时会消耗帧时间