ブログに戻る

Zero-Waste サーバーの設計:Fortnite サーバー最適化に向けた Hibernation 提案の分析

公開日 2026年2月25日
Zero-Waste サーバーの設計:Fortnite サーバー最適化に向けた Hibernation 提案の分析

すべての Multiplayer 開発者は、最終的に同じ財務的な壁にぶつかります。それは、サーバーインフラが「何もない空間」をシミュレートするために資金を浪費しているという現実です。大規模な Battle Royale、サバイバルゲーム、または MMO のために Dedicated Server を立ち上げると、CPU サイクルはアイドル状態の計算に大きく偏ります。誰も見ていない岩の重力を計算し、ターゲットのいない敵の AI ナビゲーションを処理し、プレイヤーが全くいないセクターの World States を維持するために、高額な Cloud Computing 料金を支払っているのです。

最近、Unreal Engine コミュニティにおいて、Epic Games のリーダーシップに向けた興味深い技術提案が浮上しました。その核心となるテーマは、Fortnite の大規模なスケールには、従来のメンテナンスコストの高い中央集権的なホスティングモデルから「Zero-Waste Infrastructure」モデルへの移行が必要であるというものです。著者は、シミュレーションの無駄を排除することで、Epic は Operating Expenses (Opex) を 60〜70% 削減でき、理論的には 1,000 V-Bucks の価格を 1.99 ドルまで引き下げることが可能になると主張しました。

V-Bucks の価格設定に関する経済的な実現可能性はマネタイズ担当者の議論に譲るとして、この提案の技術的な柱である Sector Physics Hibernation (SGH) は、現代のサーバーアーキテクチャにおけるマスタークラスと言えます。

この業界分析では、Fortnite サーバー最適化に向けた Hibernation 提案のメカニズムを解体し、Unreal Engine 5 における Logic-Side Culling の仕組みを調査し、自身の Multiplayer タイトルで Zero-Waste インフラを実装する方法を解説します。

シミュレーションの無駄に関する数学

なぜ Sector Physics Hibernation が必要なのかを理解するには、Dedicated Server の過酷な数学に目を向ける必要があります。

標準的な 100km² の Battle Royale マップを例にとりましょう。マッチ開始時、100 人のプレイヤーが各地の Point of Interest に降下します。最初の 5 分以内にプレイヤーの 50% が脱落し、生存者は縮小する Safe Zone へと集まります。

開始 10 分後には、マップの総面積の 70% 以上にアクティブなプレイヤーが一人もいない状態になります。しかし、標準的な Authoritative Server のセットアップでは、Dedicated Server は 30Hz でワールド全体の Tick を更新し続けます。

  • Physics Calculations: Rigid Bodies、破壊可能な環境、弾道計算がメモリ上で追跡され続けます。
  • Actor Ticking: 数千の AActor インスタンスが、毎秒 30 回 Tick() 関数を呼び出します。
  • NavMesh Processing: 徘徊する AI や動的な障害物が Navigation Mesh へのクエリを継続します。

AWS c5.2xlarge インスタンスでサーバーを運用している場合、1 台あたり 1 時間につき約 0.34 ドルを支払っています。CPU が空の空間の計算で手一杯になり、1 台のマシンで 100 人規模のゲームインスタンスを 2 つしかホストできない場合、スケーラビリティは深刻なボトルネックに直面します。

この提案では、無駄な CPU オーバーヘッドを回収することで、開発者は同じハードウェアに 5〜6 個のゲームインスタンスを詰め込む(サーバー費用を約 60% 削減する)か、回収した処理能力をサーバー全体の Tick Rate を 30Hz から 60Hz+ に引き上げるために再配分し、完璧な Hit Registration と滑らかなゲームプレイを保証できるとしています。

ディープダイブ:UE5 における Sector Physics Hibernation

提案された技術的ソリューションは、Unreal Engine 5 に既存の World Partition システムを活用することに依存していますが、その主な用途をクライアント側のメモリ管理からサーバー側の CPU 管理へと転換させています。

デフォルトの Dedicated Server の問題点

