Design wechseln

Cocos Creator Mini-Spiel-Projektstruktur: Boot, Szenen und Settlement-Seite sauber trennen

Easton editorial illustration: one Main-scene base carrying four stacked interface layers
1,9 MB
Engine-Ladegröße
Größe der Cocos Creator Engine selbst
53 %
Nutzer-Abbruchrate
Anteil mobiler Nutzer, die bei Ladezeiten über 3 Sekunden abbrechen
unter 2 Sek.
Optimierte Ladezeit
First-Screen-Zeit nach Single-Scene-Optimierung
数据来源: CSDN Blog, Tencent Cloud Fallstudien

Auf dem Bildschirm blinkt „scene not found“ – und plötzlich ist klar: Die Verzeichnisstruktur dieses Mini-Spiel-Projekts ist völlig aus dem Ruder gelaufen.

Vor drei Monaten startete ich voller Zuversicht: Menü eine Szene, Spiel-Hauptansicht eine Szene, Settlement-Seite noch eine – klingt vernünftig, oder? Das Ergebnis: Beim Szenenwechsel verschwanden Spielerpunkte, Loading-Animationen ruckelten wie eine Diashow, und manche Level brauchten 5 Sekunden zum Laden – die Hälfte der Nutzer war weg, bevor überhaupt Gameplay sichtbar wurde.

Erst dann wurde mir klar: Mini-Spiel-Architektur ist nicht einfach ein paar Scene-Dateien übereinanderstapeln. Nach dem Refactoring auf Single-Scene plus vier Layer sank die Ladezeit von 5 auf unter 2 Sekunden, und Szenenwechsel fühlen sich butterweich an.

Warum Mini-Spiele von Single-Scene-Architektur profitieren

Am Anfang habe ich beim Mini-Spiel-Entwickeln kaum an „Architektur“ gedacht. Menü, Spiel, Settlement – jede Oberfläche eine eigene Scene-Datei, simpel und direkt.

In der Praxis tauchen dann mehrere Probleme auf:

Wechsel-Overhead. Bei jedem director.loadScene() zerstört die Engine die alte Szene, baut eine neue auf und lädt Ressourcen neu. Für Mini-Spiele ist das fatal – 53 % der mobilen Nutzer brechen ab, wenn das Laden länger als 3 Sekunden dauert; bei Multi-Scene-Wechseln wird diese Schwelle schnell überschritten.

Zustandsverlust. Der Spieler schließt ein Level mit 1200 Punkten ab, wechselt zur Settlement-Seite – und die Daten sind weg. Warum? Szenenwechsel zerstört alle Nodes, es sei denn, Sie speichern Daten in globalen Variablen oder persistenten Nodes (dazu gleich mehr).

Unterbrochene Animationen. Im Menü läuft noch eine Logo-Animation, der Nutzer tippt auf Start – Szenenwechsel, Animation abgeschnitten. Schlechte UX.

Der Vorteil der Single-Scene-Architektur: Alle UI-Ebenen liegen in derselben Szene. Wechseln heißt nur, das active-Attribut eines Layers zu ändern – kein Zerstören und Neuaufbau, Zustand bleibt erhalten, Animationen laufen weiter. Bei Mini-Spielen mit wenigen Oberflächen reicht das völlig. Große Spiele mit vielen Leveln und großen Assets brauchen Multi-Scene und Subpackages – für Mini-Spiele genügt Single-Scene.

Projektverzeichnis-Vorlage

Wenn der Grund klar ist, folgt die Verzeichnisorganisation.

Das ist die Vorlage, die ich nach mehreren Fehlversuchen für Cocos Creator 3.x zusammengestellt habe:

assets/
├── Scenes/
│   └── Main.scene           # Einzige Hauptszene
├── Scripts/
│   ├── managers/
│   │   ├── GameManager.ts   # Spielzustand, Datenspeicherung
│   │   ├── UIManager.ts     # Layer-Wechsel-Logik
│   │   └── AudioManager.ts  # Sound-Verwaltung
│   ├── layers/
│   │   ├── BootLayer.ts     # Startseiten-Logik
│   │   ├── MenuLayer.ts     # Hauptmenü-Logik
│   │   ├── GameLayer.ts     # Spiel-Hauptansicht
│   │   └── SettlementLayer.ts  # Settlement-Seite
│   └── components/
│       ├── PlayerController.ts
│       └── EnemyAI.ts
├── Prefabs/
│   ├── UI/
│   │   ├── BootPanel.prefab    # Startseiten-UI-Prefab
│   │   ├── MenuPanel.prefab    # Menü-UI
│   │   ├── SettlementPanel.prefab
│   ├── Game/
│   │   ├── Player.prefab
│   │   ├── Obstacle.prefab
│   └── Effects/
│       └── Explosion.prefab
├── Resources/
│   ├── textures/             # Dynamisch geladene Bilder
│   ├── audio/                # Sounddateien
│   └── fonts/
└── bundles/                  # Asset Bundle Subpackages (WeChat-Optimierung)
    ├── core/                 # Kern-Ressourcenpaket
    └── levels/               # Level-Ressourcenpaket

