GS2-MegaField
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 worldLayerModel: 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.