言語を切り替える
テーマを切り替える

ミニゲームのステートマシン設計:ホーム画面から戦闘、決算までの完全な流れ

"カードゲームのバグの 80% は状態設計の不適切さに由来し、ステートマシンは状態判断が散らばる問題を効果的に解決します。"

- 搜狐のカードゲームアーキテクチャ記事

"ネストされたステートマシンは複雑なゲームの主流設計パターンとなっており、Battle State 内部に BeginBattle、HeroTurn、EnemyTurn、EndBattle の四つのサブ状態を含めることができます。"

あなたが作ったミニゲームがリリースされたばかりで、プレイヤーから決算画面がよくフリーズするというフィードバックがありました。ゲームが終了しているのに、画面はまだ戦闘状態を表示しています。コードを開いて見ると、画面中に if (isPlaying && !isPaused && !isGameOver && hasPlayerLeft) が散らばっています。その時、私はただ言いたかったです。このコード、誰が書いたの?ああ、私自身でした。

正直に言うと、私もこの罠にハマったことがあります。以前、カードゲームを作った時、状態判断が至る所に boolean 変数で散らばっていて、一つの機能を変更するのに十数個のファイルを確認する必要がありました。最も崩溃したのは、マルチプレイヤー対戦時、サーバー側が決算状態に切り替わったのに、クライアント側はまだ戦闘アニメーションを再生していたこと——両側の状態がドリフトしていました。後にリファクタリングでステートマシンを使い、コードはすっきりしました。

この記事では、ミニゲームのステートマシンの設計方法について話します。ホーム画面から戦闘、決算まで、三層アーキテクチャに分けて明確に説明します。ゲームフロー層、戦闘フロー層、戦闘詳細層です。そして実践で踏んだ罠も紹介します。ステートロック、サーバー側検証、タイムアウトメカニズム——これらが実戦で本当に役立つものです。

なぜミニゲームにステートマシンが必要なのか?

結局のところ、ゲームとは一連の状態が行ったり来たりするものです。

ホーム、戦闘、決算、一時停止、観戦——各画面の背後には一つの状態があります。プレイヤーが「ゲーム開始」をクリックすると、ホーム画面が消え、戦闘画面が現れます。戦闘が終わると、決算画面がポップアップします。簡単に聞こえますが、コードを書くと混乱してきます。

if-else 瀑布流の三つの罪

まず、このようなコードを見てください。あなたも似たようなものを見たことがあるでしょう。

// 状態判断が散らばっている
if (isPlaying && !isPaused && !isGameOver) {
  // プレイヤーが操作可能
}

if (isGameOver && !hasShownResult) {
  // 決算画面を表示
}

if (isPlaying && currentPlayer === 'player1') {
  // プレイヤー1のターン
}

このコードには三つの罪があります。

読みにくい:現在の状態を判断するには、ファイル全体の boolean 変数を探し回る必要があります。isPlayingisPausedisGameOver——これらの変数は至る所に散らばっていて、パズルのピースのように、自分で完全な絵を組み立てる必要があります。

変更しにくい:新しく「一時停止」状態を追加しますか?十数箇所の if 判断を変更する必要があります。一箇所でも漏らせば、バグが発生します。ある時、「観戦」状態を追加し、二日間コードを修正しましたが、リリース後も問題が発生しました——あるアニメーションが一時停止していませんでした。

デバッグしにくい:状態切り替えの順序が乱れると、どこで問題が発生したのか分かりません。isGameOver が true になったのに、hasShownResult はまだ false——どの判断が漏れたのでしょうか?

搜狐の記事によると、カードゲームのバグの 80% は状態設計の不適切さに由来します。この数字は驚くべきものですが、経験した人なら皆、本当だと分かります。

ステートマシンはどうやってこれらの問題を解決するのか?

ステートマシンは、これらの散らばった判断を一箇所にまとめます。

各状態には独自の動作があります。入る時に何をするか、実行中に何をするか、終了時に何をするか。状態間の切り替えルールも明確にします。ホーム画面から戦闘画面にしか遷移できず、直接決算画面には遷移できません。

比較してみましょう。

// ステートマシン方式
class HomeState {
  enter() { showHomeUI(); }
  exit() { hideHomeUI(); }
  handleEvent(event) {
    if (event === 'START_BATTLE') {
      manager.changeState(new BattleState());
    }
  }
}

class BattleState {
  enter() { showBattleUI(); startBattle(); }
  exit() { hideBattleUI(); cleanupBattle(); }
}