Einige Kernpunkte:

Im Scenes-Verzeichnis liegt nur eine Datei. Main.scene ist die einzige Hauptszene mit Canvas, GameManager und allen Layern. Keine weiteren Scene-Dateien hier ablegen.

managers enthält Verwaltungs-Scripts. GameManager wird per game.addPersistRootNode() persistent und speichert Punkte, Level-Fortschritt usw.; UIManager steuert Layer-Wechsel.

layers enthält die Logik-Scripts je UI-Ebene. Jeder Layer entspricht einem Prefab (unter Prefabs/UI), wird an das Prefab gehängt und unter Canvas in Main.scene platziert.

bundles ist für WeChat-/Douyin-Mini-Spiele gedacht. Erstpaket-Größenlimits gelten; alles über 4 MB lädt per Asset Bundle. Kern-Ressourcen ins core-Bundle, Level-Ressourcen bei Bedarf.

Entspricht Ihre Projektstruktur dem noch nicht, lohnt sich eine Anpassung.

Boot-Szene: Startseite gestalten

Eine Zahl vorweg: Die Cocos Creator Engine selbst ist etwa 1,9 MB groß – plus Ihre Assets sind 3–5 Sekunden First-Screen-Ladezeit normal. Für Mini-Spiele ist das zu lang: Nutzer starren 3 Sekunden auf schwarzen Bildschirm und schließen die App.

Der Boot Layer löst genau das:

Lade-Fortschritt anzeigen. Nutzer sollen sehen, dass geladen wird – nicht, dass die App hängt. Fortschrittsbalken oder Prozent reichen; nicht zu aufwendig, solange Bild-Assets noch laden.

Kern-Ressourcen vorladen. Mit resources.preload() oder Asset Bundle loadBundle() Bilder und Audio für die nächsten Schritte laden.

Markenpräsentation. Logo anzeigen, einfache Animation (z. B. sanftes Einzoomen) – nebenbei Branding.

Ablauf in Kurzform:

Engine-Init → Boot Layer sichtbar → Kern-Ressourcen vorladen → fertig → Wechsel zu MenuLayer

Codebeispiel (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() {
        // Kern-Assets vorladen
        this.preloadCoreAssets();
    }

    preloadCoreAssets() {
        resources.preloadDir('textures', (err, items) => {
            if (err) {
                console.error('Preload fehlgeschlagen:', err);
                return;
            }
            // Laden abgeschlossen, zum Menü wechseln
            this.switchToMenu();
        });
    }

    updateProgress(current: number, total: number) {
        if (this.progressBar) {
            this.progressBar.progress = current / total;
        }
    }

    switchToMenu() {
        // UIManager zum Wechsel zu MenuLayer informieren
        // Code folgt weiter unten
    }
}

WeChat-Mini-Spiel-Erstpaket-Optimierung: Auf der WeChat-Plattform gilt ein 4-MB-Erstpaket-Limit. Der Rest lädt per Asset Bundle Subpackage. Im Boot Layer können Sie Bundles so laden:

assetManager.loadBundle('levels', (err, bundle) => {
    if (err) return;
    console.log('Level-Bundle geladen');
});

H5: eigene Startseite: Cocos Creator bietet build-templates für angepasste H5-Startseiten nach dem Build. Im Projektroot build-templates/web-mobile anlegen und eine eigene index.html hinterlegen – statt schwarzem Standard-Screen Loading-Animation oder Markenbild.

Details im offiziellen Forum: Creator | Custom Splash Screen for H5.

Mit dem Boot Layer steht als Nächstes der Kern der Architektur an: wie die vier Layer wechseln.

Vier-Layer-Implementierung: vom Menü zur Settlement-Seite

Das ist der zentrale Teil. Die Hauptszene sieht so aus:

Main.scene
├── Canvas (UI-Container)
│   ├── BootLayer      → Lade-Fortschritt, Markenpräsentation
│   ├── MenuLayer      → Startbildschirm, Level-Auswahl
│   ├── GameLayer      → Spiel-Hauptansicht
│   └── SettlementLayer → Settlement (Punkte, Zeit, Weiter/Retry)
├── GameManager (persistenter Node)
│   └─ speichert: Punkte, Level-Fortschritt, Spielereinstellungen
└── AudioRoot (Audio-Verwaltungs-Node)

