Créer un jeu à partir du kit
Le modèle expo-game contient déjà le moteur : boucle à pas fixe, entrées clavier et tactile, caméra, helpers de collision, HUD, menu, pause, résultats, déverrouillages, progression sauvegardée et un exemple de jeu fonctionnel (Coin Dash). Votre travail consiste à remplacer le contenu de l'exemple par le jeu de l'utilisateur. Ne concevez pas et n'écrivez pas de moteur.
Règles pour cette création
- Écrivez du code dès votre premier tour. Ne planifiez pas longuement. Décidez des règles du jeu en 10 lignes de réflexion au maximum, puis modifiez les fichiers. Corrigez les problèmes après leur exécution.
- Gardez chaque tour petit : un fichier ou une fonctionnalité, puis exécutez
bun run typecheck. Un long passage de raisonnement sans code est un échec. - Ne réimplémentez jamais ce que le kit fournit déjà : boucle, entrées, collision, caméra, HUD, écrans, stockage.
- Rendez le jeu jouable en temps réel avant d'ajouter des menus, des graphismes ou des déverrouillages. L'exemple a déjà des menus.
Projet
Créez le projet avec omg_create_project et template: "expo-game". Suivez le chemin rapide Expo du skill omg-app-builder pour la première prévisualisation. Ensuite, modifiez.
Fichiers à modifier (dans l'ordre)
src/game/theme.ts: couleurs du design.src/games/<votre-jeu>/logic.ts: copiezcoin-dash/logic.ts. Règles pures :createGame(),stepGame(game, input, dt),finalScore(),UNLOCKS. Pas de React, pas de dessin, pas deDate.now(). Lisez les entrées uniquement viaaxisX/axisY/wasTapped(input, "p1" | "p2", ...). Utilisezseeded()pour l'aléatoire.src/games/<votre-jeu>/game-view.tsx: copiezcoin-dash/game-view.tsx. Dessinez avecWorldetSprite(formes colorées,labelemoji oureact-native-svg). Utilisez les unités du monde ; la caméra convertit.src/app/play.tsx: changez les imports de coin-dash vers votre jeu. DéfinissezkindsurTouchControls(dpad,stick,steer) et sesbuttons({ action: "a", label: "SAUTER" }). Conservez la boucle, le clavier, la pause et le câblage de fin tels quels.src/app/index.tsx,results.tsx,unlocks.tsx: titres, texte et l'import deUNLOCKS. N'écrivez pas de tests automatisés sauf si l'utilisateur le demande. Vérifiez plutôt avec l'auto-test (voir Finition).
Ne modifiez pas src/game/loop.ts, input.ts, collision.ts, camera.ts, progress.ts sauf si un bug du kit vous bloque. Ajoutez des helpers dans votre dossier de jeu.
API du kit (utilisez ces noms)
createLoop({ step })etuseGameLoop(update, { paused }): l'update s'exécute à un pas fixe de 1/60 s.createInput(),axisX/axisY,wasTapped,endFrame,setAction,setStick.useKeyboard(input, players, onEscape): P1 = WASD + Espace/E ; en mode deux joueurs, P2 = flèches + Entrée.TouchControls players={1|2} kind="dpad|stick|steer" buttons={[...]}: le mode deux joueurs divise P1 à gauche et P2 à droite. Pour un écran partagé, affichez deuxGameView, une caméra chacune (voirplay.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.- Écrans :
MenuScreen,PauseOverlay,ResultsScreen,UnlocksScreen,SettingsRow,GameButton. HUD :Hud,HudStat,HudBar,Banner.
Pièges Web
- Mesurez la zone de jeu avec
useViewSize()(onLayout).useWindowDimensions()vaut 0 dans un export Web statique jusqu'à un redimensionnement. Alert.alertavec des boutons ne fait rien sur le Web. UtilisezPauseOverlayou une modale intégrée à l'écran.- Gardez la progression dans
profileStore, pas dans l'état du composant : un rechargement doit conserver les meilleurs scores et les déverrouillages. - N'utilisez pas
setIntervalouDate.now()pour le temps de jeu. Comptez ledtde la boucle. - Ne mettez pas d'état par frame dans le
useStatede React. Gardez-le dans l'objet de jeu (une ref) ; le hook redessine à chaque frame.
Jeux 3D
Le kit est en 2D. Pour un jeu 3D (un vrai besoin, pas un choix de style), conservez la boucle, les entrées, le HUD et les écrans, et remplacez uniquement game-view.tsx.
bunx expo install three @react-three/fiber expo-gl && bun add -d @types/three
Écrivez deux petits fichiers avec les mêmes exports, pour que Metro choisisse le bon :
src/components/scene.web.tsx:import { Canvas, useFrame } from "@react-three/fiber"src/components/scene.tsx: le même code avecfrom "@react-three/fiber/native"
Placez un <Canvas camera={{ position: [0, 0, 4] }}> avec un ambientLight, un directionalLight et un <mesh> par objet de jeu (boîtes, sphères, plans, couleurs unies). Copiez les positions de votre objet de jeu à chaque rendu ; exécutez stepGame dans useGameLoop, pas dans useFrame. Conservez le HUD et les contrôles comme des vues React Native normales au-dessus du canvas. Le canvas a besoin d'un parent avec une taille (flex: 1).
Vérifié dans le spike du kit : une scène 3D s'affiche dans Expo Web, et le bundle JS Android compile. Non vérifié : rendu sur un appareil ou dans Expo Go. Dites-le dans le message final.
Finition
Exécutez bun run typecheck et bun run build. N'écrivez pas de tests automatisés sauf si l'utilisateur le demande. Ensuite, exécutez bun run check:ui : il ouvre chaque route à 390x844, sauvegarde un PNG par route dans .omg/ui-check/, et signale les pages blanches, les limites d'erreur, les erreurs console, les requêtes échouées et les boutons recouverts par un autre élément. Ouvrez chaque PNG, comparez-le avec la demande et les éventuelles références de design, corrigez ce qui ne va pas, et réexécutez jusqu'à ce que le rapport soit propre. La vérification ne joue pas au jeu, donc vérifiez aussi dans Expo Web : il doit bouger avec les touches, se terminer, afficher l'écran des résultats et conserver le meilleur score après un rechargement. Dites quelles surfaces vous avez testées ; une exécution Web ne prouve pas le comportement natif. Après avoir publié, ouvrez l'URL en direct à la taille d'un téléphone. La barre d'installation omg se trouve en dessous du jeu, qui devient une carte au-dessus d'elle, donc la barre ne couvre aucun contrôle de jeu. Ne la déplacez pas et ne la cachez pas.