BlueRose
文章97
标签28
分类7
扩展UProgressBar以实现多重进度条控件

扩展UProgressBar以实现多重进度条控件

前言

因为本人想制作一个类似血源诅咒Demo的关系,所以需要实现一个类似的进度条。它的不同之处在于血量的进度条会显示实际血量与下次攻击后的最大回复血量。也就是一个进度条显示两个量。

UProgressBar为UMG进度条空间,它本质上对Slate组件SProgressBar的封装。负责绑定数据、UI更新。所以我们应该先了解SProgressBar的绘制过程。

OnPaint

Slate控件的绘制过程在OnPaint函数。查看代码后发现,它就是在画一个个盒子,并进行剪裁:

int32 SCustomProgressBar::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
    // Used to track the layer ID we will return.
    int32 RetLayerId = LayerId;

    //获取各种数据与资源
    bool bEnabled = ShouldBeEnabled( bParentEnabled );
    const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;

    const FSlateBrush* CurrentFillImage = GetFillImage();

    const FLinearColor FillColorAndOpacitySRGB(InWidgetStyle.GetColorAndOpacityTint() * FillColorAndOpacity.Get().GetColor(InWidgetStyle) * CurrentFillImage->GetTint(InWidgetStyle));
    const FLinearColor ColorAndOpacitySRGB = InWidgetStyle.GetColorAndOpacityTint();

    TOptional<float> ProgressFraction = Percent.Get();    
    FVector2D BorderPaddingRef = BorderPadding.Get();

    const FSlateBrush* CurrentBackgroundImage = GetBackgroundImage();

    //绘制底层背景
    FSlateDrawElement::MakeBox(
        OutDrawElements,
        RetLayerId++,
        AllottedGeometry.ToPaintGeometry(),
        CurrentBackgroundImage,
        DrawEffects,
        InWidgetStyle.GetColorAndOpacityTint() * CurrentBackgroundImage->GetTint( InWidgetStyle )
    );    

    if( ProgressFraction.IsSet() )
    {
        ECutomProgressBarFillType::Type ComputedBarFillType = BarFillType;
        if (GSlateFlowDirection == EFlowDirection::RightToLeft)
        {
            switch (ComputedBarFillType)
            {
            case ECutomProgressBarFillType::LeftToRight:
                ComputedBarFillType = ECutomProgressBarFillType::RightToLeft;
                break;
            case ECutomProgressBarFillType::RightToLeft:
                ComputedBarFillType = ECutomProgressBarFillType::LeftToRight;
                break;
            }
        }

        //以下是进度条内部色块的绘制过程
        const float ClampedFraction = FMath::Clamp(ProgressFraction.GetValue(), 0.0f, 1.0f);
        switch (ComputedBarFillType)
        {
        ...
        //略去部分代码
            case ECutomProgressBarFillType::LeftToRight:
            default:
            {
                if (PushTransformedClip(OutDrawElements, AllottedGeometry, BorderPaddingRef, FVector2D(0, 0), FSlateRect(0, 0, ClampedFraction, 1)))
                {
                    // Draw Fill
                    FSlateDrawElement::MakeBox(
                        OutDrawElements,
                        RetLayerId++,
                        AllottedGeometry.ToPaintGeometry(
                            FVector2D::ZeroVector,
                            FVector2D( AllottedGeometry.GetLocalSize().X, AllottedGeometry.GetLocalSize().Y )),
                        CurrentFillImage,
                        DrawEffects,
                        FillColorAndOpacitySRGB
                        );

                    OutDrawElements.PopClip();
                }
                break;
            }
        //略去部分代码
        ...
    }

    return RetLayerId - 1;
}

在UMG中绑定Tarray类型数据并传递给Slate

在开发过程中,Slate部分很顺利。但在UMG的SynchronizeProperties函数中,在对Tarray类型的数据进行绑定并传递给Slate控件这一步遇到了问题。

也就是使用OPTIONAL_BINDING_CONVERT与PROPERTY_BINDING宏绑定数据时遇到了问题。因为一开始没有搞明白TAttribute与TOptional是啥玩意。

TAttribute为Slate包装属性用的模板类,TOptional是类似c++17 std::optional的模板类。

使用这两个宏还需要声明一些函数与委托。下面将一一介绍:

OPTIONAL_BINDING_CONVERT