デフォルトでは、UE5 の World Partition は、ストリーミングソース(プレイヤーカメラ)からの距離に基づいて、クライアントに対してセルをロード/アンロードします。これは、クライアントのメモリ使用量を低く抑え、フレームレートを高く保つのに非常に有効です。

しかし、Dedicated Server は通常、権限を維持するためにマップ全体をメモリにロードします。スナイパーが谷を越えて弾丸を撃ったり、グローバルイベントが発生したりした場合、サーバーはアクションを検証するためにコリジョンデータや Actor States を即座に利用できる必要があるからです。サーバー上でディスクから動的にデータをロード/アンロードする(Level Streaming)手法は、多くの場合低速すぎて大規模なヒッチ(カクつき)を引き起こし、Tick Rate を台無しにします。

SGH ソリューション:Logic-Side Culling

メモリからセクターをアンロードする(IO ボトルネックの原因となる)代わりに、Sector Physics Hibernation は CPU-Sleep States を提案しています。

セクターは RAM に保持されますが、すべての Tick、物理計算、および状態の更新が強制的に停止されます。セクターの Spatial Grid セルがアクティブなエンティティ(プレイヤー、プレイヤー所有の車両、またはアクティブなプロジェクタイル)を検知しなくなると、サーバーはその特定のグリッドに対する CPU 割り当てをサスペンドします。

C++ による Hibernation Manager の実装

これを Unreal Engine で構築するには、Spatial Grid セルを監視し、その中の Actor の Tick 状態を動的に切り替えるサブシステムが必要です。以下は、SectorHibernationManager を実装する方法の簡略化されたアーキテクチャプロトタイプです。

#include "SectorHibernationManager.h"
#include "EngineUtils.h"
#include "GameFramework/Actor.h"
#include "GameFramework/PlayerController.h"

void USectorHibernationManager::Initialize(FSubsystemCollectionBase& Collection)
{
    Super::Initialize(Collection);
    HibernationCheckInterval = 2.0f; // 2秒ごとにチェック
    TimeSinceLastCheck = 0.0f;
    GridCellSize = 10000.0f; // 100m グリッドセル
}

void USectorHibernationManager::Tick(float DeltaTime)
{
    TimeSinceLastCheck += DeltaTime;
    if (TimeSinceLastCheck >= HibernationCheckInterval)
    { 
        EvaluateSectors();
        TimeSinceLastCheck = 0.0f;
    }
}

void USectorHibernationManager::EvaluateSectors()
{
    UWorld* World = GetWorld();
    if (!World) return;

    // ステップ 1: アクティブなプレイヤー位置をグリッドセルにマッピング
    TSet<FIntVector> ActiveCells;
    for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
    { 
        APlayerController* PC = Iterator->Get();
        if (PC && PC->GetPawn())
        { 
            FVector PlayerPos = PC->GetPawn()->GetActorLocation();
            FIntVector CellCoord = FIntVector(
                FMath::FloorToInt(PlayerPos.X / GridCellSize),
                FMath::FloorToInt(PlayerPos.Y / GridCellSize),
                FMath::FloorToInt(PlayerPos.Z / GridCellSize)
            );
            
            // このセルと隣接するセルをアクティブとしてマーク(バッファゾーン)
            MarkAdjacentCellsActive(CellCoord, ActiveCells);
        }
    }

    // ステップ 2: 休眠可能な Actor を反復処理し、Tick を切り替える
    for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
    { 
        AActor* Actor = *ActorItr;
        
        // 重要なインフラ Actor はスキップ
        if (!Actor->ActorHasTag(FName("Hibernatable"))) continue;

        FVector ActorPos = Actor->GetActorLocation();
        FIntVector ActorCell = FIntVector(
            FMath::FloorToInt(ActorPos.X / GridCellSize),
            FMath::FloorToInt(ActorPos.Y / GridCellSize),
            FMath::FloorToInt(ActorPos.Z / GridCellSize)
        );

        bool bShouldBeActive = ActiveCells.Contains(ActorCell);
        
        if (bShouldBeActive && !Actor->IsActorTickEnabled())
        { 
            // ウェイクアップ
            Actor->SetActorTickEnabled(true);
            Actor->SetActorEnableCollision(true);
        }
        else if (!bShouldBeActive && Actor->IsActorTickEnabled())
        { 
            // スリープ
            Actor->SetActorTickEnabled(false);
            // オプション: 物理スレッドの時間を節約するため、コリジョンを単純なクエリのみに降格させる
            Actor->SetActorEnableCollision(false); 
        }
    }
}

