Перейти до основного вмісту
Жива гра

Цю сторінку ще не перекладено — показано англійську версію.

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

Як оголошувати через атрибути в 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
Unity C# is the same C# API here — ті самі атрибути компілюються в Unity на базовій версії 2021.3 — синтаксису C# 12 тут немає
[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

Scatter і 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

Як оголошувати через атрибути в 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.
Unity C# is the same C# API here — збірка майстер-клієнта виконує той самий запит; звичайний клієнт з’являється в отриманій позиції
var spawn = map.RandomPosition(r =>
{
    r.Layer("ground");
    r.AwayFrom(players, minDistance: 12);
    r.NoRepeat(lastN: 3);
});

Модель

static layer — the published map versionone map instancea query — RandomPosition, a raycastan answer, true for a named momentdynamic obstacles brought by entities shared, immutable geometrythis instance only
Запит читає опублікований статичний шар і динамічні перешкоди одного екземпляра; відповідь правильна для названого моменту

Два шари, і оголошують їх різні люди.

ШарЩо в ньому і хто його оголошує
статичнийрельєф із висотами, примітиви перешкод, межі й локації — авторський контент
динамічнийперешкоди, які під час гри приносять сутності: двері, руйновані об’єкти, платформи. Тому руйнований об’єкт — це сутність з життєвим циклом, станами й власником, і частиною мапи вона стає лише там, де приносить перешкоду. Статичний камінь оголошують у мапі, а двері — це сутність (entity), яка приносить перешкоду. Власного життєвого циклу тут вони не мають: він належить сутності

Що оголошує мапа.

Що оголошуєЩо це
key і versionмапа — це авторський контент: її оголошують у коді, адресують за key і версіонують, а версія входить до того, на що посилається кімната. Змінювати геометрію випущеної версії заборонено: будь-яка правка — це нова версія
рельєфполе висот — регулярна сітка з оголошеним кроком. Крок — це оголошена межа точності, тож запит висоти відповідає з цією точністю, а не точно. Рельєфу може не бути: арена в порожнечі цілком допустима
перешкодизакритий набір примітивів: паралелепіпед, сфера, капсула й опукла оболонка з оголошеним лімітом вершин. Довільний трикутний меш платформа не приймає — лише за цієї умови перевірка на сервері взагалі можлива
тип прохідності для кожної перешкодинепрохідна · прохідна для оголошеного класу · лише перекриває пряму видимість. Той самий примітив слугує і стіною, і кущем, а різницю оголошують, а не моделюють двічі
межіоб’єм, поза яким позиція неприпустима
яруси світуоголошені просторові яруси всередині мапи — земля, підземелля, повітря. Це оголошення геометрії й адресації
локаціїіменовані місця чи області — точка появи, зона захоплення, коридор. Локація відповідає на питання де, але ніколи — що відбувається: ігрової логіки в ній немає
генератор розстановкиза бажанням — правило, що породжує примітиви: кількість, мінімальна відстань, область, сід. Він спрацьовує під час публікації версії, детерміновано за сідом, і після цього мапа зберігає примітиви, а не правило

Ярус світу й екземпляр мапи — ніколи не синоніми.

Що це
ярус світуоголошення всередині мапи — земля, підземелля, повітря
екземпляр мапинезалежна копія опублікованої мапи під час гри. Екземпляри спільно використовують незмінну опубліковану геометрію, але динамічні перешкоди й склад сутностей у кожного свої. Кімната займає екземпляр і може вибрати яруси всередині нього

Що справджується для будь-якого запиту.

ЗавждиЩо це
єдиний геометричний канонуся геометрія задана в оголошеному платформою каноні координат, а точність кожного геометричного поля (field) оголошують на самому полі
модель світуце спрощення: геометрія на сервері — не арт-модель і не зобов’язана нею бути
відповідь називає свій екземпляр і моментна запит відповідають за статичним шаром мапи й динамічними перешкодами того екземпляра, про який спитали, і відповідь указує момент, для якого вона правильна. Динамічні перешкоди змінюються, тож відповідь — це снапшот
значенняналежать до managed, а не до seed: геометрію не підкручують щодня, тож правку з адмін-консолі платформа відхиляє, а не зберігає мовчки
рух і контакттут не обробляються: мапа відповідає, яким є простір. Чи допустима позиція і якою буде реакція, вирішують зіткнення, а застосовує це до руху переміщення

Помилки

  • Мапу, версію чи екземпляр не знайдено — відповідь not found; так само відповідає й відкликана версія. Повторювати запит марно.
  • Публікація зміненої геометрії під наявною версією — конфлікт: створіть нову версію.
  • Помилки публікації виникають під час оголошення й деплою, але ніколи під час гри: мапа понад ліміт примітивів, опукла оболонка понад ліміт вершин, довільний меш як перешкода — усе це відмови під час валідації ще до випуску.
  • Запит висоти за межами — не помилка, а оголошена відповідь «за межами». Її можна відрізнити від відповіді «всередині перешкоди», бо в одному випадку клієнт розвертається, а в іншому — об’їжджає.
  • Перевищено частоту запитів — відповідь із категорії обмеження частоти із зазначенням, коли можна повторити.

Обмеження

Конкретні числа з’являться в розділі про обмеження платформи, а що відбувається на кожній стелі, визначено вже зараз.

  • Примітивів перешкод на мапу, вершин опуклої оболонки, роздільна здатність поля висот, розмір меж, локацій на мапу: усе це платформа відхиляє під час публікації, а не під час запиту.
  • Екземплярів на мапу: створення ще одного відхиляється як конфлікт; наявні екземпляри платформа ніколи не прибирає, щоб звільнити місце.
  • Збережені версії: платформа відкликає найстарішу версію в статусі deprecating, але версію, на якій працює жива кімната, — ніколи.
  • Частота запитів до простору: обмеження частоти із зазначенням, коли можна повторити.