تطوير واجهة المستخدم باستخدام بنية ECS (Entity-Component-System) بدلاً من الوراثة التقليدية
هذه المقالة هي استمرار لخطاب Evgeny Zakharov في المؤتمر الصيفي C ++ Russia ، حيث وصف تطوير واجهة مستخدم باستخدام بنية ECS (Entity-Component-System) بدلاً من الميراث التقليدي وجزء من جهاز UI في World of Tanks Blitz.
في تقريره ، يسهب يوجين بالتفصيل في ماهية مبادئ إنشاء أطر عمل لواجهة المستخدم المستخدمة في العالم اليوم ، ويخبر أيضًا كيف يمكنك تكوين صداقات مع ECS و UI ، وما هي الإيجابيات والسلبيات التي يمكنك الحصول عليها نتيجة لذلك.
في هذه المقالة ، باستخدام مثال صغير لواجهة المستخدم في World of Tanks Blitz ، يوضح Eugene ما هي الإضافة الكبيرة لهندسة ECS في واجهة المستخدم.
قبل دراسة المقال ننصحك بمشاهدة فيديو التقرير.
تنفيذ عرض التقدم الشعاعي
, , .. , 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 – , , ( ) .