Estructura de proyecto de minijuegos en Cocos Creator: Boot, escenas y pantalla de resultados

Mirando el error “scene not found” en pantalla, me di cuenta de que la estructura de directorios de este minijuego ya se había ido por completo de las manos.
Hace tres meses empecé con confianza: un escenario para el menú, otro para el juego principal y otro para los resultados. Suena razonable, ¿no? Pues no: al cambiar de escena la puntuación del jugador desaparecía sin motivo, la animación de carga se trababa como un PowerPoint y lo peor, cargar un nivel podía tardar 5 segundos; la mitad de los usuarios se iba antes de ver el juego.
Entonces entendí que la arquitectura de un minijuego no consiste en apilar archivos .scene al azar. Reestructuré todo con escena única + cuatro Layer; el tiempo de carga pasó de 5 segundos a menos de 2 y el cambio entre pantallas quedó tan fluido como mantequilla.
Por qué se recomienda la arquitectura de escena única
Al principio ni pensaba en “arquitectura”. Menú, juego, resultados: cada pantalla en su propio .scene, simple y directo.
Al ejecutarlo, los problemas aparecieron en cascada:
Costo del cambio. Cada director.loadScene() obliga al motor a destruir la escena anterior, crear la nueva y volver a cargar recursos. Para un minijuego es un desastre: el 53% de usuarios móviles abandona si la carga supera 3 segundos, y con varias escenas la espera supera ese umbral con facilidad.
Pérdida de estado. El jugador termina un nivel con 1200 puntos, cambias a la pantalla de resultados y los datos desaparecen. ¿Por qué? El cambio de escena destruye todos los nodos, salvo que guardes datos en variables globales o en un nodo persistente (más adelante explico cómo).
Animaciones interrumpidas. Un logo animado en el menú aún no termina, el usuario pulsa Iniciar, cambias de escena y la animación se corta en seco: mala experiencia.
La ventaja de la escena única es clara: todas las capas de UI viven en la misma escena; cambiar pantalla es solo modificar active del Layer, sin destruir ni reconstruir, el estado se conserva y las animaciones no se interrumpen. En un minijuego con pocas pantallas basta. En un juego grande, con muchos niveles y recursos pesados, sí conviene multi-escena y subpaquetes. Para minijuegos, escena única alcanza.
Plantilla de estructura de directorios
Ya sabes por qué usar escena única. Veamos cómo organizar los directorios.
Esta es la plantilla que fui afinando tras varios tropiezos (ejemplo con Cocos Creator 3.x):
assets/
├── Scenes/
│ └── Main.scene # Única escena principal
├── Scripts/
│ ├── managers/
│ │ ├── GameManager.ts # Estado y datos del juego
│ │ ├── UIManager.ts # Lógica de cambio entre Layer
│ │ └── AudioManager.ts # Gestión de audio
│ ├── layers/
│ │ ├── BootLayer.ts # Lógica de pantalla de arranque
│ │ ├── MenuLayer.ts # Lógica del menú principal
│ │ ├── GameLayer.ts # Pantalla principal del juego
│ │ └── SettlementLayer.ts # Pantalla de resultados
│ └── components/
│ ├── PlayerController.ts
│ └── EnemyAI.ts
├── Prefabs/
│ ├── UI/
│ │ ├── BootPanel.prefab # Prefab UI de arranque
│ │ ├── MenuPanel.prefab # UI del menú
│ │ ├── SettlementPanel.prefab
│ ├── Game/
│ │ ├── Player.prefab
│ │ ├── Obstacle.prefab
│ └── Effects/
│ └── Explosion.prefab
├── Resources/
│ ├── textures/ # Imágenes de carga dinámica
│ ├── audio/ # Archivos de audio
│ └── fonts/
└── bundles/ # Subpaquetes Asset Bundle (WeChat)
├── core/ # Paquete de recursos core
└── levels/ # Paquete de recursos de niveles
Puntos clave:
Scenes solo tiene un archivo. Main.scene es la única escena principal, con Canvas, GameManager, cada Layer, etc. No metas más .scene ahí.
managers guarda scripts de gestión. GameManager se marca persistente con game.addPersistRootNode() y almacena puntuación, progreso, etc.; UIManager maneja el cambio entre Layer.
layers contiene la lógica de cada capa UI. Cada Layer tiene un Prefab (en Prefabs/UI), script adjunto, y se arrastra bajo Canvas en Main.scene.
bundles es para minijuegos WeChat/Douyin. Hay límite de tamaño del paquete principal; lo que pase de 4 MB va en Asset Bundle. Recursos core en core, niveles bajo demanda.
¿Tu proyecto está organizado así? Si no, conviene replantearlo.
Escena Boot: cómo diseñar la pantalla de arranque
Un dato: el motor Cocos Creator pesa unos 1.9 MB; con tus recursos, la primera carga suele tardar 3-5 segundos. Para un minijuego es mucho: el usuario abre el juego, mira pantalla negra o vacía 3 segundos y muchos cierran.
Boot Layer resuelve eso. Sus tareas son simples:
Mostrar progreso de carga. Que sepa que el juego está cargando, no colgado. Una barra o porcentaje basta; no te compliques: aún no has cargado todas las imágenes.
Precargar recursos core. Con resources.preload() o loadBundle() de Asset Bundle, carga antes imágenes y audio que necesitarás.
Mostrar la marca. Logo y una animación sencilla (por ejemplo zoom con fade-in) para reforzar la marca.
El flujo de arranque suele ser:
Inicialización del motor → Boot Layer visible → Precarga de recursos core → Carga completa → Cambio a MenuLayer
Ejemplo de código (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() {
// Precargar recursos core
this.preloadCoreAssets();
}
preloadCoreAssets() {
resources.preloadDir('textures', (err, items) => {
if (err) {
console.error('Error en precarga:', err);
return;
}
// Carga completa, cambiar al menú
this.switchToMenu();
});
}
updateProgress(current: number, total: number) {
if (this.progressBar) {
this.progressBar.progress = current / total;
}
}
switchToMenu() {
// Indicar a UIManager que cambie a MenuLayer
// El código se muestra más adelante
}
}
Optimización del paquete principal en WeChat: si publicas en WeChat, el límite del paquete principal es 4 MB. El resto va en Asset Bundle. Puedes cargar esos bundles en Boot Layer:
assetManager.loadBundle('levels', (err, bundle) => {
if (err) return;
console.log('Paquete de niveles cargado');
});
Pantalla de arranque H5 personalizada: Cocos Creator ofrece build-templates. Crea build-templates/web-mobile en la raíz del proyecto con un index.html propio y sustituyes la pantalla negra por defecto: animación de carga o imagen de marca.
Tutorial en el foro oficial: Creator | Pantalla de arranque personalizada para H5.
Con Boot Layer listo, viene el núcleo: cómo cambiar entre las cuatro capas.
Cuatro capas: del menú a los resultados
Esta es la parte central. La escena principal se ve así:
Main.scene
├── Canvas (contenedor UI)
│ ├── BootLayer → Progreso de carga, marca
│ ├── MenuLayer → Inicio, selección de nivel
│ ├── GameLayer → Pantalla principal del juego
│ └── SettlementLayer → Resultados (puntuación, tiempo, continuar/reintentar)
├── GameManager (nodo persistente)
│ └─ Almacena: puntuación, progreso, ajustes del jugador
└── AudioRoot (nodo de audio)
Las cuatro Layer están bajo el mismo Canvas. Al cambiar, solo modificas active de una Layer; las demás siguen existiendo: no pierdes estado ni cortas animaciones.
La lógica de cambio vive en 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() {
// Recoger todos los nodos 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; // Ocultar todas al inicio
}
});
// Mostrar BootLayer
this.switchLayer('BootLayer');
}
switchLayer(targetLayer: string) {
// Ocultar capa actual
const current = this.layers.get(this.currentLayer);
if (current) {
current.active = false;
}
// Mostrar capa destino
const target = this.layers.get(targetLayer);
if (target) {
target.active = true;
// Animación de fade-in (opcional)
this.playFadeIn(target);
}
this.currentLayer = targetLayer;
}
playFadeIn(node: Node) {
// Animación simple de escala con fade-in
node.setScale(new Vec3(0.9, 0.9, 1));
tween(node)
.to(0.2, { scale: new Vec3(1, 1, 1) })
.start();
}
}
Clave: usa un Map para las referencias de Layer; así no buscas con getChildByName() en cada cambio.
Pasar datos a la pantalla de resultados: el jugador termina con 1200 puntos en 45 segundos. ¿Cómo llega eso a SettlementLayer?
Respuesta: GameManager.
GameManager es persistente y vive todo el juego. En GameLayer guardas puntuación y tiempo; al pasar a SettlementLayer, los lees desde GameManager.
// GameManager.ts (versión simplificada)
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() {
// Marcar como nodo persistente
game.addPersistRootNode(this.node);
}
// GameLayer llama esto para guardar
saveResult(score: number, time: number) {
this.currentScore = score;
this.playTime = time;
}
// SettlementLayer llama esto para leer
getResult() {
return {
score: this.currentScore,
time: this.playTime
};
}
}
Al mostrar SettlementLayer, lees GameManager:
// SettlementLayer.ts
onEnable() {
const gameManager = find('GameManager')?.getComponent(GameManager);
if (!gameManager) return;
const result = gameManager.getResult();
this.scoreLabel.string = `Puntuación: ${result.score}`;
this.timeLabel.string = `Tiempo: ${result.time} s`;
}
Problema resuelto sin un sistema de eventos complejo: GameManager centraliza.
Nodo persistente: datos entre Layer
Ya lo mencionamos; aquí más detalle.
game.addPersistRootNode() es la API oficial de Cocos Creator para que un nodo no se destruya al cambiar de escena. En escena única no cambias de escena; ¿para qué entonces?
Sirve como centro de datos y eventos global.
Responsabilidades de GameManager:
Almacenar datos del jugador: puntuación, progreso, récord, niveles desbloqueados.
Almacenar ajustes: sonido, música, idioma.
Ofrecer eventos globales, por ejemplo “nivel completado”, que otras Layer pueden escuchar.
Precauciones:
El nodo persistente no va bajo Canvas. Canvas es UI y se manipula con el active de los Layer. El persistente va en la raíz de la escena.
Créalo a mano: nodo vacío “GameManager” en Main.scene, script GameManager, y en onLoad() llama game.addPersistRootNode(this.node).
Úsalo con moderación. Solo datos realmente compartidos entre pantallas; el estado UI de cada Layer (por ejemplo si un botón se muestra) déjalo en la Layer.
Ejemplo de estructura de datos:
// GameManager.ts (versión completa)
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);
// Leer datos del almacenamiento 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 y setters...
}
También incluye lectura/escritura con localStorage para persistir datos. Las plataformas de minijuegos lo soportan (WeChat usa wx.setStorageSync, pero localStorage encapsulado también funciona).
Resumen
Lista de decisiones de arquitectura para contrastar con tu proyecto:
Si haces un minijuego:
- Arquitectura de escena única (un Main.scene)
- Todas las capas UI bajo Canvas, cambio por Layer
- BootLayer para progreso y precarga
- GameManager persistente para datos
- Directorios: assets/Scripts/managers/layers/Prefabs
Si haces un juego grande:
- Multi-escena es necesaria (muchos niveles, recursos pesados)
- La UI puede seguir la idea escena única + Layer
- Asset Bundle para subpaquetes
Usé esta arquitectura en varios minijuegos, con pruebas y ajustes; creo que está bastante madura. Si tienes dudas, comenta o busca un proyecto de ejemplo en GitHub.
En el próximo artículo hablaré de animaciones al cambiar Layer: fade, deslizamiento, escala, para transiciones más fluidas.
Montar la arquitectura de escena única para minijuegos en Cocos Creator
Montar desde cero la arquitectura de un minijuego con Boot, escena principal y pantalla de resultados
⏱️ Estimated time: 30 min
- 1
Step 1: Crear la estructura de directorios del proyecto
En assets, crea directorios como Scenes, Scripts/managers, Scripts/layers, Scripts/components, Prefabs/UI, Prefabs/Game, Resources, bundles, etc., organizando archivos por responsabilidad. - 2
Step 2: Crear Main.scene y GameManager
Crea la única escena principal Main.scene, crea el nodo GameManager en la raíz y adjunta el script; en onLoad() llama a game.addPersistRootNode(this.node) para marcarlo como nodo persistente. - 3
Step 3: Crear cuatro prefabs de Layer
Crea cuatro Prefab: BootLayer, MenuLayer, GameLayer y SettlementLayer; cada uno con su script lógico correspondiente, todos en Prefabs/UI. - 4
Step 4: Implementar la lógica de cambio en UIManager
En UIManager.ts usa un Map para guardar referencias de todos los Layer, implementa switchLayer(targetLayer) cambiando la propiedad active, y añade animación de fade-in para mejorar la experiencia. - 5
Step 5: Implementar el flujo de carga de Boot Layer
En BootLayer.ts llama a resources.preloadDir() para precargar recursos core, actualiza la barra de progreso y, al terminar, usa UIManager para cambiar a MenuLayer.
FAQ
¿Por qué se recomienda la arquitectura de escena única para minijuegos?
¿Cómo se pasan los datos al cambiar de escena?
¿En qué nivel va el nodo persistente?
¿Qué hace Boot Layer?
¿Qué hacer si el paquete principal de WeChat supera 4 MB?
9 min de lectura · Publicado el: 19 may 2026 · Actualizado el: 21 ago 2026
Desarrollo de mini juegos Cocos asistido por IA
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Desarrollador indie y minijuegos: valida el gameplay antes de acumular sistemas (guía práctica de MVP)
El error más común al hacer minijuegos como indie: acumular sistemas y descubrir demasiado tarde que no divierte. Esta guía detalla la ruta de validación MVP: del core loop al test con jugadores, con el mínimo coste para confirmar si tu juego merece la pena.
Parte 2 de 21
Siguiente
Diseño de máquina de estados para minijuegos: del inicio a la batalla y la liquidación
Arquitectura de máquina de estados en tres capas para minijuegos: desde el flujo del juego hasta los detalles de combate. Incluye implementación TypeScript, casos prácticos en Cocos Creator y soluciones a deriva de estados, conflictos de concurrencia y otros errores típicos.
Parte 4 de 21



Comentarios
Inicia sesión con GitHub para dejar un comentario