Цю сторінку ще не перекладено — показано англійську версію.
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, поки не вирішено (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[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 rateroom.my<Tank>().motion.drive({ throttle: 1, steer: -0.4 }); // cl — sent at input rateroom.my(Tank).motion.drive(throttle=1.0, steer=-0.4) # cl — a bot brain drives the same wayЯк оголошувати через атрибути в 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.room.My<Tank>().Motion.Drive(throttle: 1f, steer: -0.4f); // cl — sent at input rateДії на боці сервера:
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, поки не вирішено (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.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. Якби будь-що з цього відбувалося мовчки, клієнт вважав би, що його дію застосовано, і назавжди розійшовс я б із сервером.
Обмеження
Конкретні числа з’являться разом із розділом про обмеження платформи, а поведінку на кожній межі зафіксовано вже зараз.
- Максимальна швидкість і прискорення: значення притискають, відмови не буває.
- Максимальний поворот за введення: значення притискають.
- Частота введення на актора: обмеження частоти з терміном.
- Кроки наздоганяння: понад межу кроки відкидають з оголошеним наслідком. Час симуляції помітно відстає, зате не наздоганяє стрибком, у якому всі ніби телепортуються водночас.
- Величина імпульсу: її притискають до оголошеного максимуму.
- Одночасні імпульси на сутність: новий витісняє найстаріший, і це витіснення видно. Мовчазного необмеженого підсумовування немає.
- Час життя заявленої пози: позу, старшу за оголошений період, не розглядають.