ゲームをプレイする
play() は playtest を返します。一時停止して指示を待っている、あなたのゲームです。プレイ系のメソッドは playtest を返すので、短いスクリプトなら 1 行で書けます。
const game = await play();
game.press("ArrowRight").wait("0.5s").release("ArrowRight").tap("Space");時間
テストがプレイさせるまで、何も動きません。duration(長さ)はステップ数か時間で指定します。
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"。
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 自身のピクセルで指定します。
game.tap(160, 90); // 押して離す、1 ステップ
game.pointerDown(40, 40); // 押したまま
game.pointerMove(120, 40); // ドラッグ
game.pointerUp();Keiko はポインター、マウス、クリックのイベントを最初の <canvas> に送り、ブラウザが埋めるすべての位置フィールド(clientX、pageX、offsetX、x)を設定します。canvas はページ上の位置として自分のサイズを報告するので、canvas のボックスを通して座標変換するエンジンにも、狙った点がそのまま届きます。
パラメータ
ゲームが調整用の値を公開していれば(方法)、テストから読み書きできます。
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 } }); // 最初のステップより前に設定コマンド
コマンドは、ゲームがテストに提供するショートカットです。レベルへのワープやアイテムの付与などです。これを使えば、確かめたい場面まで、ゲーム全体をプレイせずに進めます。
game.command("warp", "level-3");読み込みを待つ
play() はゲームの最初のリクエストが終わるまで待ちます。あとから追加で読み込む場合(レベルのアセットをオンデマンドで、など)は、settle() で待ちます。これは実時間で待ち、ゲーム時間は進みません。
game.command("load-level", 2);
await game.settle();
game.wait("1s");ほかの理由で実時間を待ちたいときは Keiko の sleep(ms) を使います。動いているゲームの中では、setTimeout はゲームの仮想タイマーだからです。
非同期のフロー
wait() と until() はフレームを間を空けずに連続でプレイします。でも多くのゲームはフレームの合間にも仕事をしています。トゥイーンを await するボタンのハンドラー、表示前にアセットを読み込む画面などです。run() と eventually() は、ブラウザと同じように、そうした処理や読み込みをフレームの合間に完了させます。
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() は固定の日付から始まるので、これらを使うゲームでも毎回同じようにプレイされます。これらに依存してしまっているロジックを見つけるには、決定性を見てください。
ゲームを読む
game.state; // スナップショット:__KEIKO__.state()、または debuff の inspect()
game.scene; // __KEIKO__.scene()、または debuff のアクティブなシーン
game.tick; // 起動からプレイしたステップ数
game.time; // 起動からのシミュレーション秒数
game.get("state.player.x");
game.window; // ページ。グローバルや DOM にアクセスするために