- 1. 今回の実装目的と解決した課題
- 2. 開発の背景と設計思想 (Why & How)
- 3. 実装と修正の詳細 (Technical Diff)【要約厳禁】
- 4. トラブルシューティング(発生した不具合と解決策)
- 5. 未来の自分へ:次回実装時のテンプレート(標準手順)
- [English Version]
- 1. Objective and Problem Solved
- 2. Design Philosophy (Why & How)
- 3. Implementation Details (Technical Diff) [DO NOT SUMMARIZE]
- 4. Troubleshooting
- 5. Template for Future Implementation
1. 今回の実装目的と解決した課題
Dungeon Architect(以下DA)を用いてマルチプレイ用のダンジョンを自動生成・遷移するシステムを構築する際、「ダンジョン生成(Build Dungeon)を連続で行うと、UnrealEditor_Chaos(物理エンジン)による ACCESS_VIOLATION が発生し、エディタごとクラッシュする」という致命的な課題に直面しました。
この課題を解決するため、生成リクエストの二重送信を防ぐ「排他制御(関所)」の仕組みと、サーバーとクライアント間で生成完了を同期する「ハンドシェイク(同期・タイムアウト管理)」システムを構築し、堅牢なマルチプレイダンジョン遷移を実装しました。
2. 開発の背景と設計思想 (Why & How)
【事象の分析:なぜクラッシュするのか?】
クラッシュ時のスタックトレースを解析した結果、原因は非同期処理における「競合状態(Race Condition)」でした。Destroy Dungeon で古いメッシュを破棄しても、裏で動いているChaosエンジン側の当たり判定(コリジョン)データの解放・ガベージコレクション(GC)が完了する前に新しい生成命令が走ってしまい、メモリ上で物理データが衝突して ACCESS_VIOLATION が発生していました。
【仮説の立証と設計思想】
DAには On Dungeon Build Complete という生成完了を知らせるイベント(Callback)が存在します。しかし、これはあくまで「Game Thread上のアクター生成処理の終了(論理的な完了)」を通知するものであり、「Physics Threadのコリジョン計算・登録の完了(物理的な完了)」を保証するものではありません。
この知見から、以下の設計思想(Why & How)で解決を図りました。
- アセットのイベント(Callback)を盲信しない:外部の複雑な非同期処理(DA)を扱う際は、標準の通知イベントを「目安」程度に捉え、独自に状態を管理するフラグ(Mutex)を用意する。
- 権限の集中化:クライアントは「行動報告」のみを行い、生成の進行管理や判定(権限)はすべてサーバー(
GameState)が一元管理する。 - フェイルセーフの構築:マルチプレイではクライアントの切断やフリーズが起こり得るため、進行不能を防ぐためのタイムアウト処理(Timer)を必ず仕込む。
3. 実装と修正の詳細 (Technical Diff)【要約厳禁】
- BP_MyGameState
- 対象の関数/イベント:
Event_InitDungeon(Custom Event / Server RPC) - 追加・変更した変数:
IsDungeonBuilding(Boolean / 初期値: False / Replication: Replicated)
- 実装内容:
- ダンジョン生成要求の入り口で、排他制御(二重生成防止)を行います。
Event_InitDungeonの実行ピンをBranchに繋ぎ、ConditionにIsDungeonBuildingを指定。- False の場合:
Set IsDungeonBuilding(True) を実行後、Get Actor Of ClassでBP_DungeonManagerを取得し、後続のダンジョン生成ロジックへ流す。 - True の場合:
Print Stringで「ダンジョン生成中のため要求を破棄します」と出力し、処理を強制終了させる(関所)。 - 生成ロジック内で、古いダンジョンの破棄(Destroy)の後に
Delay(0.5s) を挿入し、GCと物理スレッドのクリアを待機させる猶予時間を設ける。

- BP_MyGameState
- 対象の関数/イベント:
Server_CompleteDungeonGeneration(Custom Event / Server RPC) - 追加・変更した変数:
BuildTimeoutHandle(TimerHandle)
- 実装内容:
- クライアント全員の生成完了通知を待つハンドシェイク処理と、強制進行のタイムアウトを実装します。
- 事前にイベント外で
Set Timer by Event(Time: 15.0) を実行し、戻り値を変数BuildTimeoutHandleにセットしておく。 Server_CompleteDungeonGeneration(全員の準備完了を検知した瞬間)の直後に、Clear and Invalidate Timer by Handle(Handle:BuildTimeoutHandle) を接続してタイムアウトを解除する。- 一連のワープ・遷移処理の一番最後に
Set IsDungeonBuilding(False) を接続し、次のダンジョン生成を許可する。

