目前已经实现了主菜单的跳转,这个部分将要给按钮点击添加UI动画,具体效果如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLxMTN5UDOzMDMx0SO0MTOwcDMxEDMxkDMxIDMy0CN4YjM3gTMvwVOwEjMwIzLcRDO2IzN4EzLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.gif)
首先在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 }