[C++, Разработка игр, Дизайн игр] ESC в UI в клиенте World of Tanks Blitz
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Разработка пользовательского интерфейса с использованием архитектуры ECS (Entity-Component-System) вместо традиционного наследованияДанная статья является продолжением выступления Евгения Захарова на летней конференции С++ Russia, где была описана разработка пользовательского интерфейса с использованием архитектуры ECS (Entity-Component-System) вместо традиционного наследования и часть устройства UI в World of Tanks Blitz. В своем докладе Евгений подробно останавливается на том, какие принципы создания фреймворков для UI используются сегодня в мире, а также рассказывает, как можно подружить ECS и UI, и какие плюсы и минусы от этого можно получить в итоге.В этой статье на небольшом примере UI в World of Tanks Blitz Евгений показывает, в чем большой плюс архитектуры ECS в UI.Перед изучение статьи советуем посмотреть видео доклада.Извините, данный ресурс не поддреживается. :( Реализация радиального отображения прогрессаИногда бывают ситуации, когда нужно показать радиальный прогресс, т.е. круг, который заполняется на 360 градусов, когда прогресс какой-либо операции завершился на 100%. В нашей игре World of Tanks Blitz такое используется, например, в отображении прогресса закачки DLC на андроидах.
Чтобы создать такую кнопку у нас уже есть часть готового функционала, а именно:
- отображение картинки в контроле при помощи texture component;
- раскрашивание картинки конкретным цветом тоже при помощи texture component.
Чего не хватает, так это радиального раскрашивания определенного сектора с указанной дугой по принципу часовой стрелки. Для этого мы создали еще одну компоненту, которая хранит процент радиальной заливки как раз для таких случаев: RadialProgressComponent.Теперь пойдем по шагам того, как такое можно реализовать.Во-первых, мы берем картинку для прогресса:
Она белая и имеет прозрачный фон. Нам понадобится два контрола с этой же картинкой: один будет служить фоном, а второй мы зальем желтым цветом (саму картинку) и будем "резать" пропорционально нашему прогрессу. Мы создаем контрол и добавляем на него texture-компоненту с указанием данной картинки и белого цвета заливки.
Получилось так:
Далее мы добавляем еще один контрол сверху, ему добавляем такую же texture-компоненту с тем же путем к картинке, но желтым цветом.
Получилось вот так:
Теперь в ход вступает та самая компонента, на которой мы сейчас заострим внимание: RadialProgressComponent. Для начала добавим ее на второй контрол (который окрашен в желтый цвет) и выставим прогресс 0.5:
Получилось вот так:
А теперь, чтобы не томить, мы покажем, как это устроено в коде (система рисования радиального прогресса – RadialProgressSystem).Чтобы систематизировать кастомный способ отрисовки текстур в контролах, ранее была создана компонента ClipPolygonComponent – она хранит в себе двухмерный полигон, по которому рендер-система отрисовывает обрезанную текстуру. Потому задача RadialProgress-системы сводится к правильной подготовке двухмерного полигона в зависимости от значения прогресса.
void RadialProgressSystem::RegisterControl(Control &control)
{
auto *pieClippable = control.GetComponent<RadialProgressComponent>();
if (pieClippable && control.GetComponent<TextureComponent>())
{
pieClippable->MarkAsDirty();
this->registeredControls.insert(&control);
}
}
Это колбэк, который вызывается, когда в сцене появляется контрол с TextureComponent и RadialProgressComponent. Тут запоминаются все контролы в контейнер внутри системы, чтобы обработать их в главной функции системы, в которой происходит работа – Process.
void RadialProgressSystem::UnregisterControl(Control &control)
{
this->registeredControls.erase(&control);
}
Это колбэк, который вызывается при обратной операции, когда контрол с указанными компонентами исчезает из сцены.Далее функция Progress обрабатывает все контролы, которые не были обработаны. Для хранения статуса "обработан" есть поле dirty в RadialProgressComponent. Обработке будут "подвергаться" только те контролы, которые имеют RadialProgressComponent с флагом dirty равным true, который после обработки будет выставляться в false.
void RadialProgressSystem::Process(float elapsedTime)
{
for (Control *control : this->registeredControls)
{
auto *pieClippable = control->GetComponent<UIRadialProgressComponent>();
if (!pieClippable->IsDirty())
{
continue;
}
auto *polygon = control->GetComponent<ClipPolygonComponent>();
if (!polygon)
{
ReportError(control, "You need UIClipPolygonComponent for UIRadialProgressComponent");
continue;
}
auto *textureComponent = control->GetComponent<TextureComponent>();
if (textureComponent != nullptr && textureComponent->GetSprite() != nullptr)
{
Polygon2 &polygonData = polygon->GetPolygon();
polygonData.Clear();
const Vector2 imageSize = textureComponent->GetSprite()->GetSize();
const Vector2 pivot = CalcPivot(pieClippable);
const Vector2 center = imageSize * pivot;
const float progress = pieClippable->GetProgress();
float startAngle = pieClippable->GetNormalizedStartAngle();
float endAngle = pieClippable->GetNormalizedEndAngle();
const float currentAngle = Interpolation::Linear(startAngle, endAngle, 0, progress, 1);
const float width = pivot.x > 0 ? center.x : imageSize.x;
const float height = pivot.y > 0 ? center.y : imageSize.y;
const float initAngle = std::atan(width / height);
polygonData.AddPoint(center);
polygonData.AddPoint(CalcPointOnRectangle(startAngle, center, imageSize));
int direction = startAngle < endAngle ? 1 : -1;
float startOffset = direction > 0 ? 0 : PI_2 + pieClippable->GetAngleBias();
float squareAngle = startOffset + direction * initAngle;
const float directedStartAngle = direction * startAngle;
const float directedEndAngle = direction * endAngle;
const float directedCurrentAngle = direction * currentAngle;
float directedSqureAngle = direction * squareAngle;
const float doubledInitAngle = initAngle * 2.f;
Vector<Vector2> squares {
Vector2(imageSize.x, 0),
Vector2(imageSize.x, imageSize.y),
Vector2(0.f, imageSize.y),
Vector2(0.f, 0.f)
};
int i = 0;
while (directedSqureAngle < directedEndAngle)
{
if (directedSqureAngle < directedCurrentAngle && directedSqureAngle > directedStartAngle)
{
int squareIndex = direction > 0 ? i % 4 : 3 - i % 4;
polygonData.AddPoint(squares[squareIndex]);
}
i++;
int switcher = i % 2;
squareAngle += direction * (PI * switcher - Sign(switcher - 0.5f) * doubledInitAngle);
directedSqureAngle = direction * squareAngle;
}
polygonData.AddPoint(CalcPointOnRectangle(currentAngle, center, imageSize));
pieClippable->ResetDirty();
}
}
}
Теперь, когда мы указываем прогресс в RadialProgress-компоненте, то получаем именно то, что мы хотели – радиальный прогресс с тем значением, которое мы указали. Визуально это выглядит вот так:
ЗаключениеВ данном примере мы показали, как устроен UI в World of Tanks Blitz – крайне нетипично и при этом очень гибко. И это только маленькая часть всех наших систем. В чем очевидный плюс архитектуры ECS в UI – это четкое разделение логики и данных и, как следствие, возможность создания композиции (любого набора и логики) и компонент в совершенно любом контроле.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка под Arduino, Производство и разработка электроники, Электроника для начинающих] Исследуем энергопотребление Bluetooth LE модуля MLT-BT05
- [Разработка игр, Unreal Engine, Прототипирование] Launching Grenades in Unreal with Gameplay Ability System: Part 1
- [Разработка игр, Тестирование игр] Тестирование игр (перевод)
- [Облачные сервисы, Разработка под Arduino, DIY или Сделай сам] Погода в доме или бюджетный мониторинг CO2, PM2.5, температуры и влажности на ESP32 и Blynk
- [Программирование, C++, C, Программирование микроконтроллеров] Включаем периферию контроллера за 1 такт или магия 500 строк кода
- [Программирование, C++, ООП, Профессиональная литература] О C++ и объектно-ориентированном программировании (перевод)
- [Разработка под Android, Gradle] Как устроен билд APK файла внутри (перевод)
- [Разработка игр, Godot] Механики для реализации платформера на Godot engine. 5 часть
- [C++, C] Использование Obj библиотек в KolibriOS в языках высокого уровня
- [Программирование] Когда программисту 1С становится скучно
Теги для поиска: #_c++, #_razrabotka_igr (Разработка игр), #_dizajn_igr (Дизайн игр), #_ui, #_esc, #_wot_blitz, #_blog_kompanii_wargaming (
Блог компании Wargaming
), #_c++, #_razrabotka_igr (
Разработка игр
), #_dizajn_igr (
Дизайн игр
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 17:32
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Разработка пользовательского интерфейса с использованием архитектуры ECS (Entity-Component-System) вместо традиционного наследованияДанная статья является продолжением выступления Евгения Захарова на летней конференции С++ Russia, где была описана разработка пользовательского интерфейса с использованием архитектуры ECS (Entity-Component-System) вместо традиционного наследования и часть устройства UI в World of Tanks Blitz. В своем докладе Евгений подробно останавливается на том, какие принципы создания фреймворков для UI используются сегодня в мире, а также рассказывает, как можно подружить ECS и UI, и какие плюсы и минусы от этого можно получить в итоге.В этой статье на небольшом примере UI в World of Tanks Blitz Евгений показывает, в чем большой плюс архитектуры ECS в UI.Перед изучение статьи советуем посмотреть видео доклада.Извините, данный ресурс не поддреживается. :( Реализация радиального отображения прогрессаИногда бывают ситуации, когда нужно показать радиальный прогресс, т.е. круг, который заполняется на 360 градусов, когда прогресс какой-либо операции завершился на 100%. В нашей игре World of Tanks Blitz такое используется, например, в отображении прогресса закачки DLC на андроидах. Чтобы создать такую кнопку у нас уже есть часть готового функционала, а именно:
Она белая и имеет прозрачный фон. Нам понадобится два контрола с этой же картинкой: один будет служить фоном, а второй мы зальем желтым цветом (саму картинку) и будем "резать" пропорционально нашему прогрессу. Мы создаем контрол и добавляем на него texture-компоненту с указанием данной картинки и белого цвета заливки. Получилось так: Далее мы добавляем еще один контрол сверху, ему добавляем такую же texture-компоненту с тем же путем к картинке, но желтым цветом. Получилось вот так: Теперь в ход вступает та самая компонента, на которой мы сейчас заострим внимание: RadialProgressComponent. Для начала добавим ее на второй контрол (который окрашен в желтый цвет) и выставим прогресс 0.5: Получилось вот так: А теперь, чтобы не томить, мы покажем, как это устроено в коде (система рисования радиального прогресса – RadialProgressSystem).Чтобы систематизировать кастомный способ отрисовки текстур в контролах, ранее была создана компонента ClipPolygonComponent – она хранит в себе двухмерный полигон, по которому рендер-система отрисовывает обрезанную текстуру. Потому задача RadialProgress-системы сводится к правильной подготовке двухмерного полигона в зависимости от значения прогресса. void RadialProgressSystem::RegisterControl(Control &control)
{ auto *pieClippable = control.GetComponent<RadialProgressComponent>(); if (pieClippable && control.GetComponent<TextureComponent>()) { pieClippable->MarkAsDirty(); this->registeredControls.insert(&control); } } void RadialProgressSystem::UnregisterControl(Control &control)
{ this->registeredControls.erase(&control); } void RadialProgressSystem::Process(float elapsedTime)
{ for (Control *control : this->registeredControls) { auto *pieClippable = control->GetComponent<UIRadialProgressComponent>(); if (!pieClippable->IsDirty()) { continue; } auto *polygon = control->GetComponent<ClipPolygonComponent>(); if (!polygon) { ReportError(control, "You need UIClipPolygonComponent for UIRadialProgressComponent"); continue; } auto *textureComponent = control->GetComponent<TextureComponent>(); if (textureComponent != nullptr && textureComponent->GetSprite() != nullptr) { Polygon2 &polygonData = polygon->GetPolygon(); polygonData.Clear(); const Vector2 imageSize = textureComponent->GetSprite()->GetSize(); const Vector2 pivot = CalcPivot(pieClippable); const Vector2 center = imageSize * pivot; const float progress = pieClippable->GetProgress(); float startAngle = pieClippable->GetNormalizedStartAngle(); float endAngle = pieClippable->GetNormalizedEndAngle(); const float currentAngle = Interpolation::Linear(startAngle, endAngle, 0, progress, 1); const float width = pivot.x > 0 ? center.x : imageSize.x; const float height = pivot.y > 0 ? center.y : imageSize.y; const float initAngle = std::atan(width / height); polygonData.AddPoint(center); polygonData.AddPoint(CalcPointOnRectangle(startAngle, center, imageSize)); int direction = startAngle < endAngle ? 1 : -1; float startOffset = direction > 0 ? 0 : PI_2 + pieClippable->GetAngleBias(); float squareAngle = startOffset + direction * initAngle; const float directedStartAngle = direction * startAngle; const float directedEndAngle = direction * endAngle; const float directedCurrentAngle = direction * currentAngle; float directedSqureAngle = direction * squareAngle; const float doubledInitAngle = initAngle * 2.f; Vector<Vector2> squares { Vector2(imageSize.x, 0), Vector2(imageSize.x, imageSize.y), Vector2(0.f, imageSize.y), Vector2(0.f, 0.f) }; int i = 0; while (directedSqureAngle < directedEndAngle) { if (directedSqureAngle < directedCurrentAngle && directedSqureAngle > directedStartAngle) { int squareIndex = direction > 0 ? i % 4 : 3 - i % 4; polygonData.AddPoint(squares[squareIndex]); } i++; int switcher = i % 2; squareAngle += direction * (PI * switcher - Sign(switcher - 0.5f) * doubledInitAngle); directedSqureAngle = direction * squareAngle; } polygonData.AddPoint(CalcPointOnRectangle(currentAngle, center, imageSize)); pieClippable->ResetDirty(); } } } ЗаключениеВ данном примере мы показали, как устроен UI в World of Tanks Blitz – крайне нетипично и при этом очень гибко. И это только маленькая часть всех наших систем. В чем очевидный плюс архитектуры ECS в UI – это четкое разделение логики и данных и, как следствие, возможность создания композиции (любого набора и логики) и компонент в совершенно любом контроле. =========== Источник: habr.com =========== Похожие новости:
Блог компании Wargaming ), #_c++, #_razrabotka_igr ( Разработка игр ), #_dizajn_igr ( Дизайн игр ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 17:32
Часовой пояс: UTC + 5