Alle vier Layer hängen unter demselben Canvas. Beim Wechsel ändern Sie nur das active-Attribut eines Layers – die anderen bleiben bestehen, Zustand und Animationen bleiben erhalten.

Die Wechsel-Logik liegt im 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() {
        // Alle Layer-Nodes sammeln
        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; // Anfangs alle ausblenden
            }
        });

        // BootLayer anzeigen
        this.switchLayer('BootLayer');
    }

    switchLayer(targetLayer: string) {
        // Aktuelle Ebene ausblenden
        const current = this.layers.get(this.currentLayer);
        if (current) {
            current.active = false;
        }

        // Zielebene einblenden
        const target = this.layers.get(targetLayer);
        if (target) {
            target.active = true;
            // Optional: Fade-in-Animation
            this.playFadeIn(target);
        }

        this.currentLayer = targetLayer;
    }

    playFadeIn(node: Node) {
        // Einfache Scale-Fade-in-Animation
        node.setScale(new Vec3(0.9, 0.9, 1));
        tween(node)
            .to(0.2, { scale: new Vec3(1, 1, 1) })
            .start();
    }
}

Wichtig: Alle Layer-Referenzen in einer Map – beim Wechsel kein wiederholtes getChildByName(), effizienter.

Datenübergabe zur Settlement-Seite: Spieler schließt ein Level mit 1200 Punkten in 45 Sekunden ab – wie kommen die Werte auf die Settlement-Seite?

Antwort: GameManager.

GameManager ist persistent und lebt über das gesamte Spiel. Im GameLayer speichern Sie Punkte und Zeit im GameManager; beim Wechsel zu SettlementLayer lesen Sie sie aus.

// GameManager.ts (vereinfacht)
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() {
        // Als persistenter Node setzen
        game.addPersistRootNode(this.node);
    }

    // GameLayer ruft diese Methode zum Speichern auf
    saveResult(score: number, time: number) {
        this.currentScore = score;
        this.playTime = time;
    }

    // SettlementLayer liest mit dieser Methode
    getResult() {
        return {
            score: this.currentScore,
            time: this.playTime
        };
    }
}

Beim Anzeigen der SettlementLayer liest SettlementLayer direkt aus dem GameManager:

// SettlementLayer.ts
onEnable() {
    const gameManager = find('GameManager')?.getComponent(GameManager);
    if (!gameManager) return;

    const result = gameManager.getResult();
    this.scoreLabel.string = `Punkte: ${result.score}`;
    this.timeLabel.string = `Zeit: ${result.time} Sek.`;
}

Damit ist die Datenübergabe gelöst – ohne komplexes Event-System; GameManager reicht.

Persistente Nodes: datenübergreifende Layer-Kommunikation

Persistente Nodes wurden oben schon erwähnt – hier etwas ausführlicher.

game.addPersistRootNode() ist die offizielle Cocos-Creator-API, damit ein Node beim Szenenwechsel nicht zerstört wird. In Single-Scene wechseln wir keine Szene – warum trotzdem persistente Nodes?

Sie dienen als globales Daten- und Event-Zentrum.

Aufgaben des GameManager:

Spielerdaten speichern – Punkte, Level-Fortschritt, Highscore, freigeschaltete Level.
Einstellungen speichern – Sound, Musik, Sprache.
Globale Events bereitstellen – z. B. „Level abgeschlossen“, auf die Layer hören können.

Hinweise:

Persistente Nodes nicht unter Canvas platzieren. Canvas ist UI-Container und wird beim Umschalten der Layer-active-States mit beeinflusst. Persistente Nodes gehören auf Root-Ebene der Szene.

Manuell anlegen: In Main.scene leeren Node „GameManager“ erstellen, GameManager-Script anhängen, in onLoad() game.addPersistRootNode(this.node) aufrufen.

Sparsam nutzen: Nur wirklich übergreifend benötigte Daten – UI-Zustand eines Layers (z. B. Button-Sichtbarkeit) verwaltet der Layer selbst.

Vollständigeres Datenmodell:

// GameManager.ts (vollständig)
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);
        // Daten aus lokalem Speicher laden
        this.loadData();
    }

    loadData() {
        const saved = localStorage.getItem('playerData');
        if (saved) {
            this.playerData = JSON.parse(saved);
        }
    }

    saveData() {
        localStorage.setItem('playerData', JSON.stringify(this.playerData));
    }

    // getters und setters...
}

