Gamedev Kira Linch
1 778

О прогресс-барах, интерполяции и программистах за спиной

Способы заполнить шкалу.

В закладки

Привет. С вами Маргарита Шаповалова (aka KiraLinch), младший геймдизайнер студии игровой разработки OctoBox Interactive.

Освежила в памяти тему интерполяции и объяснила в заметке так, что сама ещё раз поняла. Немного теории и, конечно же, практическое применение на примере Sky Unlimited Inc.

Надеюсь, что для непрограммистов материал окажется полезным, ну или хотя бы развлечёт.

То, что выглядит как анимация, не всегда является таковой

Задача: улучшить обратную связь, сделав заполнение прогресс-баров более плавным в прототипе Sky Unlimited Inc. Это должно помочь игроку визуально лучше фиксировать изменения параметров.

Вариант 1 — не вариант: анимация

«Ну, конечно! Можно просто сделать анимацию, ведь я умею это делать. Это легко и быстро», – подумала я и сделала. А потом подумала еще раз.

Красиво

А как теперь сделать так, чтобы анимация отражала те изменения, которые испытывает параметр?

Путём простых размышлений я пришла к трём вариантам:

  • никак;
  • с помощью чего-то, чего я пока не знаю;
  • другим способом.

С размышлениями и вариантами я пошла к своему гуру. И гуру молвил: «Вспомни, как двигались стулья».

Вариант 2 — вариант: Lerp

Какими способами я только не двигала стулья в Unreal. Но сила мысли и Lerp, кажется, были ответом на мой вопрос.

Lerp — это сленговое в среде программистов обозначение линейной интерполяции.

У вас есть два разных узла A и B. Представьте, что они соединены прямой линией. С шагом Alpha от точки A находится точка A1, которая показывает, насколько далеко по соединительной линии вы находитесь между узлами A и B.

Затем точка A становится точкой A1, и теперь уже новая A1 определяется с тем же шагом Alpha на меньшем отрезке. Так происходит до тех пор, пока вы асимптотически не приблизитесь к точке B, то есть A всегда стремится к B, но никогда не достигает ее фактически.

Пример из прототипа

Текущее значение параметра «Эффективность» равное 0,3 подаётся на вход A. После действия игрока, по внутренней логике, число меняется и становится равным 0,4. Новое значение параметра подаётся на вход B.

Устанавливаем Alpha, равное 0,1, это 10% от отрезка между A и B.

В итоге Lerp возвращает значения, равные всем промежуточным значениям между A и B c Alpha 0,1 и асимптотически достигает B.

Функция, обновляющая прогресс с помощью Lerp
Блюпринт, использующий функцию

Вот так это выглядит в итоге с Lerp с Alpha=0,1.

Видно же, что к конечному положению прогресс-бар как бы замедляется по сравнению с началом?

Казалось, всё. Задача выполнена: всё работает хорошо, красиво, но...

Вариант 3 — вариант: FInterp To

За моей спиной часто возникают коллеги-программисты, которые показывают мне всякие крутые штуки, после которых я думаю, ну как же я жила без этого. Серьёзно.

В тот момент, когда я уже всё доделала с помощью Lerp, появился программист и говорит: «А чего не FInterp To?»

И показывает мне ещё один вариант решения поставленной задачи с помощью FInterp To Constant.

Функция, обновляющая прогресс с помощью FInterp To

FInterp To Constant – это тоже интерполяция, но при заданной скорости и времени и до константы. То есть в независимости от того, какое расстояние от текущего положения к заданному предстоит преодолеть, это будет сделано с одинаковой скоростью и за одно и то же время, и гарантировано достигнет заданного значения. В отличие от FInterp To и Lerp, которые всегда приблизительно находятся возле конечной точки.

Пример из прототипа

Текущее значение параметра Эффективность равное 0,3 подаётся на вход Current. После действия игрока по внутренней логике число меняется и становится равным 0,4. Новое значение параметра «Эффективность» подаётся на вход Target.

Устанавливаем скорость 0,25, а Delta Time берётся из Event Tick, и равна одному тику.

Блюпринт, использующий функцию

Так это выглядит с FInterp To Constant

Итог

Теперь сравним, чем же визуально отличаются друг от друга два типа интерполяции: Lerp и FInterp To.

С Lerp изменение прогресс-бара замедляется к конечной точке. Что выглядит естественно.

Lerp

А с FInterp To прогресс-бар заполняется равномерно и приходит к конечной точке.

FInterp To Constant

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

Я выбрала Lerp, потому что мне нравится его естественное движение.

Привет. Я – гауссовское распределение и я нормальное. Я создаю впечатление естественности

P. S. Если вы разбираетесь в матане или программировании лучше меня, у вас есть более правильное и чёткое объяснение тех тем, которых я сегодня коснулась — пожалуйста, напишите мне. Буду рада узнать что-то новое и дополнить материал.

Спасибо!

Найти меня можно

Telegram channel https://t.me/setvisibility

Discord chat https://discord.gg/8XxUUsc

