1Giới thiệu & bản đồ tài liệu
VFX_SHARED_*, checklist dựng. Phụ thuộc: 00-Design-Bible.md + data/_bible.json (INV-01/02/03/08/09/10/13/14/18, §10.3–10.4, §11 VFX_<ownerId>_<slug>) · 30-Art-Direction.md (§2 phép chiếu/cỡ, §3 ramp & token, §8 pipeline) · hub .claude/docs/ArtStyle.md §7 · Tools/pixelfx (docs/AUTHORING.md, skills/pixelfx/SKILL.md, src/fx/bladewake/*) · demo GDD/07-Game-Feel.md §1–§8, GDD/Data/EnemyAttacks.csv. Được dùng bởi: doc hero (41 sheet/hero), doc quái/boss, doc biome/hazard, doc UI/HUD, doc hiệu năng; agent dựng VFX; dev Unity (VfxPool, shader SpriteActor/SpriteAdd, PAL_LUT).Mục lục chi tiết (## và ###)
- 1. Đọc được giữa bầy (150–250 quái)
- 2. Luật 3/4 top-down
- 2.1 Phép chiếu (= 30 §2.1, VIEW.tilt 0.5)
- 2.2 Độ cao & bóng
- 2.3 Neo (pivot)
- 2.4 Lớp sort (5 lớp, ánh xạ Unity)
- 3. Sở hữu & màu FX
- 4. Ngôn ngữ thời gian
- 5. Hạng cỡ VFX
- 6. Ngân sách hạt & trần hiệu năng
- 6.1 Trần cứng (Steam Deck 60 fps, p95 ≤ 16.6 ms, Bible §10.4)
- 6.2 LOD dưới tải (đo p95 frame 1 s trượt, hoặc số quái sống)
- 7. Khớp hitbox ↔ hình
- 8. Nâng hạng tất định
- 9. Mẫu VFX spec (cuối)
- 10. Ba ví dụ điền đủ
- 10.1 Cung chém cận chiến — VFX_H01_a1_slash (có sẵn: heroes/varek-attack1)
- 10.2 Đạn địch tell vàng — VFX_EN_B1_03_A01_orb (Cầu Bùa của Thầy Bùa Tro)
- 10.3 Vùng sàn tím của boss — VFX_BS_B1_A02_toll_ring (Hồi Chuông Tang)
- 11. Thư viện VFX dùng chung (VFX_SHARED_<slug>, 74 mục)
- 12. Checklist — agent dựng 1 VFX với pixelfx
- 13. Quy tắc cho dev/AI
- 14. Quyết định đã chốt
21. Đọc được giữa bầy (150–250 quái)
Thứ bậc hiển thị (cao → thấp): tell Y/P/N → thân hero + vòng chân + x-ray → FX hero → số sát thương → thân địch → FX địch → hazard trang trí → môi trường. Không lớp thấp nào được che lớp cao hơn.
| # | Luật | Số |
|---|---|---|
| R1 | Tell trên màn | ≤ 4 tell Y + ≤ 3 tell P sàn từ quái thường, ≤ 5 tell P tổng (gồm hazard, Ấn, mutator, Khăn Tang) (INV-08) |
| R2 | FX hero nhường tell | trong r24 quanh viền vùng P: FX hero α × 0.70 (11 §1); FX hero không bao giờ vẽ trên lớp prioritize |
| R3 | Hình khối | FX hero = ĐẶC: lõi #fff6f0 + viền slot; FX địch = RỖNG: lõi tối/lạnh V ≤ 0.45 + viền sáng V ≤ 0.90 (INV-01c) — phân biệt được khi lọc xám |
| R4 | Độ phủ | FX additive phủ trung bình ≤ 1.5 × diện tích view; mọi pixel ≤ 6 lớp trong suốt chồng; FX 1 đòn hero (trừ ult) ≤ 25% diện tích view 480×270 |
| R5 | Trúng đòn hàng loạt | ≤ 16 VFX trúng mới/frame; quá trần: vẫn nháy trắng 0.07 s + số cho MỌI mục tiêu, bỏ tia lửa của mục tiêu xa hero nhất |
| R6 | Quái chết hàng loạt | ≤ 12 VFX chết mới/frame (Bible §10.4); quá trần: chỉ hất văng + tan dither (shader, 0 sheet) |
| R7 | Glyph trạng thái (10 §5) | địch: glyph 8×8 ramp hero đang chơi, vẽ trên vòng chân quái (neo feet, sy +4), ≤ 1 glyph, ưu tiên STUN > DISARM > PIN > BLIND > DREAD > BIND > SHOCK > FROST > EMBER > ROT > SLOW; loop thân sts_* ≤ 2/quái; hero: glyph bạc #c8c8d0 trên vòng chân hero, ≤ 3 xếp ngang cách 2 px + icon HUD có thanh thời gian |
| R8 | Môi trường động | bụi/tro/lá trang trí ≤ 40 hạt/view, α ≤ 0.35, S ≤ 40%, luôn dưới lớp tell (INV-18) |
| R9 | Chớp màn | ≤ 2 chớp/giây, α ≤ 0.35, 1–3 frame; không nháy > 3 lần/giây (an toàn nhạy sáng); tắt được ở Trợ năng |
| R10 | Số sát thương | lớp BattleDmgText, 1 mesh, ≤ 260 số; không đè HUD/chữ thông báo (07 §4) |
| R11 | Mù màu (11 §6) | Y: glint đổi thành hình thoi ◇ 5×5 (boss 7×7) + viền trắng nháy giữ; P: thêm gạch chéo 45° 1 px cách 4 px trong vùng + ▲ 7×6 cạnh !; N giữ chấm (đã khác hình) |
32. Luật 3/4 top-down
2.1 Phép chiếu (= 30 §2.1, VIEW.tilt 0.5)
Quy ước pixelfx (y màn hướng xuống, h = cao dương lên). Điểm sàn (x, z) cao h → màn (sx, sy) = (x, 0.5·z − h).
| Hình trên sàn (đơn vị px sàn thật) | Trên màn | Công thức |
|---|---|---|
| Tròn bán kính r | elip bán trục rx = r, ry = r/2 | vẽ midpoint ellipse nguyên: rx = round(r), ry = round(r/2); tâm tại pixel tâm |
| Vành (ring) r, dày w | 2 elip (r ± w/2) | dày trên màn = w ở Đông/Tây, w/2 ở Bắc/Nam |
| Rẻ quạt r, cung α, hướng θ | biên P(φ) = (r·cosφ, 0.5·r·sinφ), φ ∈ [θ − α/2, θ + α/2] | θ: 0 = e, π/2 = s (xuống màn) |
| Đoạn thẳng dài L hướng θ | dài màn L_s = L·√(cos²θ + 0.25·sin²θ) | vd Kẻ Mực 320 px: e/w 320, n/s 160, chéo 253 |
| Chữ nhật L × W (vd 200 × 32, 320 × 24) | 4 góc qua (x, 0.5·z) | rộng màn W·√(sin²θ + 0.25·cos²θ) |
| Tốc độ v theo θ | v_s = v·√(cos²θ + 0.25·sin²θ) | đạn 120 px/s bay dọc = 60 px/s trên màn (đúng, không bù) |
- Đạn bay thẳng: cao mặc định
h = 18(khoảng 16–24 theo vũ khí), bóng riêng ởh = 0. - Đạn vòng cầu (lob, ném bom, cối):
h(t) = 4·H·u·(1 − u),u = t/T; H mặc định = 0.35 × cự ly, kẹp 24–96 px; vòng tím đích vẽ từ lúc bắn (INV-13). - Nổ toả: hạt phun cả 3 chiều (
spray+spawn3), không phun phẳng như góc ngang. - Đòn vung đứng (slash) cần
tilt0.6–1.1 để không biến mất khi nhìn hướng n/s (pixelfxswing).
2.2 Độ cao & bóng
- Vật có
h > 2px = trên không → bóng sànshadow #0a0810:rx = round(0.45·rộng),ry = round(rx/2), co theorx × max(0.5, 1 − h/64), α 0.45 → 0.25 tại h = 64. - Mọi FX "đứng" (cột lửa, tia sét, cột tro) có chân trên sàn: 1 elip sáng/tối tại chân trên lớp
floor. - Sàn bị đánh: dấu sàn (nứt, cháy xém) = decal
floor_decal, sống 1.5–8 s, tan dither.
2.3 Neo (pivot)
feet (chân actor) · impact (điểm trúng) · muzzle (nòng/đầu gậy = socket tip@hit) · floor (điểm sàn, h = 0) · socket:<tên> (chest, head, overhead, hand_main… 30 §5.3). Đòn vung neo feet người đánh; FX sàn neo floor; đạn neo tâm đạn với bóng neo floor dưới đạn.
2.4 Lớp sort (5 lớp, ánh xạ Unity)
Lớp VFX (layer) | Unity sorting layer · order | Chứa |
|---|---|---|
floor_decal | FXBot · 0–9 | cháy xém, vũng, vùng sàn của hero, vũng sáng, khói sàn |
shadow | FXBot · 10 | bóng tiếp xúc, bóng vật bay |
floor_tell | FXBot · 20–29 | vùng N (20), vùng P (25), vòng đích đạn P (27) — trên bóng để bầy không che |
body | GamePlayLayer1 (Y-sort theo actor): vfx-back order −1, vfx +1; FX địch mang theo thân: GamePlayLayer2 | nửa xa của cung chém / vòng (drawBack), aura, trạng thái quanh thân |
overhead | FxLayer | trúng đòn, đạn, nổ, cung chém trên không, nhặt đồ |
overhead ưu tiên | Prioritize | glint Y, viền trắng nháy, ! tím, hào quang P, x-ray hero, glyph trạng thái, ◆ Tro Nhớ |
screen | camera overlay (UI dưới HUD) · số: BattleDmgText | chớp màn, viền tối, tint World chậm |
43. Sở hữu & màu FX
owner_class | Ai | Màu | Hình | tint |
|---|---|---|---|---|
HERO | đòn, kỹ năng, ult, triệu hồi, đạn phản, vùng sàn hero | ramp slot 30 §3.2 (r0..r7) + lõi #fff6f0; chớp #ffffff ≤ 2 frame; H01 thêm vàng kim ≤ 2 px/frame | ĐẶC, lõi trắng nóng | EXPORT (xuất --opts palette mỗi hero) |
ENEMY | đòn, đạn, phép, aura của địch | ramp thân biome 30 §3.4 + nhấn biome; lõi V ≤ 0.45, viền V ≤ 0.90, S viền ≤ 60%; cấm hue 340–10° S > 30% | RỖNG, lõi tối | BIOME + ALT (mask điểm nhấn) |
TELL | cảnh báo Y/P/N | CHỈ #ffd84a (Y), #b14cff (P), #e8e4f0 chấm (N) + trắng nháy | Y: glint 4-cánh 5×5 px (boss 7×7) + viền trắng 1 px quanh thân; P: elip fill 25% + viền 2 px + hào quang 2 px + ! 7×9; N: viền chấm 2 px + chấm thưa 15% | FIXED |
ENV | hazard trang trí, khí quyển | S ≤ 40%, α ≤ 0.35 | mềm, dưới tell | BIOME |
SHARED | hệ thống: spawn, chết, nhặt, trạng thái, khiên, màn | trung tính (ash-neutral, ngà, trắng) hoặc tô theo hero | theo mục §11 | HERO_LUT / FIXED |
HERO_LUT: sheet xám chỉ mục 9 bậc (#000…#fff→r0..r8), runtime tô bằng hàng hero trongPAL_LUT(30§3.5): 1 sheet cho 12 hero.- Trạng thái do hero gây (STS_EMBER/FROST/SHOCK/ROT/BIND/DREAD) = ramp hero đang chơi + glyph hình dạng riêng (INV-01f): EMBER lửa · FROST tinh thể · SHOCK zigzag · ROT bọt · BIND xích · DREAD mắt. Nhóm CONTROL (
10§5.1): STUN sao xoay · MUTE chuông gạch · BLIND bụi · DISARM lưỡi gãy · SLOW sóng ngang · PIN cọc. Màu không mang nghĩa nguyên tố — hình mang nghĩa. Trạng thái trên hero: bạc#c8c8d0, không ramp hero. - Đạn địch bị ĐÁP phản: đổi sang ramp hero + lõi
#fff6f0ngay frame phản (INV-01a); tốc ×1.6. - Chí mạng: chớp trắng
#ffffff(không vàng, INV-01a); số chí mạng ngà + "!" (screen-space). - Ấn tro spawn:
#e8e4f0; khi chơi H02 đổi viền gạch đứt 2 px (INV-01e). - Vật phẩm/tiền: cấm vàng bão hoà (dải tell) — Vàng Mã Vàng Mã CUR_PAPER_GOLD = giấy ngà
#d8cfb4+ dấu mực; Tro Hồn Tro Hồn CUR_SOUL_ASH =#e8e4f0; Mảnh Vọng Mảnh Vọng CUR_ECHO_SHARD = ngà-lam#cfd8e8+ viền slot hero.
54. Ngôn ngữ thời gian
Mỗi FX có 3 pha: anticipation (tụ, có thể = windup thân) → impact (frame trắng/nhả, 1–2 frame) → dissipate (nguội theo ramp, rơi, tan; kết rỗng). Nhanh vào, chậm ra: ease.outQuart/outExpo.
| Lớp hiệu ứng | Antic. | Impact | Dissipate | Tổng | fps | Frame |
|---|---|---|---|---|---|---|
| Tia trúng (hit spark) | 0 | 0.03 (1 frame trắng) | 0.20–0.27 | 0.30 | 30 | 9 |
| Cung chém hero (combo) | 0 (windup ở thân) | 0.03–0.05 | 0.15–0.25 | 0.30–0.60 | 30 | 9–18 |
| Đạn: nòng / bay / nổ | 0.10 / — / 0 | 0.03 | 0.20–0.30 | nòng 0.13 · bay loop · nổ 0.33 | 30 · bay 15 | 4 · 4–8 L · 10 |
| Vùng sàn hero (nện, nổ) | 0.05–0.10 | 0.05 | 0.30–0.60 | 0.40–0.75 (sóng lan 0.33) | 30 | 12–23 |
| Kỹ năng lớn | 0.15–0.30 | 0.05 | 0.40–1.00 | 0.6–1.4 | 30 | 18–42 |
| Ult | 0.30–0.60 | 0.07 + khựng | 1.0–3.0 | 1.5–4.0 | 30 | 45–120 |
| Tell Y | glint 1 tại đầu windup (4 frame) · glint 2 đúng 0.12 s trước va chạm (4 frame) · viền trắng nháy ≥ 2 frame | — | — | = windup | 30 | 4 + 4 |
| Tell P (sàn) | viền 2 px đúng cỡ cuối từ tick 0, α 0.6 → 1.0; fill 25% lớn dần từ tâm, đầy đúng tick va chạm; windup vùng ≥ 0.45 s (S/M), ≥ 0.60 s (L/XL) (11 Q1, chặt hơn INV-03) | 0.10 (fill → 60% rồi tắt) | 0.20 | windup + 0.30 | runtime | — |
| Vùng N | hiện 0.20 | — | tan 0.30 | theo thời lượng vùng | 12 loop | 8 L |
| Trạng thái (loop) | vào 0.10 | — | ra 0.15 | loop | 12 | 8–12 L |
| Spawn ấn tro | 0.60 (INV-14) | 0 | 0.15 | 0.75 | 30 | 23 |
| Xoáy triệu hồi | 1.00 | — | 0.20 | 1.20 | 30 | 36 |
| Chết quái | 0 | loé 0.09 | tro 0.40 | 0.50 | 30 | 15 |
| Nhặt đồ | 0 | 0.03 | 0.20 | 0.23 | 30 | 7 |
| Chớp màn | 0 | 0.033–0.10 | 0.10 | ≤ 0.20 | 30 | 1–3 + 3 |
- Sheet luôn 30 fps (
meta.pixelfx.fps); loop được 12/15 fps. Hitstop = runtime đóng băng FX + thân của 2 bên (không nướng); slow-mo chạy trên đồng hồWorld, FX của hero H12 (máy nhịp) chạy đồng hồ Hero. - Windup thân ≠ FX: runtime ép windup clip vào
startupCSV; FX impact neo đúng frame chạm (hits[0].t).
65. Hạng cỡ VFX
| Hạng | Khung tối đa (art px) | Bán kính sàn tương ứng | Ví dụ | Trần instance cùng lúc |
|---|---|---|---|---|
| S | 32×32 | r ≤ 16 | tia trúng, nhặt, glyph 8×8/12×12, glint 5×5 | 160 |
| M | 160×128 | r ≤ 56 | cung combo (vd 156×104), đạn + nổ, ĐÁP, nổ r48 | 64 |
| L | 256×176 | r ≤ 112 | kỹ năng, nện r64–96, Vỡ Âm r90 | 24 |
| XL | 480×270 | r ≤ 240 (vành, sàn: vẽ thủ tục) | ult, sóng boss, Lặng Nuốt Sân | 4 |
- Hạng tính theo khung lớn nhất trong 8 hướng; bậc thang Area (§8) có thể đẩy FX lên hạng trên (tính trần theo hạng mới).
- Tổng instance ≤ 252 < pool 320. Vòng/vùng sàn r > 112 KHÔNG làm sheet: vẽ thủ tục bằng shader elip pixel (viền + fill + chấm/gạch), sheet chỉ cho hạt/trang trí đi kèm.
- Ô (cell) sheet = khung nhỏ nhất ôm mọi frame của hướng đó (pixelfx tự cắt); PAD 2 khi đóng gói.
76. Ngân sách hạt & trần hiệu năng
6.1 Trần cứng (Steam Deck 60 fps, p95 ≤ 16.6 ms, Bible §10.4)
| Mục | Trần | Khi đầy |
|---|---|---|
Hạt (mọi Particles/Bits runtime) | 1.500 | bỏ hạt mới của FX ưu tiên thấp nhất |
| Trong đó tia lửa/mảnh | 700 | bỏ mới |
| Sheet VFX khác nhau đang phát | 40 | FX mới dùng sheet thứ 41 → thay bằng VFX_SHARED_hit_* gần nhất |
| Instance VFX (pool) | 320 (S 160 · M 64 · L 24 · XL 4) | cướp instance cũ nhất cùng hạng, trừ tell |
| VFX trúng mới / frame | 16 | R5 |
| VFX chết mới / frame | 12 | R6 |
| Vũng sáng động | 24 (hero ≤ 8) | bỏ vũng nhỏ nhất |
| Afterimage hero | 6 bản sống (1 bản/0.03 s, 60% → 0 trong 0.18 s) | — |
| Decal động | 64 | xoá cũ nhất |
| Số sát thương | 260 | vòng tròn ghi đè (07 §4) |
| Chi phí dựng 1 frame pixelfx (offline) | < 2 ms (check.mjs) | tối ưu trước export |
- Ưu tiên khi cướp/bỏ (cao → thấp): tell > FX hero đang điều khiển > ult > trúng lên Tinh Anh/Boss > FX địch > trúng lên quái S > chết quái S > môi trường.
- Mọi VFX là sheet phát bằng sprite instanced (cùng atlas, cùng material
SpriteAdd/SpriteActor); không ParticleSystem Unity cho FX gameplay.
6.2 LOD dưới tải (đo p95 frame 1 s trượt, hoặc số quái sống)
| Mức | Bật khi | Cắt |
|---|---|---|
| L0 | mặc định | — |
| L1 | quái ≥ 180 hoặc p95 > 15.0 ms | hạt ×0.6 · trúng 10/frame · chết 8/frame · môi trường động tắt · preset Deck bắt đầu ở L1 |
| L2 | quái ≥ 220 hoặc p95 > 16.6 ms | hạt ×0.35 · trúng 6 · chết 6 · đuôi đạn địch tắt · afterimage 1 bản/0.06 s · vũng sáng ≤ 12 |
| L3 | p95 > 20 ms trong 2 s | chỉ còn: tell, FX lõi hero (không hạt), nháy trắng, hitstop, số, vòng chân, x-ray |
- KHÔNG BAO GIỜ cắt: tell Y/P/N, nháy trắng trúng (INV-10), vòng chân + x-ray (INV-09), ấn spawn (INV-14), vùng đích đạn P (INV-13).
- Hạ mức ngay khi vượt ngưỡng; nâng lại sau 3 s liên tục dưới ngưỡng − 1 ms (trễ để không nhấp nháy).
87. Khớp hitbox ↔ hình
| Luật | Đòn hero | Đòn địch |
|---|---|---|
| Nguồn sự thật | hitbox từ data (CSV); VFX dựng theo số đó | như hero |
| Phủ | hình phủ ≥ 90% diện tích hitbox | hình phủ 100% hitbox |
| Lệch biên | hitbox được lớn hơn hình ≤ 4 px (dễ trúng) · hình vượt hitbox ≤ 6 px (S/M), ≤ 10 px (L/XL) | hitbox nhỏ hơn hình 2 px (công bằng); KHÔNG BAO GIỜ lớn hơn hình |
| Đạn | bán kính va = bán kính lõi + 2 px | bán kính va = bán kính lõi − 1 px; vẽ ≥ 4 px đường kính lõi |
| Tell P | — | viền 2 px nằm ĐÚNG trên biên hitbox; fill = toàn hitbox |
| Sóng lan | sát thương theo reach từng frame của sheet (packer đo nửa ngang vòng/mây đục ≥ 50% trên hàng pivot; lan theo max cộng dồn trong wave_spread_time 0.33 s) (07 §6.1) | vành sóng thủ tục: hitbox = vành đang vẽ frame đó |
| Rẻ quạt | biên P(φ) §2.1 trùng mép cung chém ± 4 px | — |
- QA: bật overlay debug (hình sàn hitbox vẽ elip 1 px) trên ảnh chụp 8 hướng (
--dirs) + 1 ảnh in-game; ghi sai số đo vào spec (hitbox.match).
98. Nâng hạng tất định
- Hạng kỹ năng I/II/III, Edge/Relic tăng
Area,Count,Duration: chỉ đổi THAM SỐ, không đổi màu (trừ hạng III: viền sáng thêm 1 bậcr6 → r7), không sheet mới cho hạng III (Bible §10.3). - Seed cố định:
seed = fnv1a32(VFX id) mod 2^16; mọi hạng dùng cùng seed → cùng hình, chỉ lớn hơn. - Thang cỡ sheet (mọi FX có hitbox theo
Area, kể cả cung combo M): xuất 4 bậcs ∈ {1.0, 1.3, 1.7, 2.2}bằng--opts {"radius": r_base × s}. Runtime: tỉ lệ đícht = r_đích / r_base→ chọn bậck = argmin |ln(t / s_k)|→ scale runtimet / s_k(luôn trong [0.87, 1.15] với 0.87 ≤ t ≤ 2.53), Point filter, làm tròn bề ngang về số chẵn px.t < 0.87→ bậc 1.0 ×0.87;t > 2.53→ bậc 2.2 ×1.15 (kẹp hình); viền sàn thủ tục vẫn đúng r thật (hitbox luôn khớp viền). - Bậc 1.0 + 1.3 nạp đầu run; bậc 1.7/2.2 nạp lười khi
Areacủa build ≥ 1.49 (tính trong ngân sách 8 trang hero; vượt → bỏ bậc 2.2, kẹp ở 1.7 ×1.15 = 1.96). - FX không có hitbox (tia trúng, nhặt, glyph): giữ cỡ, chỉ đổi số lượng (Count) hoặc thời lượng.
- Bảng tham số mỗi hạng ghi trong spec (
rank):I {r, n, dur}·II·III; cấm công thức ngẫu nhiên lúc chạy. - Ví dụ: H01 SK02 nện r64 × Area 1.4 = 89.6 → t = 1.4 → bậc 1.3, scale 1.077; Area 2.0 → t = 2.0 → bậc 2.2, scale 0.909.
109. Mẫu VFX spec (cuối)
File: Design/ArtSpecs/vfx/<VFX_ID>.md, 1 file/VFX; hàng tóm tắt đồng bộ vào bảng VFX của doc chủ (hero/quái/boss). ★ = bắt buộc trước export.
| Field | ★ | Định nghĩa | Giá trị cho phép |
|---|---|---|---|
id | ★ | ID duy nhất = tên sheet | VFX_<ownerId>_<slug>; ownerId ∈ ID Bible (Varek H01, Hàng Cọc Tro H01_SK03, Trăng Máu H01_UL1, Cầu Bùa EN_B1_03_A01, Hồi Chuông Tang BS_B1_A02, Nổ Chết EM05, Đàn Vồ MUT04, Đầm Vải Liệm B2…) hoặc SHARED UI HUB; slug snake thường ≤ 24 ký tự |
owner_class | ★ | ai sở hữu màu | HERO ENEMY TELL ENV SHARED (§3) |
tint | ★ | cách tô | EXPORT (ramp hero lúc xuất) · HERO_LUT (xám chỉ mục, tô runtime) · BIOME (+ ALT khi có mask) · FIXED |
trigger | ★ | sự kiện + mốc t | t = 0 = khung chạm / nhả / spawn; vd attack1 hit, windup start, projectile spawn, on_death, status_apply |
layer | ★ | lớp sort | floor_decal shadow floor_tell body_back body_front overhead prioritize screen (§2.4) |
size_class | ★ | hạng cỡ | S M L XL (§5) |
canvas | ★ | khung vẽ hướng e | w×h art px ≤ trần hạng; gốc origin [x,y] |
pivot | ★ | neo | feet impact muzzle floor socket:<name> (§2.3) |
dirs | ★ | số hướng | 1 (đối xứng tròn) · 8 (xuất 8, gói 5, lật 3); tilt 0.6–1.1 nếu là cung vung |
frames | ★ | số frame + fps + loop | N @30 (loop được @12/15); loop true/false; duration s |
phases | ★ | mốc 3 pha (s) | antic [a,b] · impact [c,d] · dissipate [e,f]; kết rỗng ở duration |
keyframes | ★ | diễn biến | danh sách t → mô tả (white frame, lan, giữ, nguội, rỗng) |
primitives | ★ | công cụ pixelfx | swing slash band floorRing floorDisc floorGlow shadeFloor Particles(kind) Bits(kind) Lights spray slam p.disc … + tham số chính |
palette | ★ | màu | ≤ 6 hex tối → sáng + lõi (#fff6f0 HERO / tối ENEMY); nguồn: 30 §3.2/§3.4/§3.6 hoặc PAL.<x> |
particles | hạt | kind × count, tốc px/s, đời s, màu, trọng lực (950 px/s²)/cản, bóng có/không | |
light | ánh sáng additive | k 0.2–0.5, bán kính px, đời s; vũng sàn có/không | |
easing | ★ | gia tốc | outQuart outExpo inOutQuad linear + freeze (s) |
hitbox | ★ nếu gây sát thương | hình sàn + khớp | circle r · ring r w · sector r α · line L W · rect L W · proj r; match = phủ %/lệch px đo được (§7); wave = reach hoặc procedural |
rank | nâng hạng | bảng I/II/III {r, n, dur} + thang s (§8) | |
variants | biến thể | seed (mặc định §8), Dạng Rèn (--opts palette), ALT, mù màu | |
perf | ★ | ngân sách | ô lớn nhất px, frame/hướng, trang atlas, instance trần, số Lights |
lod | ★ | mức bị cắt | never (tell, flash) · L1 · L2 · L3 |
sfx | âm đi kèm | SFX_<ownerId>_<slug> | |
source | ★ | nguồn | reuse <pixelfx id> · recolor <id> --opts · new src/fx/<cat>/<name>.js; file mượn kỹ thuật |
export | ★ | lệnh | node tools/render.mjs <id> --sheet [--dirs] --opts '{…}' --seed N --out <dir> hoặc node tools/bw-fx.mjs <name> / hero.mjs |
review | ★ | duyệt | check.mjs sạch · inv01_check 0 lỗi · overlay hitbox · art-style-reviewer pass · ngày |
1110. Ba ví dụ điền đủ
10.1 Cung chém cận chiến — H01_COMBO_A_1 (alias A1, clip attack1 32 frame, hit frame 11): t = 0 … VFX_H01_a1_slash (có sẵn: heroes/varek-attack1)
owner_classHERO ·tintEXPORT ·triggercombo A1 (HeroMoves.csv A1: startup 0.08, active 0.06, recovery 0.22, post_rate 1.6, dmg 14, radius 44, arc 150, step 10, knockback 16, hitstop 0.035).layerbody_back(nửa xa cung,vfx-back) +body_front(nửa gần + tia lửa,vfx); lên mục tiêu: on_hit (dmg_kind lưỡi sắc) t = 0 tại frame chạm hits[0].t; ≤ 16 VFX t… VFX_SHARED_hit_slash (overhead).size_classM ·canvas156×104 hướng e,origin [40,84](ôse156×127, pivot [44,80]) ·pivotfeet (chân Varek) ·dirs8,tilt1.frames32 @30, one-shot, 1.07 s gồm windup tác giả 0.30 + hiệu ứng 0.60 + freeze nướng 0.07;hitFrame11 (se). Runtime: windup ép vào 0.08 s, bỏ frame freeze, đuôi ×1.6.phases: antic [−0.30, 0] (ở thân) · impact [0, 0.05] · dissipate [0.05, 0.60].keyframes: −0.30 GUARD · −0.15 WIND (lưỡi gần ra sau vai, lean −4, crouch 4) · −0.04 cuộn sâu · 0.00 CUT snap (bước +5, lean +5, ghost bật), cung hiện, mép trước trắng nóng · 0.03 chạm: tia lửa + chớp tại (50, −22),floorGlowr30 · 0.05 THRU snap · 0.05–0.20 vệt nguội theo ramp, tia lửa rơi nảy · 0.42 GUARD · 0.60 rỗng.primitives:swing {cx:7, cd:0, cy:−24, R:27, sy:0.62, rot:−0.32, a0:−2.5, dir:1, span:3.2, W:11, t0:−0.02, tilt:1, turn:0}quabladeArcở cả 2 lớp;arcSparks(ps,R,ARC,C,8);impact(ps,lights,R,50,0,−22,0.35,C,0.8);p.floorGlow(50,0,30,C[5], 0.35 → 0 trong 0.3 s); act dust t0 x12 n5, x−10 n3.palette:#6e1024 #a8162e #e0283a #ff4836 #ff9050 #ffe0b4+ lõi#fff6f0, frame trắng#ffffff1 frame; afterimage#a01830.particles: 8SPARKdọc cung (kéo dài theo tốc, g 950, nảy), màu C[8..5]; tia chạm quaspraytoả đủ chiều; không khói.light: 1Lights.addtại điểm chạm r 18, k 0.35, đời 0.15 s.easing: quét snap-in, vệt nguộioutQuart~0.15 s; freeze 0.07 (tác giả) / 0.035 (runtime); không rung camera.hitbox:sector r44 α150hướng ngắm, gốc = chân trước bước = elip màn 88×44. Mũi lưỡi = cx + R = 34 px từ chân sau bước (+10) = 44 ✓; chớp chạm x50 vượt mép 6 px ✓.match: phủ ≥ 90%, vượt ≤ 6 px (đo trên--dirs).rank(thang Area §8): s 1.0 → R27 W11 (r44) · 1.3 → R40 W13 (r57) · 1.7 → R58 W15 (r75) · 2.2 → R80 W18 (r97, sang hạng L).variants: Dạng Rèn--opts palette; seed 4 (canon demo, giữ).perf: ô ≤ 160×130, ≤ 32 frame/hướng, ≤ 3 bản sống, 1 light ·lodL3 bỏ tia lửa (giữ cung) ·sfxHeroAttackCut heroId=H01 moveIndex=1 (H01_COMBO_A_1; H01_UL3_ECHO dư … SFX_H01_a1_swing.source:heroes/varek-attack1.js,_reaver.js(ramp bladeArc arcSparks impact keepIn),_varek.js·export:node tools/hero.mjs varek attack1→node Tools/bw-pack.mjs varek·review:act.mjs --dir s|n,check.mjssạch.
10.2 Đạn địch tell vàng — EN_B1_03_A01 (1 viên) và EN_B1_03_A02 (3 viên quạt 18°): t = 0 = rời … VFX_EN_B1_03_A01_orb (Cầu Bùa của Thầy Bùa Tro)
owner_classENEMY (+ phần TELL) ·tintBIOME + ALT (điểm nhấn 186° → alt khi chơi H10/H12) ·triggerCầu Bùa EN_B1_03_A01 (EnemyAttacks.csv: projectile gold, windup 0.55, recovery 0.40, dmg 10, tầm 300, bán kính 5, tốc 120, thời gian hồi chiêu 3.0; từ phút 3 quạt 3 viên lệch 18°, 1 token đạn).- 4 phần, 4 sheet:
| Phần | layer | Hạng · khung · dirs | Frames | Mô tả |
|---|---|---|---|---|
charge | body_front | S · 16×16 · 1 | 16 @30 (0.55 s, giữ frame cuối tới khi bắn) | quả cầu tụ ở muzzle (đầu gậy): lõi #2e464c, viền #9ff3ff lớn 2 → 12 px; glint 1 windup_start (glint 1) và impact − 0.12 s (glint 2; đạn: ETA ≤ 0.12 s… VFX_SHARED_tell_glint_y tại t = −0.55 |
flight | overhead | S · 16×16 · 1 | 6 @15 loop | lõi tối #2e464c → #476e75 đường kính 12 px, viền 1 px #9ff3ff, vành ngoài 1 px #ffd84a nháy 8 Hz (runtime bật/tắt mỗi 0.0625 s, không nướng vào sheet; 11 §2 Y_REFLECT); đuôi 3 chấm rỗng α 0.6/0.4/0.2 cách 4 px; bóng shadow 5×3 tại h = 0 |
burst | overhead | S · 32×24 · 1 | 10 @30 | chạm hero/tường: vành rỗng nở r4 → r12 (elip), 6 GRIT lạnh rơi, rỗng tại 0.33 s |
reflect | overhead | S · 16×16 · 1 | 6 @15 loop | sau ĐÁP: HERO_LUT lõi #fff6f0 + viền slot, tốc ×1.6 (192 px/s), dmg ×3, xuyên 3; chạm = on_crit t = 0 cùng frame chạm, phát sau hit_* cùng điểm (order +1) VFX_SHARED_hit_crit nếu chí mạng, không thì orb_burst tô hero |
phases: antic [−0.55, 0] · impact t = 0 (rời gậy, 1 frame viền trắng) · bay tới 2.5 s (300 px / 120) · dissipate burst 0.33 s.- Tell Y (INV-02): glint 1 đầu windup · glint 2 + viền trắng 2 frame tại
ETA − 0.12 s(runtime tính ETA = khoảng cách tới hero / tốc, cập nhật mỗi frame, bắn glint 2 khi ETA ≤ 0.12 lần đầu) · SFXtinh. Vành vàng chỉ trên đạn, không trên gậy (INV-01a). hitbox:proj r5(lõi vẽ r6 → nhỏ hơn hình 1 px ✓); bay ở h = 18; va chạm tính trên sàn (bóng).paletteB1 (30§3.3–3.4):#2e464c #476e75 #68a3a8 #9ff3ff+ tell#ffd84a+#ffffff(viền nháy); ALT:#5b586c #8a8699 #b9b5c6 #e8e4f0.light: flight 1 light r 8 k 0.2 (L2 tắt) ·perf: 4 sheet, ô ≤ 32×24, ≤ 9 viên sống (3 Thầy Bùa × 3), 0 hạt khi bay ·lod: vành vàng, glint, viền trắng =never; đuôi L2.source:src/fx/bladewake/orb.jsthêm knobhollow(mặc định false giữ demo; true = lõi tối + viền sáng),orb-burst.jsrecolor;glint-gold.jsdùng chung ·export: thêm mụcorb_hollow: {fx: "bladewake/orb", opts: {hollow: true, palette: […]}}vào bảng tên củatools/bw-fx.mjs, rồinode tools/bw-fx.mjs orb_hollow orb_burst.
10.3 Vùng sàn tím của boss — BS_B1_A02 windup start t = −0.90 s; t = 0 = boss nện chuông (clip att… VFX_BS_B1_A02_toll_ring (Hồi Chuông Tang)
owner_classTELL (vành) + ENEMY (trang trí) ·tintFIXED ·triggerHồi Chuông Tang BS_B1_A02 (EnemyAttacks.csvBS01_B2: ring violet, windup 0.90, chạy 1.1, recovery 0.70, dmg 26, tầm 240, dày 18). P2: 2 vòng cách 0.6 s. Đòn chữ ký boss: miễn token tím, nhưng khi đang windup thì không tell P nào khác được bắt đầu (INV-08).size_classXL thủ tục (không sheet cho vành) + 2 sheet phụ.- Thời gian & hình (r tính px sàn, tâm = chân boss):
| t (s) | Hình |
|---|---|
| −0.90 | hào quang P quanh boss (windup_start đòn P → impact VFX_SHARED_tell_aura_p, prioritize), ! tím (windup_start đòn P → impact VFX_SHARED_tell_bang_p) trên overhead, SFX ù trầm; vòng tầm tối đa r 240 viền 1 px #b14cff α 0.35 (floor_tell 25) |
| −0.90 → 0 | vành xuất phát r 20 dày 18: viền 2 px đúng cỡ từ tick 0, α 0.6 → 1.0; fill 25% lớn dần từ tâm dải, đầy đúng t = 0 (INV-02, 11 §1); windup 0.90 ≥ 0.60 (XL) ✓ |
| 0 | boss nện chuông (clip attack3); BS_B1_A02 t = 0 (chuông chạm sàn) và BS_B1_A14 mỗi vành (t = 0 / 0.90… VFX_BS_B1_A02_bell_flash (M, 48×32, 8 frame, overhead, #c9c2b0 → #ffffff 1 frame, không tím) |
| 0 → 1.1 | vành chạy r(t) = 20 + 200·t; dải [r − 9, r + 9]: fill 25% #b14cff, viền 2 px mép ngoài α 1.0, mép trong α 0.6; dày màn 18 px Đông/Tây, 9 px Bắc/Nam (§2.1); bụi BS_B1_A02 / BS_B1_A14: 0 → 1.10 s sau mỗi vành, 8 cái/0.1 s rải đều t… VFX_BS_B1_A02_dust (sheet phụ 2: recolor dash_start t = 0 tại chân VFX_SHARED_dash_dust ramp B1 Họ A, 1 hướng, S 16×12, 9 @30) 8 cái/0.1 s rải đều trên mép ngoài, sống 0.3 s |
| 1.1 → 1.3 | vành tại r 240 tan α → 0; vòng tầm tối đa tắt |
hitbox:ring r(t) w16(hình 18 → hitbox 16, nhỏ hơn 2 px ✓); mỗi mục tiêu trúng ≤ 1 lần/vành; Lướt xuyên dải = Né Hoàn Hảo;waveprocedural (vành đang vẽ = vùng trúng frame đó).- Mù màu: gạch chéo 45° 1 px cách 4 px trong dải (R11).
palette:#b14cff(fill α 0.25, viền α 1.0/0.6), không màu khác trong vành; hào quang/!dùng sheet chung.perf: 1 mesh vành 64 đoạn, shader elip pixel snap 1 art px (1 draw call) · bụi ≤ 24 instance S ·lod: vành, vòng tầm, hào quang,!=never; bụi L2.sfx: EnemyWindupStart attackId=BS_B1_A02|BS_B1_A14 tellClass=P (từ frame đ… SFX_BS_B1_A02_hum (−0.90), EnemyImpact attackId=BS_B1_A02 tellClass=P (attack3 f27 = vành bắt đầ… SFX_BS_B1_A02_toll (0) ·source: shaderTellRing(runtime, dùng chung mọi tell P dạng vành/đĩa/rẻ quạt) +bladewake/violet-ring.jschỉ làm tham chiếu hình (không phát sheet cho vành 1.0) ·review: chụp 8 vị trí hero quanh boss, overlay hitbox, đo lệch ≤ 1 px mỗi mép.
1312. Checklist — agent dựng 1 VFX với pixelfx
- ID & chủ: đặt
VFX_<ownerId>_<slug>(Bible §11); kiểm chưa trùng; xác địnhowner_class+tint(§3). Đòn địch: đọcadaptive_source— biến thể ASH dùng CHUNG sheet với BASE (INV-15: không đổi tell/hitbox). - Số gameplay: lấy windup/active/recovery/bán kính/cung/tốc/dmg từ CSV của chủ; tell Y/P/N từ doc chủ. Thiếu số → dừng, không đoán.
- Thư viện trước:
node tools/list.mjs <từ khoá>+docs/CATALOG.md+ §11; render 2–3 ứng viên, xemrecords/<id>/contact.png. Đủ dùng →reuse/recolor(--opts palette), sang bước 8. - Viết spec:
Design/ArtSpecs/vfx/<VFX_ID>.mdđủ field ★ (§9): hạng cỡ (§5), lớp (§2.4), pha thời gian (§4), hitbox (§7), rank (§8), perf + LOD (§6). - Nháp:
node tools/new.mjs <category>/<name>(vòng lặp:--loop); hướng mở → 3 nháp-a/-b/-c, render tất cả, giữ 1. Viết hướnge; vòng/vùng = hình sàn (floorRing,shadeFloor,band(..., p.V.k, ...)), nổ =spray+spawn3, vật bay có bóng; khôngMath.random/Date; tôn trọngo.palette,o.seed. - Nhìn – sửa ≥ 3 vòng:
node tools/render.mjs <id> [--dirs] [--all] [--mp4]; sửa lỗi tệ nhất mỗi vòng theo bảng "you see → fix" củaskills/pixelfx/SKILL.md(mush, speckle, floaty, washed out, side-view…). Có thân diễn →act+node tools/act.mjs <id> --dir svà--dir n. - Kiểm máy:
node tools/check.mjs <id>sạch (biên, kết rỗng, seam loop, seed, < 2 ms/frame, act keys). - Màu:
py -3 Tools/artcheck/inv01_check.py <png> --kind vfx_hero|vfx_enemy --id <ownerId>= 0 lỗi; HERO: viền slot + lõi#fff6f0, đặc; ENEMY: lõi tối, rỗng, không đỏ thẫm, không dải tell; TELL chỉ 3 màu tell. - Hitbox: overlay hình sàn hitbox lên sheet
--dirs+ 1 ảnh in-game; đo phủ % và lệch px; ghihitbox.match. Fail → chỉnh tham số pixelfx, không chỉnh hitbox data. - Xuất:
node tools/render.mjs <id> --sheet [--dirs] --opts '{…}' --seed N --out <dir>(FX Bladewake: thêm mục vàotools/bw-fx.mjs; hero:tools/hero.mjs) → thang Area 4 bậc nếu có (§8) →node Tools/bw-pack.mjskhi thuộc atlas actor. - Nhập Unity: PNG + JSON vào
Assets/ArtAsset/Vfx/Pixel/<ownerId>/(SHARED:SHARED/);PixelArtImportRulescắt, pivot =meta.pixelfx.pivot, PPU 16; gán sorting layer/order theolayer(§2.4); đăng ký vào bảng VFX của doc chủ (id → sheet, fps, loop, hạng, LOD). - Duyệt: contact sheet + strip + ảnh in-game ×1/×4 trên sàn biome đích giữa 200 quái → subagent
art-style-reviewer=pass; test 5 s (P4) nếu là FX hero chữ ký (nhận ra hero qua 3 s FX ≥ 80%, P6). Ghireviewvào spec. Nháp không giữ → xoá.
1413. Quy tắc cho dev/AI
- Mọi VFX phải có spec (§9) trước khi export; không spec = không vào
Assets/. - Tell chỉ dùng
#ffd84a/#b14cff/#e8e4f0chấm theo đúng hình INV-02; không FX nào khác được mượn màu hoặc hình đó (glint 4 cánh, elip fill 25% viền 2 px, viền chấm). - FX hero = đặc + lõi
#fff6f0+ viền slot; FX địch = rỗng + lõi tối; màu hero xuất qua ramp30§3.2 hoặcHERO_LUT, không tự pha. - Vòng/vùng/nón trên sàn tuân §2.1 (elip 2:1); vật bay có bóng; không phun phẳng; không xoay ảnh để ngắm.
- Vùng sàn r > 112 và mọi tell P/N = vẽ thủ tục (
TellRing), không sheet. - Hình = hitbox theo §7; đòn địch: hitbox ≤ hình; sửa hình chứ không nới hitbox địch.
- Tôn trọng trần §6.1 và LOD §6.2; không bao giờ cắt tell, nháy trắng trúng, vòng chân, x-ray, ấn spawn.
- Nâng hạng chỉ đổi tham số, seed cố định, thang 4 bậc (§8).
- Ưu tiên
reuse→recolor→new; FX mới tác giả trên nhánhart/<slug>trongTools/pixelfx, push/merge khi dev đồng ý. - Không dùng ParticleSystem Unity cho FX gameplay; không chữ nướng trong sheet (
ui/level-upcó chữ tiếng Anh → không dùng). - Không ghi tên game/studio/thương hiệu có thật vào spec hay file trong repo.
1514. Quyết định đã chốt
| Q | Quyết định | Lý do |
|---|---|---|
| Lớp sort | 5 lớp gốc + floor_tell trên shadow trong FXBot | bầy 250 quái đổ bóng không được che tell sàn (INV-18) |
| Vành/vùng lớn | r > 112 và mọi tell P/N vẽ thủ tục | đúng r tuyệt đối với hitbox, 1 draw call, không tốn trang atlas |
| 2026-11-07 — "≤ 40 sheet VFX hoạt động" (Bible §10.4) | hiểu là ≤ 40 sheet KHÁC NHAU đang phát; instance kẹp bởi pool 320 | demo đo 16 trúng/frame × 10 frame đã > 40 instance; Bible không đổi số |
| Hạng cỡ | S 32×32 · M 160×128 · L 256×176 · XL 480×270 | cung combo canon 156×104 phải là M; XL = 1 view |
| Thang Area | 4 sheet bậc 1.0/1.3/1.7/2.2, scale runtime ≤ ±15% | giữ 1 cỡ pixel (VP1) mà vẫn khớp hitbox (thay scale demo 0.6–2.6) |
| Hitbox bất đối xứng | hero ≤ hình + 4 px; địch ≤ hình − 2 px | dễ trúng cho người chơi, công bằng khi bị đánh (P3, INV-15) |
| 2026-11-07 — đạn Cầu Bùa EN_B1_03_A01 | demo "lõi trắng" → lõi tối + viền #9ff3ff + vành #ffd84a | INV-01c: FX địch rỗng, lõi trắng chỉ cho hero |
| Chí mạng | chớp trắng, không vàng (demo 07 §3 "chí mạng nháy vàng" bỏ) | INV-01a |
| Trạng thái | 1 sheet HERO_LUT + glyph/trạng thái cho 12 hero; đủ 12 STS_* (6 BUILD + 6 CONTROL của 10 §5.1), glyph trên vòng chân | INV-01f, 0 sheet thêm mỗi hero; khớp vị trí glyph của 10 §5 |
| 2026-11-07 — gộp mục thư viện | spawn_mark_dashed → biến thể của spawn_mark; magnet_trail → pickup_collect; relic_beam → chest_relic; 3 chỗ trống = sts_blind sts_disarm sts_pin; cc_stun/debuff_slow/debuff_silence đổi tên sts_stun/sts_slow/sts_mute | giữ trần 70 mục mà phủ đủ 12 trạng thái của 10 |
2026-11-07 — lõi đạn Y_REFLECT | giữ lõi tối #2e464c + viền #9ff3ff (INV-01c); 11 §2 ghi "lõi #9ff3ff tâm trắng" → doc 11 cần sửa theo Bible | Bible thắng doc con; tâm trắng = ngôn ngữ FX hero |
| LOD | 4 mức L0–L3 theo p95 + số quái, trễ nâng 3 s | giữ 60 fps Deck (MS01b) mà không bao giờ mất tell |
| Thư viện chung | đúng 74 mục VFX_SHARED_*; thêm mục cần quyết định ghi ngày | chặn trùng lặp, giữ ≤ 3 trang chung |
| 2026-11-07 — thêm 4 shader dùng chung có tham số thay ~50 tên shader runtime | #71 FloorEllipse · #72 FloorGlow · #73 ScreenFX (flash/tint/vignette/wipe) · #74 RopeLine (chain/beam/silk); TellRing = preset của FloorEllipse; 70 → 74 mục (0 sheet thêm) | spec AV đặt ~50 tên shader không tham số; khớp SHARED__vfx.json (74 mục) |