状態がカプセル化されています。HomeState のロジックを変更しても、BattleState には影響しません。新しい状態を追加するには、新しい State クラスを書いて、切り替えルールを追加するだけです。

Better Programming の記事によると、ネストされたステートマシンは複雑なゲームの主流設計パターンとなっています。ミニゲームも例外ではありません——状態が明確で、コードも明確になります。

三層ステートマシンアーキテクチャの詳細

ミニゲームのステートマシンは階層的に設計する必要があります。階層化しないと、戦闘ロジックがゲームフロー層に詰め込まれ、コードはますます肥大化します。

私はステートマシンを三層に分割します。ゲームフロー層、戦闘フロー層、戦闘詳細層です。

第一層:ゲームフロー層

これはゲームのメインフレームで、起動から決算までの大きな流れです。

BootState:ゲーム起動時、リソースをロードし、エンジンを初期化します。Cocos Creator の Boot シーンがこの役割を果たします——まず Boot を実行し、その後 Home に切り替えます。

HomeState:ホームメニュー。プレイヤーがステージを選択し、キャラクターを選択し、ランキングを表示します。ここでのロジックは比較的シンプルです。UI を表示し、ボタンクリックを処理します。

BattleState:戦闘シーン。この層は終点ではなく、コンテナです——戦闘内部にはさらにサブステートマシンがあります。

SettlementState:決算画面。勝敗を表示し、統計データを表示し、戦闘リソースをクリアします。

流れはこうなります。Boot → Home → Battle → Settlement → Home に戻る(または終了)。

コードで概略図を描いてみましょう。

ゲーム起動 → BootState
          ↓ (ロード完了)
         HomeState
          ↓ (開始クリック)
         BattleState ← 戦闘サブステートマシンはここ
          ↓ (戦闘終了)
      SettlementState
          ↓ (戻るクリック)
         HomeState

第二層:戦闘フロー層

BattleState 内部にはさらにサブステートマシンがあります。Better Programming の記事によると、Battle State は BeginBattle、HeroTurn、EnemyTurn、EndBattle の四つのサブ状態を含めることができます。

BeginBattleState:戦闘を初期化します。ステージパラメータを設定し、キャラクターデータをロードし、全プレイヤーの状態を同期します。マルチプレイヤー対戦時、このフェーズでロックをかけます——プレイヤーが途中で参加または退出することを禁止します。

PlayerTurnState:プレイヤーターン。カードを出す、攻撃する、防御する——これらの操作は全てこの状態で処理されます。ターンが終わると、次のターンまたは決算に切り替わります。

EnemyTurnState(オプション):AI の行動。シングルプレイヤーのミニゲームではスキップでき、マルチプレイヤー対戦では相手のターンになります。

EndBattleState:決算ロジック。勝敗を判定し、報酬を計算し、決算アニメーションをトリガーします。

流れ:

BattleState に入る → BeginBattleState (ロック、同期)
                  ↓ (準備完了)
              PlayerTurnState
                  ↓ (ターン終了)
              EnemyTurnState (オプション)
                  ↓ (戦闘終了)
              EndBattleState
                  ↓ (決算完了)
         BattleState を退出 → SettlementState

第三層:戦闘詳細層

さらに細分化が必要な状態もあります。例えば PlayerTurnState、内部には以下があります。

アニメーションステートマシン:キャラクターが攻撃する時は攻撃アニメーションを再生し、ダメージを受けた時はダメージアニメーションを再生します。Unity と Cocos Creator には組み込みのアニメーションステートマシンがありますが、ここでは詳しく説明しません。

ターンステートマシン:カードを出す → 攻撃する → 防御する → ターン終了。各アクションはサブ状態です。

Beast Card Clash というゲームでは、setup、scoring、results screen を独立した状態クラスとしてカプセル化しています。この設計の利点は、一つのフェーズのロジックを変更しても、他のフェーズに影響しないことです。

三層アーキテクチャの利点は責任が明確であることです。ホームのロジックを変更しても、戦闘のロジックは壊れません。ターンの詳細を変更しても、決算フローには影響しません。チームコラボレーションも容易になります——一人が戦闘フロー層を担当し、もう一人が詳細層を担当します。

ステートマシンのコアインターフェース設計

三層アーキテクチャの話は終わりました。コードをどのように書くか見てみましょう。

知乎の Unity 記事によると、ステートマシンノードの統一インターフェースは OnEnter、OnUpdate、OnExit、OnHandleEvent です。この設計を参考に、TypeScript で実装しました。

