Structure de projet mini-jeu Cocos Creator : Boot, scènes et écran de fin découplés

Face à l’écran affichant « scene not found », j’ai compris que l’arborescence de ce mini-jeu était complètement hors de contrôle.
Il y a trois mois, je démarrais le projet avec confiance : un menu par scène, une scène de jeu, un écran de fin — ça semblait logique, non ? Résultat : le score disparaissait mystérieusement à chaque changement de scène, le loading saccadait comme un diaporama, et le pire — certains niveaux mettaient 5 secondes à charger ; la moitié des utilisateurs partaient avant même de voir le jeu.
C’est alors que j’ai compris : l’architecture d’un mini-jeu ne se résume pas à empiler des fichiers scene. Après refactorisation en mono-scène + quatre Layer, le chargement est passé de 5 secondes à moins de 2, et les bascules glissent comme sur de l’huile.
Pourquoi une architecture mono-scène pour les mini-jeux
Au début, je ne pensais pas du tout à « l’architecture ». Menu, jeu, écran de fin — une scene par écran, simple et direct.
En pratique, les problèmes se sont accumulés :
Coût des bascules. Chaque director.loadScene() détruit l’ancienne scène, en crée une nouvelle et recharge les ressources. Pour un mini-jeu, c’est désastreux — 53 % des utilisateurs mobiles abandonnent au-delà de 3 secondes de chargement, et les multi-scènes dépassent facilement ce seuil.
Perte d’état. Le joueur termine un niveau avec 1200 points, bascule vers l’écran de fin — les données ont disparu. Pourquoi ? Le changement de scène détruit tous les nœuds, sauf si vous stockez dans une variable globale ou un nœud persistant (voir plus bas).
Interruption des animations. Le logo animé du menu n’a pas fini de jouer, l’utilisateur appuie sur Démarrer — la scène change et l’animation est coupée net, très mauvaise impression.
L’avantage de la mono-scène est clair : toutes les couches UI restent dans la même scène ; basculer ne fait qu’activer/désactiver un Layer via active, sans destruction ni reconstruction, l’état est conservé et les animations continuent. Pour un mini-jeu avec peu d’écrans, c’est largement suffisant. Les gros jeux, avec nombreux niveaux et grosses ressources, ont besoin du multi-scène et du chargement par paquets. Pour un mini-jeu ? La mono-scène suffit.
Modèle d’arborescence du projet
Maintenant que le pourquoi est clair, voyons comment organiser les dossiers.
Voici le modèle que j’ai stabilisé après plusieurs erreurs (exemple Cocos Creator 3.x) :
assets/
├── Scenes/
│ └── Main.scene # Scène principale unique
├── Scripts/
│ ├── managers/
│ │ ├── GameManager.ts # État et stockage des données
│ │ ├── UIManager.ts # Logique de bascule Layer
│ │ └── AudioManager.ts # Gestion audio
│ ├── layers/
│ │ ├── BootLayer.ts # Logique écran de démarrage
│ │ ├── MenuLayer.ts # Logique menu principal
│ │ ├── GameLayer.ts # Interface de jeu
│ │ └── SettlementLayer.ts # Écran de fin
│ └── components/
│ ├── PlayerController.ts
│ └── EnemyAI.ts
├── Prefabs/
│ ├── UI/
│ │ ├── BootPanel.prefab # Prefab UI démarrage
│ │ ├── MenuPanel.prefab # Menu
│ │ ├── SettlementPanel.prefab
│ ├── Game/
│ │ ├── Player.prefab
│ │ ├── Obstacle.prefab
│ └── Effects/
│ └── Explosion.prefab
├── Resources/
│ ├── textures/ # Images chargées dynamiquement
│ ├── audio/ # Fichiers audio
│ └── fonts/
└── bundles/ # Asset Bundle (optimisation WeChat mini-jeu)
├── core/ # Paquet ressources cœur
└── levels/ # Paquet niveaux
Quelques points clés :
Un seul fichier dans Scenes. Main.scene est la scène principale unique : Canvas, GameManager, Layer, etc. N’y ajoutez pas d’autres scene.
managers pour les scripts de gestion. GameManager devient nœud persistant via game.addPersistRootNode(), stocke score et progression ; UIManager gère les bascules entre Layer.
layers pour la logique de chaque couche UI. Chaque Layer correspond à un Prefab (dans Prefabs/UI), script sur le Prefab, puis glissé sous Canvas dans Main.scene.
bundles pour WeChat / Douyin mini-jeux. Limite de volume du premier paquet ; au-delà de 4 Mo, utilisez Asset Bundle. Ressources cœur dans core, niveaux chargés à la demande.
Votre projet est-il organisé ainsi ? Sinon, un réalignement peut valoir le coup.
Boot : concevoir l’écran de démarrage
Quelques chiffres : le moteur Cocos Creator pèse environ 1,9 Mo ; avec vos ressources, le premier écran charge souvent 3 à 5 secondes par défaut. Trop long — l’utilisateur ouvre le jeu, fixe un écran noir ou vide 3 secondes, et ferme.
Le Boot Layer règle ce problème. Ses rôles :
Afficher la progression. Montrer que le jeu charge, pas qu’il est bloqué. Barre ou pourcentage suffisent — pas de fioritures, les images ne sont pas toutes chargées.
Précharger les ressources essentielles. resources.preload() ou loadBundle() d’Asset Bundle pour images et audio nécessaires ensuite.
Marque. Logo, animation simple (zoom progressif), un peu de visibilité pour la marque.
Flux typique :
Initialisation moteur → Boot Layer affiché → préchargement cœur → fin → bascule MenuLayer
Exemple de code (BootLayer.ts) :
import { _decorator, Component, Node, resources, ProgressBar } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('BootLayer')
export class BootLayer extends Component {
@property(ProgressBar)
progressBar: ProgressBar | null = null;
start() {
// Précharger les ressources essentielles
this.preloadCoreAssets();
}
preloadCoreAssets() {
resources.preloadDir('textures', (err, items) => {
if (err) {
console.error('Échec du préchargement :', err);
return;
}
// Chargement terminé, bascule vers le menu
this.switchToMenu();
});
}
updateProgress(current: number, total: number) {
if (this.progressBar) {
this.progressBar.progress = current / total;
}
}
switchToMenu() {
// Demander à UIManager de basculer vers MenuLayer
// Code détaillé plus bas
}
}
Optimisation premier paquet WeChat : limite 4 Mo. Au-delà, Asset Bundle. Vous pouvez charger les bundles dans Boot Layer :
assetManager.loadBundle('levels', (err, bundle) => {
if (err) return;
console.log('Paquet niveaux chargé');
});
Écran de démarrage H5 personnalisé : Cocos Creator propose build-templates. Créez build-templates/web-mobile à la racine avec un index.html personnalisé pour remplacer l’écran noir par défaut — animation ou image de marque.
Tutoriel forum officiel : Creator | 自定义启动页之H5.
Boot Layer en place : le cœur de l’architecture — comment basculer entre les quatre Layer.
Quatre couches : du menu à l’écran de fin
Partie la plus centrale. Structure de la scène principale :
Main.scene
├── Canvas (conteneur UI)
│ ├── BootLayer → progression, marque
│ ├── MenuLayer → démarrage, sélection niveau
│ ├── GameLayer → interface de jeu
│ └── SettlementLayer → score, temps, continuer / réessayer
├── GameManager (nœud persistant)
│ └─ stocke : score, progression, paramètres joueur
└── AudioRoot (nœud audio)
Les quatre Layer sont sous le même Canvas. En basculant, on change seulement active — les autres restent, l’état et les animations sont préservés.
La logique centrale est dans UIManager :
// UIManager.ts
import { _decorator, Component, Node, tween, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('UIManager')
export class UIManager extends Component {
private layers: Map<string, Node> = new Map();
private currentLayer: string = 'BootLayer';
onLoad() {
// Collecter tous les nœuds Layer
const canvas = this.node.getChildByName('Canvas');
if (!canvas) return;
canvas.children.forEach(child => {
if (child.name.endsWith('Layer')) {
this.layers.set(child.name, child);
child.active = false; // Tous masqués au départ
}
});
// Afficher BootLayer
this.switchLayer('BootLayer');
}
switchLayer(targetLayer: string) {
// Masquer la couche courante
const current = this.layers.get(this.currentLayer);
if (current) {
current.active = false;
}
// Afficher la cible
const target = this.layers.get(targetLayer);
if (target) {
target.active = true;
// Animation de fondu (optionnel)
this.playFadeIn(target);
}
this.currentLayer = targetLayer;
}
playFadeIn(node: Node) {
// Fondu simple par scale
node.setScale(new Vec3(0.9, 0.9, 1));
tween(node)
.to(0.2, { scale: new Vec3(1, 1, 1) })
.start();
}
}
Point clé : Map pour toutes les références Layer — pas de getChildByName() à chaque bascule.
Données vers l’écran de fin : le joueur termine avec 1200 points en 45 secondes — comment les transmettre ?
Réponse : GameManager.
Nœud persistant sur toute la durée de vie du jeu. GameLayer enregistre score et temps ; SettlementLayer les lit à l’affichage.
// GameManager.ts (version simplifiée)
import { _decorator, Component, game } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameManager')
export class GameManager extends Component {
public currentScore: number = 0;
public currentLevel: number = 1;
public playTime: number = 0;
onLoad() {
// Nœud persistant
game.addPersistRootNode(this.node);
}
// Appelé par GameLayer pour enregistrer
saveResult(score: number, time: number) {
this.currentScore = score;
this.playTime = time;
}
// Appelé par SettlementLayer pour lire
getResult() {
return {
score: this.currentScore,
time: this.playTime
};
}
}
À l’activation de SettlementLayer :
// SettlementLayer.ts
onEnable() {
const gameManager = find('GameManager')?.getComponent(GameManager);
if (!gameManager) return;
const result = gameManager.getResult();
this.scoreLabel.string = `Score : ${result.score}`;
this.timeLabel.string = `Temps : ${result.time} s`;
}
Transmission réglée sans système d’événements complexe — GameManager centralise.
Nœud persistant : données entre Layer
Déjà évoqué ; quelques précisions.
game.addPersistRootNode() est l’API officielle Cocos Creator pour qu’un nœud survive aux changements de scène. En mono-scène, la scène ne change pas — pourquoi un nœud persistant ?
Il sert quand même de centre de données et d’événements global.
Rôles de GameManager :
Stocker les données joueur — score, progression, record, niveaux débloqués.
Stocker les paramètres — son, musique, langue.
Émettre des événements globaux — ex. « niveau terminé », écoutés par les Layer.
Points d’attention :
Ne pas placer le nœud persistant sous Canvas. Canvas est le conteneur UI manipulé avec les Layer ; le nœud persistant reste à la racine de la scène.
Création manuelle : nœud vide « GameManager » dans Main.scene, script attaché, game.addPersistRootNode(this.node) dans onLoad().
Modération : seules les données vraiment partagées entre écrans ; l’état UI propre à un Layer reste dans ce Layer.
Exemple de structure complète :
// GameManager.ts (version complète)
interface PlayerData {
highestScore: number;
unlockedLevels: number[];
currentLevel: number;
}
interface GameSettings {
soundEnabled: boolean;
musicEnabled: boolean;
language: 'zh' | 'en';
}
@ccclass('GameManager')
export class GameManager extends Component {
private playerData: PlayerData = {
highestScore: 0,
unlockedLevels: [1],
currentLevel: 1
};
private settings: GameSettings = {
soundEnabled: true,
musicEnabled: true,
language: 'zh'
};
onLoad() {
game.addPersistRootNode(this.node);
// Lecture depuis le stockage local
this.loadData();
}
loadData() {
const saved = localStorage.getItem('playerData');
if (saved) {
this.playerData = JSON.parse(saved);
}
}
saveData() {
localStorage.setItem('playerData', JSON.stringify(this.playerData));
}
// getters et setters...
}
Lecture/écriture localStorage pour persister les données. Les plateformes mini-jeu le supportent (WeChat utilise wx.setStorageSync, mais un localStorage encapsulé fonctionne aussi).
Synthèse
Liste de décisions d’architecture pour votre projet :
Si vous faites un mini-jeu :
- Architecture mono-scène (un Main.scene)
- Toutes les UI sous Canvas, bascule par Layer
- BootLayer pour progression et préchargement
- GameManager persistant pour les données
- Arborescence assets/Scripts/managers/layers/Prefabs
Si vous faites un gros jeu :
- Multi-scène nécessaire (niveaux et ressources volumineux)
- L’UI peut rester mono-scène + Layer
- Asset Bundle pour le chargement par paquets
J’ai appliqué cette architecture à plusieurs mini-jeux, avec itérations — elle me semble mature. Questions en commentaire, ou comparez avec un projet exemple sur GitHub.
Prochain article : animations de bascule Layer — fondu, glissement, scale — pour des transitions encore plus fluides.
Mettre en place une architecture mono-scène Cocos Creator pour mini-jeu
Construire de zéro l'architecture Boot, scène principale et écran de fin d'un mini-jeu
⏱️ Estimated time: 30 min
- 1
Step 1: Créer l'arborescence du projet
Sous assets, créez Scenes, Scripts/managers, Scripts/layers, Scripts/components, Prefabs/UI, Prefabs/Game, Resources, bundles, etc., en séparant les fichiers par responsabilité. - 2
Step 2: Créer Main.scene et GameManager
Créez la scène principale unique Main.scene, un nœud GameManager à la racine avec son script, et appelez game.addPersistRootNode(this.node) dans onLoad() pour le rendre persistant. - 3
Step 3: Créer les quatre prefabs Layer
Créez BootLayer, MenuLayer, GameLayer et SettlementLayer, chacun avec son script, regroupés dans Prefabs/UI. - 4
Step 4: Implémenter la logique de bascule UIManager
Dans UIManager.ts, stockez les références Layer dans une Map, implémentez switchLayer(targetLayer) en pilotant active, et ajoutez une animation de fondu pour l'expérience. - 5
Step 5: Implémenter le flux de chargement Boot Layer
Dans BootLayer.ts, appelez resources.preloadDir() pour précharger les ressources essentielles, mettez à jour la barre de progression, puis basculez vers MenuLayer via UIManager une fois terminé.
FAQ
Pourquoi recommander une architecture mono-scène pour les mini-jeux ?
Comment transmettre les données lors d'un changement de scène ?
À quel niveau placer le nœud persistant ?
Quel rôle pour le Boot Layer ?
Que faire si le premier paquet WeChat mini-jeu dépasse 4 Mo ?
8 min de lecture · Publié le: 19 mai 2026 · Mis à jour le: 27 juil. 2026
Développement de mini-jeux Cocos assisté par IA
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Développeur indé : mini-jeux — valider le gameplay avant d'empiler les systèmes (guide MVP)
Le piège le plus fréquent des indés sur les mini-jeux : empiler les systèmes avant de découvrir que le gameplay ne plaît pas. Parcours MVP : de la boucle principale au test prototype, valider au moindre coût si le jeu vaut la peine. Cas réels et leçons d'échec.
Partie 2 sur 21
Suivant
Machine à états pour mini-jeux : du menu à la bataille jusqu'à la conclusion
Architecture à trois niveaux de machine à états pour mini-jeux, de la couche flux de jeu à la couche détails de combat. Interfaces TypeScript, cas pratique Cocos Creator et solutions aux pièges : dérive d'état, conflits concurrents.
Partie 4 sur 21



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire