UE4 Slate三 SlateUI代码讲解
原创文章,转载请注明出处。点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》点击观看下一篇《UE4 Slate四 SlateUI如何做动画》虚幻引擎 SlateUI介绍1>前言2>该继承自哪个基类来写呢?SCompoundWidget/SPanel/SLeafWidget2.1>基类:SUserWidget或者是SCompoundWidget2.
原创文章,转载请注明出处。
点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
点击观看下一篇《UE4 Slate四 SlateUI如何做UI动画》
虚幻引擎 SlateUI介绍
- 1>前言
- 2>该继承自哪个基类来写呢?SCompoundWidget/SPanel/SLeafWidget
- 3>该如何新建一个S类组件呢?SNew和SAssignNew
- 4>代码入口,如何在这个插件里面将SMainSlate显示到我们的插件面板内
- 5>Slate编程写法,以继承自SCompoundWidget的类举例
- 5.1>空类中必须要有如下的代码
- 5.2>宏讲解 SLATE_BEGIN_ARGS(){} SLATE_END_ARGS()
- 5.3>真正去构件我们的SlateUI,对照着UMG来写
1>前言
本篇文章主要是介绍Slate的编码,针对上一篇文章末尾的.h和.cpp做描述
点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
先看下上篇文章代码的效果图
2>该继承自哪个基类来写呢?SCompoundWidget/SPanel/SLeafWidget
都知道UMG里面我们会继承自UUserWidget做一个自己的基类来写,那么Slate里面应该继承自谁?
在这个部分我们要了解一个插槽的概念:
Slot概念很重要, 插槽就是说我们在他(控件)下面可以添加多少个子控件,下面有三个我们写Slate会经常继承的类。
2.1>基类:SUserWidget或者是SCompoundWidget
单个Slot的类,单个插槽。SUserWidget 或者 SCompoundWidget有一个插槽,
固定一个 比如 SCheckBox,SButton, SBox
SUserWidget是继承自SCompoundWidget,这两个我们都可以继承,引擎更多的是继承自SCompoundWidget。其实引擎推荐我们自己写的单个插槽类的时候是继承自SUserWidget。
Slate二讲解中我们是继承自SCompoundWidget的,也一样的。
2.2>基类:SPanel
多个Slot的类,多个插槽。SPanel: 多个插槽 比如SVerticalBox, SHorizontalBox,SScrollBox
2.3>基类:SLeafWidget
没有插槽, 比如STextBlock
3>该如何新建一个S类组件呢?SNew和SAssignNew
在UMG里面我们是UButton* pBtn = NewObject, 那么Slate里面我们是如何构造控件?
可以把SNew和SAssignNew就理解成我们平常调用的NewObject,只不过它在slate里面就是要这么写。因为Slate都是S类,非U类。从这点也说明了我们头文件中为什么都是智能指针包着的S类。
这个是引擎内的SUserWidget给我们抛来的Demo代码
TSharedRef<SButton> MyButton = SNew(SButton);
* or
* TSharedPtr<SButton> MyButton;
* SAssignNew( MyButton, SButton );
3.1>SNew和SAssignNew
比如我们要新建一个Button,分别用SNew和SAssignNew写一下。
注意TSharedRef不能在.h的类内声明,
UE4智能指针->智能指针详细介绍链接
TSharedRef<SButton> MyButton = SNew(SButton);
TSharedPtr<SButton> MyButton;
SAssignNew( MyButton, SButton );
直接说比较含蓄,需要写一下才知道
SNew:
1>返回值不同:返回共享引用
SAssignNew:
1>返回值不同:返回共享指针
2>使用方式不同:链式编程中直接获取值,直接赋值,在链式编程中想获取值就用SAssignNew
3.2>链式编程优缺点
优点:
1>效率比UMG要高,因为UMG封装的就是Slate
缺点:
1>不能断点调试,断点无法命中链式内部
2>编写界面制作麻烦且不易维护
4>代码入口,如何在这个插件里面将SMainSlate显示到我们的插件面板内
因为我们是基于UE4 Plugin创建了一个Editor Standlone Window,
在这个插件代码中(test5_EditorStandlonWindow.cpp)中的OnSpawnPluginTab()方法中,
就是创建Slate的部分,所以我们在这个位置写就好了。
TSharedRef<SDockTab> Ftest5_EditorStandlonWindowModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
FText WidgetText = FText::Format(
LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
FText::FromString(TEXT("Ftest5_EditorStandlonWindowModule::OnSpawnPluginTab")),
FText::FromString(TEXT("test5_EditorStandlonWindow.cpp"))
);
//第一种方式
MyMainSlate = SNew(SMainSlate);
return SNew(SDockTab)
[
MyMainSlate->AsShared()
];
//第二种方式
/*SAssignNew(MyMainSlate, SMainSlate);
return SNew(SDockTab)
[
MyMainSlate->AsShared()
];*/
//第三种方式
/*return SNew(SDockTab)
[
SNew(SMainSlate)
];*/
//第四种方式
/*return SNew(SDockTab)
[
SAssignNew(MyMainSlate, SMainSlate)
];*/
//原来的代码注释掉了
//.TabRole(ETabRole::NomadTab)
//[
// // Put your tab content here!
// SNew(SBox)
// .HAlign(HAlign_Center)
// .VAlign(VAlign_Center)
// [
// SNew(STextBlock)
// .Text(WidgetText)
// ]
//];
}
5>Slate编程写法,以继承自SCompoundWidget的类举例
5.1>空类中必须要有如下的代码
//这个类的作用就是去用Slate代码实现一次这个UMG(WidgetBlueprint'/Game/Blogs_Slate/ReferUMGBP.ReferUMGBP')
class SMainSlate : public SCompoundWidget /*public SUserWidget*/
{
public:
//SLATE_BEGIN_ARGS+SLATE_END_ARGS 其实是一个结构体, 内部写的东西都相当于写在了一个结构体里面
SLATE_BEGIN_ARGS(SMainSlate)
{
}
SLATE_END_ARGS()
//外部执行SNew或者SAssignNew时候会调用Construct()
void Construct(const FArguments& InArgs);
5.2>宏讲解 SLATE_BEGIN_ARGS(){} SLATE_END_ARGS()
这个呢,其实就是一个结构体, 内部写的东西都相当于写在了一个结构体里面。
为什么要有这个?
为了方便我们在外部进行SNew/SAssignNew之后能直接传参数过来,写法是如下:
下面SNew完了之后的通过 .点出来的参数其实都是在基类的上面宏内(结构体)定义的
用用就明白了,关于里面定义参数的宏,有很多,可以转到Engine里面去看。
后面会介绍Widget拾取器,可以更便捷的找到我们想看的Engine_Slate的实现位置。
SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ContentPadding(0.f)
.OnClicked(this, &SMainSlate::OnFirstSButton_OnClicked) //事件绑定的技巧, 转到定义, 看那边的代理是怎么定义的, 把参数和返回值拿过来定义一个函数即可
.OnPressed(this, &SMainSlate::OnFirstSButton_OnPressed)
.OnReleased(this, &SMainSlate::OnFirstSButton_OnReleased)
.OnHovered(this, &SMainSlate::OnFirstSButton_OnHovered)
.OnUnhovered(this, &SMainSlate::OnFirstSButton_OnUnhovered)
5.3>真正去构件我们的SlateUI,对照着UMG来写
这里介绍一下在SConstraintCanvas里面新建一个STextBlock,不会针对每一个控件都讲一下(太多啦)。其他的在我上传的代码里面都有。
建议自己做一个UMG,然后用Slate你去实现一下试试。很多就都明白了。
5.3.1>首先我们要在Construct()函数里面作为入口开写
void SMainSlate::Construct(const FArguments& InArgs)
{
5.3.2>在构造里面敲出ChildSlot[],在[]内进行S类的控件创建
ChildSlot
[
SNew(S类………………)
.属性设置
[
SNew(被上面S类包裹的子控件)
.子控件属性设置
[
//如果有想New的继续写
]
]
];
5.3.3> 对应我们的参考UMG,剖析代码
可以看到,我们的UMG最上层有一个Canvas Panel(UCanvasPanel类型),那么我们在代码中最开也去创建一个Canvas Panel(用它的主要目的是为了调整坐标方便)。
5.3.3.1> 如何找到U类用的是哪个S类?也就是说我们要SNew哪一个类
那么我们如何确定UCanvasPanel这个控件到底用的是哪一个S类呢?
1>首先在UMG编辑器中左侧UI列表上选中我们的Canvas Panel(UCanvasPanel类型),
2>然后再详细面板中跳转到我们这个类型的C++代码里面,
3>再Ctrl+End到头文件底部,就发现了一个被智能指针包裹的S类,没错,就是它了。
4>TIPS:通过这个方式我们确定了这个UCanvasPanel所用的S类是SConstraintCanvas,通过这种方式也可以找到其他UMG控件的实现S类。
找到了SConstraintCanvas之后,所以就有了我们的创建SConstraintCanvas代码
5.3.3.2> 在ChildSlot[]中创建一个SConstraintCanvas(UMG里面的UCanvasPanel)
ChildSlot
[
SAssignNew(CanvasPanel_0, SConstraintCanvas)
//对应UMG(WidgetBlueprint'/Game/Blogs_Slate/ReferUMGBP.ReferUMGBP')中的<Mesh合并界面>文本
+ SConstraintCanvas::Slot()
.Anchors(0.f) //对应UMG这个文本控件上的Anchors属性,拷贝过来即可
.Offset(FMargin(20.0, 12.f, 600, 72)) //这个可能会迷惑, 第一个参数在这是PositionX, 第二个参数在这是PositionY, 第三个参数在这是SizeX, 第四个参数在这是SizeY.找不到设置坐标的同学注意看这里
.Alignment(FVector2D(0.f, 0.f)) //同样的, 对应Alignment是个FVector2D
.AutoSize(false) //对应AutoSize
.ZOrder(0)
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_TextBlock1", "Mesh合并界面"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 53))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 0, 0.361307, 1)))
]
CanvasPanel_0是我们在.h中定义的
//最外层的层, 就是我们默认创建UMG带的层
TSharedPtr<class SConstraintCanvas> CanvasPanel_0;
5.3.3.3> 在我们的SConstraintCanvas加一个(SNew一个)文本出来
我们想在CanvasPanel_0加一个STextBlock,写法如上(+ SConstraintCanvas::Slot()),没啥原因,按着写就好了。
但是参数是什么意思呢?
一般我们会使用(下面这句代码) 的方式做为添加子控件的开始
+ SConstraintCanvas::Slot()
然后直接通过.点出来一些属性,调整我们这个控件的属性,
+ SConstraintCanvas::Slot()
.Anchors(0.f) //对应UMG这个文本控件上的Anchors属性,拷贝过来即可
.Offset(FMargin(20.0, 12.f, 600, 72)) //这个可能会迷惑, 第一个参数在这是PositionX, 第二个参数在这是PositionY, 第三个参数在这是SizeX, 第四个参数在这是SizeY.找不到设置坐标的同学注意看这里
.Alignment(FVector2D(0.f, 0.f)) //同样的, 对应Alignment是个FVector2D
.AutoSize(false) //对应AutoSize
.ZOrder(0)
5.3.3.4> 关于创建子控件的参数对应UMG做介绍
5.3.3.4.1> Anchors
比如下面的Anchors就是锚点,对应UMG的下图
5.3.3.4.2> Offset
比如下面的Offset就是FMargin(PositionX,PositionY,SizeX,SizeY) ,对应UMG的下图
5.3.3.4.3> Alignment
.Alignment(FVector2D(0.f, 0.f)),对应UMG的下图
5.3.3.4.4> AutoSize
.AutoSize(false) //对应AutoSize,对应UMG的下图
5.3.3.4.5> ZOrder
.ZOrder(0),对应UMG的下图
5.3.3.5> 创建子控件
然后再再[]中添加我们的控件,比如
像一些文本颜色字体的设置都是如下的写法。其他的S类比如会有其他的属性和事件绑定,也是类似的写法。
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_TextBlock1", "Mesh合并界面"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 53))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 0, 0.361307, 1)))
]
5.3.3.6> S类的事件绑定
5.3.3.6.1> 举例SButton的Event绑定
该部分需要了解UE4代理->代理详细介绍链接
分别对OnClicked/OnPressed/OnReleased/OnHovered/OnUnhovered都做了绑定
.h中
protected:
//SButton的事件绑定
FReply OnFirstSButton_OnClicked();
void OnFirstSButton_OnPressed();
void OnFirstSButton_OnReleased();
void OnFirstSButton_OnHovered();
void OnFirstSButton_OnUnhovered();
cpp中
SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ContentPadding(0.f)
.OnClicked(this, &SMainSlate::OnFirstSButton_OnClicked) //事件绑定的技巧, 转到定义, 看那边的代理是怎么定义的, 把参数和返回值拿过来定义一个函数即可
.OnPressed(this, &SMainSlate::OnFirstSButton_OnPressed)
.OnReleased(this, &SMainSlate::OnFirstSButton_OnReleased)
.OnHovered(this, &SMainSlate::OnFirstSButton_OnHovered)
.OnUnhovered(this, &SMainSlate::OnFirstSButton_OnUnhovered)
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_Button_Text1", "---->"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
]
FReply SMainSlate::OnFirstSButton_OnClicked()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnClicked"));
++nDynamicAddNum;
FString s2 = TEXT("new add") + FString::FromInt(nDynamicAddNum);
MyScrollBoxLeft->AddSlot()
[
SNew(STextBlock)
.Text(FText::FromString(s2))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 0, 1, 1)))
];
MyScrollBoxRight->AddSlot()
[
SNew(STextBlock)
.Text(FText::FromString(s2))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 1, 0, 1)))
];
return FReply::Handled();
}
void SMainSlate::OnFirstSButton_OnPressed()
{
//UE_LOG(LogTemp, Warning, TEXT("OnFirstSButton_OnPressed"));
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnPressed"));
}
void SMainSlate::OnFirstSButton_OnReleased()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnReleased"));
}
void SMainSlate::OnFirstSButton_OnHovered()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnHovered"));
}
void SMainSlate::OnFirstSButton_OnUnhovered()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnUnhovered"));
}
5.3.3.6.2> 举例SCheckBox的Event绑定
我一共创建了三个CheckBox,并通过SAssignNew在链式编程里面将返回值放到了我们TArray数组中,
这个就是要用SAssignNew的情况。
//对应UMG(WidgetBlueprint'/Game/Blogs_Slate/ReferUMGBP.ReferUMGBP')中的<CheckBox_1>
+ SConstraintCanvas::Slot()
.Anchors(0.f)
.Offset(FMargin(1260.0, 172.0, 600.0, 40.0)) //这个可能会迷惑, 第一个参数在这是PositionX, 第二个参数在这是PositionY, 第三个参数在这是SizeX, 第四个参数在这是SizeY.找不到设置坐标的同学注意看这里
.Alignment(FVector2D(0.f, 0.f)) //同样的, 对应Alignment是个FVector2D
.AutoSize(false) //对应AutoSize
.ZOrder(0)
[
SAssignNew(CheckBoxArray[0], SCheckBox)
.IsChecked(false)
.IsEnabled(true)
.OnCheckStateChanged(this, &SMainSlate::MyOnCheckStateChanged<0>)
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_checkbox_Text1", "按材质合并成SingleMesh"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
]
]
我希望在三个checkbox之中同时只有一个被选中,所以有了下面的逻辑。
.h中
//SChechBox的事件绑定
template<int32 T>
void MyOnCheckStateChanged(ECheckBoxState emState)
{
if (CheckBoxArray.IsValidIndex(T))
{
for (int32 i = 0; i < CheckBoxArray.Num(); i++)
{
if (i==T)
{
if (CheckBoxArray[i].IsValid() || CheckBoxArray[i].Get())
{
CheckBoxArray[i]->SetIsChecked(emState);
}
}
else
{
if (CheckBoxArray[i].IsValid() || CheckBoxArray[i].Get())
{
CheckBoxArray[i]->SetIsChecked(ECheckBoxState::Unchecked);
}
}
}
}
}
点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
点击观看下一篇《UE4 Slate四 SlateUI如何做UI动画》
谢谢,创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 <( ̄︶ ̄)>
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐
所有评论(0)