Cocos Creator ミニゲームのプロジェクト構成:Boot、シーン、リザルト画面の分割方法
画面に “scene not found” と表示され、このミニゲームのディレクトリ構成が完全に破綻していることに気づきました。
3 か月前、意気込んで開発を始めた頃は、メニューに 1 シーン、ゲーム本編に 1 シーン、リザルト画面にもう 1 シーン——理にかなっているように聞こえますよね? 結果は、シーン切り替えのたびにプレイヤーのスコアが謎のゼロに、loading アニメーションはカクカク。最悪だったのは、あるステージの読み込みに 5 秒もかかり、ゲーム画面を見る前にユーザーの半分が離脱したことです。
そのとき初めて分かりました。ミニゲームのアーキテクチャは、scene ファイルを適当に並べれば済む話ではない。プロジェクト全体を「単一シーン+4 層 Layer」にリファクタリングしたところ、読み込み時間は 5 秒から 2 秒以内に短縮。画面切り替えは驚くほど滑らかになりました。
ミニゲームで単一シーンアーキテクチャが推奨される理由
正直、最初にミニゲームを作ったときは「アーキテクチャ」なんて考えていませんでした。メニュー、ゲーム、リザルト——各画面に別々の scene ファイル。シンプルで分かりやすい。
動かしてみると、問題が次々と出てきます。
切り替えコスト。director.loadScene() のたびに、エンジンは旧シーンを破棄し、新シーンを生成し、リソースを再読み込みします。ミニゲームにとってこれは致命的——読み込みが 3 秒を超えるページでは、モバイルユーザーの 53% が離脱します。マルチシーン構成だと、切り替え待ちだけで簡単にこの閾値を超えます。
状態の消失。1 ステージクリア、スコア 1200、リザルト画面へ切り替え——データが消える。なぜか? シーン切り替えですべてのノードが破棄されるからです。グローバル変数か常駐ノードに保存しない限り、データは残りません(後述します)。
アニメーションの中断。メニューのロゴアニメーションがまだ再生中なのに、ユーザーがスタートを押してシーン切り替え——アニメーションが途中で切れ、見栄えが最悪です。
単一シーンアーキテクチャの利点は明確です。すべての UI 層が同じシーン内にあり、切り替えは Layer の active プロパティを変えるだけ。破棄・再生成のコストがなく、状態も自然に保持され、アニメーションも中断されません。ミニゲームは画面数が少ないことが多く、この構成で十分です。大規模ゲームは別——ステージが多く、リソースも大きいので、マルチシーン+分包読み込みが必要です。ミニゲームなら、単一シーンで足ります。
プロジェクトディレクトリ構成テンプレート
単一シーンを選ぶ理由が分かったところで、ディレクトリの整理方法を見ていきましょう。
何度も失敗してまとめたテンプレートです(Cocos Creator 3.x を例に):
assets/
├── Scenes/
│ └── Main.scene # 唯一のメインシーン
├── Scripts/
│ ├── managers/
│ │ ├── GameManager.ts # ゲーム状態、データ保存
│ │ ├── UIManager.ts # Layer 切り替えロジック
│ │ └── AudioManager.ts # 効果音管理
│ ├── layers/
│ │ ├── BootLayer.ts # 起動画面ロジック
│ │ ├── MenuLayer.ts # メインメニューロジック
│ │ ├── GameLayer.ts # ゲーム本編
│ │ └── SettlementLayer.ts # リザルト画面
│ └── components/
│ ├── PlayerController.ts
│ └── EnemyAI.ts
├── Prefabs/
│ ├── UI/
│ │ ├── BootPanel.prefab # 起動画面 UI プレハブ
│ │ ├── MenuPanel.prefab # メニュー UI
│ │ ├── SettlementPanel.prefab
│ ├── Game/
│ │ ├── Player.prefab
│ │ ├── Obstacle.prefab
│ └── Effects/
│ └── Explosion.prefab
├── Resources/
│ ├── textures/ # 動的読み込み画像
│ ├── audio/ # 効果音ファイル
│ └── fonts/
└── bundles/ # Asset Bundle 分包(WeChat ミニゲーム最適化)
├── core/ # コアリソースパック
└── levels/ # ステージリソースパック
いくつかのポイント:
Scenes ディレクトリには 1 ファイルだけ。Main.scene が唯一のメインシーンで、Canvas、GameManager、各 Layer などのノードを含みます。このディレクトリに他の scene を入れないでください。
managers に管理クラススクリプトを配置。GameManager は game.addPersistRootNode() で常駐ノードに設定し、スコアやステージ進捗などのデータを保持。UIManager は Layer 間の切り替えロジックを担当します。
layers ディレクトリに各 UI 層のロジックスクリプト。各 Layer は Prefab(Prefabs/UI 配下)に対応し、スクリプトを Prefab にアタッチして Main.scene の Canvas ノード下に配置します。
bundles ディレクトリは WeChat/Douyin ミニゲーム向け。初回パッケージにサイズ制限があり、4MB を超える部分は Asset Bundle で分包読み込みします。コアリソースは core bundle に、ステージリソースは必要に応じて読み込みます。
あなたのプロジェクトもこの構成ですか? 違うなら、見直しを検討してみてください。
Boot シーン:起動画面の設計
まず数字から。Cocos Creator エンジン本体は約 1.9MB、ゲームリソースを足すと初回読み込みはデフォルトで 3〜5 秒。ミニゲームには長すぎます——ユーザーがゲームを開いて、黒画面や空白を 3 秒見つめたら、多くの人はそのまま閉じてしまいます。
Boot Layer はこの問題を解決します。役割はシンプルです。
読み込み進捗の表示。ゲームが読み込まれていることを伝え、フリーズしていないことを示す。プログレスバーかパーセント表示で十分——この段階では画像リソースがまだ読み込まれていません。
コアリソースのプリロード。resources.preload() や Asset Bundle の loadBundle() で、これから必要になる画像・音声を先に読み込みます。
ブランド表示。ロゴを置き、シンプルなアニメーション(例:ロゴのスケールフェードイン)でブランドを印象づけます。
起動フローのイメージ:
エンジン初期化 → Boot Layer 表示 → コアリソースプリロード → 読み込み完了 → MenuLayer へ切り替え
コード例(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() {
// コアリソースをプリロード
this.preloadCoreAssets();
}
preloadCoreAssets() {
resources.preloadDir('textures', (err, items) => {
if (err) {
console.error('プリロード失敗:', err);
return;
}
// 読み込み完了、メニューへ切り替え
this.switchToMenu();
});
}
updateProgress(current: number, total: number) {
if (this.progressBar) {
this.progressBar.progress = current / total;
}
}
switchToMenu() {
// UIManager に MenuLayer への切り替えを指示
// コードは後述
}
}
WeChat ミニゲーム初回パッケージ最適化:WeChat ミニゲームに公開する場合、初回パッケージは 4MB 制限があります。超過分は Asset Bundle で分包します。Boot Layer 内で bundle を読み込めます:
assetManager.loadBundle('levels', (err, bundle) => {
if (err) return;
console.log('ステージパック読み込み完了');
});
H5 プラットフォームのカスタム起動画面:Cocos Creator には build-templates 機構があり、H5 公開後の起動画面をカスタマイズできます。プロジェクトルートに build-templates/web-mobile を作成し、カスタム index.html を置けば、デフォルトの黒い起動画面を置き換えられます——loading アニメーションやブランド画像を追加できます。
詳細は公式フォーラムのチュートリアルを参照:Creator | 自定义启动页之H5。
Boot Layer が整ったら、次はアーキテクチャの核心——4 層 Layer の切り替え方法です。
4 層アーキテクチャの実装:メニューからリザルトまで
ここが最も重要な部分です。メインシーン全体の構造:
Main.scene
├── Canvas (UI コンテナ)
│ ├── BootLayer → 読み込み進捗、ブランド表示
│ ├── MenuLayer → スタート画面、ステージ選択
│ ├── GameLayer → ゲーム本編
│ └── SettlementLayer → リザルト画面(スコア、時間、続ける/リトライ)
├── GameManager (常駐ノード)
│ └─ 保存:スコア、ステージ進捗、プレイヤー設定
└── AudioRoot (音声管理ノード)
4 つの Layer はすべて同じ Canvas 配下にあります。切り替え時は特定 Layer の active プロパティを変えるだけ——他の Layer は存在したままなので、状態も失われず、アニメーションも中断されません。
Layer 切り替えの核心ロジックは 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() {
// すべての 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; // 初期状態はすべて非表示
}
});
// BootLayer を表示
this.switchLayer('BootLayer');
}
switchLayer(targetLayer: string) {
// 現在の層を非表示
const current = this.layers.get(this.currentLayer);
if (current) {
current.active = false;
}
// 目標の層を表示
const target = this.layers.get(targetLayer);
if (target) {
target.active = true;
// フェードインアニメーション(任意)
this.playFadeIn(target);
}
this.currentLayer = targetLayer;
}
playFadeIn(node: Node) {
// シンプルなスケールフェードイン
node.setScale(new Vec3(0.9, 0.9, 1));
tween(node)
.to(0.2, { scale: new Vec3(1, 1, 1) })
.start();
}
}
ポイント:Map ですべての Layer 参照を保持すれば、切り替えのたびに getChildByName() で探す必要がなく、効率が上がります。
リザルト画面へのデータ受け渡し:1 ステージクリア、スコア 1200、プレイ時間 45 秒——このデータをリザルト画面へどう渡す?
答えは GameManager です。
GameManager は常駐ノードで、ゲーム全体を通じてライフサイクルが続きます。GameLayer でステージをクリアしたら、スコアと時間を GameManager に保存。SettlementLayer へ切り替えたら、GameManager からデータを取得して表示します。
// GameManager.ts(簡易版)
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() {
// 常駐ノードに設定
game.addPersistRootNode(this.node);
}
// GameLayer から呼び出してデータを保存
saveResult(score: number, time: number) {
this.currentScore = score;
this.playTime = time;
}
// SettlementLayer から呼び出してデータを取得
getResult() {
return {
score: this.currentScore,
time: this.playTime
};
}
}
SettlementLayer 表示時に GameManager から直接読み取ります:
// SettlementLayer.ts
onEnable() {
const gameManager = find('GameManager')?.getComponent(GameManager);
if (!gameManager) return;
const result = gameManager.getResult();
this.scoreLabel.string = `スコア: ${result.score}`;
this.timeLabel.string = `プレイ時間: ${result.time}秒`;
}
これでデータ受け渡しは解決です。複雑なイベントシステムは不要——GameManager がすべて受け持ちます。
常駐ノード:Layer 間のデータ受け渡し
上でも触れましたが、常駐ノードについてもう少し詳しく説明します。
game.addPersistRootNode() は Cocos Creator 公式 API で、シーン切り替え時にノードを破棄しないようにします。単一シーン構成ではシーン自体は切り替わりませんが、常駐ノードは依然として有用——グローバルなデータセンター兼イベントハブとして機能します。
GameManager の役割:
プレイヤーデータの保存——スコア、ステージ進捗、最高記録、解放済みステージ一覧。
設定データの保存——効果音 ON/OFF、BGM ON/OFF、言語選択。
グローバルイベントの提供——例:「ステージクリア」イベントを各 Layer が購読して反応。
注意点:
常駐ノードは Canvas の下に置けません。Canvas は UI コンテナで、Layer の active 状態に連動して操作されます。常駐ノードは独立し、シーンのルート階層に配置します。
常駐ノードは手動で作成します。Main.scene に空ノード “GameManager” を作成し、GameManager スクリプトをアタッチ。onLoad() で game.addPersistRootNode(this.node) を呼び出します。
常駐ノードは慎重に使いましょう。すべてを詰め込まず、画面間で本当に共有が必要なデータだけを保存。各 Layer 固有の UI 状態(ボタンの表示有無など)は GameManager に入れず、Layer 自身で管理します。
完全なデータ構造の例:
// GameManager.ts(完全版)
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);
// ローカルストレージからデータを読み込み
this.loadData();
}
loadData() {
const saved = localStorage.getItem('playerData');
if (saved) {
this.playerData = JSON.parse(saved);
}
}
saveData() {
localStorage.setItem('playerData', JSON.stringify(this.playerData));
}
// getters と setters...
}
ここでは localStorage の読み書きも追加し、プレイヤーデータをローカルに永続化しています。ミニゲーム各プラットフォームは localStorage をサポートします(WeChat ミニゲームは wx.setStorageSync ですが、ラップ後の localStorage も使えます)。
まとめ
最後に、アーキテクチャ判断のチェックリストです。自分のプロジェクトと照らし合わせてみてください。
ミニゲームを作っている場合:
- 単一シーンアーキテクチャ(Main.scene 1 つ)
- すべての UI 層を Canvas 下に置き、Layer で切り替え
- BootLayer で読み込み進捗とプリロードを担当
- GameManager を常駐ノードとしてデータを保存
- ディレクトリは assets/Scripts/managers/layers/Prefabs で整理
大規模ゲームを作っている場合:
- マルチシーンが必要(ステージ多・リソース大)
- ただし UI 層は単一シーン+ Layer の考え方を流用可能
- Asset Bundle で分包読み込み
このアーキテクチャは、いくつかのミニゲームプロジェクトで実践し、何度も修正を重ねたものです。質問があればコメントをどうぞ。GitHub のサンプルプロジェクトと照らし合わせるのもおすすめです。
次回は Layer 切り替えのアニメーション——フェードイン/アウト、スライド遷移、scale 拡大縮小など、より滑らかな切り替えについて書く予定です。
Cocos Creator ミニゲーム単一シーンアーキテクチャの構築
ゼロから Boot、メインシーン、リザルト画面の 3 層構造を持つミニゲームプロジェクトアーキテクチャを構築
⏱️ 目安時間: 30 分
- 1
ステップ1: プロジェクトディレクトリ構成の作成
assets 配下に Scenes、Scripts/managers、Scripts/layers、Scripts/components、Prefabs/UI、Prefabs/Game、Resources、bundles などのディレクトリを作成し、責務ごとにファイルを整理します。 - 2
ステップ2: Main.scene と GameManager の作成
唯一のメインシーン Main.scene を作成し、ルート階層に GameManager ノードを作成してスクリプトをアタッチ。onLoad() で game.addPersistRootNode(this.node) を呼び出して常駐ノードに設定します。 - 3
ステップ3: 4 つの Layer プレハブの作成
BootLayer、MenuLayer、GameLayer、SettlementLayer の 4 つの Prefab を作成し、各 Prefab に対応するロジックスクリプトをアタッチ。Prefabs/UI ディレクトリに統一して配置します。 - 4
ステップ4: UIManager 切り替えロジックの実装
UIManager.ts 内で Map ですべての Layer 参照を保存し、switchLayer(targetLayer) メソッドを実装。active プロパティで画面を切り替え、フェードインアニメーションを追加して体験を向上させます。 - 5
ステップ5: Boot Layer 読み込みフローの実装
BootLayer.ts 内で resources.preloadDir() を呼び出してコアリソースをプリロードし、プログレスバーを更新。読み込み完了後に UIManager を呼び出して MenuLayer に切り替えます。
FAQ
ミニゲームで単一シーンアーキテクチャが推奨される理由は?
シーン切り替え時にデータはどう受け渡す?
常駐ノードはどの階層に配置する?
Boot Layer の役割は?
WeChat ミニゲームの初回パッケージが 4MB を超えたらどうする?
4分で読めます · 公開日: 2026年5月19日 · 更新日: 2026年6月8日
関連記事
セルフホスト Dev Sandbox:Docker と Go でプレビュー環境を作る
セルフホスト Dev Sandbox:Docker と Go でプレビュー環境を作る
Cloudflare Pro か Business か?3 つの軸で判断するアップグレード判断ツリー
Cloudflare Pro か Business か?3 つの軸で判断するアップグレード判断ツリー
社内ネットワーク Docker pull タイムアウトのトラブルシューティング:DNS・プロキシ・ミラー加速の完全ガイド
コメント
GitHubアカウントでログインしてコメントできます