キットからゲームを構築する
expo-gameテンプレートには既にエンジン(固定タイムステップロープ、キーボードとタッチ入力、カメラ、衝突ヘルパー、HUD、メニュー、一時停止、結果表示、アンロック、保存された進行状況、そして動作するサンプルゲーム「Coin Dash」)が含まれています。あなたの仕事は、サンプルのコンテンツをユーザーのゲームに置き換えることです。エンジンを設計または記述しないでください。
このビルドのルール
- 最初のターンでコードを書いてください。長々と計画を立てないでください。ゲームのルールを最大10行の思考で決め、その後ファイルを編集します。問題は実行後に修正します。
- 各ターンは小さく保ちます:1つのファイルまたは1つの機能、その後
bun run typecheckを実行します。コードなしで長い推論パスを取るのは失敗です。 - キットが既に持っているもの(ループ、入力、衝突、カメラ、HUD、画面、ストレージ)を再実装しないでください。
- メニュー、アート、アンロックを追加する前に、ゲームをリアルタイムでプレイ可能にしてください。サンプルには既にメニューがあります。
プロジェクト
omg_create_project と template: "expo-game" を使用してプロジェクトを作成します。最初のプレビューでは、omg-app-builder スキルのExpo高速パスに従ってください。その後編集します。
編集するファイル(順番に)
src/game/theme.ts:デザインから色を設定。src/games/<your-game>/logic.ts:coin-dash/logic.tsをコピー。純粋なルール:createGame()、stepGame(game, input, dt)、finalScore()、UNLOCKS。React、描画、Date.now()は使用しないでください。axisX/axisY/wasTapped(input, "p1" | "p2", ...)を介してのみ入力を読み取ります。ランダム性にはseeded()を使用します。src/games/<your-game>/game-view.tsx:coin-dash/game-view.tsxをコピー。WorldとSprite(色付きの図形、絵文字label、またはreact-native-svg)を使用して描画します。ワールド単位を使用します。カメラが変換します。src/app/play.tsx:インポートをcoin-dashから自分のゲームに変更します。TouchControlsにkind(dpad、stick、steer)とそのbuttons({ action: "a", label: "JUMP" })を設定します。ループ、キーボード、一時停止、終了の配線はそのままにします。src/app/index.tsx、results.tsx、unlocks.tsx:タイトル、テキスト、およびUNLOCKSのインポート。 自動テストはユーザーから依頼がない限り記述しないでください。代わりにセルフチェックで検証します(「仕上げ」を参照)。
キットのバグがブロックしない限り、src/game/loop.ts、input.ts、collision.ts、camera.ts、progress.ts は編集しないでください。ヘルパーは自分のゲームフォルダに追加します。
キットAPI(これらの名前を使用)
createLoop({ step })およびuseGameLoop(update, { paused }):updateは固定1/60秒のステップで実行されます。createInput()、axisX/axisY、wasTapped、endFrame、setAction、setStick。useKeyboard(input, players, onEscape):P1 = WASD + Space/E; 2プレイヤーモードではP2 = 矢印キー + Enter。TouchControls players={1|2} kind="dpad|stick|steer" buttons={[...]}:2プレイヤーモードではP1が左、P2が右に分割されます。分割画面では、2つのGameViewをレンダリングし、それぞれに1つのカメラを設定します(play.tsxを参照)。circlesOverlap、rectsOverlap、circleRectOverlap、pushCircleOutOfRect、keepCircleInside、angleDiff、seeded、clamp、lerp。createCamera、followCamera(cam, target, dt, { velocity, lookAhead, world, viewW, viewH })。profileStore.finishRun({ gameId, score, stats }, UNLOCKS)、useProfile()、profileStore.select(group, id)、setSettings。- 画面:
MenuScreen、PauseOverlay、ResultsScreen、UnlocksScreen、SettingsRow、GameButton。HUD:Hud、HudStat、HudBar、Banner。
Webの落とし穴
- ゲーム領域は
useViewSize()(onLayout)で計測します。useWindowDimensions()は静的Webエクスポートではリサイズまで0になります。 - ボタン付きの
Alert.alertはWebでは何もしません。PauseOverlayまたは画面内モーダルを使用してください。 - 進行状況は
profileStoreに保持し、コンポーネントの状態には保持しないでください:リロード時にベストスコアとアンロックが維持される必要があります。 - ゲーム時間に
setIntervalやDate.now()を使用しないでください。ループのdtをカウントします。 - フレームごとの状態をReactの
useStateに入れないでください。ゲームオブジェクト(ref)に保持します。フックは毎フレーム再描画します。
3Dゲーム
キットは2Dです。3Dゲーム(スタイルの選択ではなく実際のニーズ)の場合は、ループ、入力、HUD、画面はそのままにし、game-view.tsx のみを置き換えます。
bunx expo install three @react-three/fiber expo-gl && bun add -d @types/three
同じエクスポートを持つ2つの小さなファイルを記述して、Metroが正しい方を選択するようにします:
src/components/scene.web.tsx:import { Canvas, useFrame } from "@react-three/fiber"src/components/scene.tsx:from "@react-three/fiber/native"を使用した同じコード
ゲームオブジェクトごとに1つの <mesh>(ボックス、球体、平面、フラットカラー)を持つ <Canvas camera={{ position: [0, 0, 4] }}> を ambientLight、directionalLight とともに配置します。各レンダリングでゲームオブジェクトから位置をコピーします。stepGame は useGameLoop で実行し、useFrame では実行しません。HUDとコントロールは、キャンバスの上にある通常のReact Nativeビューとして維持します。キャンバスにはサイズを持つ親(flex: 1)が必要です。
キットのスパイクで確認済み:3DシーンはExpo WebとAndroid JSバンドルでレンダリングされます。デバイス上またはExpo Goでのレンダリングは未確認です。最終メッセージでその旨を伝えてください。
仕上げ
bun run typecheck と bun run build を実行します。ユーザーから依頼がない限り、自動テストは記述しないでください。次に bun run check:ui を実行します:すべてのルートを390x844で開き、ルートごとに1つのPNGを .omg/ui-check/ に保存し、空白ページ、エラーバウンダリ、コンソールエラー、失敗したリクエスト、および別の要素で覆われたボタンを報告します。各PNGを開き、リクエストとデザインリファレンスと比較し、問題を修正して、レポートがクリーンになるまで再度実行します。このチェックはゲームをプレイしないため、Expo Webでも確認してください:キーで動き、終了し、結果画面を表示し、リロード後もベストスコアが保持される必要があります。テストした画面を伝えてください。Web実行はネイティブ動作の証拠にはなりません。公開後、ライブURLを電話サイズで開いてください。omgインストールバーはゲームの下に配置され、ゲームはその上のカードになるため、バーがゲームコントロールを覆うことはありません。移動したり非表示にしたりしないでください。