從套件建立遊戲
expo-game 模板已包含引擎:固定時間步長循環、鍵盤與觸控輸入、攝影機、碰撞輔助工具、HUD、選單、暫停、結果、解鎖、進度存檔,以及一個可運作的範例遊戲(Coin Dash)。你的任務是將範例內容替換為使用者想要的遊戲。請勿設計或撰寫引擎。
此建置的規則
- 在第一次回合中就撰寫程式碼。不要長時間規劃。用最多 10 行思考來決定遊戲規則,然後編輯檔案。在問題發生後再修復。
- 保持每次回合範圍小:一個檔案或一個功能,然後執行
bun run typecheck。長時間的推理過程若沒有程式碼即為失敗。 - 絕不重新實作套件已有的功能:循環、輸入、碰撞、攝影機、HUD、畫面、儲存。
- 在添加選單、美術素材或解鎖功能之前,先讓遊戲即時且可遊玩。範例已經有選單。
專案
使用 omg_create_project 並設定 template: "expo-game" 來建立專案。對於首次預覽,請遵循 omg-app-builder 技能中的 Expo 快速路徑。然後開始編輯。
你需要編輯的檔案(按順序)
src/game/theme.ts:根據設計設定顏色。src/games/<你的遊戲>/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/<你的遊戲>/game-view.tsx:複製coin-dash/game-view.tsx。使用World和Sprite(彩色形狀、emojilabel或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 }):更新以固定 1/60 秒的步長執行。createInput()、axisX/axisY、wasTapped、endFrame、setAction、setStick。useKeyboard(input, players, onEscape):P1 使用 WASD + Space/E;雙人模式下 P2 使用方向鍵 + Enter。TouchControls players={1|2} kind="dpad|stick|steer" buttons={[...]}:雙人模式會將 P1 控制在左側,P2 在右側。對於分割畫面,請渲染兩個GameView,每個使用各自的攝影機(請參閱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)測量遊戲區域。在靜態 Web 匯出中,useWindowDimensions()在調整大小前會是 0。 - 在 Web 上,帶有按鈕的
Alert.alert不會有任何作用。請使用PauseOverlay或畫面內的彈出視窗。 - 進度應儲存在
profileStore而非元件狀態中:重新載入後必須保留最高分數和解鎖狀態。 - 不要使用
setInterval或Date.now()來計算遊戲時間。請計算循環的dt。 - 不要將逐幀狀態放在 React 的
useState中。請將其保留在遊戲物件(ref)中;hook 會在每一幀重新繪製。
3D 遊戲
此套件是 2D 的。對於 3D 遊戲(實際需求,而非風格選擇),請保留循環、輸入、HUD 和畫面,僅取代 game-view.tsx。
bunx expo install three @react-three/fiber expo-gl && bun add -d @types/three
撰寫兩個小型檔案,並提供相同的匯出項目,以便 Metro 選擇正確的檔案:
src/components/scene.web.tsx:import { Canvas, useFrame } from "@react-three/fiber"src/components/scene.tsx:使用相同的程式碼,但改為from "@react-three/fiber/native"
放置一個 <Canvas camera={{ position: [0, 0, 4] }}>,並包含 ambientLight、directionalLight 以及每個遊戲物件對應的一個 <mesh>(盒子、球體、平面、單色)。在每次渲染時從你的遊戲物件複製位置;在 useGameLoop 中執行 stepGame,而不是在 useFrame 中。保持 HUD 和控制項作為常規的 React Native 視圖疊加在 canvas 之上。Canvas 需要一個具有尺寸的父層(flex: 1)。
已在套件 spike 中驗證:3D 場景可在 Expo Web 中渲染,且 Android JS bundle 可編譯。未驗證:在實體裝置上或 Expo Go 中渲染。請在最終訊息中說明這一點。
完成
執行 bun run typecheck 和 bun run build。除非使用者要求,否則不要撰寫自動化測試。然後執行 bun run check:ui:它會以 390x844 的解析度打開每個路由,在 .omg/ui-check/ 中為每個路由儲存一張 PNG 圖片,並回報空白頁面、錯誤邊界、控制台錯誤、失敗的請求以及被其他元素遮擋的按鈕。打開每張 PNG,與需求及任何設計參考進行比較,修正錯誤,然後再次執行直到報告乾淨為止。此檢查不會遊玩遊戲,因此也請在 Expo Web 中檢查:遊戲必須能透過按鍵移動、結束、顯示結果畫面,並在重新載入後保留最高分數。說明你測試了哪些表面;Web 執行不代表原生行為已驗證。發佈後,請以手機尺寸開啟即時 URL。omg 安裝列位於遊戲下方,遊戲會變成其上方的一張卡片,因此安裝列不會遮蓋任何遊戲控制項。請勿移動或隱藏它。