# Siklus hidup dan alur layar

## Hook `defineGame`

```ts
import { defineGame } from '@ralyoo/sdk';

export default defineGame({
  locales: { id: { mulai: 'Mulai!' } },
  async preload(ctx) {},
  onStart(ctx) {},
  onInput(ctx, player, input) {},
  onTick(ctx, dt) {},
  onPlayers(ctx, players) {},
  onPause(ctx) {},
  onResume(ctx) {},
  onStop(ctx) {},
});
```

Hanya `onStart` yang wajib.

| Hook                   | Kapan                                                         | Untuk apa                                                                            |
| ---------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `preload`              | Begitu game dipasang, selama platform menghitung "3·2·1·GAS!" | Memuat aset, menyiapkan renderer, menyusun layar. Boleh `async`. Game belum terlihat |
| `onStart`              | Saat "GAS!", setelah `preload` selesai                        | Memulai game dan mengirim layout pertama ke HP                                       |
| `onInput`              | Tiap tombol ditekan atau dilepas, tiap event widget           | Input berbasis kejadian                                                              |
| `onTick`               | Tiap frame                                                    | Simulasi dan gambar. `dt` dalam detik, paling besar 0,1                              |
| `onPlayers`            | Pemain join, putus, atau kembali                              | Menyesuaikan game dengan siapa yang ada                                              |
| `onPause` / `onResume` | Menu host dibuka / ditutup                                    | Membekukan dan melanjutkan (lihat di bawah)                                          |
| `onStop`               | Game dilepas                                                  | Membersihkan                                                                         |

Error yang terlempar dari hook dilaporkan ke platform dan tidak menghentikan game.

## `ctx`

|                                               |                                                                                        |
| --------------------------------------------- | -------------------------------------------------------------------------------------- |
| `ctx.players`                                 | Semua pemain di room, urut slot                                                        |
| `ctx.setController(target, layout)`           | Isi layar satu HP (`1`–`4`) atau semuanya (`'all'`): lihat [Controller](controller.md) |
| `ctx.input(player)`                           | Keadaan pad terbaru: `buttons`, `lx`, `ly`, `rx`, `ry`                                 |
| `ctx.end(skor, opsi?)`                        | Mengakhiri game                                                                        |
| `ctx.exit('again' \| 'lobby')`                | Setelah layar hasil milik game                                                         |
| `ctx.t(kunci, vars?)`                         | Teks dari `locales`, dengan `{nama}`                                                   |
| `ctx.random(seed?)`                           | Angka acak ber-seed                                                                    |
| `ctx.audio`                                   | Bunyi dan musik: lihat [Audio](audio.md)                                               |
| `ctx.assets.url(path)`                        | URL file di bundel                                                                     |
| `ctx.storage.get/set`                         | Simpanan per game di perangkat host                                                    |
| `ctx.reducedMotion`                           | Host meminta gerak dikurangi                                                           |
| `ctx.paused`                                  | Sedang dijeda                                                                          |
| `ctx.gameId`, `ctx.locale`, `ctx.platformSdk` | Id game, bahasa room, versi SDK platform                                               |

Tanda tangan lengkapnya ada di [Referensi API](referensi-api.md).

## Waktu: pakai `onTick`

Simpan waktu game di variabel yang kamu kurangi di `onTick`, bukan di `setTimeout`:

```ts
let tunggu = 1.5;
onTick(ctx, dt) {
  tunggu -= dt;
  if (tunggu <= 0) ronde berikutnya();
}
```

Dengan begitu game otomatis berhenti saat dijeda, dan tes bisa memajukan waktu frame demi frame. `setTimeout` terus berjalan di belakang menu dan tidak bisa dikendalikan tes.

## Acak yang sama di setiap host

`ctx.random()` mengembalikan fungsi acak yang diturunkan dari seed sesi. Seed yang sama memberi urutan yang sama, jadi tes bisa diulang persis. Jangan pakai `Math.random()` untuk aturan game.

```ts
const acak = ctx.random();
const pilihan = Math.floor(acak() * 4);
```

## Mengakhiri game

```ts
ctx.end({ 1: 3, 2: 5 }); // skor per slot pemain
```

Platform menampilkan layar hasil dengan peringkat, lalu pemimpin room memilih main lagi atau kembali ke lobi.

**Game co-op** yang dimenangkan atau dikalahkan bersama:

```ts
ctx.end({ 1: 120, 2: 120 }, { team: 'win' }); // atau 'lose'
```

Layar hasil menampilkan hasil tim dan skor tim, tanpa peringkat.

## Simpanan

```ts
const rekor = await ctx.storage.get('rekor'); // null kalau belum ada
await ctx.storage.set('rekor', 42);
```

Nilai apa pun yang bisa di-JSON-kan, sampai 16 KB per kunci; kunci sampai 64 karakter. Disimpan per game **di perangkat host**, jadi cocok untuk setelan dan rekor lokal, bukan untuk akun.

## Alur layar milik game

Secara bawaan platform menangani pembuka, jeda, dan layar hasil. Empat hal berikut membuat game mengambil alih bagiannya. Semuanya opsional.

### Jeda: `onPause` / `onResume`

Menu host terbuka lewat ☰ di HP, tombol Menu, atau `Esc`.

- Game **dengan** `onPause`: input dilepas, `onPause` dipanggil sekali, dan `onTick` berhenti sampai menu ditutup. Lalu `onResume` dipanggil. Frame pertama sesudahnya membawa `dt` 0. Loop render milikmu sendiri (ticker Pixi, `setTimeout`) kamu bekukan di `onPause`.
- Game **tanpa** `onPause` tetap berjalan di belakang menu, hanya tanpa input.

Mendefinisikan `onPause() {}` kosong sudah cukup untuk game yang seluruh waktunya di `onTick`.

### Pembuka milik game: `"opening": "game"`

Di `ralyoo.json`. Platform tidak menampilkan "3·2·1·GAS!". Game langsung dipasang, dan `onStart` jalan begitu `preload` selesai; game menjalankan pembuka dan aba-abanya sendiri. Kalau `preload` lebih lama dari 3 detik, host menampilkan indikator muat. Pemimpin bisa membatalkan dengan B.

### Layar hasil milik game: `end(…, { screen: 'game' })` + `exit`

```ts
ctx.end(skor, { screen: 'game' }); // skor tetap dicatat platform
// … perayaan milik game; input dan layout HP tetap milik game …
ctx.exit('again'); // atau 'lobby'
```

- `exit('again')` memasang ulang game dengan keadaan bersih (butuh minimal dua pemain tersambung; kalau tidak, kembali ke lobi). `exit('lobby')` kembali ke lobi.
- Pengaman: kalau `exit` tidak datang dalam 60 detik, atau game error, platform menampilkan layar hasilnya sendiri.

### Tema menu jeda: `"theme"`

```json
"theme": {
  "panel": "#2B2730",
  "accent": "#FFE14D",
  "text": "#FFF6E0",
  "frame": "layar/bingkai-menu.png",
  "labels": { "id": { "menu.title": "Gerobak istirahat", "pad.menuWait": "{name} lagi buka menu." } }
}
```

- Warna wajib `#rrggbb`, dan teks di atas panel, aksen di atas panel, serta teks di atas item terpilih harus berkontras minimal 4,5:1. Kalau ada yang kurang, semua warna diabaikan dan menu kembali ke gaya Ralyoo (`lint` memperingatkan).
- `frame` adalah gambar di bundel yang dipasang di sekeliling panel; bagian tengahnya harus transparan.
- `labels` hanya `menu.title` dan `pad.menuWait`, masing-masing sampai 80 karakter.
