メインコンテンツまでスキップ

「Architecture」タグの記事が7件件あります

Game architecture and design patterns

全てのタグを見る

クロスシーンイベント:誰も語らないが誰もがハマる永続化の問題

TinyGiants
GES Creator & Unity Games & Tools Developer

AudioManagerがBGMを再生している。プレイヤーが新しいエリアに入った時にトラックを切り替えるため、OnLevelStartにサブスクライブしている。AudioManagerDontDestroyOnLoadオブジェクトに配置して、シーンロードをまたいで永続化させた。開発中は常に同じシーンでテストしているのですべて正常に動作する。

ある日、誰かが初めてレベル1からレベル2をロードする。BGMが切り替わらなくなる。AudioManagerはまだ生きている——DontDestroyOnLoadがその仕事を果たした——しかしイベントサブスクリプションはシーン遷移を生き延びなかった。あるいはもっと悪い状況:古いサブスクリプションがまだ残っていて、破棄されたレベル1のイベント発火元を指しており、次に何かがそれを呼び出そうとすると、ゲームプレイの最中にMissingReferenceExceptionが発生する。

これが永続化問題であり、複数のシーンを持つすべてのUnityプロジェクトがいずれぶつかるものだ。

リリース後に発覚するイベントシステムの罠:メモリリーク、データ汚染、再帰トラップ

TinyGiants
GES Creator & Unity Games & Tools Developer

ゲームを5分間テストしてきた。快適に動作する。するとQAがレポートを上げてくる:「30分のプレイセッションでメモリ使用量が着実に増加。6シーンをロードした後、フレームレートが60から40に低下。」プロファイリングすると、本来12であるべきイベントに847のリスナーが登録されている。各シーンロードが新しいサブスクリプションを追加しつつ、古いものを削除していなかった。オブジェクトは破棄されていたが、デリゲート参照が残り続け、ガベージコレクタが手を出せない場所に死んだMonoBehaviourをピン留めしていた。

あるいはこっち:「2回目のPlay Modeセッションでヘルス値がおかしい。1回目は問題なし。」Playを押す。戦闘をテスト。停止。もう一度Play。プレイヤーのHPが100ではなく73でスタートする。前回のセッションのScriptableObjectの状態が持ち越されていた。誰もリセットしなかったから。

あるいは定番:3秒間ゲームがハングし、Unityがクラッシュする。イベントAのリスナーがイベントBを発火。イベントBのリスナーがイベントAを発火。スタックオーバーフロー。ただし、クラッシュしないこともある——目に見えるエラーを出さずにCPUを食い潰す無限ループでハングするだけ。

これらは仮定の話ではない。実際に本番ゲームで出荷されたのを見たバグだ。そしてすべて同じ根本原因を持つ:単独では正しく見えるが、スケールすると壊れるイベントシステムパターン。

パラレルかシーケンシャルか:すべてのイベントシステムに必要な2つの実行パターン

TinyGiants
GES Creator & Unity Games & Tools Developer

プレイヤーが死ぬ。死亡サウンドと死亡パーティクルは同じ瞬間に始まるべきだ——片方を待ってからもう片方を始める理由がない。でも画面フェードはリスポーンポイントのロード前に絶対に完了しなければならない。リスポーンはプレイヤーのテレポート前に完了しなければならない。テレポートは画面フェードイン前に完了しなければならない。

1つのイベントからトリガーされる同じフロー内でのパラレルとシーケンシャル実行。そして不都合な真実:Unityのほとんどのイベントシステムはパターンを1つだけ提供する。イベントを発火し、すべてのリスナーが応答し、終わり。それらのレスポンスが同時に起きるべきか厳密な順序で起きるべきか?あなたの問題だ。

だから解決する。コルーチンで。コールバックで。_hasFadeFinishedという名前のブール値で。そして気づく前に、6つのファイルに散らばった場当たり的なステートマシンを構築してしまい、未来の自分を含めて誰もフォローできない。

イベント200個超え:なぜイベント管理は破綻するのか

TinyGiants
GES Creator & Unity Games & Tools Developer

新しいUnityプロジェクトを始める。イベントを10個作る。OnPlayerDeathOnScoreChangedOnLevelComplete。分かりやすい名前を付けて、フォルダに入れて、次に進む。快適。イベント構造全体が頭に入る。

