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

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

Locomotion

Ви оголошуєте, як рухається об’єкт, і ніхто не пише інтегратор. Модель руху перетворює пронумероване введення на авторитетний рух. Вона працює разом із зіткненнями, записує історію для передбачення і враховує бафи, дебафи та рельєф.

Коли це потрібно

  • Сутності (entity) рухаються за введенням гравця — танки, персонажі, техніка, — і рух має бути авторитетним на сервері.
  • Ви воліли б оголосити обмеження швидкості, прискорення й повороту, ніж писати інтегратор.
  • Геймплей штовхає тіла: відкидання через Impulse, Teleport і модифікатори на кшталт багнюки, що діють певний час.
  • Рух має відчуватися миттєвим: та сама оголошена модель рахує кроки і на сервері, і в циклі передбачення.
  • Не потрібен, якщо позиції змінюються лише дискретними кроками. Для цього вистачить синхронізованого поля на сутності.

Хто що робить

АкторЩо робить на цій сторінці
schema-authorоголошує моделі руху, обмеження й біндинги
room-ownerзастосовує імпульси, телепорт і модифікатори з хоста
playerнадсилає пронумероване введення; читає стан руху

Яких кімнат це стосується

Модуль працює там, де кроки симуляції обчислює платформа: у кімнатах (room), оголошених із Host = "Backend". Якщо симуляцією володіє ваш власний ігровий сервер (а PlayServ працює як метасервер), рух, зіткнення й передбачення лишаються на боці рушія. Тоді ця сторінка описує варіант із хостингом на платформі, а не обов’язкову вимогу.

Коротко

Оголошення моделі руху Tank: Locomotion.Tank з обмеженнями швидкості, прискорення й повороту
[Entity("tank")]
public class Tank
{
    [Sync] public Vector3 Position;
    [Motion(Model.Tank, MaxSpeed = 8f, Acceleration = 14f, TurnRateDeg = 120f)]
    public Motion Motion;
}
@Entity('tank')
export class Tank {
  @Sync() position!: Vector3;
  @Motion({ model: 'tank', maxSpeed: 8, acceleration: 14, turnRateDeg: 120 }) motion: Motion;
}
@entity("tank")
class Tank:
    position: Vector3 = sync()
    motion = locomotion.motion(model="tank", max_speed=8.0, acceleration=14.0, turn_rate_deg=120.0)
Незабаром — Go

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

UCLASS(PSEntity = "tank")
class UTank : public UObject
{
    GENERATED_BODY()

    UPROPERTY(PSSync) FVector3f Position;

    UPROPERTY(PSMotion = (Model = "Tank", MaxSpeed = "8.0", Acceleration = "14.0", TurnRateDeg = 120))
    FPSMotion Motion;
};
// 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 тут немає
[Entity("tank")]
public class Tank
{
    [Sync] public Vector3 Position;
    [Motion(Model.Tank, MaxSpeed = 8f, Acceleration = 14f, TurnRateDeg = 120f)]
    public Motion Motion;
}

Введення клієнта — це пронумерований намір. Кроки руху обчислює платформа:

Введення клієнта як пронумерований намір: Motion.Drive надходить із частотою введення, кроки рахує сервер
room.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f);   // cl — sent at input rate
room.my<Tank>().motion.drive({ throttle: 1, steer: -0.4 });   // cl — sent at input rate
room.my(Tank).motion.drive(throttle=1.0, steer=-0.4)   # cl — a bot brain drives the same way
Незабаром — Go

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

Room->Entities->Of<UTank>()->Select().GetMine().Then(
    TPSOnResult<UTank*>::CreateWeakLambda(this, [this](const TPSResult<UTank*>& Result)
    {
        if (!Result.HasValue()) { return; }
        // client — sent at input rate, numbered so the platform can acknowledge
        Result.Value()->Motion->SubmitInput(FPSMoveInput{ .Throttle = 1.f, .Steer = -0.4f }, InputSequence);
    }));
// 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 — працює в Unity без змін зі згенерованими типами
room.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f);   // cl — sent at input rate

Дії на боці сервера:

Серверні дії: імпульс відкидання, модифікатор багнюки на 3 секунди, чистий телепорт
tank.Motion.Impulse(knockback);
tank.Motion.Modify("mud", speedMultiplier: 0.6f, duration: 3.Seconds());
tank.Motion.Teleport(spawn);
tank.motion.impulse(knockback);
tank.motion.modify('mud', { speedMultiplier: 0.6, duration: seconds(3) });
tank.motion.teleport(spawn);
tank.motion.impulse(knockback)
tank.motion.modify("mud", speed_multiplier=0.6, duration=seconds(3))
tank.motion.teleport(spawn)
Незабаром — Go

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

// on a dedicated server / master-client host
Tank->Motion->Impulse(EPSImpulseKind::Impulse, KnockbackVelocity);
Tank->Motion->Modify({ .Modifier = TEXT("mud"), .SpeedMultiplier = 0.6f, .For = FPSDuration::Seconds(3.f) });
Tank->Motion->Teleport(SpawnPosition, SpawnFacing);
// 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 — у збірці майстер-клієнта є ті самі дії хоста; звичайний клієнт бачить їхній результат як передбачений і реконсильований рух
tank.Motion.Impulse(knockback);
tank.Motion.Modify("mud", speedMultiplier: 0.6f, duration: 3.Seconds());
tank.Motion.Teleport(spawn);