「ウェイクアップ」フェーズの複雑さ

上記のコードは基本概念を示していますが、真のエンジニアリングの課題はウェイクアップフェーズにあります。プレイヤーがスリープ状態のセクターに向かって高速のスナイパーライフルを発射した場合、2 秒の評価ループが検知する前にプロジェクタイルが境界を越えてしまいます。

弾丸が到達した「後」にセクターがウェイクアップすると、致命的な Desync(同期ずれ)が発生します。コリジョンが無効化されていたため、弾丸が休眠中の車両を通り抜けてしまうかもしれません。この現象は、サーバーの状態とクライアントの予測のタイミングの不一致がシミュレーションを完全に壊してしまう問題として、The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It で詳しく解説している内容と密接に関連しています。

これを解決するために、Zero-Waste インフラには Predictive Wake-Ups が必要です。プレイヤーの位置を追跡するだけでなく、サーバーはすべてのアクティブなプロジェクタイルと高速車両の前方速度ベクトルを計算しなければなりません。ベクトルがスリープ中のグリッドセルと交差する場合、サーバーはオブジェクトが到達する前に、その特定のセルに対して即座にウェイクアップイベントを強制する必要があります。

Zero-Waste サーバーの大規模なオーケストレーション

ゲームエンジン内で Logic-Side Culling を実装するのは、戦いの半分に過ぎません。残りの半分はインフラのオーケストレーションです。

UE5 Dedicated Server が CPU フットプリントを動的に 60% 削減することに成功した場合、サーバーホスティング環境は、そのリソース使用量の低下を認識し、同じハードウェアノードに新しいゲームインスタンスを詰め込むことができるほどスマートである必要があります。

このオーケストレーションを自前で構築するには、膨大な量の DevOps エンジニアリングが必要です。Kubernetes クラスターのデプロイ、ゲームサーバーのライフサイクル管理のための Agones の設定、CPU 使用率に基づいたカスタムスケーリングメトリクスの作成、プレイヤーを正しいインスタンスにルーティングするためのロードバランサーの管理などが必要です。これには優に 4〜6 ヶ月の専任インフラ作業が必要となり、ゲーム制作そのものに充てるべき時間が削られてしまいます。

horizOn を使用すれば、これらのバックエンドオーケストレーションサービスは事前に設定済みです。このプラットフォームは、動的なインスタンスパッキング、リアルタイムのサーバー負荷に基づく Auto-Scaling、および Dedicated Server ビルドの自動デプロイパイプラインを処理します。専門の Backend-as-a-Service にインフラを任せることで、Kubernetes のマニフェストと半年間戦う代わりに、Multiplayer ゲームをリリースすることに集中できます。

さらに、単一のノードに多くのインスタンスを詰め込むと、ネットワークスレッドに影響を与える Noisy Neighbor 問題のリスクが高まります。これらのボトルネックに対して Netcode を保護することは極めて重要であり、これについては The Uefn Server Performance Exploit Explained Hard Armoring Your Unreal Engine Netcode で詳しく説明しています。

Zero-Waste Multiplayer アーキテクチャのベストプラクティス

100 人規模の Battle Royale でも、永続的なオープンワールドサバイバルゲームでも、Hibernation と Zero-Waste テクニックの実装には厳格な設計規律が必要です。プレイヤー体験を損なうことなくサーバー Opex を低く抑えるための、実戦で証明された 5 つのベストプラクティスを紹介します。

1. ゲーム状態を Tick ループから分離する

