Перейти к основному содержимому
Модель игры

Эта страница ещё не переведена — показана английская версия.

Entity presets

Пресет собирает аспекты сущности — данные, состояния, RPC, события, хуки — в именованный набор под один игровой сценарий. Пресет можно применить как есть, подстроить его числа или вывести из него свой. Применённый пресет добавляет аспекты вашему типу, но ни подо что этот тип не ставит: пресет — не модуль, и у него нет ничего своего, от чего можно было бы наследоваться. Характеристики, способности, снаряды, таблицы выпадения и объекты мира — это пять пресетов, а не пять подсистем: то же объявление, та же синхронизация, тот же порядок хуков.

Когда это нужно

  • У объекта в игре есть числа, которые упираются в границы, восстанавливаются и на границах запускают переход.
  • Действию нужны стоимость, перезарядка, фазы и эффекты, а вызываться оно должно одной командой клиента.
  • Что-то летит, и попадание нужно честно засчитать стрелку с лагом.
  • Добыча должна выпадать по взвешенным шансам, и если игрок оспорит выпадение, результат должен воспроизводиться в точности.
  • На карте есть обстановка — двери, кнопки, ловушки, разрушаемые объекты, — и её состояния должны сохраниться для игрока, который вошёл посреди раунда.
  • Пресеты не нужны, если сущность (entity) — это просто синхронизируемые данные. Объявите поля, и всё.

Кто что делает

АкторЧто делает на этой странице
schema-authorобъявляет характеристики, способности, снаряды, таблицы выпадения, объекты мира
room-ownerнастраивает числа пресетов, разыгрывает таблицы выпадения, создаёт объекты мира
playerприменяет способности, стреляет, подбирает добычу, взаимодействует с объектами

Коротко

Crate из двух готовых пресетов: объявляется один раз, создаётся одной строкой и настраивается на 250 HP
// derived once, in the schema
[Entity("crate", Persistence = Persistence.Runtime, Presets = new[] { Preset.WorldObjects, Preset.Stats })]
public class Crate { [Stat(Max = 100, AtMin = "broken")] public Stat Hp; }

// then one line per crate, on the room host
var crate = await room.Create<Crate>(at: pos, tune: c => c.Hp.Max = 250);
// derived once, in the schema
@Entity('crate', { persistence: Persistence.Runtime, presets: [Preset.WorldObjects, Preset.Stats] })
export class Crate { @Stat({ max: 100, atMin: 'broken' }) hp: Stat; }

// then one line per crate, on the room host
const crate = await room.create<Crate>({ at: pos, tune: (c) => { c.hp.max = 250; } });
# derived once, in the schema
@entity("crate", persistence=Persistence.RUNTIME, presets=[Preset.WORLD_OBJECTS, Preset.STATS])
class Crate:
    hp = stat(max=100, at_min="broken")

# then one line per crate, on the room host
crate = await room.create(Crate, at=pos, tune={"hp.max": 250})
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

// declarations compile into the same pushed model — playserv push from the UE project or CI
UCLASS(PSEntity = (Name = "crate", Persistence = "Runtime", Presets = "world-objects, stats"))
class UCrate : public UObject
{
    GENERATED_BODY()

    UPROPERTY(PSStat = (Max = 100, AtMin = "broken")) FPSStat Hp;
};

// then one line per crate, on the room host (dedicated server / master-client)
Room->Entities->Of<UCrate>()->Create(FPSIdempotencyKey(CrateId),
    [SpawnPosition](UCrate& Crate) { Crate.Position = SpawnPosition; });   // Position — from the world-objects preset
// 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 — то же объявление на C#; создавать ящики может хост комнаты, а клиент Unity видит, как ящик появляется
// derived once, in the schema
[Entity("crate", Persistence = Persistence.Runtime, Presets = new[] { Preset.WorldObjects, Preset.Stats })]
public class Crate { [Stat(Max = 100, AtMin = "broken")] public Stat Hp; }

// then one line per crate, on the room host
var crate = await room.Create<Crate>(at: pos, tune: c => c.Hp.Max = 250);

Модель

Пресет не вводит новых понятий. Всё, что он добавляет, выражается средствами, которые уже даёт сущность: аспектами, машинами состояний, хуками, режимом хранения.

Что даёт каждый пресет и где он настраивается.

ПресетЧто даёт ему контрактГде настраивается
statsаспект числовых характеристик (stat) с границами, восстановлением и модификаторами, а также хук на достижение порога: 0 HP становится переходом машины состояний, а не if в вашем кодев объявлении поля; числа можно менять в панели прямо на живой игре
abilitiesаспект с набором способностей (ability), машину фаз применения, стоимость и перезарядкув объявлении способности
projectilesтип с режимом хранения runtime, аспект баллистики и событие (event) попаданияв объявлении снаряда: замена одного атрибута меняет модель полёта
dropsаспект таблицы выпадения (drop table) с весами и хук после смертив записях таблицы и их весах
world objectsмашину состояний интерактивного объекта и аспект условия взаимодействияв объявлении пресета или для отдельного экземпляра при создании
inventoryтип с владельцем, который хранит ref на предмет каталога (catalog), аспект стопки с приращением и предел на одного владельца с объявленным поведением при переполнениив объявлении типа

У Inventory есть и отдельная страница.

Что верно для любого пресета.

ВсегдаЧто это
где он находитсяна сущности, в виде аспектов: его данные синхронизируются как любые другие, его состояния — это состояния сущности, его RPC — RPC сущности, а хуки выполняются в порядке хуков сущности
настройкаэто живая конфигурация, а не повторный деплой. Поэтому панель показывает границу характеристики, перезарядку и вес выпадения в одном дереве
свой пресетвыводится композицией, а не наследованием: Crate — это world-objects плюс stats, и результат по-прежнему остаётся аспектами на сущности

Ошибки

  • Объявить характеристику, способность, снаряд, таблицу выпадения или объект мира — значит изменить схему (schema), а это могут fn или adm. Если это попытается сделать игрок (player) или клиентский ключ, он получит forbidden, и ничего не будет объявлено — ни целиком, ни наполовину. Это другой отказ, чем те, что игрок встречает внутри разрешённого ему вызова: перезарядка ещё идёт, не хватает средств, нет item:key.bronze. У каждого из них свой код.

Остальные отказы пресета — это отказы сущности. Два касаются именно пресетов:

  • Частично заполненный пресет не проходит проверку при деплое. Пресет несёт согласованный набор: половину объявления отклонят ещё до выпуска, а не позволят ей странно вести себя в матче.
  • Пресет нельзя пометить свойством, которое противоречит его собственной механике: например, аспект, для которого у клиента нет правил, нельзя объявить предсказуемым. Это тоже ловится при деплое.

Ограничения

Потолки здесь те же, что у сущности: число аспектов и машин состояний на тип, размер хранимого экземпляра, частота изменений одного экземпляра. Сам пресет объявляет только предел на одного владельца — его несёт пресет с владельцем. На границе предел ведёт себя одним из трёх способов, и значения по умолчанию нет: refuse · redirect в объявленную корзину владельца · discard with event. Конкретные числа появятся вместе с главой об ограничениях платформы.