Модель

Що оголошує сутність, щоб рухатися.

Що оголошуєЩо це
модель рухуодна з готових моделей — steering, tank, character, vehicle, flying. Кожна складається з кількох реалізацій одного кроку з оголошеними умовами вибору та реалізацією за замовчуванням. Крок — чиста функція (pose, input, dt) → pose
параметриоголошені значення, які клієнт може прочитати; без них передбачення (prediction) систематично розходиться із сервером. Вони мають позначку seed: їх налаштовує геймдизайнер, і деплой не має мовчки губити його правки. Обмеження, на які спирається античит, можна позначити managed — тоді адмін-панель не прийме правку
обмеженнямаксимальна швидкість, прискорення й гальмування, максимальний поворот за одне введення, множник заднього ходу й окрема швидкість обертання частин. Поворот за введення навмисно оголошено окремо від швидкості обертання: перший обмежує миттєвий стрибок, друга — неперервну швидкість, і це різні лінії захисту
поведінка на застарілому введенніstop (зупинитися), continue until a declared deadline (рухатися до оголошеного терміну) або continue indefinitely (рухатися без обмежень). Варіанта «продовжувати як раніше» за замовчуванням немає: інакше гравець (player), у якого зникла мережа, так і їхав би далі
допуск позинаскільки заявлена клієнтом поза може відрізнятися від серверної. Допуск може залежати від стану: на місці, у русі й одразу після респавну — це три різні допуски
частота кроку й межа наздоганянняяк часто виконується крок і скільки кроків можна зробити за раз, коли сервер відстає
види імпульсівкожен зі своєю величиною і своїм способом згасання

Що справджується для кожного кроку.

ЗавждиЩо це
модулю належатьпозиція й орієнтація сутності в часі, і більше нічого. Історію цих позицій зберігає модуль Entity, а не цей, тож на питання «де був гравець 300 мс тому» є рівно одна відповідь, а не два буфери з різними періодами
авторитетністьналежить серверу: у режимі симуляція платформи клієнт надсилає намір, а не результат
зіткненнятут не розв’язуються. Чи допустима позиція і якою буде реакція, модуль питає в модуля Collision і власної таблиці реакцій не має
введенняце намір: «вперед», «праворуч», «поверни башту туди». Його приймають як є, бо про світ воно нічого не стверджує
заявлена позаце твердження, а не факт. Якщо вона виходить за оголошений допуск, її притискають до найближчої допустимої пози, і з’являється видима подія pose_clamped
нумерація введенняобов’язкова: той самий номер ніколи не застосовують двічі, а введення з меншим номером відкидають
обмеженняне відхиляє, а притискає: «десять метрів уперед за цей тік» перетворюється на те, що допустимо, а не на помилку. Тож сервер не може отримати недопустиму позу, а клієнт не тоне у відмовах щокадру
імпульс підкоряється тим самим обмеженнямвіддача, поштовх, вибух і відкидання приходять не з введення, але жоден із них не оминає модуль Collision: віддача не вжене танк у скелю
однакові правила, а не однакові бітипобітово однакового результату на різних платформах ніхто не обіцяє. Обіцяють ті самі правила й відтворюваність у межах однієї авторитетної сторони
крокчиста функція, яка виконується потіково. Той самий код рахує рух на сервері й у клієнтському циклі передбачення, і саме тому реконсиляція точна

Помилки

  • Сутність без моделі руху, частково заповнений пресет та імпульс без оголошеного згасання — усе це помилки валідації під час деплою, а не під час роботи.
  • Очікуване покоління не збіглося — це порушення передумови. Повторювати варто, лише перечитавши стан: введення, надіслане до респавну, не можна застосовувати після нього.
  • Якщо перевищено частоту введення, відповідь приходить у категорії обмеження частоти разом із терміном.
  • Сутністю не можна керувати — це конфлікт, і повторювати запит має сенс лише після того, як зміниться стан.
  • Жоден із трьох випадків не є відмовою, і всі три видно. Застаріле введення відкидають, намір понад обмеження притискають, а позу поза допуском притискають із подією pose_clamped. Якби будь-що з цього відбувалося мовчки, клієнт вважав би, що його дію застосовано, і назавжди розійшовся б із сервером.

Обмеження

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

  • Максимальна швидкість і прискорення: значення притискають, відмови не буває.
  • Максимальний поворот за введення: значення притискають.
  • Частота введення на актора: обмеження частоти з терміном.
  • Кроки наздоганяння: понад межу кроки відкидають з оголошеним наслідком. Час симуляції помітно відстає, зате не наздоганяє стрибком, у якому всі ніби телепортуються водночас.
  • Величина імпульсу: її притискають до оголошеного максимуму.
  • Одночасні імпульси на сутність: новий витісняє найстаріший, і це витіснення видно. Мовчазного необмеженого підсумовування немає.
  • Час життя заявленої пози: позу, старшу за оголошений період, не розглядають.