- BP_MyGameState
- 対象の関数/イベント: 階層移動のインクリメント修正(内部ロジック)
- 追加・変更した変数:
Floor Index(Integer / Replication: RepNotify)
- 実装内容:
- プレイヤーを次階層にワープさせる
For Each Loopの Completedピン から、加算後の値を用いてSet Floor Indexを実行するようにノードを繋ぎ直す(ループ中ではなく、確実に全員が移動した後に階層を進める)。 - その後、
Branchの判定条件をFloor Index > 3(3階層以上でボス部屋やクリアなどの条件)に修正。
- プレイヤーを次階層にワープさせる
4. トラブルシューティング(発生した不具合と解決策)
不具合: 連続生成による UnrealEditor_Chaos の ACCESS_VIOLATION。
解決策:
抜本的な解決にはC++レイヤーでのPhysics Thread完了検知が必要ですが、今回はBlueprintsレイヤーでの堅牢化を行いました。
IsDungeonBuildingによる入り口でのリクエスト破棄により、物理エンジンが悲鳴を上げる前に論理的に命令を遮断しました。- 古いダンジョンのDestroyと新しいBuildの間に
Delay (0.5s)を挿入することで物理エンジンに掃除の時間を与えました。
※Delayによる対応は「クラッシュ率を下げる暫定処置」ですが、現在のゲーム仕様(短時間で連続してダンジョンを生成・遷移するプレイングは不可能)と排他制御(フラグ)を組み合わせることで、実質的な回避策として成立させています。
5. 未来の自分へ:次回実装時のテンプレート(標準手順)
今回得た知見をもとに、非同期アセットやマルチプレイでの状態遷移を実装する際のチェックリストを残します。
- 防衛的プログラミングの徹底:
- 外部アセットの完了イベント(
On ○○ Complete)は、Game Threadの終了であって物理(Chaos)やレンダリングの終了ではないと疑うこと。 - 連続で呼ばれるとマズい処理には、必ず先頭に
BooleanによるBranchの関所を作る(排他制御)。
- マルチプレイの同期原則:
- クライアントはRPCで「行動報告」だけを行う。
GameStateなどのサーバー権限側でフラグを管理する。- 同期待ちが発生する処理には、必ず
Set Timer by Eventでタイムアウト(10〜15秒など)を設定し、万が一の切断・フリーズ時にClear and Invalidate Timer by Handleで解除するか、強制進行させるフェイルセーフを構築する。完成時の「面白さ」と同じくらい、「壊れないためのガード」にコストをかけること。
※ English version below.
※ The English text is AI-translated from the original Japanese article.
[English Version]
UE5 + Dungeon Architect: Implementing Concurrency Control and Physics Interference Avoidance for Multiplayer
1. Objective and Problem Solved
While building an automatic dungeon generation and transition system for a multiplayer game using Dungeon Architect (DA), I encountered a critical issue: Executing dungeon generation (Build Dungeon) repeatedly caused an ACCESS_VIOLATION in UnrealEditor_Chaos (the physics engine), crashing the entire editor.
To solve this, I implemented a robust multiplayer dungeon transition system by introducing a “Mutex (Mutual Exclusion)” to prevent duplicate generation requests and a “Handshake (Synchronization & Timeout Management)” system to synchronize generation completion between the server and clients.
2. Design Philosophy (Why & How)
[Analysis of the Issue: Why does it crash?]
Analyzing the stack trace upon crashing revealed the root cause to be a “Race Condition” in asynchronous processing.
Even if the old meshes are destroyed using Destroy Dungeon, the next generation command triggers before the background Chaos engine finishes clearing collision data and performing Garbage Collection (GC). This results in a memory collision of physics data, triggering the ACCESS_VIOLATION.
[Hypothesis Validation and Design Philosophy]
DA provides an event (Callback) called On Dungeon Build Complete. However, this only notifies the “completion of Actor generation on the Game Thread (Logical Completion)” and does NOT guarantee the “completion of collision calculation and registration on the Physics Thread (Physical Completion)”.
Based on this insight, I applied the following design philosophies:
- Never blindly trust asset callbacks: When dealing with complex asynchronous external systems (like DA), treat standard notification events as approximations. Always implement custom state management flags (Mutex).
- Centralize Authority: Clients should only send “Action Reports.” The server (
GameState) must handle all progression management and validation (Authority). - Build Failsafes: In multiplayer environments, client disconnects or freezes are inevitable. Always implement a timeout mechanism (
Timer) for processes waiting for synchronization to prevent softlocks.
3. Implementation Details (Technical Diff) [DO NOT SUMMARIZE]
- BP_MyGameState
- Target Function/Event:
Event_InitDungeon(Custom Event / Server RPC) - Variables Added/Modified:
IsDungeonBuilding(Boolean / Default: False / Replication: Replicated)
- Details:
- Implemented a mutex at the entry point of the dungeon generation request to prevent duplicate execution.
- Connected the execution pin of
Event_InitDungeonto aBranch, setting the Condition toIsDungeonBuilding. - If False: Execute
Set IsDungeonBuilding(True), then useGet Actor Of Class(BP_DungeonManager) to proceed with the dungeon generation logic. - If True: Execute
Print String(“Dungeon generation in progress, discarding request”) and immediately terminate the execution (Gatekeeper). - Inside the generation logic, inserted a
Delay(0.5s) after destroying the old dungeon to provide a buffer period for GC and the Physics Thread to clear data.
📸 [Insert image of the entire BP showing the Gatekeeper (Branch) and Delay in Event_InitDungeon here]
📸 [Insert image of the console log showing “Dungeon generation in progress, discarding request” here]
- BP_MyGameState
- Target Function/Event:
Server_CompleteDungeonGeneration(Custom Event / Server RPC) - Variables Added/Modified:
BuildTimeoutHandle(TimerHandle)
- Details:
- Implemented a handshake process waiting for all clients to report completion, along with a forced progression timeout.
- Previously, outside this event, executed
Set Timer by Event(Time: 15.0) and saved the return value to theBuildTimeoutHandlevariable. - Immediately after
Server_CompleteDungeonGeneration(the moment all clients are verified as ready), connectedClear and Invalidate Timer by Handle(Handle:BuildTimeoutHandle) to cancel the timeout. - At the very end of the warping/transition sequence, connected
Set IsDungeonBuilding(False) to allow subsequent dungeon generation requests.
📸 [Insert image of the Clear and Invalidate Timer by Handle logic inside Server_CompleteDungeonGeneration here]
- BP_MyGameState
- Target Function/Event: Floor Transition Increment Fix (Internal Logic)
- Variables Added/Modified:
Floor Index(Integer / Replication: RepNotify)
- Details:
- Rerouted the execution from the Completed pin of the
For Each Loop(which warps players to the next floor) to executeSet Floor Indexusing the incremented value. This ensures the floor increments only after everyone has definitely warped, not during the loop. - After that, modified the
Branchcondition toFloor Index > 3(e.g., conditions for boss rooms or clearing after 3 floors).
- Rerouted the execution from the Completed pin of the
4. Troubleshooting
Bug: ACCESS_VIOLATION in UnrealEditor_Chaos due to consecutive generation requests.
Fix:
While a fundamental fix requires detecting the completion of the Physics Thread on the C++ level, I hardened the system at the Blueprint level.
- By discarding requests at the entry point using
IsDungeonBuilding, I logically blocked commands before the physics engine could overload. - By inserting a
Delay (0.5s)between destroying the old dungeon and building the new one, I gave the physics engine time to clean up.
Note: Although the Delay is a “provisional measure to reduce crash rates,” combining it with the mutex flag and the actual game design (which prevents players from triggering transitions repeatedly in a short timeframe) successfully functions as a practical workaround.
5. Template for Future Implementation
Based on this experience, here is a standardized checklist for implementing asynchronous assets or state transitions in multiplayer in the future:
- Strict Defensive Programming:
- Always suspect that an external asset’s completion event (e.g.,
On XX Complete) only means the Game Thread has finished, not the Physics (Chaos) or Render threads. - For processes that cause fatal errors if called consecutively, always set up a Gatekeeper using a
Branchwith aBooleanat the very beginning (Mutex).
- Multiplayer Synchronization Principles:
- Clients must only use RPCs for “Action Reports.”
- Flags must be managed by the server authority (e.g.,
GameState). - For any process requiring synchronization waits, always set a timeout (e.g., 10-15 seconds) using
Set Timer by Event. Build a failsafe to either force progression or useClear and Invalidate Timer by Handleupon success, preventing softlocks due to client crashes or disconnects. Budgeting time for “guards to prevent breaks” is just as vital as building the “fun factor.”


コメント