半年後。イベントが200個ある。Projectウィンドウはもう、ScriptableObjectファイルの壁だ。OnPlayerHealthDepletedが必要。いやOnPlayerHPLowだったっけ?それともOnPlayerHealthZero?全部OnPlayerで始まる名前のリストをスクロールしながら目を凝らす。3分後、欲しいイベントが既にあるかすら分からないので諦めて新しいのを作る。

これがイベント駆動のUnityプロジェクトが最終的に行き着く場所だ。イベントパターンが間違っているからじゃない。スケールでのイベント管理ツーリングを誰も作っていないからだ。Unityにはアニメーションウィンドウ、Shader Graph、Timeline、Input Systemデバッガがある。イベントが使えるのは...Projectウィンドウ。

ゼロリフレクション、ゼロGC:「高性能」イベントシステムの本当の意味

TinyGiants
GES Creator & Unity Games & Tools Developer

Unity Asset Storeのイベントシステムプラグインはどれも説明文のどこかに「高性能」と書いている。「使いやすい」と「完全なドキュメント付き」の間くらいに。でも考えてみてほしい。1msも0.001msも人間の感覚では両方とも速い。でも片方はもう片方の1000倍遅い。プラグインが「高性能」と言うとき、実際に何を意味している?何と比べて?どうやって計測した?

以前は気にしなかった。ほとんどの人がそうだ。イベントを配線して、開発マシンで問題なく動いて、出荷する。でもモバイルプロジェクトで何百ものエンティティがそれぞれ複数のイベントをリスンしている案件に携わったとき、「高性能」はマーケティングのチェックボックスではなくなった。60 FPSとスライドショーの違いだった。

この記事は、イベントシステムにとって「高性能」が実際に何を意味すべきか、なぜほとんどの実装が不十分なのか、GESがExpression Treeコンパイルを通じてどうゼロに近いオーバーヘッドを実現するのかについて。実際の数字で、手を振るのではなく。

Unityジェネリクスシリアライゼーションの壁:型安全なイベントにボイラープレート税は不要

TinyGiants
GES Creator & Unity Games & Tools Developer

GameEvent<T>を作った。クリーンで、型安全で、エレガント。ヘルス更新用にGameEvent<float>フィールドを作って[SerializeField]を付けた。Inspectorに切り替える。フィールドがない。ただ...消えている。Unityがゼロ除算を頼まれたかのように、空白のパネルでこっちを見つめている。

これはUnity最古のアーキテクチャ的な頭痛の種だ。シリアライゼーションシステムはジェネリクスを理解しない。今までずっとそうだった。型安全でデータ駆動のイベントシステムを作ろうとしたすべての開発者が、この壁に正面からぶつかっている。

些細な不便じゃない。アーキテクチャ全体を蝕む種類の制約だ。型安全性を諦めるか、ボイラープレートの海に溺れるか、美しいジェネリック設計がInspectorに触れることはないと受け入れるか。何年もの間、コミュニティの答えは「具象クラスを手書きしろ」だった。でも考えてみてほしい。ボイラープレートが100%予測可能なら、なぜ人間が書いているんだ?

さよなら見えないスパゲッティ:あなたのイベントシステムがプロジェクトを壊している理由

TinyGiants
GES Creator & Unity Games & Tools Developer

メソッド名を1つだけ変えた。OnPlayerDiedOnPlayerDefeatedに。ゲームデザイナーから「表現をもう少しソフトにして」と頼まれたから。Playを押す。何も起きない。コンパイルエラーなし。警告なし。Inspectorで紐づけていた10個のシーンオブジェクトのUnityEventが、ただ...動かなくなった。無言で。そしてそのことに気づくのは、3日後にQAが報告してくれたとき。最悪の場合、プレイヤーが気づくことになる。

心当たりがあるなら、おめでとう。あなたは「見えないスパゲッティコード」に出会っている。IDEにも表示されない。コンパイラ警告も出ない。依存関係グラフにも出てこない。ただそこに潜んでいて、最悪のタイミングで壊れるのを待っている。

これはスキルの問題じゃない。アーキテクチャの問題だ。そして、ほとんどのUnity開発者が認めたがらないほどよくある話だ。