UE4 从无到有纯 C++ & Slate 开发沙盒游戏(十五) UI动画实现

时间:2021-09-10
本文章向大家介绍UE4 从无到有纯 C++ & Slate 开发沙盒游戏(十五) UI动画实现,主要包括UE4 从无到有纯 C++ & Slate 开发沙盒游戏(十五) UI动画实现使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

目前已经实现了主菜单的跳转,这个部分将要给按钮点击添加UI动画,具体效果如下:

 菜单结构图如下,

 首先在SlAiType文件添加动画状态枚举

D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Public\Data\SlAiTypes.h

 1 #include "CoreMinimal.h"
 2 
 3 /**
 4 * UENUM()是UE4反射的枚举,UE4蓝图中将可以调用
 5 */
 6 UENUM()
 7 enum class ECultureTeam : uint8
 8 {
 9     EN = 0,
10     ZH
11 };
12 
13 
14 // 菜单按钮的类型
15 namespace EMenuItem
16 {
17     enum Type
18     {
19         None,
20         StartGame,                    //开始游戏
21         GameOption,                    //游戏设置
22         QuitGame,                        //退出游戏
23         NewGame,                        //新游戏
24         LoadRecord,                    //加载存档
25         StartGameGoBack,            //从 开始游戏菜单  返回
26         GameOptionGoBack,        //从 游戏设置菜单  返回
27         NewGameGoBack,            //从 新游戏菜单     返回
28         ChooseRecordGoBack,    //从 加载存档菜单  返回
29         EnterGame,                    //进入游戏
30         EnterRecord                    //进入存档
31     };
32 }
33 
34 //菜单界面类型
35 namespace EMenuType
36 {
37     enum Type
38     {
39         None,
40         MainMenu,                //主菜单
41         StartGame,            //开始游戏菜单:显示新游戏菜单 || 加载存档菜单
42         GameOption,            //游戏设置菜单
43         NewGame,                //新游戏菜单
44         ChooseRecord        //加载存档菜单
45     };
46 }
47 
48 //Menu 动画状态枚举
49 namespace EMenuAnim
50 {
51     enum Type
52     {
53         Stop,        //停止动画
54         Close,    //关闭Menu
55         Open        //打开Menu
56     };
57 }

SSlAiMenuWidget.h 中声明相关内容

D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Public\UI\Widget\SSlAiMenuWidget.h

 1 #include "CoreMinimal.h"
 2 #include "Data/SlAiTypes.h"
 3 #include "Widgets/SCompoundWidget.h"
 4 
 5 class SBox;
 6 class STextBlock;
 7 class SVerticalBox;
 8 struct MenuGroup;
 9 class SSlAiGameOptionWidget;
10 class SSlAiNewGameWidget;
11 class SSlAiChooseRecordWidget;
12 
13 class SLAICOURSE_API SSlAiMenuWidget : public SCompoundWidget
14 {
15 public:
16     SLATE_BEGIN_ARGS(SSlAiMenuWidget)
17     {}
18 
19     SLATE_END_ARGS()
20 
21     /** Constructs this widget with InArgs */
22     void Construct(const FArguments& InArgs);
23 
24     //重写tick函数,动画播放过程中调用,判断EMenuAnim::Type AnimState 当前的状态
25     virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
26 
27 private:
28     //绑定到各个MenuItem的方法
29     void MenuItemOnClicked(EMenuItem::Type ItemType);
30     /**
31      * 由于MenuWidget是主菜单,我们要在这里实例化所有的功能控件,所有的事件都会放到这里
32      * 这样的好处是到后面加了其他场景后,用到这些组件的时候,实例化一个别的方法添加到事件里,就可以重复调用了
33      */
34     //修改语言
35     void ChangeCulture(ECultureTeam Culture);
36     //修改音量
37     void ChangeVolume(const float MusicVolume, const float SoundVolume);
38     //初始化所有的控件
39     void InitializedMenuList();
40     //选择显示的界面
41     void ChooseWidget(EMenuType::Type WidgetType);
42     //修改菜单大小
43     void ResetWidgetSize(float NewWidget, float NewHeight);
44     //初始化动画组件
45     void InitializedAnimation();
46     //播放关闭动画
47     void PlayClose(EMenuType::Type NewMenu);
48 
49 
50 private:
51     //保存根节点,用来动态的修改SBox的大小
52     TSharedPtr<SBox> RootSizeBox;
53     //获取MenuStyle
54     const struct FSlAiMenuStyle* MenuStyle;
55     //保存标题
56     TSharedPtr<STextBlock> TitleText;
57     //用来保存垂直列表,所有的菜单按钮组件都会放置到这ContentBox中
58     TSharedPtr<SVerticalBox> ContentBox;
59     //保存菜单组
60     TMap<EMenuType::Type, TSharedPtr<MenuGroup>> MenuMap;
61     //游戏设置Widget的指引
62     TSharedPtr<SSlAiGameOptionWidget> GameOptionWidget;
63     //新游戏控件指针
64     TSharedPtr<SSlAiNewGameWidget> NewGameWidget;
65     //选择存档控件指针
66     TSharedPtr<SSlAiChooseRecordWidget> ChooseRecordWidget;
67 
68     //动画播放器
69     FCurveSequence MenuAnimation;
70     //曲线控制器
71     FCurveHandle MenuCurve;
72     //用来保存新的长度
73     float CurrentHeight;
74     //是否已经显示Menu组件,显示为Ture
75     bool IsMenuShow;
76     //是否锁住按钮,锁住为Ture
77     bool ControlLocked;
78     //保存当前的动画状态
79     EMenuAnim::Type AnimState;
80     //保存当前的菜单
81     EMenuType::Type CurrentMenu;
82 };