IGameState インターフェース

各状態はこのインターフェースを実装する必要があります。

interface IGameState {
  name: string;                    // 状態名、デバッグ時に便利
  
  enter(params?: any): void;       // 状態に入る時に初期化
  update(dt: number): void;        // 毎フレーム更新(オプション)
  exit(): void;                    // 状態を終了する時にクリア
  handleEvent(event: GameEvent): void;  // イベントを処理、状態切り替えをトリガー
}

五つのメソッドの役割:

name:デバッグ用。ログ出力時に現在の状態が “HomeState” か “BattleState” か分かり、boolean 変数を見るより直感的です。

enter:状態に入る時に呼び出されます。HomeState の enter はホーム UI を表示し、プレイヤーデータをロードします。BattleState の enter は戦闘パラメータを初期化し、戦闘サブステートマシンに入ります。

update:毎フレーム更新します。戦闘シーンでよく使われます——カウントダウンを更新し、プレイヤー操作を検知します。ホームでは基本的に使いません。

exit:状態を終了する時にクリアします。リソースを解放し、UI を非表示にし、リスナーをキャンセルします。

handleEvent:イベントドリブンの切り替え。「ゲーム開始」をクリックすると START_BATTLE イベントがトリガーされ、HomeState がこのイベントを処理し、BattleState に切り替わります。

GameStateManager 状態管理器

ステートマシンには状態を切り替える管理器が必要です。Stack Exchange の記事では CGameEngine クラスの設計を紹介しています。Init、Cleanup、ChangeState、PushState、PopState です。

少し簡略化しました。

class GameStateManager {
  private currentState: IGameState | null = null;
  private stateStack: IGameState[] = [];  // スタック状態をサポート(一時停止が戦闘を覆う)
  
  // 初期化
  init(firstState: IGameState) {
    this.currentState = firstState;
    this.currentState.enter();
  }
  
  // 状態切り替え
  changeState(newState: IGameState, params?: any) {
    if (this.currentState) {
      this.currentState.exit();
    }
    this.currentState = newState;
    this.currentState.enter(params);
  }
  
  // スタック状態をプッシュ(一時停止、ポップアップ用)
  pushState(state: IGameState) {
    if (this.currentState) {
      // 現在の状態は終了せず、一時停止のみ
      this.stateStack.push(this.currentState);
    }
    this.currentState = state;
    state.enter();
  }
  
  // スタック状態をポップ
  popState() {
    if (this.currentState) {
      this.currentState.exit();
    }
    this.currentState = this.stateStack.pop();
    // 再度 enter する必要はなく、以前は一時停止していただけ
  }
  
  // 毎フレーム更新
  update(dt: number) {
    if (this.currentState) {
      this.currentState.update(dt);
    }
  }
  
  // イベント処理
  handleEvent(event: GameEvent) {
    if (this.currentState) {
      this.currentState.handleEvent(event);
    }
  }
}

changeState:状態を切り替えます。古い状態が終了し、新しい状態に入ります。ホームから戦闘への切り替えはこれを使います。

pushState / popState:スタック状態。戦闘中に一時停止ボタンを押すと、PauseState がスタックのトップにプッシュされ、BattleState を覆います。一時停止をキャンセルすると、PauseState がポップされ、BattleState が復帰します。

スタック状態は重要です。一時停止、ポップアップ、確認ダイアログ——これらは全て現在の状態を覆う必要がありますが、破壊しません。

実戦で踏んだ罠と解決策

理論の話は終わりました。実戦で踏んだ罠について話しましょう。搜狐の記事はカードゲームの経験をまとめていますが、私はいくつかの典型的な問題を詳しく説明します。

ステートドリフト:サーバーとクライアントの状態不一致

マルチプレイヤー対戦で最も踏みやすい罠です。

シナリオはこうです。サーバー側が決算状態に切り替わったのに、あるプレイヤーのクライアントはまだ戦闘アニメーションを再生しています——ネットワーク遅延で、状態切り替えのブロードキャストを受信できていない。画面には「戦闘中」と表示されていますが、実際には終了しています。

原因:状態切り替えのメッセージが失われた、または遅延が長すぎる。

解決策

  1. 状態同期メカニズム:サーバー側が状態を切り替える時、全クライアントにブロードキャストします。クライアントはメッセージを受信したら直ちに同期します。

  2. ハートビート検知:クライアントは数秒ごとにサーバーにハートビートを送信し、現在の状態を添付します。サーバー側が状態の不一致を発見したら、強制的に同期メッセージをプッシュします。

