GS2-MegaField

Efficient player position synchronization in large 3D spaces

GS2-MegaField provides the ability to efficiently share and synchronize the position information of a large number of players in a huge 3D space.

In titles such as MMORPGs, open worlds, and metaverse-style services where a large number of players log in to the same world simultaneously, distributing every player’s position to every player would overwhelm both network bandwidth and CPU. GS2-MegaField solves this problem by efficiently retrieving “only the players near you”.

Areas and Layers

The space of GS2-MegaField is composed of two concepts: “Area” and “Layer”.

  • AreaModel: Master data that represents a single large 3D space. For example, you create one per logical section within a world such as “the inside of a town”, “a dungeon”, or “a field”.
  • LayerModel: Per-purpose layers that exist within a single area. For example, you can stack the same location for different purposes such as “for players”, “for NPCs”, or “for items”.

An area model contains multiple layer models and is defined as master data. A player’s position is expressed as “in which area, on which layer, and at which coordinates”.

graph TD
  AreaModel["AreaModel: town"] --> LayerPlayer["LayerModel: player"]
  AreaModel --> LayerNpc["LayerModel: npc"]
  AreaModel --> LayerItem["LayerModel: item"]
  Player1["Player A"] -- Spot --> LayerPlayer
  Player2["Player B"] -- Spot --> LayerPlayer

Spatial Index

GS2-MegaField manages player positions within a layer using a spatial index (a structure based on R-Tree). This allows neighborhood searches such as “get all players within N meters around me” to be processed efficiently regardless of the number of players.

The spatial index is independent per layer, so you can handle different searches such as “proximity between players” and “proximity to NPCs” separately.

Player position information

Each player sends their “current position” to GS2-MegaField as MyPosition. MyPosition includes the following information.

  • position: 3D coordinates (x, y, z)
  • vector: Direction vector (x, y, z)
  • r: Radius of vision / interest

When sending a position, the player can simultaneously specify the “range they are interested in” as a Scope, and receive information about other players within that range as a response. Scope includes the following information.

  • layerName: Name of the layer to be searched
  • r: Search radius
  • limit: Maximum number of results to return

By specifying multiple Scopes, you can also retrieve neighboring players from multiple layers simultaneously.

Position synchronization between game clients

GS2-MegaField provides player position information as “a thinned-out list of nearby players”. This list contains each player’s position, direction vector, and last sync time, and the game client can render with smooth position synchronization by interpolating this information.

However, GS2-MegaField itself is not a always-on protocol that guarantees real-time performance. For cases that require high-frequency action synchronization or strict input-based synchronization, combining with GS2-Realtime is effective.

Transaction Actions

GS2-MegaField does not provide transaction actions.

Master Data Management

Registering master data allows you to configure data and behaviors available to the microservice.

Master data types include:

  • AreaModel: Definition of a logical section (area) within the world
  • LayerModel: Definition of per-purpose layers within an area (defined as a child of AreaModel)

Below is a JSON example of master data that configures AreaModel / LayerModel:

{
  "version": "2019-09-09",
  "areaModels": [
    {
      "name": "town",
      "metadata": "starter town",
      "layerModels": [
        { "name": "player", "metadata": "players" },
        { "name": "npc", "metadata": "npcs" }
      ]
    },
    {
      "name": "dungeon",
      "layerModels": [
        { "name": "player" },
        { "name": "enemy" }
      ]
    }
  ]
}

Master data can be registered via the management console, imported from GitHub, or registered from CI using GS2-Deploy.

Example Implementation

Get list of area models

Retrieve which areas exist in the world.

    var items = await gs2.MegaField.Namespace(
        namespaceName: "namespace-0001"
    ).AreaModelsAsync(
    ).ToListAsync();
    const auto It = Gs2->MegaField->Namespace(
        "namespace-0001" // namespaceName
    )->AreaModels(
    );
    TArray<Gs2::UE5::MegaField::Model::FEzAreaModelPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

Update your position and get nearby players

Register your current position with GS2-MegaField and retrieve the list of other players around you. Typical usage is to repeatedly call this at regular intervals to update the position.

    var spatials = await gs2.MegaField.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Spatial(
        areaModelName: "town",
        layerModelName: "player"
    ).UpdateAsync(
        position: new Gs2.Unity.Gs2MegaField.Model.EzMyPosition
        {
            Position = new Gs2.Unity.Gs2MegaField.Model.EzPosition
            {
                X = 10.0f,
                Y = 0.0f,
                Z = 20.0f,
            },
            Vector = new Gs2.Unity.Gs2MegaField.Model.EzVector
            {
                X = 1.0f,
                Y = 0.0f,
                Z = 0.0f,
            },
            R = 1.0f,
        },
        scopes: new [] {
            new Gs2.Unity.Gs2MegaField.Model.EzScope
            {
                LayerName = "player",
                R = 50.0f,
                Limit = 100,
            },
        }
    );
    const auto Future = Gs2->MegaField->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Spatial(
        "town",   // areaModelName
        "player"  // layerModelName
    )->Update(
        MakeShared<Gs2::UE5::MegaField::Model::FEzMyPosition>(
            MakeShared<Gs2::UE5::MegaField::Model::FEzPosition>(10.0f, 0.0f, 20.0f),
            MakeShared<Gs2::UE5::MegaField::Model::FEzVector>(1.0f, 0.0f, 0.0f),
            1.0f
        ),
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::UE5::MegaField::Model::FEzScope>>>();
            v->Add(MakeShared<Gs2::UE5::MegaField::Model::FEzScope>(TEXT("player"), 50.0f, 100));
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Spatials = Future->GetTask().Result();

Reference the position information of a specific player

You can also directly retrieve the position information of a player whose user ID is known.

    var spatial = await gs2.MegaField.Namespace(
        namespaceName: "namespace-0001"
    ).User(
        userId: "user-0001"
    ).Spatial(
        areaModelName: "town",
        layerModelName: "player"
    ).ModelAsync();
    const auto Future = Gs2->MegaField->Namespace(
        "namespace-0001" // namespaceName
    )->User(
        "user-0001" // userId
    )->Spatial(
        "town",   // areaModelName
        "player"  // layerModelName
    )->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Spatial = Future->GetTask().Result();

More practical information

Combining with GS2-Realtime

GS2-MegaField is suited for handling the “macro-scale arrangement of players in a vast world”. On the other hand, it is not suitable for scenes that require millisecond-level synchronization, such as close-range combat or cooperative play with friends.

In such cases, a hybrid configuration is effective: use the nearby players obtained from GS2-MegaField as a trigger to aggregate them into a GS2-Realtime room, and perform real-time communication only between nearby players.

graph LR
  Player["Player"] -- Macro position sync --> MegaField["GS2-MegaField"]
  MegaField -- Nearby players --> Player
  Player -- Close-range combat, etc. --> Realtime["GS2-Realtime"]
  Realtime -- Same-room players --> Player

Designing update frequency

The call frequency of position updates directly translates into server load and network bandwidth. In general, a design that updates at a frequency lower than the rendering frame rate (for example around 5 to 10 Hz) and performs interpolation on the client side tends to be the most balanced.

Optimizing Scope specification

The radius and result count limit specified in Scope should be adjusted according to the game’s view range and presentation. If all players perform searches with a large radius and many results, the load becomes high; it is effective to query with a smaller radius and fewer results for off-screen determinations, and obtain a wider range only when focused.

Detailed Reference