本文へスキップ

ゲームをプレイする ​

play() は playtest を返します。一時停止して指示を待っている、あなたのゲームです。プレイ系のメソッドは playtest を返すので、短いスクリプトなら 1 行で書けます。

ts
const game = await play();
game.press("ArrowRight").wait("0.5s").release("ArrowRight").tap("Space");

時間 ​

テストがプレイさせるまで、何も動きません。duration(長さ)はステップ数か時間で指定します。

ts
game.wait();        // 1 ステップ
game.wait(30);      // 30 ステップ
game.wait("0.5s");  // 0.5 秒:毎秒 60 ステップなら 30 ステップ
game.wait("250ms"); // 15 ステップ

1 ステップは仮想時計の 1 フレーム(デフォルトは 1/60 秒、変えるなら play({ fps }))、または debuff のように自前でステップするエンジンの固定ステップ 1 回分です。どちらなのかは game.stepSeconds でわかります。

ステップ中、Keiko はブラウザと同じように performance.now() と Date.now() を進めます。次に、期限が来たタイマー(setTimeout、setInterval、requestIdleCallback)を発火させ、それから requestAnimationFrame のコールバックを実行します。タイマーで組んだゲームプレイ(「2 秒ごとにウェーブ」など)も、ループ内のゲームプレイとまったく同じようにステップします。

キーボード ​

キーは KeyboardEvent.code の値で指定します:"ArrowLeft"、"KeyZ"、"Space"、"Enter"、"Escape"、"ShiftLeft"、"Digit1"。

ts
game.press("ArrowRight");          // 押しっぱなし
game.release("ArrowRight");        // 離す
game.hold("KeyZ", "0.2s");         // 押して、待って、離す
game.tap("Space");                 // 1 ステップだけ押す

各キーは code、key、旧式の keyCode を持つ本物の keydown/keyup イベントとして、フォーカス中の要素かゲームの canvas に送られ、document と window までバブリングします。なので、ゲームが window、document、canvas のどこで受け取っていても動きます。

ポインター ​

座標は canvas 自身のピクセルで指定します。

ts
game.tap(160, 90);            // 押して離す、1 ステップ
game.pointerDown(40, 40);     // 押したまま
game.pointerMove(120, 40);    // ドラッグ
game.pointerUp();

Keiko はポインター、マウス、クリックのイベントを最初の <canvas> に送り、ブラウザが埋めるすべての位置フィールド(clientX、pageX、offsetX、x)を設定します。canvas はページ上の位置として自分のサイズを報告するので、canvas のボックスを通して座標変換するエンジンにも、狙った点がそのまま届きます。

パラメータ ​

ゲームが調整用の値を公開していれば(方法)、テストから読み書きできます。

ts
game.set("player.speed", 300);
game.set({ "player.gravity": 1200, "player.coyote": 0 });
game.param("player.speed"); // 300
game.params();              // { player: { speed: 300, gravity: 1200, coyote: 0 } }

const tuned = await play({ params: { "player.jumpSpeed": 320 } }); // 最初のステップより前に設定

コマンド ​

コマンドは、ゲームがテストに提供するショートカットです。レベルへのワープやアイテムの付与などです。これを使えば、確かめたい場面まで、ゲーム全体をプレイせずに進めます。

ts
game.command("warp", "level-3");

読み込みを待つ ​

play() はゲームの最初のリクエストが終わるまで待ちます。あとから追加で読み込む場合(レベルのアセットをオンデマンドで、など)は、settle() で待ちます。これは実時間で待ち、ゲーム時間は進みません。

ts
game.command("load-level", 2);
await game.settle();
game.wait("1s");

ほかの理由で実時間を待ちたいときは Keiko の sleep(ms) を使います。動いているゲームの中では、setTimeout はゲームの仮想タイマーだからです。

非同期のフロー ​

wait() と until() はフレームを間を空けずに連続でプレイします。でも多くのゲームはフレームの合間にも仕事をしています。トゥイーンを await するボタンのハンドラー、表示前にアセットを読み込む画面などです。run() と eventually() は、ブラウザと同じように、そうした処理や読み込みをフレームの合間に完了させます。

ts
game.tap(play.x, play.y);
await game.eventually((s) => s.screen === "game", { within: "5s" });
await game.run("2s");

ゲーム時間はあくまでフレーム単位でしか進まないので、ネットワークが遅くても結果は変わりません。ゲームの読み込み中に until() がタイムアウトした場合は、エラーが eventually() を提案してくれます。

画面サイズ ​

画面サイズに応じてレイアウトするゲーム向けに、play({ viewport: { width: 390, height: 844 } }) でウィンドウサイズを指定できます(デフォルトは 960×540)。

乱数と時計 ​

Math.random() にはシードが入り(play({ seed })、デフォルトは 1)、Date.now() は固定の日付から始まるので、これらを使うゲームでも毎回同じようにプレイされます。これらに依存してしまっているロジックを見つけるには、決定性を見てください。

ゲームを読む ​

ts
game.state;               // スナップショット:__KEIKO__.state()、または debuff の inspect()
game.scene;               // __KEIKO__.scene()、または debuff のアクティブなシーン
game.tick;                // 起動からプレイしたステップ数
game.time;                // 起動からのシミュレーション秒数
game.get("state.player.x");
game.window;              // ページ。グローバルや DOM にアクセスするために