Hier kommt localStorage zum Persistieren der Spielerdaten hinzu. Mini-Spiel-Plattformen unterstützen localStorage (WeChat nutzt intern wx.setStorageSync; gekapseltes localStorage funktioniert ebenfalls).

Fazit

Zum Abschluss eine Architektur-Checkliste für Ihr Projekt:

Bei Mini-Spielen:

  • Single-Scene-Architektur (eine Main.scene)
  • Alle UI-Ebenen unter Canvas, Wechsel per Layer
  • BootLayer für Lade-Fortschritt und Preloading
  • GameManager als persistenter Node für Daten
  • Verzeichnisstruktur: assets/Scripts/managers/layers/Prefabs

Bei großen Spielen:

  • Multi-Scene sinnvoll (viele Level, große Assets)
  • UI-Ebenen können trotzdem Single-Scene + Layer nutzen
  • Asset Bundle Subpackages für Ladevorgänge

Diese Architektur habe ich in mehreren Mini-Spiel-Projekten eingesetzt – mit einigen Umwegen, aber inzwischen recht ausgereift. Bei Fragen gerne kommentieren oder ein Beispielprojekt auf GitHub zum Vergleich suchen.

Als Nächstes plane ich Layer-Wechsel-Animationen – Fade, Slide, Scale – damit Übergänge noch flüssiger wirken.

Cocos Creator Single-Scene-Architektur für Mini-Spiele aufsetzen

Von null an Boot-, Hauptszene- und Settlement-Struktur für Mini-Spiele aufbauen

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Projektverzeichnisstruktur anlegen

    Unter assets die Verzeichnisse Scenes, Scripts/managers, Scripts/layers, Scripts/components, Prefabs/UI, Prefabs/Game, Resources und bundles anlegen – Dateien nach Verantwortlichkeit trennen.
  2. 2

    Step 2: Main.scene und GameManager erstellen

    Die einzige Hauptszene Main.scene anlegen, auf Root-Ebene einen GameManager-Node mit Script erstellen und in onLoad() game.addPersistRootNode(this.node) aufrufen, um den Node persistent zu machen.
  3. 3

    Step 3: Vier Layer-Prefabs erstellen

    Die vier Prefabs BootLayer, MenuLayer, GameLayer und SettlementLayer anlegen, jeweils mit dem passenden Logik-Script versehen und einheitlich unter Prefabs/UI ablegen.
  4. 4

    Step 4: UIManager-Wechsel-Logik implementieren

    In UIManager.ts alle Layer-Referenzen in einer Map speichern, switchLayer(targetLayer) implementieren und per active-Attribut die Oberfläche wechseln – optional mit Fade-in-Animation.
  5. 5

    Step 5: Boot-Layer-Ladeablauf implementieren

    In BootLayer.ts resources.preloadDir() für Kern-Assets aufrufen, Fortschrittsbalken aktualisieren und nach Abschluss per UIManager zu MenuLayer wechseln.

FAQ

Warum empfiehlt sich für Mini-Spiele eine Single-Scene-Architektur?
Single-Scene vermeidet Zerstörungs- und Neuaufbau-Overhead beim Szenenwechsel, Zustandsverlust und unterbrochene Animationen. Durch Umschalten des active-Attributs der Layer bleibt der Zustand erhalten und der Wechsel wirkt flüssiger.
Wie werden Daten beim Szenenwechsel übergeben?
GameManager als persistenter Node speichert datenübergreifende Werte. Spielerpunkte, Level-Fortschritt usw. liegen im GameManager; die Settlement-Seite liest sie über gameManager.getResult().
Auf welcher Ebene platziert man persistente Nodes?
Persistente Nodes gehören auf die Root-Ebene der Szene, nicht unter Canvas. Canvas ist ein UI-Container und wird beim Layer-Wechsel mit manipuliert – persistente Nodes müssen unabhängig existieren.
Welche Aufgabe hat der Boot Layer?
Der Boot Layer zeigt Lade-Fortschritt, lädt Kern-Ressourcen vor und präsentiert das Marken-Logo. So wissen Nutzer, dass das Spiel lädt – und nicht hängt.
Was tun, wenn das WeChat-Mini-Spiel-Erstpaket 4 MB überschreitet?
Asset Bundle Subpackages nutzen. Kern-Ressourcen ins core-Bundle, Level-Ressourcen ins levels-Bundle; im Boot Layer bei Bedarf laden – im Erstpaket nur das Nötigste behalten.

8 Min. Lesezeit · Veröffentlicht am: 19. Mai 2026 · Aktualisiert am: 14. Juli 2026

Ähnliche Beiträge

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog