pixelfx
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesepixelfx
pixelfx
Pixel art effects as code. Each effect is one file, , that draws into two buffers:
color (pixels: opaque, dithered or truly translucent) and light (additive glow). No images, so an effect
can be played live, recolored, mirrored, reseeded and rendered to a sprite sheet for any engine.
src/fx/<category>/<name>.jsThe method is Hugo Duprez's (How to make (good) pixel art effects with
Claude): code is clay, trust
your eyes, feed your references. One-shot prompts make mediocre effects. Drafts you render, look at and refine
make good ones.
用代码实现像素艺术特效。每个特效都是一个文件,,会绘制到两个缓冲区:
color(像素:不透明、抖动或真正的半透明)和 light(叠加式发光)。无需使用图片,因此一个特效
可以实时播放、重新着色、镜像、重新设定随机种子,并渲染为适用于任意引擎的 Sprite Sheet。
src/fx/<category>/<name>.js这种方法来自 Hugo Duprez 的文章(How to make (good) pixel art effects with
Claude):代码如同黏土,要相信自己的眼睛,并提供参考素材。一次性提示词制作出来的效果只能算平庸。先渲染草稿、观察并打磨,才能做出优秀的效果。
Before anything
开始之前
- Work in a pixelfx checkout. If the working directory has no , clone one next to the project:
tools/render.mjs. No install step: the tools use only Node.git clone https://github.com/patch-gg/pixelfx - Check it runs: writes
node tools/render.mjs fire/torch. Open that PNG with your image-reading tool. That contact sheet is how you see every effect from now on.records/fire/torch/contact.png
- 在 pixelfx 仓库中工作。如果当前工作目录没有 ,就在项目旁克隆一个:
tools/render.mjs。无需安装步骤:这些工具只使用 Node。git clone https://github.com/patch-gg/pixelfx - 确认它能运行:会写入
node tools/render.mjs fire/torch。使用图像读取工具打开该 PNG。此后,你将通过这张 contact sheet 查看每个特效。records/fire/torch/contact.png
1. Look for an existing effect first
1. 先查找已有特效
node tools/list.mjs <words>node tools/list.mjs frostnode tools/list.mjs slash loopdocs/CATALOG.md- : the main ramp, dark to bright, as hex strings. Any
paletteramp inPAL(fire ember lava smoke dust ice water electric volt poison holy heal arcane shadow blood earth stone gold leaf wood metal bone neon cyber matrix candy night) or your own. A blue fire issrc/core/color.js.{"palette": PAL.ice} - for a different but equivalent take;
seedto face left;flipto rotate;angle(0..1+) for glow strength; plus each effect's own knobs in itslight(size, count, radius, text, height...).meta.options - Try options without editing code: .
node tools/render.mjs fire/torch --opts '{"palette":["#102","#215","#43a","#8cf","#fff"]}'
node tools/list.mjs <words>node tools/list.mjs frostnode tools/list.mjs slash loopdocs/CATALOG.md- :主色阶,从暗到亮,以十六进制字符串表示。可使用
palette中的任意src/core/color.js色阶(fire ember lava smoke dust ice water electric volt poison holy heal arcane shadow blood earth stone gold leaf wood metal bone neon cyber matrix candy night),也可以使用自己的色阶。蓝色火焰就是PAL。{"palette": PAL.ice} - 可获得不同但等效的版本;
seed可向左朝向;flip可旋转;angle(0..1+)控制发光强度;此外还有每个特效在light中自己的调节项(大小、数量、半径、文本、高度……)。meta.options - 不改代码也可以尝试选项:。
node tools/render.mjs fire/torch --opts '{"palette":["#102","#215","#43a","#8cf","#fff"]}'
2. Put it in the game
2. 放入游戏
Canvas or WebGL games in JS or TS (plain canvas, Phaser, PixiJS, Kaplay, three.js): use the code directly. It
has no dependencies; copy and the effect files you need, or depend on the repo.
src/corejs
import { createFrame, clearFrame, createFx, composite } from "pixelfx"; // src/core/index.js
import * as frostNova from "pixelfx/fx/ice/frost-nova"; // src/fx/ice/frost-nova.js
const frame = createFrame(320, 180); // the game's pixel resolution; ox, oy scroll it with the camera
const img = new ImageData(320, 180);
const fx = createFx(frostNova, { x: hero.x, y: hero.y, flip: hero.facing < 0 });
// every tick
fx.update(dt);
clearFrame(frame);
fx.draw(frame, "back"); // what goes behind characters
drawCharacters(); // your own sprites, or draw them into frame.color yourself
fx.draw(frame, "front");
composite(frame, img.data, background); // color over the background plus light; or null for transparent
ctx.putImageData(img, 0, 0); // then scale the canvas up with image-rendering: pixelated
if (fx.done()) removeIt(); // one-shots end on their own; fx.stop() fades a loop outSeveral effects can share one frame. In a WebGL engine, upload the composited RGBA to a texture each frame, or
render the effect to a sprite sheet once (below) and play it like any other animation.
Any engine (Godot, Unity, GameMaker, Defold, LÖVE, Bevy, Construct): export sprite sheets.
writes (transparent
frames in a grid) and (Aseprite-style frames, so Phaser's atlas loader, Godot and Unity Aseprite
importers and most tools read it; has fps, loop, frame size, columns and the pivot). Set the
pivot to (the effect's origin, e.g. the feet), import with nearest-neighbour filtering and no
compression, and play at . Glow is baked into the sheet as color with alpha, which reads as
additive over dark scenes; for bright scenes, render a recolored version or keep the effect on a dark layer.
node tools/render.mjs ice/frost-nova --sheetrecords/ice/frost-nova/frost-nova.pngfrost-nova.jsonmeta.pixelfxmeta.pixelfx.pivotmeta.pixelfx.fpsFor the full library: does a whole category.
node tools/render.mjs <category> --sheetA hit needs a body behind it. An effect alone looks like a sticker. Effects with a character carry an : when the body winds
up, when to freeze, how the target reacts. Use it whichever way the game is built:
act- With the stock character: instead of
createAct(slash, { x, y }).createFx,act.update(dt), and throw the picture byact.draw(frame). It holds still by itself during a freeze.act.shakebakes the lot into a sprite sheet.node tools/act.mjs <id> --sheet - With the game's own character: read the timing, or
slash.actin a sheet's JSON (meta.pixelfx.act,windup,recoverywithhitsandt). Play the character's attack animationfreezeseconds before the effect, and at each hit stop updating everything, effect and characters both, forwindupseconds. Host-side hit-stop works on any effect, with or without an act.freeze
src/fx/moves/使用 JS 或 TS 的 Canvas 或 WebGL 游戏(原生 Canvas、Phaser、PixiJS、Kaplay、three.js):可直接使用代码。它没有依赖项;复制 和所需特效文件,或者直接依赖该仓库。
src/corejs
import { createFrame, clearFrame, createFx, composite } from "pixelfx"; // src/core/index.js
import * as frostNova from "pixelfx/fx/ice/frost-nova"; // src/fx/ice/frost-nova.js
const frame = createFrame(320, 180); // the game's pixel resolution; ox, oy scroll it with the camera
const img = new ImageData(320, 180);
const fx = createFx(frostNova, { x: hero.x, y: hero.y, flip: hero.facing < 0 });
// every tick
fx.update(dt);
clearFrame(frame);
fx.draw(frame, "back"); // what goes behind characters
drawCharacters(); // your own sprites, or draw them into frame.color yourself
fx.draw(frame, "front");
composite(frame, img.data, background); // color over the background plus light; or null for transparent
ctx.putImageData(img, 0, 0); // then scale the canvas up with image-rendering: pixelated
if (fx.done()) removeIt(); // one-shots end on their own; fx.stop() fades a loop out多个特效可以共用同一帧。在 WebGL 引擎中,每帧将合成后的 RGBA 上传到纹理,或者只渲染一次特效 Sprite Sheet(见下文),再像播放其他动画一样播放它。
任意引擎(Godot、Unity、GameMaker、Defold、LÖVE、Bevy、Construct):导出 Sprite Sheet。
会写入 (网格中排列的透明帧)和 (Aseprite 风格帧信息,因此 Phaser 的 atlas loader、Godot 和 Unity 的 Aseprite importer 以及大多数工具都能读取; 包含 fps、loop、帧大小、列数和 pivot)。将 pivot 设置为 (特效原点,例如脚部),导入时使用 nearest-neighbour filtering(最近邻过滤)且不压缩,并以 播放。辉光会作为带 alpha 的颜色烘焙进 Sprite Sheet,在暗场景中会呈现叠加效果;对于明亮场景,请渲染一个重新着色的版本,或将特效保留在单独的暗色图层上。
node tools/render.mjs ice/frost-nova --sheetrecords/ice/frost-nova/frost-nova.pngfrost-nova.jsonmeta.pixelfxmeta.pixelfx.pivotmeta.pixelfx.fps要处理完整库: 会渲染整个分类。
node tools/render.mjs <category> --sheet命中特效的背后需要角色身体。 单独看特效,就像一张贴纸。带角色的特效包含一个 :何时让身体蓄力、何时定格、目标如何反应。无论游戏采用哪种实现方式,都可以这样使用:
act- 使用内置角色:用 代替
createAct(slash, { x, y })。调用createFx、act.update(dt),并通过act.draw(frame)抖动画面。定格期间它会自行保持静止。act.shake会把整套动作烘焙成 Sprite Sheet。node tools/act.mjs <id> --sheet - 使用游戏自己的角色:读取时序信息,即 Sprite Sheet JSON 中的 或
slash.act(meta.pixelfx.act、windup,以及带有recovery和t的freeze)。在特效开始前hits秒播放角色攻击动画;每次命中时,让所有内容——特效和角色——停止更新windup秒。宿主端 hit-stop 对任何特效都适用,无论它是否包含 act。freeze
src/fx/moves/3. Make a new one
3. 制作新特效
Read first: it is the contract (meta, for pure effects or for
simulated ones, local coordinates, seamless loops, ending empty), the toolkit, the painter API and the quality bar.
Then:
docs/AUTHORING.mddraw(p, t, o)create(o)- Say it in one line with art direction: what it is, how it moves, its palette, its timing. "A frost nova: crystals erupt in a ring from the feet outward, a white flash at the center, shards spin off, the floor stays frosted and fades."
- Find references in the library: , then read the two or three closest effect files and borrow their techniques (
node tools/list.mjs <words>andParticlesinBitsfor sparks, smoke and debris,src/core/sim.jsfor translucent volumes,p.discfor seamless particles,cyclescrolls for flames and water). Look at the closest of Hugo Duprez's effects too: they are the quality bar. If his has more detail, light or life at the same size on screen, yours isn't done.tileFbm - Draft. (add
node tools/new.mjs <category>/<name>for a loop) gives a working file. When the direction is open, write 3 to 5 genuinely different drafts (--loop..<name>-a.js), render them all (-e.js) and look at every contact sheet before choosing. Show the user the strongest two or three if they're around (node tools/render.mjs "<category>/<name>-*"makes animations to share).--gif - Look, judge, fix. Render, open , and judge like a picky pixel artist. Fix the worst thing, render again. Three rounds minimum; a showpiece takes more.
records/<id>/contact.pngshows every frame,--allshows motion at full speed.--mp4 - Act it out if a character is involved: add (see "Acts" in
export const act), with a wind-up before t = 0, a pose change on the release frame, a freeze on contact and a recovery.docs/AUTHORING.mdshows consecutive 12 fps steps: judge motion from those and fromnode tools/act.mjs <id>, not from a contact sheet's samples.--mp4 - Check. catches drawing outside the canvas, one-shots that don't end empty, loop seams, unseeded randomness and slow frames, and in an act: keys out of order, unknown pose fields and hands that teleport. Fix until it passes clean. Delete the drafts you didn't keep.
node tools/check.mjs <id>
What to look for, and the usual fix:
| you see | fix |
|---|---|
| it reads as mush at 1x | fewer colors, a clear silhouette, one focal point, bigger shapes |
| speckle: lone noise pixels inside shapes | threshold noise less, quantize with |
| floaty, no punch | fast in, slow out ( |
| stiff or mechanical | vary every particle (size, speed, delay, life), add secondary motion (sparks, smoke, debris) |
| washed out | less glow (k 0.2 to 0.5), darker edge steps, keep white for the hottest core only |
| flat | light from one side: brighter top-left, darker bottom-right step; a darker ramp step at the base |
| smooth gradients, vector look | hard ramp steps in color; softness only in light and in translucent smoke |
| a checkerboard where smoke should be | |
| seam when a loop wraps | |
| a one-shot that pops off | let it shrink, dither out or fall away before t = duration |
| a great effect on a dead character | give it an act: wind-up, release pose, freeze on contact, a target that reacts, recovery |
| the act feels weightless | a wind-up the opposite way to the blow, a longer freeze (up to 0.2 s), a bigger silhouette change, shake |
| the hit lands out of sync | move the hit key to the effect's own constant, not to a time you eyeballed |
| he holds the weapon badly | keep a bend in the elbow, shafts in front of the chest; on a swing the blade lags the hand; arms reach about 15 px and don't stretch, so step the body in |
| his arms cross or the pose looks flat | he faces three-quarters towards us: for open arms the near hand goes left ( |
Directional effects face right, ground effects stand on y = 0, actor effects wrap (a 24 x 38 stock
character's mask and pixels), and nothing uses . Generic motifs are fine; never recreate a specific game's
sprites, characters or logos.
o.actorMath.random先阅读 :它是规范文档,涵盖 、纯特效所用的 或模拟特效所用的 、局部坐标、无缝循环、空帧结束、工具包、Painter API 和质量标准。
然后:
docs/AUTHORING.mdmetadraw(p, t, o)create(o)- 用一行描述并给出美术方向:它是什么、如何运动、使用什么调色板、节奏如何。“一个冰霜新星:水晶从脚边向外呈环形爆发,中心出现一道白色闪光,碎片旋转飞散,地面保持结霜并逐渐消退。”
- 在库中寻找参考:,然后阅读最接近的两三个特效文件并借鉴其技法(
node tools/list.mjs <words>中的src/core/sim.js和Particles可用于火花、烟雾和碎屑,Bits用于半透明体积,p.disc用于无缝粒子,滚动的cycle可用于火焰和水)。再查看 Hugo Duprez 的特效中最接近本需求的作品:Hugo Duprez's effects。这里是质量基准。如果他的作品在同一屏幕尺寸下细节、光照或生命力更丰富,你的作品就还没完成。tileFbm - 草稿。 (添加
node tools/new.mjs <category>/<name>可创建循环)会生成一个可用文件。如果方向尚未明确,请编写 3 到 5 个真正不同的草稿(--loop..<name>-a.js),全部渲染(-e.js),并在选择前查看每张 contact sheet。如果用户在场,展示其中最强的两三个(node tools/render.mjs "<category>/<name>-*"可生成便于分享的动画)。--gif - 观察、评判、修复。 渲染,打开 ,并像严格的像素艺术家一样评判。修复最糟糕的问题,再次渲染。至少进行三轮;展示级作品需要更多轮。
records/<id>/contact.png显示每一帧,--all以全速显示运动。--mp4 - 如果涉及角色,将动作演示出来:添加 (参见
export const act中的 “Acts”),其中包含 t = 0 之前的蓄力、释放帧上的姿态变化、命中时的定格以及恢复阶段。docs/AUTHORING.md会显示连续的 12 fps 步骤:应根据这些步骤和node tools/act.mjs <id>判断动作,而不要根据 contact sheet 中的采样帧判断。--mp4 - 检查。 会检测绘制超出画布、一次性特效未以空画面结束、循环接缝、未设定种子的随机数、帧耗时过长;在 act 中还会检测键顺序错误、未知姿态字段和手部瞬移等问题。修复到检查干净通过为止。删除未保留的草稿。
node tools/check.mjs <id>
需要观察什么,以及常见修复方法:
| 你看到的问题 | 修复方法 |
|---|---|
| 在 1x 下像一团糊状物 | 减少颜色,塑造清晰轮廓,设置一个视觉焦点,增大形状 |
| 零散噪点:形状内部出现孤立噪点 | 降低噪声阈值,使用 |
| 漂浮、缺乏冲击感 | 快进慢出( |
| 僵硬或机械 | 让每个粒子都有变化(大小、速度、延迟、寿命),加入次级运动(火花、烟雾、碎屑) |
| 色彩发白 | 减少辉光(k 0.2 到 0.5),使用更暗的边缘色阶,只在最热的核心处保留白色 |
| 平淡 | 让光从一侧照来:左上更亮,右下使用更暗的色阶;底部增加一层更暗的色阶 |
| 平滑渐变、矢量感 | 色彩使用硬色阶;只让 light 和半透明烟雾表现柔和 |
| 烟雾位置出现棋盘格 | 使用 |
| 循环回绕时出现接缝 | |
| 一次性特效突然消失 | 在 t = duration 前让它收缩、抖动消散或坠落 |
| 特效很棒,但角色像死了 | 为它添加 act:蓄力、释放姿态、命中定格、目标反应和恢复阶段 |
| 动作显得没有重量 | 蓄力方向与挥击方向相反,延长定格(最长 0.2 s),加大轮廓变化,添加 shake |
| 命中不同步 | 把命中键移到特效自身的常量上,而不是凭肉眼估算的时间 |
| 武器姿势不自然 | 肘部保持弯曲,杆状武器置于胸前前方;挥击时刀刃滞后于手;手臂伸展约 15 px 且不要拉伸,因此让身体移动来配合 |
| 双臂交叉或姿态扁平 | 角色以四分之三侧身朝向观察者:张开双臂时,近手向左( |
定向特效朝右,地面特效立在 y = 0 上,角色特效包裹 (一个 24 x 38 内置角色的遮罩和像素),并且任何内容都不使用 。可以使用通用母题,但绝不复制特定游戏的 Sprite、角色或 Logo。
o.actorMath.random4. Native code instead of JS
4. 使用原生代码而非 JS
If the game can't run JS and a sprite sheet isn't enough (an effect that follows a moving target, or needs
per-instance colors at runtime), port the effect. Pure effects are a function of time; simulated ones step the particles in
. Keep the two-buffer idea, the 4x4 Bayer threshold for dithered alpha and the palette ramp lookups.
Render the JS original and your port side by side to check they match.
src/core/sim.js如果游戏无法运行 JS,而 Sprite Sheet 也不够用(例如特效需要跟随移动目标,或需要在运行时为每个实例设置颜色),就移植该特效。纯特效是时间函数;模拟特效在 中推进粒子。保留双缓冲区思路、用于抖动 alpha 的 4x4 Bayer threshold,以及调色板色阶查找。并排渲染 JS 原版和移植版,检查二者是否匹配。
src/core/sim.js