TAttribute<TOptional<TArray<float> >> PercentBinding=OPTIONAL_BINDING_CONVERT(TArray<float>, PercentArray, TOptional<TArray<float>>, ConvertFloatToOptionalFloatArray);

因为本人使用的变量名为PercentArray,类型为TArray,所以需要声明返回TArray类型的委托。

DECLARE_DYNAMIC_DELEGATE_RetVal(TArray<float>, FGetFloatArray);

并且委托名为PercentArrayDelegate。(也就是变量名+“Delegate”)

UPROPERTY()
FGetFloatArray PercentArrayDelegate;

另外需要实现函数ConvertFloatToOptionalFloatArray

TOptional<TArray<float>> ConvertFloatToOptionalFloatArray(TAttribute<TArray<float>> InFloatArray) const
{
    return InFloatArray.Get();
}

PROPERTY_BINDING

TAttribute<TArray<FSlateColor>> FillColorAndOpacityBinding = PROPERTY_BINDING(TArray<FSlateColor>, FillColorAndOpacityArray);

因为本人使用的变量名为FillColorAndOpacityArray,类型为TArray,但需要声明的委托为返回TArray类型。

DECLARE_DYNAMIC_DELEGATE_RetVal(TArray<FLinearColor>, FGetLinearColorArray);

并且委托名为FillColorAndOpacityArrayDelegate。(也就是变量名+“Delegate”)

UPROPERTY()
FGetLinearColorArray FillColorAndOpacityArrayDelegate;

另外还需要在类内使用PROPERTY_BINDING_IMPLEMENTATION宏

PROPERTY_BINDING_IMPLEMENTATION(TArray<FSlateColor>, FillColorAndOpacityArray);

其他主要操作

向Slate传递数据

void UMultipleProgressBar::SynchronizeProperties()
{
    Super::SynchronizeProperties();

    TAttribute< TOptional<TArray<float> >> PercentBinding=OPTIONAL_BINDING_CONVERT(TArray<float>, PercentArray, TOptional<TArray<float>>, ConvertFloatToOptionalFloatArray);
    TAttribute<TArray<FSlateColor>> FillColorAndOpacityBinding = PROPERTY_BINDING(TArray<FSlateColor>, FillColorAndOpacityArray);

    MyProgressBar->SetStyle(&WidgetStyle);
    MyProgressBar->SetBarFillType(BarFillType);
    MyProgressBar->SetBorderPadding(BorderPadding);

    MyProgressBar->SetPercentArray(PercentBinding);
    MyProgressBar->SetFillColorAndOpacityArray(FillColorAndOpacityBinding);
}

实现数据设置函数

UMultipleProgressBar::SetPercentArray
UMultipleProgressBar::SetFillColorAndOpacityArray

SMultipleProgressBar::SetPercentArray
SMultipleProgressBar::SetFillColorAndOpacityArray

SMultipleProgressBar::Construct

OnPaint中的绘制逻辑

int32 SMultipleProgressBar::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
...
//略去部分代码

//从传递进来的数据中取得Percent与Color数组
TArray<float> PercentArrayList = PercentArray.Get().GetValue();
TArray<FSlateColor> FillColorAndOpacityArrayList = FillColorAndOpacityArray.Get();

while (FillColorAndOpacityArrayList.Num() < PercentArrayList.Num())
{
    FillColorAndOpacityArrayList.Add(FSlateColor());
}

for (int i = 0; i < PercentArrayList.Num(); i++)
{
    //在循环中获取每个Percent与color,最后进行绘制
    const float ClampedFraction = FMath::Clamp(PercentArrayList[i], 0.0f, 1.0f);
    const FLinearColor FillColorAndOpacitySRGB = (InWidgetStyle.GetColorAndOpacityTint() * FillColorAndOpacityArrayList[i].GetColor(InWidgetStyle) * CurrentFillImage->GetTint(InWidgetStyle));

    switch (ComputedBarFillType)
    {
        case EMultipleProgressBarFillType::RightToLeft:
        ...
        case EMultipleProgressBarFillType::FillFromCenter:
        ...
        case EMultipleProgressBarFillType::TopToBottom:
        ...
        case EMultipleProgressBarFillType::BottomToTop:
        ...
        case EMultipleProgressBarFillType::LeftToRight:
        default:
        ...
    }
}
//略去部分代码
...

结语

具体操作的可以参考我的写的插件https://github.com/blueroseslol/BRPlugins

感觉有帮助的请给我的项目Star。