Цю сторінку ще не перекладено — показано англійську версію.
Prediction & Lag Compensation
Передбачення вперед і компенсація назад — над даними, які пам’ятають, коли подія сталася насправді. Клієнт відгукується мит тєво, сервер лишається правим, а влучання оцінюють за часовою лінією стрільця.
Коли це потрібно
- Введення має відчуватися миттєвим навіть із затримкою, а вирішувати й далі має сервер. Передбачення (prediction) на клієнті забігає вперед. Коли стан розходиться із сервером, клієнт проводить реконсиляцію (reconciliation).
- Влучання треба оцінювати за тим, що бачив стрілець:
ResolveAtвідмотує хітбокси до тіку, який був у нього на екрані. - Дуги прицілювання й маркери приземлення мають збігатися з результатом: клієнт і сервер будують той самий прогноз
Trajectory. - Критичні для гри поля (field) ніколи не мають відкочуватися: ви оголошуєте, що передбачає клієнт, а що чекає на сервер.
- Ефект гумки (rubber-banding) треба налаштувати: вікна й допуски для кожної сутності (entity), телеметрія помилок передбачення.
- Не потрібен, якщо затримка не заважає. Покроковим і неквапливим іграм вистачає звичайних дельт Data & Subscriptions.
Хто що робить
| Актор | Що робить на цій сторінці |
|---|---|
schema-author | оголошує, які поля передбачає клієнт, а які змінює лише авторитетна сторона; задає вікно передбачення |
room-owner | перевіряє влучання за історичним станом; відмотує світ назад |
player | передбачає рух і проводить реконсиляцію; підписується на корекції |
Яких кімнат це стосується
Модуль працює там, де кроки симуляції обчислює платформа: у кімнатах (room), оголошених із Host = "Backend". Якщо симуляцією володіє ваш власний ігровий сервер (а PlayServ працює як метасервер), рух, зіткнення й передбачення лишаються на боці рушія. Тоді ця сторінка описує варіант із хостингом на платформі, а не обов’язкову вимогу. Див. Хто виконує тік.
Коротко
За одним словом стоять три різні механізми. Рішення в них ухвалюють різні сторони, і ламаються вони по-різному, тому кожному присвячено окрему статтю:
| Механізм | Що робить | Де працює | Ціна помилки |
|---|---|---|---|
| Передбачення власного руху | застосовує оголошену модель до вашого власного введення, не чекаючи на сервер | клієнт | корекція: введення програється заново, а результат згладжується |
| Як показувати інших гравців | малює чужі сутності в проміжках між станами, що надходять | клієнт | помітний ривок |
| Lag Compensation | відмотує цілі до моменту, який бачив стрілець | сервер | хтось гине несправедливо |
Чотири пресети, і відмова від передбачення — теж один із них.
| Пресет | Передбачає своє | Компенсує | Згладжує чужих |
|---|---|---|---|
| shooter | так | у вікні приблизно півтори секунди | так |
| arcade | так | ні | так |
| observer | ні | ні | так |
| no prediction | ні | ні | ні — стан надходить від авторитетної сторони з оголошеним вікном інтерполяції |
Покроковим іграм, стратегіям і більшості мобільних проєктів передбачення не потрібне взагалі. Пресет no prediction каже клієнтові показувати стан таким, який він є, а не вгадувати.
У модуля немає власної моделі руху, таблиці реакцій, геометрії чи вікна історії. Усе це належить відповідно модулям Locomotion, Collision, Map і Entity. Передбачення застосовує їх раніше або читає назад у часі, але ніколи не оголошує другу копію.
Tank: поля, які передбачає клієнт, Hp лише в авторитетної сторони, вікно на 8 тіків уперед і 64 назад[Entity("tank")]
[Prediction(ForwardTicks = 8, MaxRewindTicks = 64)]
public class Tank
{
[Sync, Predicted] public Vector3 Position; // rolls back and replays
[Sync, Predicted] public Vector3 Velocity;
[Stat(Max = 100), AuthoritativeOnly] public Stat Hp; // never predicted
}@Entity('tank')
@Prediction({ forwardTicks: 8, maxRewindTicks: 64 })
export class Tank {
@Sync() @Predicted() position!: Vector3; // rolls back and replays
@Sync() @Predicted() velocity!: Vector3;
@Stat({ max: 100 }) @AuthoritativeOnly() hp: Stat; // never predicted
}@entity("tank")
@prediction(forward_ticks=8, max_rewind_ticks=64)
class Tank:
position: Vector3 = sync(predicted=True) # rolls back and replays
velocity: Vector3 = sync(predicted=True)
hp = stat(max=100, authoritative_only=True) # never predictedЯк оголошувати через атрибути в Go, поки не вирішено (O-1). Приклади з ’являться, коли оберуть спосіб запису.
UCLASS(PSEntity = "tank", PSPrediction = (ForwardTicks = 8, MaxRewindTicks = 64))
class UTank : public UObject
{
GENERATED_BODY()
UPROPERTY(PSSync = (Predicted = "true")) FVector3f Position; // rolls back and replays
UPROPERTY(PSSync = (Predicted = "true")) FVector3f Velocity;
UPROPERTY(PSStat = (Max = 100, AuthoritativeOnly = "true")) FPSStat Hp; // never predicted
};
// declarations compile into the same pushed model — playserv push from the UE project or CI[Entity("tank")]
[Prediction(ForwardTicks = 8, MaxRewindTicks = 64)]
public class Tank
{
[Sync, Predicted] public Vector3 Position; // rolls back and replays
[Sync, Predicted] public Vector3 Velocity;
[Stat(Max = 100), AuthoritativeOnly] public Stat Hp; // never predicted
}Перевірка з компенсацією лагів (lag compensation) відповідає на питання «де був кожен у момент пострілу»:
ResolveAt(shooterViewTick) відмотує хітбокси до того, що бачив стрілець[After(Projectiles.HitReported)]
public static void Validate(HitReport hit) =>
hit.ResolveAt(hit.ShooterViewTick); // rewinds hitboxes, sub-tick interpolatedexport const validate = after(Projectiles.hitReported, (hit: HitReport) =>
hit.resolveAt(hit.shooterViewTick)); // rewinds hitboxes, sub-tick interpolated@after(projectiles.hit_reported)
def validate(hit: HitReport):
hit.resolve_at(hit.shooter_view_tick) # rewinds hitboxes, sub-tick interpolatedЯк оголошувати через атрибути в Go, поки не вирішено (O-1). Приклади з’являться, коли оберуть спосіб запису.
Хук — це хмарна функція: він виконується на платформі, а не в рушії. Пишіть його на C#, TypeScript або Python, а Unreal підписується на події, які він породжує. Функції, написані в рушії, заплановано: графи Blueprint і C++ (його транслюють у скриптовий формат, який перевіряє платформа). Обидва варіанти аналізують і пушать, як будь-яке оголошення, а виконуються вони все одно на платформі.
Хук — це хмарна функція: він виконується на платформі, а не в рушії. Пишіть його на C#, TypeScript або Python, а Unity підписується на події, які він породжує.
Прогноз траєкторії спільний для сервера й клієнта (дуги прицілювання, маркери приземлення). Прогноз — це операція цього модуля: він екстраполює рух із урахуванням перешкод мапи, тож з тих самих вхідних даних обидві сторони малюють ту саму дугу:
Trajectory: прогноз із урахуванням зіткнень, спільний для сервера й прев’ю прицілуvar arc = room.Prediction.Trajectory(from, velocity, steps: 30); // collision-awareconst arc = room.prediction.trajectory(from, velocity, { steps: 30 }); // collision-awarearc = room.prediction.trajectory(origin, velocity, steps=30) # collision-awareЯк оголошувати через атрибути в Go, поки не вирішено (O-1). Приклади з’являться, коли оберуть спосіб запису.
// collision-aware: the arc the platform itself would walk
Room->Prediction->Trajectories->Get(LaunchPosition, LaunchVelocity, /*Steps*/ 30,
TPSOnResult<FPSTrajectory>::CreateWeakLambda(this, [this](const TPSResult<FPSTrajectory>& Result)
{
if (!Result.HasValue()) { return; }
DrawArc(Result.Value());
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.var arc = room.Prediction.Trajectory(from, velocity, steps: 30); // collision-awareМодель
Передбачуваність оголошують на аспекті. Аспект, який змінює лише авторитетна сторона за правилами, яких клієнт не має, не можна позначити передбачуваним: це помилка валідації під час деплою, а не сюрприз під час гри.
Що оголошують.
| Що оголошує | Що це |
|---|---|
передбачувані аспекти | які з них клієнт може прораховувати раніше за авторитетну сторону |
поріг розбіжності | нижче порога корекцію згладжують, вище — приймають стан авторитетн ої сторони як є. Поріг оголошують і позначають managed: це не повзунок, який геймдизайнер крутить щодня |
режим показу чужих сутностей | інтерполяція між отриманими станами або екстраполяція |
затримка інтерполяції | наскільки відстає показ чужих сутностей. Її оголошують, а не підбирають на око |
вікно екстраполяції | за його межами сутність позначають застарілою, і екстраполяція припиняється |
вікно компенсації | як далеко назад може сягати відкат; вікно позначене managed |
Що відмотують назад, а що навмисно ні.
| Що це | |
|---|---|
відмотують | позиції й орієнтації цілей, а також геометрію динамічних перешкод, якщо тип оголошує її історичною |
не відмотують | стан життя (мертві не оживають, щоб у них можна було влучити), а також власника, рахунок та інвентар |
правило поділу | рішення ухвалюють у минулому, а наслідки застосовують у теперішньому |
| Завжди | Що це |
|---|---|
авторитетний стан називає введення, яке врахував | він містить номер останнього застосованого введення. Завдяки цьому реконсиляція точна, а не приблизна |
розбіжність | видно: клієнт знає, що його передбачення виправили, і не дрейфує непомітно |
момент огляду | це твердження, а не факт: актор повідомляє, який момент бачив. За межами вікна платформа відмовляє, а не екстраполює: мовчазна екстраполяція — подарунок для чітера, якому досить надіслати старіший час |
відкат не обіцяє відтворюваності обчислень із рухомою комою | це те саме обмеження, що й усюди в контракті |
одне кільце історії, два споживачі | реконсиляція і запити з компенсацією лагів читають ту саму миттєву історію сутності. Відкат належить цьому модулю, а не тим, які відмотують: кільце відновлює пози спірного тіку, а потім модулю Collision ставлять звичайне питання про перетин цих поз. Власної історії він не зберігає і про «тік огляду» нічого не знає |
client tick: apply input locally (predict) → buffer it → send, tick-stamped
server tick: step the same movement model → authoritative state → delta out
client recv: authoritative state for tick T → if divergence beyond tolerance:
rewind to T → replay buffered inputs T+1..now → smooth
Відкочуються лише поля, які оголошено передбачуваними. Дрібний дрейф клієнт згладжує, а справжня розбіжність змушує його відмотати стан і програти введення заново.
Помилки
- Момент огляду за межами вікна — це конфлікт: надішліть актуальний.
- Запит історії за межами вікна — теж конфлікт.
- Два дефекти оголошення деплой ловить одразу: вікно компенсації, більше за буфер історії, і аспект, позначений передбачуваним, хоча клієнт не має правил, щоб його передбачати. Жоден із них не потрапить у живий матч.
- Два випадки не є помилками, і обидва видно. Коли буфер введення заповнений, передбачення призупиняється до підтвердження, а не відкидає введення мовчки. А розбіжність понад поріг означає, що клієнт приймає стан авторитетної сторони як є: це оголошена корекція, а не збій.
Обмеження
Конкретні числа з’являться разом із розділом про обмеження платформи, а поведінку на кожній межі зафіксовано вже зараз.
- Вікно компенсації: за його межами — відмова, ніколи не екстраполяція.
- Вікно екстраполяції для чужих сутностей: сутність позначають застарілою, екстраполяція припиняється.
- Буфер непідтвердженого введення: передбачення призупиняється до підтвердження; введення ніколи не відкидають мовчки.
- Глибина історії для відкату: не менша за вікно компенсації, це перевіряє деплой.
- Частота дій із моментом огляду: обмеження частоти з терміном.
- Сутності, які клієнт передбачає одночасно: понад межу передбачення не виконується, і це оголошена деградація, а не відмова.