Эта страница ещё не переведена — показана английская версия.
Map
Статичный мир: границы, рельеф, препятствия и вопрос «куда что можно поставить?». Физическая модель намеренно гораздо проще визуальной: примитивы с площадью основания и высотой, слои с правилами и один запрос допустимой позиции, которым пользуются все остальные модули.
Когда это нужно
- Вам нужен статичный мир — границы, рельеф, препятствия, — к которому сервер может обращаться с запросами, а не только отрисовывать его.
- Точки появления, выпадающие предметы и декорации должны попадать в допустимые места: для этого есть один запрос
RandomPositionна правилах, и обойти его нельзя. - Арены должны генерироваться заново на каждый матч: объявленный
Seedвоспроизводит ту же карту (map) в баг-репорте. - Ящики и стены ломаются и появляются снова: разрушаемые объекты с HP и таймерами респауна.
- Ботам (bot) и снарядам нужны raycast и проверка прямой видимости относительно набора препятствий.
- Не нужно, если мир чисто визуальный и серверный код не спрашивает, куда что можно поставить.
Кто что делает
| Актор | Что делает на этой странице |
|---|---|
schema-author | объявляет карты, примитивы препятствий, разрушаемые объекты, слои и их правила |
room-owner | привязывает карту к комнате (room); запрашивает позиции появления; делает raycast |
operator | расставляет и убирает препятствия и слои из панели |
Коротко
arena: сид и границы, рельеф, камни, ящики с респауном, слой с правилами[Map("arena", Seed = 42, Bounds = "160x160")]
public static class Arena
{
[Terrain(HeightNoise = 0.3f)] public static Terrain Height; // 3D height field
[Scatter("rock", Count = 40, MinSpacing = 6)] public static ObstacleSet Rocks;
[Destructible("crate", Count = 12, Hp = 100, RespawnAfter = "30s")] public static ObstacleSet Crates;
[Layer("ground", NotInside = "water")] public static Layer Ground;
}
// or: Maps.Named("arena-caves-v3") — authored in the panel or loaded from an asset@Map('arena', { seed: 42, bounds: '160x160' })
export class Arena {
@Terrain({ heightNoise: 0.3 }) height: Terrain; // 3D height field
@Scatter('rock', { count: 40, minSpacing: 6 }) rocks: ObstacleSet;
@Destructible('crate', { count: 12, hp: 100, respawnAfter: '30s' }) crates: ObstacleSet;
@Layer('ground', { notInside: 'water' }) ground: Layer;
}
// or: Maps.named('arena-caves-v3') — authored in the panel or loaded from an asset@Map("arena", seed=42, bounds="160x160")
class Arena:
height = terrain(height_noise=0.3) # 3D height field
rocks = scatter("rock", count=40, min_spacing=6)
crates = destructible("crate", count=12, hp=100, respawn_after="30s")
ground = layer("ground", not_inside="water")
# or: maps.named("arena-caves-v3") — authored in the panel or loaded from an assetКак объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
USTRUCT(PSMap = (Name = "arena", Seed = 42, Bounds = "160x160"))
struct FArena
{
GENERATED_BODY()
UPROPERTY(PSTerrain = (HeightNoise = "0.3")) FPSTerrain Height; // 3D height field
UPROPERTY(PSScatter = (Obstacle = "rock", Count = 40, MinSpacing = 6)) FPSObstacleSet Rocks;
UPROPERTY(PSDestructible = (Obstacle = "crate", Count = 12, Hp = 100,
RespawnAfter = "30s")) FPSObstacleSet Crates;
UPROPERTY(PSStratum = (Name = "ground", NotInside = "water")) FPSStratum Ground;
};
// or: PS::Maps::Named(TEXT("arena-caves-v3")) — authored in the panel or loaded from an asset
// declarations compile into the same pushed model — playserv push from the UE project or CI[Map("arena", Seed = 42, Bounds = "160x160")]
public static class Arena
{
[Terrain(HeightNoise = 0.3f)] public static Terrain Height; // 3D height field
[Scatter("rock", Count = 40, MinSpacing = 6)] public static ObstacleSet Rocks;
[Destructible("crate", Count = 12, Hp = 100, RespawnAfter = "30s")] public static ObstacleSet Crates;
[Layer("ground", NotInside = "water")] public static Layer Ground;
}
// or: Maps.Named("arena-caves-v3") — authored in the panel or loaded from an assetScatter и Destructible — генераторы расстановки, а не броски кубика во время игры. Генератор срабатывает при публикаци и версии карты: сорок камней превращаются в сорок объявленных примитивов, и в опубликованной версии лежат примитивы, а не правило. Поэтому один и тот же Seed даёт те же сорок камней в матче, в повторе и в баг-репорте. Геометрические ограничения проверяются один раз, на этом готовом наборе, ещё до того, как версия попадёт в окружение (environment).
Запрос, которым пользуются все остальные:
RandomPosition: честная точка появления на земле, подальше от игроков, без повторовvar spawn = map.RandomPosition(r =>
{
r.Layer("ground");
r.AwayFrom(players, minDistance: 12);
r.NoRepeat(lastN: 3);
});const spawn = map.randomPosition((r) => {
r.layer('ground');
r.awayFrom(players, { minDistance: 12 });
r.noRepeat({ lastN: 3 });
});spawn = map.random_position(rules=lambda r: (
r.layer("ground"),
r.away_from(players, min_distance=12),
r.no_repeat(last_n=3),
))Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
// Dedicated-server host: place a spawn through the same rule-based query
Map->Positions->GetRandom({ .Stratum = PSKeys::Strata::Ground,
.AwayFrom = Players,
.MinDistance = 12.f,
.NoRepeatLastN = 3 },
TPSOnResult<FVector>::CreateLambda([](const TPSResult<FVector>& Result)
{
if (!Result.HasValue()) { return; }
PlaceSpawn(Result.Value());
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.var spawn = map.RandomPosition(r =>
{
r.Layer("ground");
r.AwayFrom(players, minDistance: 12);
r.NoRepeat(lastN: 3);
});Модель
Два слоя, и объявляют их разные люди.
| Слой | Что в нём и кто его объявляет |
|---|---|
статический | рельеф с высотами, примитивы препятствий, границы и локации — авторский контент |
динамический | препятствия, которые во время игры приносят сущности: двери, разрушаемые объекты, платформы. Поэтому разрушаемый объект — это сущность с жизненным циклом, состояниями и владельцем, и картой она становится только в той части, где приносит препятствие. Статичный камень объявляется в карте, а дверь — это сущность (entity), которая приносит препятствие. Собственного жизненного цикла здесь у них нет: он принадлежит сущности |
Что объявляет карта.
| Что объявляется | Что это |
|---|---|
key и version | карта — это авторский контент: она объявляется в коде, адресуется по key и версионируется, причём версия входит в то, на что ссылается комната. Менять геометрию выпущенной версии запрещено: любая правка — это новая версия |
рельеф | карта высот — регулярная сетка с объявленным шагом. Шаг — это объявленный предел точности, поэтому запрос высоты отвечает с этой точностью, а не точно. Рельефа может не быть: арена в пустоте вполне допустима |
препятствия | закрытый набор примитивов: параллелепипед, сфера, капсула и выпуклая оболочка с объявленным лимитом вершин. Произвольный треугольный меш не принимается — только при этом условии проверка на сервере вообще возможна |
тип проходимости для каждого препятствия | непроходимое · проходимое для объявленного класса · только перекрывает прямую видимость. Один и тот же примитив служит и стеной, и кустом, а разница объявляется, а не моделируется дважды |
границы | объём, за пределами которого позиция недопустима |
ярусы мира | объявленные пространственные ярусы внутри карты — земля, подземелье, воздух. Это объявления геометрии и адресации |
локации | именованные места или области — точка появления, зона захвата, коридор. Локация отвечает на вопрос где, но никогда — что происходит: игровой логики в ней нет |
генератор расстановки | при желании — правило, которое порождает примитивы: количество, минимальное расстояние, область, сид. Он срабатывает при публикации версии, детерминированно по сиду, и после этого карта хранит примитивы, а не правило |
Ярус мира и экземпляр карты — никогда не синонимы.
| Что это | |
|---|---|
ярус мира | объявление внутри карты — земля, подземелье, воздух |
экземпляр карты | независимая копия опубликованной карты во время игры. Экземпляры делят неизменяемую опубликованную геометрию, но динамические препятствия и списки сущностей у них свои. Комната занимает экземпляр и может выбрать ярусы внутри него |
Что верно для любого запроса.
| Всегда | Что это |
|---|---|
единый геометрический канон | вся геометрия задаётся в объявленном платформой каноне координат, а точность каждого геометрического поля (field) объявляется на самом поле |
модель мира | это упрощение: геометрия на сервере — не арт-модель и не обязана ею быть |
ответ называет свой экземпляр и момент | на запрос отвечают по статическому слою карты и динамическим препятствиям того экземпляра, о котором спросили, и в ответе указан момент, для которого он верен. Динамические препятствия меняются, поэтому ответ — это снапшот |
значения | относятся к managed, а не к seed: геометрию не подкручивают каждый день, поэтому правка из админ-консоли отклоняется, а не сохраняется молча |
движение и контакт | здесь не обрабатываются: карта отвечает, что представляет собой пространство. Допустима ли позиция и какой будет реакция, решают столкновения, а применяет это к движению перемещение |
Ошибки
- Не найдена карта, версия или экземпляр — ответ not found; так же отвечает и отозванная версия. Повторять запрос бессмысленно.
- Публикация изменённой геометрии под существующей версией — конфликт: сделайте новую версию.
- Ошибки публикации возникают при объявлении и при деплое, но никогда во время игры: карта сверх лимита примитивов, выпуклая оболочка сверх лимита вершин, произвольный меш в роли препятствия — всё это отказы при валидации ещё до выпуска.
- Запрос высоты за границами — не ошибка, а объявленный ответ «за границами». Его можно отличить от ответа «внутри препятствия», потому что в одном случае клиент разворачивается, а в другом — объезжает.
- Превышена частота запросов — ответ из категории ограничения частоты с указанием, когда можно повторить.
Ограничения
Конкретные числа появятся в главе об ограничениях платформы, а что происходит при достижении каждого потолка, определено уже сейчас.
- Примитивов препятствий на карту, вершин выпуклой оболочки, разрешение карты высот, размер границ, локаций на карту: всё это отклоняется при публикации, а не во время запроса.
- Экземпляров на карту: создание ещё одного отклоняется как конфликт; существующие экземпляры ради свободного места никогда не освобождаются.
- Хранимые версии: отзывается самая старая версия в статусе deprecating, но версия, на которой работает живая комната, — никогда.
- Частота запросов к пространству: ограничение частоты с указанием, когда можно повторить.