時間逆行ですみまそん。
ゆるしてくだそん。
当初の計画から変更して、Day23 の内容を Day22 に前倒して書いて、Day23 については別の内容を書くことになりました。
イベントテーブル?
イベントテーブルとは、ゲームにおける様々な発生するイベントをまとめたものになります。
これがあることでイベントを管理して、ゲーム内でイベントを発生させることができます。
今回はJSON
という形式を用いてイベントを管理しましょう。
あ、JSON については解説してるか。
Plan
[
{
"event_type": "say",
"event_data": {
"message": "Hello, World!"
}
}
]
こんな感じでevent_type
とevent_data
を持つオブジェクトを配列に入れたものをイベントテーブルのデータとしましょう。
(すなわち、テーブル辞書にこれが値として入る)
現在はsay
しかありませんが、今後はいろいろ増えていくことでしょう。
よりイベントテーブルっぽくするため、マップ固有イベントとしてファイル名に MapID を付けてsrc/assets/table/map/<MapID>.json
という形式で保存しましょう。
その際はイベント ID の辞書にしましょう。
{
"event_id": {
"event_type": "say",
"event_data": {
"message": "Hello, World!"
}
}
}
src
以下に配置されたファイルはfetch
で取得することが出来ます。
イベントトリガー
まずはイベントトリガーの前に、イベント毎のスクリプトファイルを用意しましょう。
export type SayEventData = {
name?: string;
message: string;
hide?: boolean;
speed?: number;
};
export type SayEvent = {
event_type: "say";
event_data: SayEventData;
};
こんな感じで、イベントの型を定義しておきます。
また、event_type
は複数ある想定なので、イベントをまとめた型も用意しておきます。
import { SayEvent } from "./events/say";
/// 本来はもっとイベントがある。
export type EventType = SayEvent | ...;
イベントをまとめた型をEventType
として定義しておきます。
そうすると今度、EventType
として渡されたイベントのデータがどのイベントタイプなのかを判断する必要があります。
EventType
はSayEvent
か...のどれかなのでそれを確定させる処理が必要という訳ですね。
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
で取得することが出来ます。
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 ファイルを読み込むことは出来ましたね。
じゃあ後はイベントを発生させるだけですよ。
おわりに
今日の課題です。
- マップに
Collision
と同じようにManualEvents
(そのタイルに話しかけたら発生)とAutoEvents
(そのタイルを踏んだら発生)を追加しておきましょう。 - イベント発生の処理を書きましょう。
Collision
の時に話したかどうかは分かりませんが、StoreAPI
を使うことで一応ファイル作成をすることができ、中に JSON を書き込むことができます。
これを上手く使えば、マップエディターの状態をファイルとして保存できるので、開発中はぜひ使ってみてください。