【Day22】イベントテーブルとイベントトリガー【QAC24】


# 時間逆行ですみまそん。

ゆるしてくだそん。

> 当初の計画から変更して、Day23 の内容を Day22 に前倒して書いて、Day23 については別の内容を書くことになりました。

# イベントテーブル？

イベントテーブルとは、ゲームにおける様々な発生するイベントをまとめたものになります。

これがあることでイベントを管理して、ゲーム内でイベントを発生させることができます。

今回は`JSON`という形式を用いてイベントを管理しましょう。
あ、JSON については解説してるか。

## Plan

```json
[
  {
    "event_type": "say",
    "event_data": {
      "message": "Hello, World!"
    }
  }
]
```

こんな感じで`event_type`と`event_data`を持つオブジェクトを配列に入れたものをイベントテーブルのデータとしましょう。
（すなわち、テーブル辞書にこれが値として入る）

現在は`say`しかありませんが、今後はいろいろ増えていくことでしょう。

よりイベントテーブルっぽくするため、マップ固有イベントとしてファイル名に MapID を付けて`src/assets/table/map/<MapID>.json`という形式で保存しましょう。
その際はイベント ID の辞書にしましょう。

```json:src/assets/table/map/sample_map.json
{
  "event_id": {
    "event_type": "say",
    "event_data": {
      "message": "Hello, World!"
    }
  }
}
```

`src`以下に配置されたファイルは`fetch`で取得することが出来ます。

# イベントトリガー

まずはイベントトリガーの前に、イベント毎のスクリプトファイルを用意しましょう。

```typescript:src/lib/events/say.ts
export type SayEventData = {
  name?: string;
  message: string;
  hide?: boolean;
  speed?: number;
};
export type SayEvent = {
  event_type: "say";
  event_data: SayEventData;
};
```

こんな感じで、イベントの型を定義しておきます。
また、`event_type`は複数ある想定なので、イベントをまとめた型も用意しておきます。

```typescript:src/lib/event.ts
import { SayEvent } from "./events/say";

/// 本来はもっとイベントがある。
export type EventType = SayEvent | ...;
```

イベントをまとめた型を`EventType`として定義しておきます。
そうすると今度、`EventType`として渡されたイベントのデータがどのイベントタイプなのかを判断する必要があります。
`EventType`は`SayEvent`か...のどれかなのでそれを確定させる処理が必要という訳ですね。

```typescript:src/lib/events/say.ts
import { EventType } from "../event";

export type SayEventData = {
  name?: string;
  message: string;
  hide?: boolean;
  speed?: number;
};
export type SayEvent = {
  event_type: "say";
  event_data: SayEventData;
};

export const isSayEvent = (event: EventType): event is SayEvent =>
  event.event_type === "say";
```

このように`isSayEvent`という関数を作りました。
これは TypeGuard のような関数で、渡された`event`が`SayEvent`であるかを判定します。
なので返り値は`event is SayEvent`となります。

実装としては、ただの boolean を返すものになり、`true`であればその型の保証がされます。
(つまり、`if`文とかでこれを使えば、そのスコープ内で型判定が有効になる)

この`TypeGuard`のようなものはすご～い大事なので覚えておきましょうね。

# イベントローダー

実際にファイルを読み込んでみる処理を書いてみましょう。
先にも書いた通り、`fetch`で取得することが出来ます。

```typescript:src/lib/event.ts
import { path } from "@tauri-apps/api";

...

const TablePath = "./src/assets/table" as const;

export type EventTable = EventType[];
export type EventTableStore = { [eventId: string]: EventTable };

const loadMapEventTables = async (mapId: string) => {
  if (mapId.includes(".") || mapId.includes("/") || mapId.includes("\\")) {
    throw new Error(
      "Invalid map ID. Map ID must not include '.' '/' '\\' characters."
    );
  }
  const eventTableResponse = await fetch(
    await path.join(TablePath, "map", `${mapId}.json`);
  );
  if (eventTableResponse.status === 200) {
    const eventTable = (await eventTableResponse.json()) as EventTableStore;
    return eventTable;
  } else {
    throw new Error("Failed to load event table.");
  }
}
```

今回は`path`というものを使って、パスの結合をしています。
実際のところは普通に文字列結合でもいいのですが、OS 毎の差異があったら面倒なのでしっかりと使っていきます。

`fetch`で取得して、HTTP Status Code が 200 であれば JSON として取得して返します。

この HTTP Status Code というのは、皆さんの身近な例でいえば `404 Not Found`とかいうやつですね。
HTTP レスポンスの状態を数字で表します。

200 というのは`OK`という意味で、リクエストが成功したということを示します。
実際のところ、200 じゃない場合はおおよそファイルが見つからなかったことになるので、エラーを投げています。

これで JSON ファイルを読み込むことは出来ましたね。
じゃあ後はイベントを発生させるだけですよ。

# おわりに

今日の課題です。

1. マップに`Collision`と同じように`ManualEvents`(そのタイルに話しかけたら発生)と`AutoEvents`(そのタイルを踏んだら発生)を追加しておきましょう。
2. イベント発生の処理を書きましょう。

`Collision`の時に話したかどうかは分かりませんが、`StoreAPI`を使うことで一応ファイル作成をすることができ、中に JSON を書き込むことができます。
これを上手く使えば、マップエディターの状態をファイルとして保存できるので、開発中はぜひ使ってみてください。