原创文章,转载请注明出处。

点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
点击观看下一篇《UE4 Slate四 SlateUI如何做UI动画》

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动画》

谢谢,创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 <( ̄︶ ̄)>

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