// クライアントハートビート
class BattleState {
  enter() {
    this.startHeartbeat();
  }
  
  startHeartbeat() {
    setInterval(() => {
      socket.send({
        type: 'HEARTBEAT',
        state: this.manager.currentState.name,
        roomId: this.roomId
      });
    }, 3000);  // 3秒ごとに送信
  }
}

同時実行の競合:複数人が同時に操作

カードテーブルで、二人のプレイヤーが同時にカードを出し、状態切り替えの順序が乱れました。

原因:ステートロックがなく、同時実行操作がキューに入っていない。

解決策:ステートロック + 「現在の操作者ID」メカニズム。

class DealingState implements IGameState {
  private lock: boolean = true;
  private currentOperator: string = '';
  
  enter() {
    this.lock = true;  // カード配布フェーズでロック
    // プレイヤーの参加退出を禁止
    // 全員の初期データを同期
    
    setTimeout(() => this.unlock(), 3000);  // 3秒後にロック解除
  }
  
  unlock() {
    this.lock = false;
    this.currentOperator = this.getFirstPlayerId();
    this.manager.changeState(new PlayerTurnState());
  }
  
  handleEvent(event: GameEvent) {
    if (this.lock) {
      // ロック状態、操作を拒否
      return;
    }
    
    // 現在の操作者のみがイベントをトリガー可能
    if (event.playerId === this.currentOperator) {
      // 操作を処理
    }
  }
}

ロックの利点は、重要な瞬間(カード配布、決算)に一つのフローしか走っておらず、プレイヤーの操作に中断されないことです。

決算の安全性:改ざんパッケージが虚偽のスコアをアップロード

クライアントが決算データをアップロードします。スコア、勝敗。改ざんパッケージがスコアを変更したら、サーバー側はどうやって真偽を判断しますか?

原因:サーバー側がクライアントのデータを信用している。

解決策:サーバー側で独立して計算し、クライアントの結果を信用しない。

// サーバー側決算ロジック
class EndBattleState {
  enter() {
    // クライアントがアップロードしたスコアを受け付けない
    // サーバー側が戦闘記録に基づいて独立して計算
    const result = this.calculateResultFromLog(battleLog);
    
    // 全クライアントに結果を送信
    this.broadcastResult(result);
  }
  
  calculateResultFromLog(log: BattleLog) {
    // 戦闘記録(カードを出した順序、攻撃データ)に基づいて独立して計算
    // クライアントのログは偽造可能、重要なデータはサーバー側で検証
  }
}

核心原則:サーバー側はクライアントを信用しない。クライアントは表示のみで、サーバー側が判定を行います。

タイムアウトによるフリーズ:状態に終了条件がない

ある状態でスタックし、永遠に次の状態に切り替わらない。

原因:状態にタイムアウトメカニズムがなく、待っているイベントがトリガーされない。

解決策:各状態にタイムアウトを設定します。

class PlayerTurnState implements IGameState {
  private timeoutTimer: number;
  
  enter() {
    this.timeoutTimer = setTimeout(() => {
      // タイムアウトで自動スキップ
      this.manager.handleEvent({
        type: 'TIMEOUT_SKIP',
        playerId: this.currentPlayer
      });
    }, 30000);  // 30秒タイムアウト
  }
  
  exit() {
    clearTimeout(this.timeoutTimer);  // 終了時にタイムアウトをクリア
  }
}

タイムアウトメカニズムは重要です。プレイヤーが切断したり、ネットワークが詰まったり——状態は永遠に待てず、フォールバック方案が必要です。

四つの罠の話は終わりました。これらの経験は理論より重要です——私はステートドリフトの罠を踏み、二日間調査してやっと原因を見つけました。後にハートビート検知を追加し、問題はなくなりました。

Cocos Creator 実践例

理論もあり、インターフェースもあり、踏んだ罠の経験もあります。Cocos Creator でどのように実装するか見てみましょう。

前の記事『Cocos Creator ミニゲームプロジェクト構造』で、Boot、シーン、決算ページの分割について話しました。この記事ではその考えを続けます。ステートマシンを Layer とどう組み合わせるか。

単一シーンアーキテクチャ下のステートマシン

Cocos Creator ミニゲームでは、単一シーン + 複数 Layer が推奨されます。一つのシーン、複数の Layer が表示非表示を切り替えます。ステートマシンはこのアーキテクチャにちょうど対応します。

