102 lines
5.1 KiB
Markdown
102 lines
5.1 KiB
Markdown
# Memakai Atlas di Game
|
||
|
||
Kontrak proyek terbaru: [gameplay-standard.md](gameplay-standard.md) dan [character-workflow.md](character-workflow.md). Saat ini loader masih satu karakter ARCO; format baru tidak otomatis menjadi registry multi-karakter.
|
||
|
||
Atlas yang sudah lolos QA masih bisa terlihat salah kalau cara menggambarnya keliru. Aturan di sini
|
||
berlaku untuk canvas 2D, Phaser, PixiJS, Godot, Unity, dll.
|
||
|
||
## File yang dipakai
|
||
|
||
- `sprite-sheet-alpha.png` — semua frame, latar transparan
|
||
- `manifest.json` — koordinat tiap frame
|
||
|
||
Salin keduanya dari **run** (hasil `compose-atlas`), bukan dari `frames/`. Setiap kali atlas di-compose ulang,
|
||
salin ulang **keduanya** bersamaan.
|
||
|
||
Bila game dibuka lewat `file://` (tanpa server), `fetch` JSON diblok browser: bungkus manifest jadi file JS
|
||
(`window.SPRITE_MANIFEST = {...}`) yang dibuat otomatis dari `manifest.json` — jangan diedit manual.
|
||
|
||
## Baca koordinat dari manifest, jangan menebak grid
|
||
|
||
```js
|
||
const r = manifest.frame_layout.rows[state][frameIndex]; // {x, y, w, h}
|
||
const fps = manifest.animation.rows[state].fps;
|
||
const loop = manifest.animation.rows[state].loop;
|
||
```
|
||
|
||
Jumlah frame per gerakan bisa berubah setelah curation — selalu ambil dari manifest.
|
||
|
||
## Anchor di kaki
|
||
|
||
Frame sudah disejajarkan sehingga **kaki** berada di:
|
||
|
||
```
|
||
anchorX = cellWidth / 2
|
||
anchorY = cellHeight - safe_margin_y // dari manifest.cell
|
||
```
|
||
|
||
Posisi karakter di dunia game = posisi kaki. Gambar frame dengan offset `(-anchorX, -anchorY)`.
|
||
|
||
```js
|
||
ctx.save();
|
||
ctx.translate(Math.round(x), Math.round(y)); // x,y = kaki
|
||
ctx.scale(facing, 1); // facing: 1 kanan, -1 kiri
|
||
ctx.drawImage(sheet, r.x, r.y, r.w, r.h, -anchorX, -anchorY, r.w, r.h);
|
||
ctx.restore();
|
||
```
|
||
|
||
- **Flip kiri/kanan di sekitar anchor** (bukan di sekitar pojok sel) → karakter tidak "teleport" saat berbalik.
|
||
- Semua sprite digambar menghadap kanan; arah kiri selalu hasil flip.
|
||
|
||
## Jangan menambah blur di runtime
|
||
|
||
| Aturan | Canvas 2D | Lainnya |
|
||
|---|---|---|
|
||
| Matikan smoothing | `ctx.imageSmoothingEnabled = false` + CSS `image-rendering: pixelated` | Phaser `pixelArt: true`; Pixi `SCALE_MODES.NEAREST`; Godot filter *Nearest*; Unity Filter Mode *Point*, Compression *None* |
|
||
| Posisi dibulatkan | `Math.round(x)` saat menggambar (simulasi boleh float) | "pixel snap" / round di shader |
|
||
| Skala sprite lokal konstan | 1× pada ARCO; jangan mengubah skala per pose | sama |
|
||
| Proyeksi viewport seragam | Dunia dapat diproyeksikan fractional untuk responsif, tetapi x/y memakai rasio yang sama | jangan meregangkan badan |
|
||
| Efek tidak mengubah ukuran tubuh | Tanpa squash/stretch per state; VFX terpisah | sama |
|
||
|
||
Kalau salah satu dilanggar, frame yang sudah rapi di atlas bisa terlihat berbeda ketajamannya di layar —
|
||
gejalanya persis seperti masalah ukuran frame.
|
||
|
||
## Kecepatan animasi
|
||
|
||
- Pakai `fps` dari manifest sebagai default untuk gerakan di tempat (idle, serang, hurt).
|
||
- **Jalan/lari: frame maju berdasarkan jarak yang ditempuh**, supaya kaki yang menapak tidak meluncur:
|
||
|
||
```js
|
||
// stride = jarak satu siklus penuh (2 langkah) ≈ 2 × jarak kaki saat menapak (lihat measure_atlas.py)
|
||
walkPhase += dt * (Math.abs(vx) / stride) * frameCount;
|
||
frame = Math.floor(walkPhase) % frameCount;
|
||
```
|
||
|
||
Ukur `stride` dari atlas (jarak kaki terbesar × 2), jangan ditebak. Kalau hasil fps terlalu lambat/cepat,
|
||
ubah **kecepatan gerak** karakter, bukan fps-nya. Contoh: orc stride 208 px, jalan 170 px/s → ±4.9 fps.
|
||
|
||
Nilai dari lebar siluet kaki adalah **estimate**, bukan pengukuran stride same-foot contact yang terverifikasi. Runtime ARCO memakai estimate tersebut; jangan menyebutnya gait yang sudah tersertifikasi mulus. Periksa pose contact/passing dan pergantian kaki secara visual.
|
||
- Gerakan non-loop (`loop: false`) berhenti di frame terakhir, lalu kembali ke idle.
|
||
|
||
## Hitbox dari data, bukan tebakan
|
||
|
||
Ukur jangkauan senjata dari atlas (bounding box alpha tiap frame relatif anchor) dan simpan sebagai angka di
|
||
kode. Setelah atlas di-compose ulang dengan skala berbeda, **ukur ulang** hitbox.
|
||
|
||
## Checklist integrasi
|
||
|
||
- [ ] Atlas dan manifest disalin bersamaan dari run terbaru
|
||
- [ ] Anchor = (cellWidth/2, cellHeight − safe_margin_y)
|
||
- [ ] Flip dengan `scale(-1, 1)` di anchor
|
||
- [ ] Smoothing mati, posisi sprite dibulatkan, skala tubuh konstan dan proyeksi viewport seragam
|
||
- [ ] Kecepatan walk ikut kecepatan gerak
|
||
|
||
## Playback khusus ARCO yang menjadi baseline
|
||
|
||
- Jump menahan satu pose atletis pada rise/apex/fall; tidak replay crouch di udara.
|
||
- Doublejump memutar satu tuck pose360° dalam 0,28 s. Baca pivot lokal dan world-pivot dari metrics, lalu ukur ulang untuk karakter baru.
|
||
- Skill I menampilkan pose persiapan/tembak sebelum projectile release, lalu melepas input saat peluru keluar; tidak menunggu frame recovery nominal habis.
|
||
- Ultimate menggunakan pose cast 0,50 s, sementara drone/cut-in memakai timeline terpisah2,85 s. Jangan mengunci owner sepanjang durasi efek.
|
||
- `MECHA_METRICS.playback`, `states`, dan `emitters` melengkapi manifest. Koordinat emitter relatif anchor, bukan koordinat layar.
|
||
- Salto, facing flip, dan transform viewport tidak boleh menyebabkan per-state scale yang berbeda.
|