Twitter https://twitter.com/setvisibility

Vk https://vk.com/setvisibility.gamedev

#опыт #геймдизайн

Материал дополнен редакцией

Материал опубликован пользователем. Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Kira Linch", "author_type": "self", "tags": ["\u043e\u043f\u044b\u0442","\u0433\u0435\u0439\u043c\u0434\u0438\u0437\u0430\u0439\u043d"], "comments": 20, "likes": 33, "favorites": 27, "is_advertisement": false, "subsite_label": "gamedev", "id": 16827, "is_wide": false }
{ "id": 16827, "author_id": 50024, "diff_limit": 1000, "urls": {"diff":"\/comments\/16827\/get","add":"\/comments\/16827\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/16827"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 64954 }

20 комментариев 20 комм.

Популярные

По порядку

Написать комментарий...
10

Вы каждый тик обновляете значение прогресс бара, даже тогда, когда изменений - нет. Сделайте простой свич для обхода вычислений если изменений не произошло.

Lerp по тику плох тем, что он не компенсирует частоту кадров. У людей в с FPS 120 этот переход будет в 4 раза быстрее чем у людей с FPS 30. Хорошо если вы делаете это для однопользовательской игры, или в части которая не влияет на мультиплеер. (FInterp как раз для этого требудет Delta)

Ответить
4

лучше уж тогда Action и UI подписать на изменение параметра.
Ну и можно ж сделать шаг, ака Time.deltatime в Юнити, что по идее должно скомпенсировать разность фпс, в анриале должно быть так же.
ну и вообще функций кривых много, и под каждые визуальные задачи можно найти свой изинг анимации вот как пример.
http://easings.net/ru

Ответить
0

Функция Set Percent задаёт значение для параметра UMG виджета (Progress Bar), а это автоматом добавляет его в очередь перерисовки.

Ответить
2

Да, это не для мультиплеера.
О том, о чем вы рассказывает, я не думала. Спасибо большое за эту информацию)

Ответить
0

Эти проблемы решается через СОП и линейную интерполяцию на основе дельты времени

Ответить
0

FInterp как раз для этого требует Delta

СОП - Event-driven programming?

Ответить
0

Да, именно это

Ответить
2

Плюшевое гауссово распределение - это супер! :)

Ответить
1

Привет. Спасибо за текст. Чутка отредактировали и перетащили в Gamedev. Если что-то поправить захотите — пишите.

Ответить
0

Спасибо большое)

Ответить
1

Я в свое время тоже столкнулся с подобной задачей. Но я сделал проще хотя и костыльно. Когда происходило изменение параметра, я вызывал макрос и передавал значение к которому надо придти. В макросе циклично прибавлялся прогресс с шагом в 0.005 и delay'ем в 0.01. Таким образом можно контролировать плавность и скорость заполнения, и не приходится запрашивать значение параметра на каждом тике.

Ответить
1

Очень странно, что эта задача вызывает проблемы, в свое время кучу баров написал. Обычно под такие штуки создается кривая безье, которая настраивается аниматорами по их прихоти. Кривая лежит в 2д пространстве. x - время, y - значение. Эта кривая и используется для контроля заполнения бара. Отскоки, ускорения, замедления все что душе угодно. Либо используется твиновый движок, с готовыми анимационными функциями, например dot tween.

Ответить
0

Lerp? А не проще ли было в progress bar создать бинд и привязать к ней переменную Money и тд.

https://docs.unrealengine.com/latest/INT/Engine/UMG/UserGuide/PropertyBinding/index.html

Ответить
2

Тут же речь идёт не об отображении текущего значения, а о твининге между предыдущим и будущим положениями.
Если вы предлагаете Function Bind и в нём делать расчёт то это ещё хуже чем делать его по тику. Т.к. бинд опрашивается 1-2 раза за тик.

Ответить
0

Смысл в том, что надо менять значение плавно, в прогресс баре. Просто бинд тут не поможет, т.к. он будет менять значение по факту изменения, а делать плавные изменения параметров в игровой механике ради визуала ну так себе идея, ИМХО. В целом, представленные советы считаю полезными в купе с советами Alexey Mak.

Ответить
0

Moral(e). Спасибо, извините, глаз режет.

Ответить
1

После прототипирования, всё вычитаю и исправлю. Извините за порезанные глаза :3 это непредумышленно.

Ответить
0

Reigns: Unknown Project делаете?)

Ответить
0

Типа того, хотя есть принципиальное различие в системе ивентов, которое очень сильно меняет ту элегантную основу, которую предлагает Reigns.
Пока мучаюсь с тем, что придумала, клон всегда успею сделать)

Ответить
0

Чтобы закрыть вопрос
http://www.gizma.com/easing/

Ответить
0

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjog" } } }, { "id": 10, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-250597-0", "render_to": "inpage_VI-250597-0-1134314964", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=clmf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudo", "p2": "ftjf" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvc" } } } ]
Уве Болл вернулся в кино
и начал экранизировать flash-игры
Подписаться на push-уведомления