D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Private\UI\Widget\SSlAiMenuWidget.cpp

  1 #include "SlateOptMacros.h"
  2 #include "Common/SlAiHelper.h"
  3 #include "Data/SlAiDataHandle.h"
  4 #include "Widgets/Layout/SBox.h"
  5 #include "Widgets/Images/SImage.h"
  6 #include "Widgets/Text/STextBlock.h"
  7 #include "Widgets/SBoxPanel.h"
  8 #include "UI/Style/SlAiStyle.h"
  9 #include "UI/Style/SlAiMenuWidgetStyle.h"
 10 #include "UI/Widget/SSlAiMenuWidget.h"
 11 #include "UI/Widget/SSlAiGameOptionWidget.h"
 12 #include "UI/Widget/SSlAiMenuItemWidget.h"
 13 #include "UI/Widget/SSlAiNewGameWidget.h"
 14 #include "UI/Widget/SSlAiChooseRecordWidget.h"
 15 
 16 
 17 /**
 18  * 每一个结构体对应一个菜单*/
 19 struct MenuGroup
 20 {
 21     //菜单标题
 22     FText MenuName;
 23     //菜单高度
 24     float MenuHeight;
 25     //下属组件
 26     TArray<TSharedPtr<SCompoundWidget>> ChildWidget;
 27 
 28     //构造函数
 29     MenuGroup(const FText Name, const float Height, TArray<TSharedPtr<SCompoundWidget>>* Children)
 30     {
 31         MenuName = Name;
 32         MenuHeight = Height;
 33         for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It(*Children); It; It++)
 34         {
 35             ChildWidget.Add(*It);
 36         }
 37     }
 38 };
 39 
 40 
 41 BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
 42 void SSlAiMenuWidget::Construct(const FArguments& InArgs)
 43 {
 44     //获取MenuStyle
 45     MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle");
 46     /**
 47     *切换语言
 48     //FInternationalization::Get().SetCurrentCulture(TEXT("en"));
 49     //FInternationalization::Get().SetCurrentCulture(TEXT("ch"));
 50     */
 51     //转换为中文
 52     SlAiDataHandle::Get()->ChangeLocalizationCulture(ECultureTeam::ZH);
 53 
 54     ChildSlot
 55     [
 56         /**
 57         *没有Slot,没有Slot要么不能插入子组件,要么只能插入一个子组件,SizeBox 只能插入一个子组件
 58         */
 59         SAssignNew(RootSizeBox, SBox)
 60         [
 61             SNew(SOverlay)
 62 
 63             +SOverlay::Slot()                                        //主菜单背景
 64                 .HAlign(HAlign_Fill)
 65                 .VAlign(VAlign_Fill)
 66                 .Padding(FMargin(0.f, 50.f, 0.f, 0.f))        //FMargin 间隔(左 上 右 下)
 67                 [
 68                     SNew(SImage)
 69                     .Image(&MenuStyle->MenuBackgroundBrush)
 70                 ]
 71 
 72             +SOverlay::Slot()                                        //菜单左侧图片
 73                 .HAlign(HAlign_Left)
 74                 .VAlign(VAlign_Center)
 75                 .Padding(FMargin(0.f, 25.f, 0.f, 0.f))
 76                 [
 77                     SNew(SImage).Image(&MenuStyle->LeftIconBrush)
 78                 ]
 79 
 80             +SOverlay::Slot()                                        //菜单右侧图片
 81                 .HAlign(HAlign_Right)
 82                 .VAlign(VAlign_Center)
 83                 .Padding(FMargin(0.f, 25.f, 0.f, 0.f))
 84                 [
 85                     SNew(SImage).Image(&MenuStyle->RightIconBrush)
 86                 ]
 87 
 88             +SOverlay::Slot()                                        //菜单标题图片
 89                 .HAlign(HAlign_Center)
 90                 .VAlign(VAlign_Top)
 91                 [
 92                     SNew(SBox)
 93                     .WidthOverride(400.f)
 94                     .HeightOverride(100.f)
 95                     [
 96                         SNew(SBorder)
 97                         .BorderImage(&MenuStyle->TitleBorderBrush)
 98                         .HAlign(HAlign_Center)
 99                         .VAlign(VAlign_Center)
100                         [
101                             SAssignNew(TitleText, STextBlock)
102                             .Font(SlAiStyle::Get().GetFontStyle("MenuItemFort"))
103                             .Text(NSLOCTEXT("SlAiMenu", "Menu", "Menu"))
104                             .Font(MenuStyle->Font_60)
105                         ]
106                     ]
107                 ]
108 
109             +SOverlay::Slot()                            //菜单按钮组件
110                 .HAlign(HAlign_Center)
111                 .VAlign(VAlign_Top)
112                 .Padding(FMargin(0.f, 130.f, 0.f, 0.f))
113                 [
114                     //菜单组件创建到这里
115                     SAssignNew(ContentBox, SVerticalBox)
116                 ]
117         ]
118     ];
119     InitializedMenuList();
120     InitializedAnimation();
121 }
122 
123 void SSlAiMenuWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
124 {
125     switch (AnimState)
126     {
127     case EMenuAnim::Stop:
128         break;
129     case EMenuAnim::Close:                        //如果菜单为关闭状态
130         if (MenuAnimation.IsPlaying())
131         {
132             //MenuCurve.GetLerp()  动态修改Menu的大小,从1到0
133             ResetWidgetSize(MenuCurve.GetLerp() * 600.f, -1.f);
134             //Menu的大小在缩小了40%的时候不显示组件
135             if (MenuCurve.GetLerp() < 0.6f && IsMenuShow) ChooseWidget(EMenuType::None);
136         }
137         else
138         {
139             //动画播放完毕,GetLerp到0后,设置状态为打开
140             AnimState = EMenuAnim::Open;
141             //开始播放打开的动画, Play 0到1
142             MenuAnimation.Play(this->AsShared());
143         }
144         break;
145     case EMenuAnim::Open:
146         //如果正在播放
147         if (MenuAnimation.IsPlaying())
148         {
149             //实时修改Menu的大小
150             ResetWidgetSize(MenuCurve.GetLerp() * 600.f, CurrentHeight);
151             //打开60%后显示组件, 并且没有显示组件, 调用ChooseWidget(),WidgetType != EMenuType::None 时 IsMenuShow 将会变为True
152             if (MenuCurve.GetLerp() > 0.6f && !IsMenuShow) ChooseWidget(CurrentMenu);
153         }
154         if (MenuAnimation.IsAtEnd())
155         {
156             //修改动画状态为停止
157             AnimState = EMenuAnim::Stop;
158             //解锁按钮
159             ControlLocked = false;
160         }
161         break;
162     }
163 }
164 
165 END_SLATE_FUNCTION_BUILD_OPTIMIZATION
166 
167 //按钮点击事件,实现界面跳转
168 void SSlAiMenuWidget::MenuItemOnClicked(EMenuItem::Type ItemType)
169 {
170     //如果锁住了,直接return
171     if (ControlLocked) return;
172     //设置锁住按钮,避免播放动画过程中操作其他按钮造成bug
173     ControlLocked = true;
174 
175     switch (ItemType)
176     {
177     case EMenuItem::StartGame:
178         PlayClose(EMenuType::StartGame);
179         break;
180     case EMenuItem::GameOption:
181         PlayClose(EMenuType::GameOption);
182         break;
183     case EMenuItem::QuitGame:
184         SlAiHelper::Debug(FString("退出游戏功能"), 5.f);
185         ControlLocked = false;        //测试用,免得解不了锁
186         break;
187     case EMenuItem::NewGame:
188         PlayClose(EMenuType::NewGame);
189         break;
190     case EMenuItem::LoadRecord:
191         PlayClose(EMenuType::ChooseRecord);
192         break;
193     case EMenuItem::StartGameGoBack:
194         PlayClose(EMenuType::MainMenu);
195         break;
196     case EMenuItem::GameOptionGoBack:
197         PlayClose(EMenuType::MainMenu);
198         break;
199     case EMenuItem::NewGameGoBack:
200         PlayClose(EMenuType::StartGame);
201         break;
202     case EMenuItem::ChooseRecordGoBack:
203         PlayClose(EMenuType::StartGame);
204         break;
205     case EMenuItem::EnterGame:
206         SlAiHelper::Debug(FString("进入新游戏功能"), 5.f);
207         ControlLocked = false;        //测试用,免得解不了锁
208         break;
209     case EMenuItem::EnterRecord:
210         SlAiHelper::Debug(FString("进入存档功能"), 5.f);
211         ControlLocked = false;        //测试用,免得解不了锁
212         break;
213     }
214 }
215 
216 void SSlAiMenuWidget::ChangeCulture(ECultureTeam Culture)
217 {
218     SlAiDataHandle::Get()->ChangeLocalizationCulture(Culture);
219 }
220 
221 void SSlAiMenuWidget::ChangeVolume(const float MusicVolume, const float SoundVolume)
222 {
223     SlAiDataHandle::Get()->ResetMenuVolume(MusicVolume, SoundVolume);
224 }
225 
226 /*
227 * 游戏运行后会调用到 InitializedMenuList() 实例化所有的界面组件
228 */
229 void SSlAiMenuWidget::InitializedMenuList()
230 {
231     //实例化主界面
232     TArray<TSharedPtr<SCompoundWidget>> MainMenuList;
233     MainMenuList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "StartGame", "StartGame")).ItemType(EMenuItem::StartGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
234     MainMenuList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GameOption", "GameOption")).ItemType(EMenuItem::GameOption).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
235     MainMenuList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "QuitGame", "QuitGame")).ItemType(EMenuItem::QuitGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
236 
237     MenuMap.Add(EMenuType::MainMenu, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "Menu", "Menu"), 510.f, &MainMenuList)));
238 
239     //开始游戏界面
240     TArray<TSharedPtr<SCompoundWidget>> StartGameList;
241     StartGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame")).ItemType(EMenuItem::NewGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
242     StartGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "LoadRecord", "LoadRecord")).ItemType(EMenuItem::LoadRecord).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
243     StartGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::StartGameGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
244 
245     MenuMap.Add(EMenuType::StartGame, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "StartGame", "StartGame"), 510.f, &StartGameList)));
246 
247     //游戏设置界面
248     TArray<TSharedPtr<SCompoundWidget>> GameOptionList;
249     //实例化游戏设置Widget(语言和声音)
250     SAssignNew(GameOptionWidget, SSlAiGameOptionWidget).ChangeCulture(this, &SSlAiMenuWidget::ChangeCulture).ChangeVolume(this, &SSlAiMenuWidget::ChangeVolume);
251     GameOptionList.Add(GameOptionWidget);
252     GameOptionList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::GameOptionGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
253 
254     MenuMap.Add(EMenuType::GameOption, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "GameOption", "GameOption"), 610.f, &GameOptionList)));
255 
256     //新游戏界面
257     TArray<TSharedPtr<SCompoundWidget>> NewGameList;
258     SAssignNew(NewGameWidget, SSlAiNewGameWidget);
259     NewGameList.Add(NewGameWidget);
260     NewGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "EnterGame", "EnterGame")).ItemType(EMenuItem::EnterGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
261     NewGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::NewGameGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
262 
263     MenuMap.Add(EMenuType::NewGame, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame"), 510.f, &NewGameList)));
264 
265     //选择存档界面
266     TArray<TSharedPtr<SCompoundWidget>> ChooseRecordList;
267     SAssignNew(ChooseRecordWidget, SSlAiChooseRecordWidget);
268     ChooseRecordList.Add(ChooseRecordWidget);
269     ChooseRecordList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "EnterRecord", "EnterRecord")).ItemType(EMenuItem::EnterRecord).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
270     ChooseRecordList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::ChooseRecordGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked));
271 
272     MenuMap.Add(EMenuType::ChooseRecord, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "LoadRecord", "LoadRecord"), 510.f, &ChooseRecordList)));
273 }
274 
275 void SSlAiMenuWidget::ChooseWidget(EMenuType::Type WidgetType)
276 {
277     //是否已经显示菜单,显示菜单为True
278     IsMenuShow = WidgetType != EMenuType::None;
279 
280     //移除所有组件
281     ContentBox->ClearChildren();
282 
283     if (WidgetType == EMenuType::None) return;
284 
285     //添加菜单的下属组件
286     for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It((*MenuMap.Find(WidgetType))->ChildWidget); It; It++)
287     {
288         ContentBox->AddSlot().AutoHeight()[(*It)->AsShared()];
289     }
290 
291     //更改标题
292     TitleText->SetText((*MenuMap.Find(WidgetType))->MenuName);
293 }
294 
295 //如果不修改高度,NewHeight传入-1
296 void SSlAiMenuWidget::ResetWidgetSize(float NewWidget, float NewHeight)
297 {
298     RootSizeBox->SetWidthOverride(NewWidget);
299     if (NewHeight < 0)
300     {
301         return;
302     }
303     RootSizeBox->SetHeightOverride(NewHeight);
304 }
305 
306 /**
307  * FCurveSequence 动画播放器
308  * FCurveHandle 是曲线控制器
309  * 我们会通过动画控制器 FCurveSequence 的一些方法 (如 Play Stop 等……) 让曲线 FCurveHandle 0到1、1到0实时的变化
310  * 我们可以实时的获取曲线 FCurveHandle 的值,获取曲线的值动态的修改界面的大小
311  */
312 void SSlAiMenuWidget::InitializedAnimation()
313 {
314     //开始延时
315     const float StartDelay = 0.3f;
316 
317     //持续时间
318     const float AnimDuration = 0.6f;
319 
320     //将动画播放器实例化
321     MenuAnimation = FCurveSequence();
322 
323     //曲线控制器注册进动画播放器,参数:开始延时、持续时间、播放类型
324     MenuCurve = MenuAnimation.AddCurve(StartDelay, AnimDuration, ECurveEaseFunction::QuadInOut);
325 
326     //初始设置Menu大小
327     ResetWidgetSize(600.f, 510.f);
328     
329     //初始显示主界面
330     ChooseWidget(EMenuType::MainMenu);
331 
332     //是否允许点击按钮
333     ControlLocked = false;
334 
335     //设置动画状态为停止
336     AnimState = EMenuAnim::Stop;
337 
338     //设置动画播放器跳到结尾 1
339     MenuAnimation.JumpToEnd();
340 }
341 
342 void SSlAiMenuWidget::PlayClose(EMenuType::Type NewMenu)
343 {
344     //设置新的界面
345     CurrentMenu = NewMenu;
346     //设置新高度
347     CurrentHeight = (*MenuMap.Find(CurrentMenu))->MenuHeight;
348     //设置播放状态是Close, 代表关闭Menu
349     AnimState = EMenuAnim::Close;
350     //播放反向动画,从1播放到0,动画播放器初始化时 MenuAnimation.JumpToEnd(); 设置到了1
351     MenuAnimation.PlayReverse(this->AsShared());
352 }

原文地址:https://www.cnblogs.com/CooCoChoco/p/15250578.html