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

「Advanced」タグの記事が6件件あります

Advanced techniques and patterns

全てのタグを見る

ランタイムでイベントフローを構築する:ビジュアルエディタでは足りない時

TinyGiants
GES Creator & Unity Games & Tools Developer

プロシージャルダンジョンジェネレータが3つの感圧板とスパイクトラップのある部屋を生成した。次の部屋にはロックされたドアに繋がるレバーパズル。その次の部屋はボスアリーナで、ボスのヘルスフェーズに応じて環境ハザードが起動する。これらのイベントリレーションシップはエディタ時点では存在しなかった。ダンジョンレイアウトはプレイヤーが30秒前に入力したシードで決定されたものだ。

イベントをどうワイヤリングする?

従来のアプローチでは、巨大なswitch文を書く。各部屋タイプごとに手動でイベントハンドラをサブスクライブ・アンサブスクライブする。各AIの難易度ごとに手動で異なる攻撃パターンをチェインする。各MODコンテンツごとに手動でコンフィグファイルをパースしてイベント接続に変換する。「手動」の部分が問題だ——ランタイムでトポロジーが変わるたびにイベントワイヤリングロジックを再実装している。

ビジュアルノードエディタはデザインタイムで分かっているフローに最適だ。しかしゲームが実行されるまで存在しないフローを根本的に扱えない。そしてますます、最も興味深いゲームシステムはイベントグラフが動的なものだ。

実行順序のバグ:「誰が先に反応するか」に潜む危険

TinyGiants
GES Creator & Unity Games & Tools Developer

プレイヤーが25ダメージを受ける。ヘルスシステムが現在のHPからダメージを差し引く。UIがヘルスバーを更新する。...はずが、ヘルスバーに表示されるのは75ではなく100。20分間コードを見つめた末に気づく。UIのリスナーがヘルスシステムのリスナーより先に実行されていた。UIは古いHP値を読み取り、それを描画し、その後にヘルスシステムがデータを更新した。データが正しくなった頃には、フレームはすでに描画済みだった。

あなたが発見したのは実行順序バグだ。イベント駆動アーキテクチャで何かをリリースした経験があるなら、気づかないうちにこのバグをいくつも出荷している可能性が高い。テスト中はスクリプトがたまたま正しい順序で初期化されたから動いていただけで、本番環境ではUnityのロード順が変わって壊れる——そういう類のバグだ。

これはレアなエッジケースではない。ほとんどのイベントシステム——UnityのUnityEventや標準のC# eventデリゲートを含む——が持つ構造的な欠陥だ。そして一度理由を理解してしまうと、もう元には戻れない。

時間ベースイベント:なぜコルーチンは遅延と繰り返しに向かないのか

TinyGiants
GES Creator & Unity Games & Tools Developer

グレネードが着弾してから2秒後に爆発を遅延させたい。シンプルだ。コルーチンを書く。IEnumerator DelayedExplosion()、yield return new WaitForSeconds(2f)、爆発ロジックを呼ぶ。丁寧に書いて10行くらい。気分がいい。

次にデザイナーが「プレイヤーが爆弾を解除できるようにしたい」と言う。オーケー、StopCoroutine()を呼べるようにCoroutine参照を保存する必要がある。でも待って——コルーチンが開始する前にプレイヤーが解除したら?nullチェックが必要。待機中にゲームオブジェクトが破壊されたら?もう一つnullチェック。コルーチンが完了したまさにそのフレームでプレイヤーが解除したら?レースコンディション。10行が25行になり、まだ「解除メッセージを表示 vs. 爆発を表示」の分岐すら処理していない。

これがUnityのすべての時間ベースイベントの物語だ。最初の実装はクリーン。2番目の要件でコード量が倍増。3番目で転職を考え始める。

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

TinyGiants
GES Creator & Unity Games & Tools Developer

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

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

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

if-else地獄からの脱出:ビジュアル条件ロジックの正しいやり方

TinyGiants
GES Creator & Unity Games & Tools Developer

すべてのゲームは基本的に条件の巨大な山だ。「敵が耐性を持っていない、かつプレイヤーが炎バフを持っている、かつランダムクリティカル判定をパスした場合にのみ炎ダメージを与える。」プロトタイプ中は、コールバックにif文を放り込んで先に進む。30秒。動く。生産性を感じる。

そしてプロトタイプがプロダクションに入る。その30秒のif文が増殖し始める。1つが5つに。5つが50に。50が「2体目のボスのルートドロップ率を制御する条件ってどこにあるの?」になる。そして今、デザイナーが後ろに立ってダメージ閾値を0.3から0.25に変更できるか聞いている。そして君は再コンパイルが必要だと説明している。

if-else地獄へようこそ。住民:3ヶ月以上続いたすべてのUnityプロジェクト。

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

TinyGiants
GES Creator & Unity Games & Tools Developer

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

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

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