Init
This commit is contained in:
225
03-UnrealEngine/UI/CommonUI.md
Normal file
225
03-UnrealEngine/UI/CommonUI.md
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
title: CommonUI
|
||||
date: 2023-03-31 09:53:26
|
||||
excerpt: 摘要
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
# 前言
|
||||
CommonUI主要解决了**层叠式UI**输入事件处理以及**多平台**多输入设备管理问题。
|
||||
其中比较重要的机制是CommonUI实现的**输入路由**功能,其代码位于:`CommonGameViewportClient.h` 和 `CommonUIActionRouterBase.h` 。
|
||||
|
||||
## 官方教学
|
||||
- 文档
|
||||
- https://docs.unrealengine.com/5.1/en-US/common-ui-quickstart-guide-for-unreal-engine/
|
||||
- https://docs.unrealengine.com/5.1/en-US/overview-of-advanced-multiplatform-user-interfaces-with-common-ui-for-unreal-engine/
|
||||
- 视频
|
||||
- [Introduction to Common UI | Inside Unreal](https://www.youtube.com/live/TTB5y-03SnE?feature=share)
|
||||
- [Lyra Cross-platform UI Development | Tech Talk](https://www.bilibili.com/video/BV1mT4y167Fm/?spm_id_from=333.999.0.0&vd_source=d47c0bb42f9c72fd7d74562185cee290)
|
||||
|
||||
# CommonUI
|
||||
|
||||
- 数据表
|
||||
- CommonInputActionDataBase:各平台输入绑定。
|
||||
- 蓝图类资产
|
||||
- CommonInputBaseControllerData:各平台按键数据(图标指定、鼠标键盘/手柄按键绑定)。在`ProjectSettings - Game - CommonInputSettings - PlatformInput - XX - Default - ControllerData`中指定。
|
||||
- CommonUIInputData:UI Click/Back Action 绑定用数据。之后在`ProjectSettings - Game - CommonInputSettings - InputData - `中指定。
|
||||
- Comfirm/Cancel DataTable需要填写CommonInputActionDataBase数据表。
|
||||
|
||||
|
||||
## CommonUI特有功能
|
||||
1. ⭐⭐**自带过渡效果**:在Stack控件的`Detail-Transition`中可以设置控件激活/注销时过渡效果,同时还可以设置效果曲线类型与曲线数值。默认是**渐隐(Fade Only)**。
|
||||
2. ⭐⭐⭐**CommonActivatableWidgetStack**:会自动激活Stack顶部Widget。
|
||||
3. ⭐BackHandle:后退动作处理,除了勾选BackHandle与IsModel之外,需要还需重写OnHandleBackAction(),
|
||||
|
||||
## CommonUI Setup&Data
|
||||
1. 启用CommonUI插件。
|
||||
2. `Engine - GeneralSettings - DefaultClasses - GameViewportClientClass`将其指定为**CommonGameViewportClient**或其子类。
|
||||
3. 创建一个基于CommonInputActionDataBase的数据表,并且创建按键事件映射表。
|
||||
4. 创建新的按钮类继承自`CommonButtonbase,并在TriggeringInputAction应用数据表
|
||||
5. 创建一个继承自`CommonActivatbleWidget`的界面并设置为自动激活,并将按钮放入其中。
|
||||
1. 响应按键映射的前提是自己或某一Parent为已经激活的`CommonActivatbleWidget`
|
||||
|
||||
### CommonInputActionDataBase
|
||||
默认的数据表可以填写**键盘**、**手柄**、**平板**的输入事件表。但对于其他主机平台(PS、Xbox、Switch)往往需要调换手柄按键位置,这个时候可以通过**GamepadInputOverrides**对指定平台进行按键修改。需要**安装主机平台SDK**后才会显示对应选项。
|
||||
|
||||
### CommonBaseControllerData
|
||||
用于指定对应平台的**图标**资源。之后在`ProjectSettings - Game - CommonInputSettings - PlatformInput - XX - Default - ControllerData`中指定。
|
||||
|
||||
### CommonUIInputData
|
||||
UI Click/Back Action 绑定用数据。之后在`ProjectSettings - Game - CommonInputSettings - InputData - `中指定。
|
||||
|
||||
## CommonStyle
|
||||
与UMG、Slate不同,CommonUI没有通过**SlateWidgetStyle**而是通过继承对应的类来创建。每个CommonUI空间都有一个对应的Style类。之后可以在`ProjectSettings - Plugins - CommonUIEditor`的**TemplateTextStyle**、**TemplateButtonStyle**、**TemplateBorderStyle**指定CommonUI的默认风格。
|
||||
|
||||
## CommonUI控件
|
||||
### UCommonUserWidget
|
||||
CommonUI控件基类主要定义了:
|
||||
1. `TArray<FUIActionBindingHandle> ActionBindings`:来管理控件的ActionBindings。
|
||||
2. `TArray<TWeakObjectPtr<const UWidget>> ScrollRecipients`:滚动条控件。
|
||||
3. `bool bConsumePointerInput`:鼠标滑过控件时是否会产生反应。(为true时返回FReply::Handled())
|
||||
|
||||
其他都是相关虚函数以及工具函数。
|
||||
|
||||
### CommonActivatableWidget
|
||||
大致翻译官方注释的话:
|
||||
CommonActivatableWidget的基础,是能够在其生命周期内被 "**activated** "和 "**deactivated**"而不被其他方式修改或销毁。通常是为了以下几个目的:
|
||||
1. 该控件可以自由打开或者关闭,同时不会被从层次结构(WidgetTree)中移除。
|
||||
2. 能从其他控件“退回”到这个控件(例如使用SwitchWidget或者StackWidget)。
|
||||
3. 这个部件在层次结构中的位置是这样的:它在可激活的部件树中定义了一个有意义的结点,通过这个结点,输入被路由到所有部件。(不太懂这条,应该是不会破坏输入路由的意思)
|
||||
|
||||
同时它还具备以下性质:
|
||||
1. 创建后不会自动激活,需要调用函数手动**activated**。
|
||||
2. 默认情况下不会注册BackAction。
|
||||
3. 如果注册了BackAction,那会在执行BackAction后自动**deactivated**。
|
||||
|
||||
主要实现了"**activated** "和 "**deactivated**"的相关逻辑,主要是可见性以及输入路由。
|
||||
|
||||
### UCommonActivatableWidgetContainerBase
|
||||
添加的控件使用`FUserWidgetPool GeneratedWidgetsPool`控件池进行创建与生命周期管理。`TArray<TObjectPtr<UCommonActivatableWidget>> WidgetList`存储添加的控件指针。
|
||||
|
||||
内部使用SOverlay,来显示SCommonAnimatedSwitcher(用于显示控件)与SSpacer。
|
||||
|
||||
### CommonButtonBase
|
||||
与其他的CommonUI组件不同,需要手动实现内部构造。
|
||||
|
||||
与普通的Button相比,拥有更好的可定制性,比如:
|
||||
- 其Style增加了设置各种事件发出声音的选项。
|
||||
- 拥有一个**Selectable**选项可以用来实现CheckBox或者多选框。
|
||||
|
||||
### 其他控件
|
||||
- CommonActionWidget:继承自UWidget,用来显示指定游戏平台对应键盘/手柄按键图标。需要注意ProjectSettings-Game-CommonInputSettings中的DefaultGamepadName需要与CommonInputBaseControllerData资产的GamepadName相同,否则无法显示对应的按键图标。
|
||||
- CommonActivatableWidgetSwitcher:继承自UCommonAnimatedSwitcher,重写几个虚函数来管理内部CommonActivatableWidget的 "**activated** "和 "**deactivated**"。
|
||||
- CommonAnimatedSwitcher:继承自UWidgetSwitcher,主要实现了过渡动画(具体在Slate控件里)以及OnActiveWidgetIndexChanged、OnTransitioningChanged委托。
|
||||
- CommonActivaableWidgetStack:继承自UCommonActivatableWidgetContainerBase,重写OnWidgetAddedToList(),添加控件时并显示末尾控件。
|
||||
- CommonActivaableWidgetQueue:继承自UCommonActivatableWidgetContainerBase,重写OnWidgetAddedToList(),添加控件时并显示第一个控件。
|
||||
- CommonBorder:继承UBorder,主要适配了CommonStyle。
|
||||
- CommonHardwareVisibilityBorder:继承CommonBorder,重写OnWidgetRebuilt(),根据CommonInputSubsystem(ULocalPlayerSubsystem)的ComputedVisibilityTags,来决定是否显示。
|
||||
- CommonCustomNavigation:继承UBorder,暴露一个FOnCustomNavigationEvent委托,用于定义自定义的Navigation行为。
|
||||
- CommonTextBlock:继承自UTextBlock,主要适配了CommonStyle,并且添加了TextScroll。
|
||||
- CommonDateTimeTextBlock:继承自UCommonTextBlock,主要存储了时间(FDateTime、int32、float)并对文字进行格式化,位于UpdateUnderlyingText(),提供了若干DateTime相关的方法。
|
||||
- CommonNumericTextBlock:继承自UCommonTextBlock,实现了Number、Percentage、Seconds、Distance相关数字文字的格式化方法。
|
||||
- CommonLazyImage:继承自UImage,使用异步加载Image或者Material,并在加载完之后替换图片。控制载入的函数为SetBrushFromLazyMaterial()、SetBrushFromLazyDisplayAsset(),在加载完成前显示默认图片。本质是使用UAssetManager::GetStreamableManager().RequestAsyncLoad()进行加载。其中MyLoadGuard(SLoadGuard)为旋转载入图标控件。
|
||||
- CommonLazyWidget:继承自UWidget,默认Visibility为SelfHitTestInvisible。控制载入的函数为SetLazyContent(),本质是使用UAssetManager::GetStreamableManager().RequestAsyncLoad()进行加载。在RebuildWidget()与OnStreamingComplete()中设置MyLoadGuard与Content的可见性。
|
||||
- CommonListVIew:继承自UListView,主要是重写OnGenerateEntryWidgetInternal()针对UCommonButtonBase进行适配;内部使用SCommonListView控件。
|
||||
- CommonLoadGuard:继承自UContentWidget,维护一个MyLoadGuard(SLoadGuard)。并且暴露样式、委托以及SetLoadingText()、SetIsLoading()、IsLoading()。
|
||||
- CommonVisibilitySwitcher:继承自UOverlay,一个适配了UCommonActivatableWidget的基础控件切换器。控件存在`TArray<TObjectPtr<UPanelSlot>> Slots;`。
|
||||
- CommonBoundActionBar:继承自UDynamicEntryBoxBase,适配CommonUI输入控制的版本。主要在OnWidgetRebuilt()中调用MonitorPlayerActions()绑定CommonUI输入路由的OnBoundActionsUpdated委托。
|
||||
- CommonWidgetCarousel:继承自UPanelWidget,卡片旋转式的WidgetSwitcher。
|
||||
- CommonWidgetCarouselNavBar:继承自UWidget,控制CommonWidgetCarousel控件用的导航栏。
|
||||
- List
|
||||
- CommonTileView:继承自UTileView,替换了内部使用的Slate控件为SCommonTileView。并且重写OnGenerateEntryWidgetInternal()对UCommonButtonBase进行适配(替换成SCommonButtonTableRow)。
|
||||
- CommonTreeView:继承自UTreeView,替换了内部使用的Slate控件为SCommonTreeView。并且重写OnGenerateEntryWidgetInternal()对UCommonButtonBase进行适配(替换成SCommonButtonTableRow)。
|
||||
- Pannel
|
||||
- CommonHierarchicalScrollBox:继承自UScrollBox,替换了内部使用的Slate控件为SCommonHierarchicalScrollBox。
|
||||
- CommonVisualAttachment:继承自USizeBox,使用Slate控件为SVisualAttachmentBox。添加一个Widget作为另一个Widget的零尺寸附件。其设计目的为不改变Widget的尺寸计算。
|
||||
|
||||
# 输入路由相关
|
||||
## UCommonGameViewportClient
|
||||
主要重写InputKey()、InputAxis()、InputTouch(),之后通过HandleRerouteInput() 、HandleRerouteAxis()、HandleRerouteTouch()进行输入路由判断。其路由逻辑主要编写在
|
||||
UCommonUIActionRouterBase,是一个ULocalPlayerSubsystem子类。
|
||||
|
||||
## UCommonUIActionRouterBase
|
||||
主要用于处理注册/卸载ActionBindings、维护ActivateTree以及输入控制。
|
||||
|
||||
## Input Binding & Process
|
||||
### Process
|
||||
以ProcessInput()为例,其关键代码为:
|
||||
```c++
|
||||
PersistentActions->ProcessHoldInput(ActiveMode, Key, InputEvent);
|
||||
ActiveRootNode->ProcessHoldInput(ActiveMode, Key, InputEvent);
|
||||
PersistentActions->ProcessNormalInput(ActiveMode, Key, Event);
|
||||
ActiveRootNode->ProcessNormalInput(ActiveMode, Key, Event);
|
||||
```
|
||||
|
||||
在对各种结果**依次**进行判断处理之后,最终返回结果:ERouteUIInputResult枚举。
|
||||
```c++
|
||||
bool FActionRouterBindingCollection::ProcessNormalInput(ECommonInputMode ActiveInputMode, FKey Key, EInputEvent InputEvent) const
|
||||
{
|
||||
for (FUIActionBindingHandle BindingHandle : ActionBindings)
|
||||
{
|
||||
if (TSharedPtr<FUIActionBinding> Binding = FUIActionBinding::FindBinding(BindingHandle))
|
||||
{
|
||||
if (ActiveInputMode == ECommonInputMode::All || ActiveInputMode == Binding->InputMode)
|
||||
{
|
||||
for (const FUIActionKeyMapping& KeyMapping : Binding->NormalMappings)
|
||||
{
|
||||
// A persistent displayed action skips the normal rules for reachability, since it'll always appear in a bound action bar
|
||||
const bool bIsDisplayedPersistentAction = Binding->bIsPersistent && Binding->bDisplayInActionBar;
|
||||
if (KeyMapping.Key == Key && Binding->InputEvent == InputEvent && (bIsDisplayedPersistentAction || IsWidgetReachableForInput(Binding->BoundWidget.Get())))
|
||||
{
|
||||
// Just in case this was in the middle of a hold process with a different key, reset now
|
||||
Binding->CancelHold();
|
||||
Binding->OnExecuteAction.ExecuteIfBound();
|
||||
if (Binding->bConsumesInput)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FActivatableTreeNode::ProcessNormalInput(ECommonInputMode ActiveInputMode, FKey Key, EInputEvent InputEvent) const
|
||||
{
|
||||
if (IsReceivingInput())
|
||||
{
|
||||
for (const FActivatableTreeNodeRef& ChildNode : Children)
|
||||
{
|
||||
if (ChildNode->ProcessNormalInput(ActiveInputMode, Key, InputEvent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return FActionRouterBindingCollection::ProcessNormalInput(ActiveInputMode, Key, InputEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Binding
|
||||
可以看得出主要是ActionBindings,而其注册逻辑位于UCommonUIActionRouterBase::Tick()=>ProcessRebuiltWidgets=>RegisterWidgetBindings(),之后依次对构建的FActivatableTree中**CommonUserWidget**的绑定数据进行注册(RegisterWidgetBindings、RegisterInputTreeNode)。每个**CommonUserWidget**都使用这个RegisterUIActionBinding()来注册绑定信息。
|
||||
```c++
|
||||
FUIActionBindingHandle UCommonUserWidget::RegisterUIActionBinding(const FBindUIActionArgs& BindActionArgs)
|
||||
{
|
||||
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
|
||||
{
|
||||
FBindUIActionArgs FinalBindActionArgs = BindActionArgs;
|
||||
if (bDisplayInActionBar && !BindActionArgs.bDisplayInActionBar)
|
||||
{
|
||||
FinalBindActionArgs.bDisplayInActionBar = true;
|
||||
}
|
||||
FUIActionBindingHandle BindingHandle = ActionRouter->RegisterUIActionBinding(*this, FinalBindActionArgs);
|
||||
ActionBindings.Add(BindingHandle);
|
||||
return BindingHandle;
|
||||
}
|
||||
|
||||
return FUIActionBindingHandle();
|
||||
}
|
||||
```
|
||||
|
||||
其绑定参数`FBindUIActionArgs`数据为:
|
||||
|
||||
| | |
|
||||
| ---------------------- | ----------------------------------------------------------------- |
|
||||
| ActionTag | 目前没找到用处,可能用于按下时给角色添加按下的Tag |
|
||||
| LegacyActionTableRow | 按键的映射表行 |
|
||||
| InputMode | 输入模式,只有当当前输入模式覆盖了Key的输入模式才触发事件,默认all |
|
||||
| KeyEvent | 什么状态下触发(按下、松开、etc...) |
|
||||
| bIsPersistent | 持久化,为true则无视Activate规则始终响应按键输入 |
|
||||
| bConsumeInput | 是否消耗掉本次输入(相同按键绑定不再能触发),默认为true |
|
||||
| bDisplayInActionBar | 是否在CommonActionBar上实时显示按键提示 |
|
||||
| OverrideDisplayName | 新的显示名 |
|
||||
| OnExecuteAction | 当按键触发时执行的回调函数 |
|
||||
| OnHoldActionProgressed | 当按住时的回调 |
|
||||
|
||||
# Lyra中的用法
|
||||
- Safe-Zone
|
||||
|
||||
创建了若干控件:
|
||||
- MaterialProgressBar:继承自UCommonUserWidget
|
||||
- LyraBoundActionButton:继承自UCommonBoundActionButton
|
40
03-UnrealEngine/UI/STreeView与STableRow.md
Normal file
40
03-UnrealEngine/UI/STreeView与STableRow.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: STreeView与STableRow
|
||||
date: 2022-08-09 13:55:15
|
||||
tags: Slate
|
||||
rating: ⭐️
|
||||
---
|
||||
# STreeView
|
||||
参考网址:[UE4 STreeView的使用和示例(C++实现)](https://blog.csdn.net/wymdhr/article/details/104313596)
|
||||
- RequestTreeRefresh 更新树形结构
|
||||
|
||||
## TreeView鼠标多选功能
|
||||
代码位于:STableRow.h的OnMouseButtonDown()。它作用是按住Ctrl 或者Shift时按下方向键可以多选物体。
|
||||
|
||||
### DragDrop机制
|
||||
STreeView的拖拽事件会先在STableRow相应:
|
||||
- OnDragDetected():检测到拖拽事件。
|
||||
- OnDragEnter():鼠标(拖拽状态)进入。
|
||||
- OnDragLeave():鼠标(拖拽状态)离开。
|
||||
- OnDragOver():鼠标拖拽物体过来。
|
||||
- OnDrop:拖拽结束。
|
||||
|
||||
拖拽操作类都继承自FDragDropOperation,并调用类中的New函数来填充数据。并在对应的Event中获取到数据。
|
||||
```c++
|
||||
TSharedPtr<FDragAIGraphNode> DragNodeOp = DragDropEvent.GetOperationAs<FDragAIGraphNode>();
|
||||
const TArray< TSharedRef<SGraphNode> >& DraggedNodes = DragNodeOp->GetNodes();
|
||||
for (int32 Idx = 0; Idx < DraggedNodes.Num(); Idx++)
|
||||
{
|
||||
UAIGraphNode* DraggedNode = Cast<UAIGraphNode>(DraggedNodes[Idx]->GetNodeObj());
|
||||
}
|
||||
```
|
||||
|
||||
DragDrop不适用于多选的实现,所以可以使用使用给Event绑定Lambda,之后将节点实例传入进行操作。
|
||||
|
||||
### SListView的选择机制
|
||||
STreeView的选择实现代码位于SListView,声明于ITypedTableView.h,几个主要的接口函数为:
|
||||
- Private_IsItemSelected()
|
||||
- Private_IsItemSelectableOrNavigable()
|
||||
- Private_SetItemSelection()
|
||||
- Private_ClearSelection()
|
||||
- Private_SelectRangeFromCurrentTo()
|
199
03-UnrealEngine/UI/Slate学习笔记(一):Slate动态控制与其他技巧.md
Normal file
199
03-UnrealEngine/UI/Slate学习笔记(一):Slate动态控制与其他技巧.md
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
title: Slate学习笔记(一):Slate动态控制与其他技巧
|
||||
date: 2021-02-19 9:22:10
|
||||
tags: Slate
|
||||
rating: ⭐️
|
||||
---
|
||||
## 前言
|
||||
最近在写MessageBox控件,想兼容UMG与c++中调用Slate。略有心得,遂有此文。
|
||||
|
||||
## 动态Slate
|
||||
首先Slate控件树中创建占位用的NullWidget并使用.Expose()绑定Slot指针,使得可以进行后续操作。
|
||||
```c++
|
||||
ChildSlot
|
||||
.VAlign(VAlign_Fill)
|
||||
.HAlign(HAlign_Fill)
|
||||
[
|
||||
SNew(SOverlay)
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
.Expose(UseMenuSlot) // Expose it to a pointer so we can change the widget at runtime
|
||||
[
|
||||
SNullWidget::NullWidget
|
||||
]
|
||||
];
|
||||
```
|
||||
之后就可以通过函数对Slot进行操作了,下面演示如何动态添加或者删除Button:
|
||||
```c++
|
||||
UseMenuSlot->AttachWidget(
|
||||
InText.Get().IsEmpty() ?
|
||||
SNullWidget::NullWidget
|
||||
:
|
||||
SNew(SButton)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### 支持Expose()的容器
|
||||
常用的:
|
||||
SHorizontalBox
|
||||
SVerticalBox
|
||||
SOverlay
|
||||
|
||||
其他还有若干也支持的
|
||||
|
||||
## 设置指定长度的Widget
|
||||
有的时候需要让控件保持指定的大小,使用SBox的WidthOverride与HeightOverride即可。其他的控件都不支持这2个属性。
|
||||
```c++
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBox)
|
||||
.WidthOverride(256)
|
||||
.HeightOverride(100)
|
||||
[
|
||||
SNew(SButton)
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## 指定大小控件的UMG与Slate兼容问题
|
||||
UMG的根节点是一个CanvasPanel(UCanvasPanelSlot)。我认为直接给控件加一个SBox来控制大小的方式不太好。这会使得在UMG中的调整大小变得麻烦。所以我的方法是:使用c++调用Slate控件时,在外面再套一层SConstraintCanvas。
|
||||
|
||||
UCanvasPanelSlot本质上是SConstraintCanvas。UMG编辑器中显示的Size实际上是Offset。所以想要指定大小就需要设置Offset的Right与Bottom。
|
||||
```c++
|
||||
CurrentWidget = SNew(SConstraintCanvas)
|
||||
+ SConstraintCanvas::Slot()
|
||||
.Anchors(0.5)
|
||||
.Alignment(FVector2D(0.5,0.5))
|
||||
.Offset(FMargin(0,0,500,400))
|
||||
[
|
||||
SNew(SCustomWidget)
|
||||
];
|
||||
|
||||
GetGameInstance()->GetGameViewportClient()->AddViewportWidgetContent(CurrentWidget.ToSharedRef());
|
||||
```
|
||||
|
||||
## c++获取UMG控件
|
||||
```c++
|
||||
UPROPERTY(Meta = (BindWidget)) //直接获取蓝图中的按钮1
|
||||
UButton* ButtonOne;
|
||||
```
|
||||
```c++
|
||||
bool UFWAffectWidget::Initialize()
|
||||
{
|
||||
if (!Super::Initialize()) return false;
|
||||
//方法1
|
||||
RootPanel = Cast<UCanvasPanel>(GetRootWidget()); //获取根控件
|
||||
if (RootPanel)
|
||||
{
|
||||
BGImage = Cast<UImage>(RootPanel->GetChildAt(0)); //下标1的BGImage控件
|
||||
}
|
||||
//方法2
|
||||
UButton* ButtonTwo = (UButton*)GetWidgetFromName(TEXT("ButtonTwo"));
|
||||
|
||||
//绑定按钮事件方法1:
|
||||
ButtonOne->OnClicked.__Internal_AddDynamic(this,&UFWAffectWidget::ButtonOneEvent,FName("ButtonOneEvent"));
|
||||
|
||||
//绑定事件方法2:委托FScriptDelegate
|
||||
FScriptDelegate ButTwoDel;
|
||||
ButTwoDel.BindUFunction(this, "ButtonTwoEvent");
|
||||
ButtonTwo->OnReleased.Add(ButTwoDel);
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
参考文章:https://blog.csdn.net/weixin_44200074/article/details/109100521
|
||||
|
||||
## 从UMG控件中获取Slate
|
||||
```c++
|
||||
TSharedRef<SWidget> UWidget::TakeWidget()
|
||||
```
|
||||
|
||||
## 反射
|
||||
### 遍历UProperty
|
||||
```c++
|
||||
for(TFiledIterator<UProperty> It(/*UClass**/);It;++It)
|
||||
{
|
||||
UProperty *newProperty=It;
|
||||
}
|
||||
```
|
||||
### 根据名称寻找UProperty
|
||||
```c++
|
||||
UProperty *foundProperty = FindField<UProperty>(/*UClass**/,TEXT("PropertyName"));
|
||||
```
|
||||
### Get/Set UProperty
|
||||
```c++
|
||||
//根据PropertyPath获取属性值
|
||||
FCachedPropertyPath PropertyPath(FString(TEXT("PropertyName")));
|
||||
T Value;
|
||||
ProperthPathHelpers::GetPropertyValue(Object,PropertyPath,Value);
|
||||
//有获取就有设置
|
||||
ProperthPathHelpers::SetPropertyValue(Object,PropertyPath,Value);
|
||||
//还可以获取成String
|
||||
FString PropertyValueStr;
|
||||
ProperthPathHelpers::GetPropertyValueAsString(Object,PropertyPath,PropertyValueStr);
|
||||
//同样,反向也可以
|
||||
ProperthPathHelpers::SetPropertyValueFromString(Object,PropertyPath,PropertyValueStr);
|
||||
//实际上,我们平时在UE引擎中复制粘贴属性值就是将属性值获取为String以及从String这只属性值的整个过程。
|
||||
```
|
||||
引用文章:https://zhuanlan.zhihu.com/p/121006155
|
||||
更多反射技巧:https://bebylon.dev/ue4guide/engine-programming/uobject-reflection/uobject-reflection/
|
||||
|
||||
## SCompoundWidget、SPanel、SLeafWidget的区别
|
||||
### SCompoundWidget
|
||||
SCompoundWidget对应于UMG中的“WidgetBlueprint(控件蓝图)”,用来作为控件容器。当我们在C++类向导中创建Slate类时,创建的就是SCompoundWidget。
|
||||
可以通过成员ChildSlot结合重载的[]操作符往控件里面添加其他控件
|
||||
```c++
|
||||
void SStandardSlateWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
ChildSlot
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Font(FSlateFontInfo("Veranda", 100))
|
||||
.Text(NSLOCTEXT("HelloSlate", "HelloSlateText", "Hello, Slate!"))
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### SPanel
|
||||
即为SPanel子布局的属性,包括Padding、Size、Horizontal Align、Vertical Align。
|
||||
使用Slot添加子控件可以使用“+ Slot()”,也可以调用AddSlot函数,还是以教程1中的SStandardSlateWidget为例
|
||||
```c++
|
||||
void SStandardSlateWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(VerticalBoxPtr, SVerticalBox)
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.Padding(1.0)
|
||||
.FillHeight(0.3f)
|
||||
.HAlign(HAlign_Fill)
|
||||
.VAlign(VAlign_Top)
|
||||
[
|
||||
SNew(SButton)
|
||||
]
|
||||
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(0.5f)
|
||||
.HAlign(HAlign_Center)
|
||||
[
|
||||
SNew(SButton)
|
||||
]
|
||||
];
|
||||
|
||||
VerticalBoxPtr->AddSlot()
|
||||
.FillHeight(0.2f)
|
||||
[
|
||||
SNew(SButton)
|
||||
];
|
||||
}
|
||||
```
|
||||
### SLeafWidget
|
||||
叶子控件,故名思意,该控件不能添加子控件。常用的为STextBlock与SImage。
|
255
03-UnrealEngine/UI/Slate学习笔记(二):UI混合使用方案.md
Normal file
255
03-UnrealEngine/UI/Slate学习笔记(二):UI混合使用方案.md
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: Slate学习笔记(二):UI混合使用方案
|
||||
date: 2021-03-12 15:40:04
|
||||
tags: Slate
|
||||
rating: ⭐️
|
||||
---
|
||||
## 前言
|
||||
之前在学习OnlineSubsytem,其中有一个将网络错误显示给用户的需求,这就需要实现一个MessageBox方案。所以在开发前我构造了以下几个思路:
|
||||
1. UMG作为MessageBox主体,使用GameInstance来管理UI。
|
||||
2. Slate作为MessageBox主体,使用GameViewportClient来管理UI。
|
||||
3. 混合使用,蓝图上使用UMG,c++使用Slate。
|
||||
|
||||
下面说一下我的测试结果——上述方案的优缺点:
|
||||
### UMG方案
|
||||
这种方案也有两种实现思路:
|
||||
1. 使用编辑器中创建的UMG Asset
|
||||
2. 使用C++创建的UUserWidget
|
||||
|
||||
第一种方案除开需要用反射系统来绑定委托就是完美方案。但因为本人没解决这个问题,所以就用了C++方案。当然使用第一种方案也会导致使用GameInstanceSubsystem会变得比较麻烦(可以通过函数的方式来赋予UMG Class,但对美术来说比较麻烦)<br>
|
||||
**优点**:使用蓝图迭代速度较快。
|
||||
|
||||
### Slate方案
|
||||
使用GameViewportClient作为UI管理。<br>
|
||||
**优点**:可以管理所有的UI,也就是说所有添加到屏幕上或者移除的UI都会调用AddViewportWidgetContent与RemoveViewportWidgetContent。在ShooterGame中通过重写这两个函数,使用2个SWidget数组控制各种情况下UI的显示与隐藏。比如:当LoadingScreen出现时隐藏所有UI,结束时回复。
|
||||
|
||||
那GameViewportClient可以管理UMG么?经过测试,调用RemoveViewportWidgetContent移除UMG对应的Slate(UMG本质就是使用UWidget包裹的Slate),UUserWiget也能正常销毁,理论是可以的。但很遗憾还在存在几个问题:
|
||||
1. 对于UMG这种套了一层UWidget的Slate,你无法分辨UWidget类型。
|
||||
2. 在GameViewportClient里调用CreateWidget()与RemoveFromParent()会出现Access Violation Rendering Location 0x000000 的问题,尝试将函数与成员都写在GameInstance中,之后在GameViewportClient调用时,RemoveFromParent()还是会出现这个问题。
|
||||
|
||||
## 实现
|
||||
对于**混合使用**:本人不太喜欢混合使用方案,本人认为应该把相关的管理代码都写在一个地方。本人最后使用了方案1。使用Slate实现底层。分别创建UWidget(作为UMG编辑器中的控件)与UUserWidget版本(MessageBox主体)。这么做的好处在于:
|
||||
1. 类型统一方便管理
|
||||
2. 默认样式统一
|
||||
3. C++与蓝图使用同一个函数
|
||||
|
||||
### 通过c++设置UUserWidget内容
|
||||
最早UMG刚出来是,可以通过RootWigdet(以前默认是UCanvasPannel)以AddChild方式添加UWiget。不过现在版本获取到RootWidget是空指针。所以只能通过RebuildWidget来设置UI控件了。这里定义了UMessageBoxWidget对象(UWidget),需要在Initialize()中初始化,NativeConstruct()会在RebuildWidget()后面调用。大致代码如下:
|
||||
```c++
|
||||
class RPGGAMEPLAYABILITY_API UMessageBox : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual bool Initialize() override;
|
||||
|
||||
UPROPERTY(Instanced, BlueprintReadWrite,EditAnywhere)
|
||||
UMessageBoxWidget* MessageBox;
|
||||
};
|
||||
|
||||
bool UMessageBox::Initialize()
|
||||
{
|
||||
if (!Super::Initialize())
|
||||
return false;
|
||||
|
||||
MessageBox = NewObject<UMessageBoxWidget>(this, TEXT("MessageBox"));
|
||||
MessageBoxSize = FVector2D(500, 400);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UMessageBox::RebuildWidget()
|
||||
{
|
||||
return SNew(SConstraintCanvas)
|
||||
+ SConstraintCanvas::Slot()
|
||||
.Anchors(0.5)
|
||||
.Alignment(FVector2D(0.5, 0.5))
|
||||
.Offset(FMargin(0, 0, MessageBoxSize.X, MessageBoxSize.Y))
|
||||
[
|
||||
MessageBox->TakeWidget()
|
||||
];
|
||||
}
|
||||
```
|
||||
如果想要在子类中预览结果,可以实现SynchronizeProperties()与NativeTick();
|
||||
|
||||
### 绑定委托
|
||||
Slate上的FOnClicked委托是静态的,UWidget为了能实时绑定委托用的是动态多播委托(BlueprintAssignable只支持动态多播委托),而蓝图中UFUNCTION只支持动态单播委托作为形参。
|
||||
|
||||
所以这里我在UMessageBoxWidget中声明了用于在蓝图UFUNCTION中绑定的动态委托。之后再在与Slate静态委托绑定的Handle函数中调用即可。
|
||||
```c++
|
||||
FReply UMessageBoxWidget::SlateHandleConfirmButtonClicked()
|
||||
{
|
||||
TransmitOnConfirmButtonClickedEvent.ExecuteIfBound();
|
||||
OnConfirmButtonClicked.Broadcast();
|
||||
return FReply::Handled();
|
||||
}
|
||||
```
|
||||
注意2个委托的顺序,下面这段代码里我通过判断动态多播委托是否绑定函数,来决定是否需要绑定RemoveFromParent()来起到移除MessageBox的作用。因为上面的Handle函数在UWidget中非UUserWidget中,无法直接调用RemoveFromParent。无论是再传递委托到UUserWidget或者传递指针,都感觉比较啰嗦,所以我就这么写了。
|
||||
```c++
|
||||
UMessageBox* URPGGameInstanceBase::ShowMessageBox(const FText& MessageText, const FText& ConfirmText, const FText& CancelText,const FTransmitDynamicEvent& OnConfirm, const FTransmitDynamicEvent& OnCancel, const FVector2D MessageBoxSize)
|
||||
{
|
||||
UMessageBox* MessageBox=CreateWidget<UMessageBox>(GetWorld(), UMessageBox::StaticClass());
|
||||
MessageBox->MessageBox->MessageText = MessageText;
|
||||
MessageBox->MessageBox->ConfirmText = ConfirmText;
|
||||
MessageBox->MessageBox->CancelText = CancelText;
|
||||
MessageBox->MessageBoxSize= MessageBoxSize;
|
||||
|
||||
//判断是否有绑定,没绑定则直接绑定RemoveFromParent()
|
||||
if (!MessageBox->MessageBox->OnConfirmButtonClicked.IsBound())
|
||||
{
|
||||
MessageBox->MessageBox->OnConfirmButtonClicked.AddDynamic(MessageBox, &UMessageBox::RemoveFromParent);
|
||||
}
|
||||
if (!MessageBox->MessageBox->OnCancelButtonClicked.IsBound())
|
||||
{
|
||||
MessageBox->MessageBox->OnCancelButtonClicked.AddDynamic(MessageBox, &UMessageBox::RemoveFromParent);
|
||||
}
|
||||
|
||||
MessageBox->MessageBox->TransmitOnConfirmButtonClickedEvent = OnConfirm;
|
||||
MessageBox->MessageBox->TransmitOnCancelButtonClickedEvent = OnCancel;
|
||||
|
||||
MessageBox->AddToViewport((int32)EUIZOrder::EUIZOrder_MessageBox);
|
||||
return MessageBox;
|
||||
}
|
||||
```
|
||||
最后直接调用就可以了。
|
||||
```c++
|
||||
ShowMessageBox(FText::FromString("TestMessage!!!"),FText::FromString(TEXT("确定")),FText::FromString(TEXT("取消")));
|
||||
```
|
||||
|
||||
PS.在开发过程中我还遇到按钮需要点两下才能按到情况,后来发现是因为在Slate中Button的IsFocusable设置为false造成的,改成true就可以了。
|
||||
|
||||
## BindWidget meta
|
||||
在找资料的过程中发现这个网站的UI资料挺不错的:https://benui.ca/unreal,这里我简单说明一下BindWidget meta的用法:<br>
|
||||
使用c++开发UI时其中一个最大的问题就是:如何从C ++控制蓝图创建的Widget?
|
||||
可以使用BindWidget meta标签来解决。
|
||||
```c++
|
||||
#pragma once
|
||||
|
||||
#include "BindExample.generated.h"
|
||||
|
||||
UCLASS(Abstract)
|
||||
class UBindExample : public UUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidget))
|
||||
class UTextBlock* ItemTitle = nullptr;
|
||||
};
|
||||
```
|
||||
```
|
||||
#include "BindExample.h"
|
||||
#include "Components/TextBlock.h"
|
||||
|
||||
void UBindExample::NativeConstruct()
|
||||
{
|
||||
// Call the Blueprint "Event Construct" node
|
||||
Super::NativeConstruct();
|
||||
|
||||
// ItemTitle can be nullptr if we haven't created it in the
|
||||
// Blueprint subclass
|
||||
if (ItemTitle)
|
||||
{
|
||||
ItemTitle->SetText(TEXT("Hello world!"));
|
||||
}
|
||||
}
|
||||
```
|
||||
这样就可以在C++获得对应控件的指针了,除此之外还有其他meta选择:
|
||||
|
||||
- DesignerRebuild
|
||||
- BindWidget
|
||||
- BindWidgetOptional
|
||||
- OptionalWidget
|
||||
- BindWidgetAnim
|
||||
- BindWidgetAnimOptional
|
||||
- IsBindableEvent
|
||||
|
||||
其中Optional代表可选,也就是Widget不一定处于绑定状态,推荐使用。
|
||||
```c++
|
||||
// [PropertyMetadata] This property if changed will rebuild the widget designer preview. Use sparingly, try to update most properties by
|
||||
// setting them in the SynchronizeProperties function.
|
||||
// UPROPERTY(meta=(DesignerRebuild))
|
||||
DesignerRebuild,
|
||||
|
||||
// [PropertyMetadata] This property requires a widget be bound to it in the designer. Allows easy native access to designer defined controls.
|
||||
// UPROPERTY(meta=(BindWidget))
|
||||
BindWidget,
|
||||
|
||||
// [PropertyMetadata] This property optionally allows a widget be bound to it in the designer. Allows easy native access to designer defined controls.
|
||||
// UPROPERTY(meta=(BindWidgetOptional))
|
||||
BindWidgetOptional,
|
||||
|
||||
// [PropertyMetadata] This property optionally allows a widget be bound to it in the designer. Allows easy native access to designer defined controls.
|
||||
// UPROPERTY(meta=(BindWidget, OptionalWidget=true))
|
||||
OptionalWidget,
|
||||
|
||||
// [PropertyMetadata] This property requires a widget animation be bound to it in the designer. Allows easy native access to designer defined animations.
|
||||
// UPROPERTY(meta=(BindWidgetAnim))
|
||||
BindWidgetAnim,
|
||||
|
||||
// [PropertyMetadata] This property optionally allows a animation widget be bound to it in the designer. Allows easy native access to designer defined animation.
|
||||
// UPROPERTY(meta=(BindWidgetAnimOptional))
|
||||
BindWidgetAnimOptional,
|
||||
|
||||
// [PropertyMetadata] Exposes a dynamic delegate property in the details panel for the widget.
|
||||
// UPROPERTY(meta=(IsBindableEvent))
|
||||
IsBindableEvent,
|
||||
```
|
||||
|
||||
该网站其他感觉有用的技巧:
|
||||
- 虚幻4提高UI性能技巧:https://benui.ca/unreal/ui-performance/
|
||||
- UPROPERTY的编辑条件(EditCondition)与可以编辑条件(CanEditChange):https://benui.ca/unreal/uproperty-edit-condition-can-edit-change/
|
||||
- 虚幻4UI本地化技巧:https://benui.ca/unreal/ui-localization/
|
||||
|
||||
## 有关通过反射绑定Delegate的资料
|
||||
在写本文的时候找到了一些资料,打算以后再尝试。<br>
|
||||
Unlua的**FDelegatePropertyDesc**
|
||||
```c++
|
||||
virtual bool SetValueInternal(lua_State *L, void *ValuePtr, int32 IndexInStack, bool bCopyValue) const override
|
||||
{
|
||||
UObject *Object = nullptr;
|
||||
const void *CallbackFunction = nullptr;
|
||||
int32 FuncIdxInTable = GetDelegateInfo(L, IndexInStack, Object, CallbackFunction); // get target UObject and Lua function
|
||||
if (FuncIdxInTable != INDEX_NONE)
|
||||
{
|
||||
FScriptDelegate *ScriptDelegate = DelegateProperty->GetPropertyValuePtr(ValuePtr);
|
||||
FCallbackDesc Callback(Object->GetClass(), CallbackFunction);
|
||||
FName FuncName = FDelegateHelper::GetBindedFunctionName(Callback);
|
||||
if (FuncName == NAME_None)
|
||||
{
|
||||
// no delegate function is created yet
|
||||
lua_rawgeti(L, IndexInStack, FuncIdxInTable);
|
||||
int32 CallbackRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
FDelegateHelper::Bind(ScriptDelegate, DelegateProperty, Object, Callback, CallbackRef);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptDelegate->BindUFunction(Object, FuncName); // a delegate function is created already
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
https://zhuanlan.zhihu.com/p/333751025
|
||||
ZhuRong-HomoStation/UnrealEvent
|
||||
```c++
|
||||
void FEventBinder::BindToObject(UObject* InObject)
|
||||
{
|
||||
for (auto & BindMap : EventBindMap)
|
||||
{
|
||||
FMulticastDelegateProperty* Property = static_cast<FMulticastDelegateProperty*>(
|
||||
InObject->GetClass()->FindPropertyByName(BindMap.Key));
|
||||
|
||||
for (auto& Delegate : BindMap.Value.AllDelegates)
|
||||
{
|
||||
if (Delegate.BindFunction == NAME_None || !Delegate.TargetActor) continue;
|
||||
|
||||
FScriptDelegate BindDelegate;
|
||||
BindDelegate.BindUFunction(Delegate.TargetActor.Get(), Delegate.BindFunction);
|
||||
Property->AddDelegate(BindDelegate, InObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
12
03-UnrealEngine/UI/UMG组件作用笔记.md
Normal file
12
03-UnrealEngine/UI/UMG组件作用笔记.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: UMG组件作用笔记
|
||||
date: 2022-11-14 18:13:13
|
||||
excerpt:
|
||||
tags:
|
||||
rating: ⭐
|
||||
---
|
||||
|
||||
- ScaleBox:可以根据ParentWidget的大小来对Children进行缩放。
|
||||
- SizeBox:一个方便控制Size的控件,有OverrideSize、MinDesiredSize、MaxDesiredSize。
|
||||
- Overlay:可以让多个UMG控件叠在一起(拥有同一个锚点),可以认为是一个组合功能。
|
||||
- RetainerBox:优化用的控件,可以调整UI的刷新频率,根据帧数。
|
209
03-UnrealEngine/UI/扩展UProgressBar以实现多重进度条控件.md
Normal file
209
03-UnrealEngine/UI/扩展UProgressBar以实现多重进度条控件.md
Normal file
@@ -0,0 +1,209 @@
|
||||
---
|
||||
title: 扩展UProgressBar以实现多重进度条控件
|
||||
date: 2020-02-20 16:42:36
|
||||
tags: Slate
|
||||
rating: ⭐️
|
||||
---
|
||||
## 前言
|
||||
因为本人想制作一个类似血源诅咒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<float>,所以需要声明返回TArray<float>类型的委托。
|
||||
```
|
||||
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<FSlateColor>,但需要声明的委托为返回TArray<FLinearColor>类型。
|
||||
```
|
||||
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。
|
Reference in New Issue
Block a user