BootLayerHomeLayerBattleLayerSettlementLayer

各 Layer は一つの状態に対応します。

import { director, Node } from 'cc';

// HomeState
class HomeState implements IGameState {
  name = 'Home';
  private homeLayer: Node | null = null;
  
  enter() {
    // HomeLayer を表示
    const scene = director.getScene();
    this.homeLayer = scene?.getChildByName('HomeLayer') ?? null;
    if (this.homeLayer) {
      this.homeLayer.active = true;
      this.initHomeUI();
    }
  }
  
  exit() {
    // HomeLayer を非表示
    if (this.homeLayer) {
      this.homeLayer.active = false;
      this.cleanupHome();
    }
  }
  
  handleEvent(event: GameEvent) {
    if (event.type === 'START_BATTLE') {
      // BattleState に切り替え
      this.manager.changeState(new BattleState(), event.params);
    }
  }
  
  initHomeUI() {
    // プレイヤーデータをロード、ボタンクリックイベントを設定
  }
  
  cleanupHome() {
    // ホームリソースを解放
  }
}

// BattleState
class BattleState implements IGameState {
  name = 'Battle';
  private battleLayer: Node | null = null;
  
  enter(params?: any) {
    const scene = director.getScene();
    this.battleLayer = scene?.getChildByName('BattleLayer') ?? null;
    if (this.battleLayer) {
      this.battleLayer.active = true;
      this.startBattle(params);
    }
  }
  
  exit() {
    if (this.battleLayer) {
      this.battleLayer.active = false;
      this.cleanupBattle();
    }
  }
  
  startBattle(params: any) {
    // 戦闘サブステートマシンに入る
    this.battleStateManager.init(new BeginBattleState(params));
  }
  
  cleanupBattle() {
    // 戦闘リソースをクリア
  }
}

Layer の表示非表示は、状態切り替えの視覚的表現です。enter で表示、exit で非表示。

ステートマシンとデータ受け渡し

状態を切り替える時にパラメータを渡します。例えばホームでステージを選んだら、BattleState はどのステージかを知る必要があります。

// HomeState が切り替えを開始
handleEvent(event: GameEvent) {
  if (event.type === 'START_BATTLE') {
    // ステージパラメータを渡す
    this.manager.changeState(new BattleState(), {
      levelId: event.levelId,
      difficulty: event.difficulty
    });
  }
}

// BattleState がパラメータを受け取る
enter(params?: any) {
  const levelId = params?.levelId ?? 'default';
  const difficulty = params?.difficulty ?? 'normal';
  this.loadLevel(levelId, difficulty);
}

状態の永続化には localStorage を使います。戦闘中に退出し、次回入った時に進捗を復元します。

// 進捗を保存
exit() {
  localStorage.setItem('battle_progress', JSON.stringify({
    levelId: this.levelId,
    round: this.currentRound,
    score: this.score
  }));
}

// 進捗を復元
enter() {
  const saved = localStorage.getItem('battle_progress');
  if (saved) {
    const progress = JSON.parse(saved);
    this.resumeFromProgress(progress);
  }
}

WeChat ミニゲームの注意点

CSDN の記事によると、WeChat ミニゲームのステートマシンはグローバルファイルに置くと変更しやすいです。

パッケージサイズ制限:WeChat ミニゲームのメインパッケージは 4MB。ステートマシンのコードは簡潔にし、クラスを書きすぎないようにします。

サブパッケージロード:コアステートマシンはメインパッケージに(Boot、Home、Battle)、拡張状態はサブパッケージに(特別ステージ、イベント画面)。

グローバル状態ファイル:WeChat ミニゲームのエントリーファイル game.js でステートマシンを初期化し、他のモジュールはグローバル変数でアクセスします。

// game.js
import { GameStateManager } from './states/GameStateManager';
import { BootState } from './states/BootState';

// グローバル状態管理器
window.gameStateManager = new GameStateManager();
window.gameStateManager.init(new BootState());

WeChat API を呼び出す時、グローバル変数で状態を取得します。

// WeChat ロインコールバック
wx.login({
  success: (res) => {
    const state = window.gameStateManager.currentState;
    if (state.name === 'HomeState') {
      state.handleEvent({ type: 'LOGIN_SUCCESS', code: res.code });
    }
  }
});

このアーキテクチャの利点は、WeChat API がステートマシンを import する必要がなく、グローバル変数でアクセスできることです。