サーバーパフォーマンスの最大の敵は、データの継続的なポーリングです。イベントが発生すべきかどうかを確認するために Tick() を使用してはいけません。完全に Event-Driven Architecture に移行してください。キャンプファイアを 5 分後に消す必要がある場合、毎フレーム Tick させて時間を減算するのではなく、300 秒後に一度だけ実行されるタイマーデリゲートを設定します。これにより、キャンプファイアの Actor は 4 分 59 秒間、完全にスリープ状態を維持できます。

2. アグレッシブな NetCullDistanceSquared の実装

Unreal Engine は、NetCullDistanceSquared に基づいて、どの Actor をどのクライアントにレプリケートするかを決定します。多くの開発者はこれをデフォルト値のままにしており、プレイヤーから数百メートル離れた Actor のデータをサーバーにシリアライズおよび圧縮させています。Cull 距離を監査してください。ドロップされた武器を 5,000 ユニット(50 メートル)以上にレプリケートする必要はありません。ゲームプレイのループに必要な絶対最小半径を計算し、厳格に適用してください。

3. O(1) ルックアップのための Spatial Hash Grids の活用

どの Actor をスリープさせるべきかを計算する際、100,000 個のエンティティがある場合、ワールド内のすべての Actor を反復処理(TActorIterator)すること自体がボトルネックになります。Spatial Hash Grid を実装してください。Actor が移動すると、ハッシュマップ内の位置が更新されます。これにより、Hibernation マネージャーは「グリッドセル X には何があるか?」を O(1) の時間計算量で照会でき、CPU での Hibernation 評価を実質的に無料にできます。

4. シームレスなウェイクアップのためのバッファゾーンの利用

プレイヤーの視界の端ギリギリでセクターを休眠させてはいけません。アクティブなエンティティの周囲には、常に少なくとも 1 グリッドセル分の幅を持つアクティブセクターの「バッファゾーン」を維持してください。グリッドセルが 100 メートル幅で、プレイヤーがセル A にいる場合、隣接するすべてのセル(3x3 グリッド)は完全にアクティブなままでなければなりません。これにより、プレイヤーが突然境界を越えてダッシュしても、移動先のセルはすでに完全に初期化され、Tick が動作していることが保証されます。

5. Dedicated Server ビルドの定期的なプロファイリング

何が CPU を消費しているかを推測しないでください。シミュレートされた負荷をかけたパッケージ済みの Dedicated Server 環境で Unreal Insights を使用してください。特に GameThread のタイミングに注目してください。プレイヤーが静止しているときに PhysicsTickTime がスレッドグラフを支配している場合、Hibernation ロジックが機能していません。Zero-Waste アーキテクチャが理論だけでなく現実でも機能していることを検証する唯一の方法は、テレメトリです。

サーバー Opex の未来

Fortnite コミュニティの提案は、重要な真実を照らし出しています。それは、高価な Cloud Compute でサーバーパフォーマンスを力技で解決するという現在の業界標準は持続不可能であるということです。ワールドが広大になり、プレイヤー数が増加するにつれて、インフラコストの線形的なスケーリングは、ライブ運用の予算を徐々に枯渇させていくでしょう。

Sector Physics Hibernation、Logic-Side Culling、および動的なインスタンスパッキングは、もはや AAA スタジオだけの最適化手法ではありません。あらゆる規模の Multiplayer ゲームにとっての生存要件です。開発サイクルの早い段階で Zero-Waste の考え方を採用することで、プレイヤーベースの拡大に合わせてゲームの収益性をスケールさせることができます。

DevOps の煩わしさなしに動的なサーバースケーリングを実装する準備ができているなら、horizOn を無料でお試しいただくか、API ドキュメント をチェックして、Multiplayer インフラがいかにシームレスになり得るかを確認してください。


出典: [Technical Proposal] Unified Operational Sovereignty: Decoupling Opex to Enable a $1.99 V-Buck Economy

このダッシュボードは以下のチームによって愛情を込めて作られています Projectmakers

© 2026 projectmakers.de

unknown-v1.91.1 / unknown-v--