この記事と前の記事『Cocos Creator ミニゲームプロジェクト構造』を一緒に読むと、理解がより深まります。プロジェクト構造はステートマシンのコンテナで、ステートマシンはプロジェクト構造の振る舞いロジックです。

まとめ

いろいろ話しましたが、ステートマシンとは、ゲームフローを明確なノードに分割することです。

三層アーキテクチャ——ゲームフロー層、戦闘フロー層、戦闘詳細層——はミニゲームから大規模プロジェクトまで使えます。三層の利点は、一層を変更しても他の層に影響せず、チームコラボレーションも容易になることです。

踏んだ罠の経験は理論より重要です。ステートドリフト、同時実行の競合、決算の安全性、タイムアウトによるフリーズ——この四つの問題は私が踏んだことがあり、調査は非常に苦痛でした。後にステートロック、ハートビート検知、サーバー側検証、タイムアウトメカニズムを追加し、問題はなくなりました。コードを正しく書けば、バグは大幅に減ります。

もし実際に試してみたいなら、完全なコードサンプルをダウンロードできます(GitHub リンク)。まず前の記事『Cocos Creator ミニゲームプロジェクト構造』を読んで、Layer の分割を理解してから、この記事でステートマシンの動作を理解してください。二つの記事を一緒に読めば、アーキテクチャが明確になります。

次のステップでは、AI がステートマシンテストを支援する方法について話します——AI がテストケースを生成し、様々な状態切り替えパスをカバーします。テストが自動化されれば、罠を踏む確率はさらに低くなります。

ミニゲームの三層ステートマシンアーキテクチャを設計

ホーム画面から戦闘、決算までの完全な状態管理実装フロー

⏱️ 目安時間: 60 分

  1. 1

    ステップ1: ゲームフロー層の状態を定義

    ゲームのメインフローの状態をリストアップ:BootState、HomeState、BattleState、SettlementState。各状態は Layer またはシーンに対応し、状態間の切り替えルールを決定します。
  2. 2

    ステップ2: 戦闘フロー層のサブ状態を分割

    BattleState 内部にサブステートマシンを構築:BeginBattleState(初期化とロック)、PlayerTurnState(プレイヤーターン)、EnemyTurnState(オプションの AI ターン)、EndBattleState(決算判定)。
  3. 3

    ステップ3: 状態インターフェースとマネージャーを実装

    IGameState インターフェース(name、enter、exit、update、handleEvent)を作成し、GameStateManager で状態切り替え(changeState)とスタック操作(pushState、popState)を管理します。
  4. 4

    ステップ4: 問題防止メカニズムを追加

    重要な状態(カード配布、決算)でロックを追加し同時実行の競合を防止、タイムアウトを設定してフリーズを回避、ハートビート検知でステートドリフトを解決、サーバー側の独立計算で決算の改ざんを防止します。
  5. 5

    ステップ5: Cocos Creator Layer と連携

    enter で対応する Layer を表示しデータを初期化、exit で Layer を非表示にしリソースをクリア。ステートマシンが UI 切り替えを駆動し、コードロジックと視覚表現を分離します。

FAQ

ミニゲームに必ずステートマシンが必要ですか?
必須ではありませんが、状態が増えると(ホーム、戦闘、一時停止、決算など)if-else が混乱します。ステートマシンはロジックを整理し、変更しやすくなります。
三層ステートマシンは過剰設計ではありませんか?
ミニゲームでは三層不要かもしれませんが、このアーキテクチャを理解すると複雑なシーンに対応できます。シンプルなゲームは二層、複雑なゲームは三層に拡張します。
ステートマシンとシーン切り替えの違いは何ですか?
シーン切り替えは Cocos のリソース管理概念で、ステートマシンはロジック概念です。一つのシーンが一つの状態に対応することもあれば、一つの状態が UI の表示非表示を管理することもあります。
マルチプレイヤー対戦時のステートドリフトをどう解決しますか?
サーバー側で状態切り替えをブロードキャスト + クライアント側でハートビート検知。3秒ごとにハートビートを送信し、サーバー側が不一致を発見したら強制同期します。
決算データの改ざんをどう防ぎますか?
サーバー側で独立して計算し、クライアント側からアップロードされた結果を信用しません。クライアントは表示のみで、判定ロジックは全てサーバー側にあります。

6 min read · 公開日: 2026年5月19日 · 更新日: 2026年5月19日

関連記事

コメント

GitHubアカウントでログインしてコメントできます