UE4 从无到有纯 C++ & Slate 开发沙盒游戏(十六) UI音乐播放与调节
到目前为止主菜单基本功能均已实现,目前还差进入游戏、进入存档、退出游戏、UI音乐音效四个部分,这部分将会实现UI音效
声音文件要通过 WidgetStyle 来获取,所以要在 MenuWidgetStyle.h 文件下添加注册
D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Public\UI\Style\SlAiMenuWidgetStyle.h
1 #include "CoreMinimal.h" 2 #include "Styling/SlateWidgetStyle.h" 3 #include "Styling/SlateWidgetStyleContainerBase.h" 4 #include "Styling/SlateBrush.h" 5 #include "Fonts/SlateFontInfo.h" 6 #include "Sound/SlateSound.h" 7 8 #include "SlAiMenuWidgetStyle.generated.h" 9 10 11 /** 12 * 13 */ 14 USTRUCT() 15 struct SLAICOURSE_API FSlAiMenuStyle : public FSlateWidgetStyle //构造体 16 { 17 GENERATED_USTRUCT_BODY() 18 19 FSlAiMenuStyle(); 20 virtual ~FSlAiMenuStyle(); 21 22 // FSlateWidgetStyle 23 virtual void GetResources(TArray<const FSlateBrush*>& OutBrushes) const override; 24 static const FName TypeName; 25 virtual const FName GetTypeName() const override { return TypeName; }; 26 static const FSlAiMenuStyle& GetDefault(); 27 28 /*定义笔刷 29 * 主背景图片*/ 30 UPROPERTY(EditAnywhere, Category = MenuHUD) 31 FSlateBrush MenuHUDBackgroundBrush; 32 33 /*定义笔刷 34 * 主背景图片*/ 35 UPROPERTY(EditAnywhere, Category = Menu) 36 FSlateBrush MenuBackgroundBrush; 37 38 /*定义笔刷 39 * Menu左图标的Brush*/ 40 UPROPERTY(EditAnywhere, Category = Menu) 41 FSlateBrush LeftIconBrush; 42 43 /*定义笔刷 44 * Menu右图标的Brush*/ 45 UPROPERTY(EditAnywhere, Category = Menu) 46 FSlateBrush RightIconBrush; 47 48 /*定义笔刷 49 * Menu标题Border的Brush*/ 50 UPROPERTY(EditAnywhere, Category = Menu) 51 FSlateBrush TitleBorderBrush; 52 53 /*定义笔刷 54 * MenuItem的Brush*/ 55 UPROPERTY(EditAnywhere, Category = MenuItem) 56 FSlateBrush MenuItemBrush; 57 58 /** 59 * 30号字体 */ 60 UPROPERTY(EditAnywhere, Category = Common) 61 FSlateFontInfo Font_30; 62 63 /** 64 * 40号字体 */ 65 UPROPERTY(EditAnywhere, Category = Common) 66 FSlateFontInfo Font_40; 67 68 /** 69 * 60号字体 */ 70 UPROPERTY(EditAnywhere, Category = Common) 71 FSlateFontInfo Font_60; 72 73 /** 74 * 白色颜色 */ 75 UPROPERTY(EditAnywhere, Category = Common) 76 FSlateColor FontColor_White; 77 78 /** 79 * 黑色颜色 */ 80 UPROPERTY(EditAnywhere, Category = Common) 81 FSlateColor FontColor_Black; 82 83 /** 84 * GameSet的背景*/ 85 UPROPERTY(EditAnywhere, Category = GameOption) 86 FSlateBrush GameOptionBGBrush; 87 88 /** 89 * CheckedBox的Brush被选中*/ 90 UPROPERTY(EditAnywhere, Category = GameOption) 91 FSlateBrush CheckedBoxBrush; 92 93 /** 94 * CheckedBox的Brush不被选中*/ 95 UPROPERTY(EditAnywhere, Category = GameOption) 96 FSlateBrush UnCheckedBoxBrush; 97 98 /** 99 * slider的背景Brush*/ 100 UPROPERTY(EditAnywhere, Category = GameOption) 101 FSlateBrush SliderBarBrush; 102 103 /** 104 * 指定slider的样式*/ 105 UPROPERTY(EditAnywhere, Category = GameOption) 106 FSliderStyle SliderStyle; 107 108 /** 109 * 开始游戏声音 */ 110 UPROPERTY(EditAnywhere, Category = Sound) 111 FSlateSound StartGameSound; 112 113 /** 114 * 结束游戏声音 */ 115 UPROPERTY(EditAnywhere, Category = Sound) 116 FSlateSound ExitGameSound; 117 118 /** 119 * 转换按钮声音 */ 120 UPROPERTY(EditAnywhere, Category = Sound) 121 FSlateSound MenuItemChangeSound; 122 123 /** 124 * 菜单背景声音 */ 125 UPROPERTY(EditAnywhere, Category = Sound) 126 FSlateSound MenuBackgroudMusic; 127 }; 128 129 /** 130 */ 131 UCLASS(hidecategories=Object, MinimalAPI) 132 class USlAiMenuWidgetStyle : public USlateWidgetStyleContainerBase 133 { 134 GENERATED_BODY() 135 136 public: 137 /** The actual data describing the widget appearance. */ 138 UPROPERTY(Category=Appearance, EditAnywhere, meta=(ShowOnlyInnerProperties)) 139 FSlAiMenuStyle WidgetStyle; 140 141 virtual const struct FSlateWidgetStyle* const GetStyle() const override 142 { 143 return static_cast< const struct FSlateWidgetStyle* >( &WidgetStyle ); 144 } 145 };
然后再UE4中,MenuSound文件夹下添加 wav 的音乐文件,(MP3的不能使用)
每一个音乐文件对应一个Cue,新建Cue后对其进行编辑
拖入音乐文件,将文件与Output连接,保存
然后再 BPSlAiMenuStyle 文件中对 Sound 进行配置。这个部分是让 C++ 调用对应的音乐蓝图
然后再代码中的相关事件,调用相关的音频资源
这里将要实现一个延时调用方法的功能,点击播放完音频后调用相关事件
D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Public\Common\SlAiHelper.h
1 #include "CoreMinimal.h" 2 #include "Engine/World.h" 3 #include "Engine/GameEngine.h" 4 #include "Framework/Application/SlateApplication.h" 5 #include "Engine/GameEngine.h" 6 7 namespace SlAiHelper 8 { 9 FORCEINLINE void Debug(FString Message, float Duration = 3.f) 10 { 11 if (GEngine) 12 { 13 //输出一个持续X时间的黄色文字到控制台 14 GEngine->AddOnScreenDebugMessage(-1, Duration, FColor::Yellow, Message); 15 } 16 } 17 18 /** 19 * 播放一段音乐后调用一个方法 20 * FTimerHandle 时间控制的一个句柄 21 * UWorld 的指针可以获取到 TimeWidget 22 * FSlateSound 使UBaseSound可用于 Slate 播放声音的中介 23 * FTimerDelegate 函数委托指针,TRawMethodDelegate 声明基于c++指针的委托实例类型,委托定义的那个类型的指针 24 */ 25 template<class UserClass> 26 FORCEINLINE FTimerHandle PlayerSoundAndCall(UWorld* World, const FSlateSound Sound, UserClass* InUserObject, typename FTimerDelegate::TRawMethodDelegate<UserClass>::FMethodPtr InMethod) 27 { 28 //先播放传进来的音乐 29 FSlateApplication::Get().PlaySound(Sound); 30 //时间控制的句柄 31 FTimerHandle Result; 32 //获取音乐播放的长度(时间), 最少0.1秒 33 const float SoundDuration = FMath::Max(FSlateApplication::Get().GetSoundDuration(Sound), 0.1f); 34 //时间委托 35 FTimerDelegate Callback; 36 //给 Callback 这个委托绑定 传入的UserClass的指针,以及 FMethodPtr 的函数委托指针 37 Callback.BindRaw(InUserObject, InMethod); 38 //通过 World 获取时间管理器,设置Time,传入时间控制的句柄、时间委托、延迟时间、 39 World->GetTimerManager().SetTimer(Result, Callback, SoundDuration, false); 40 41 return Result; 42 } 43 } 44 45 /** 46 * 定时器相关知识 47 * 定义定时器句柄 FTimerHandle mTimer 48 * 定义调用的委托 FTimerDelegate timeDele; 49 * 绑定函数到委托 timeDele.BindRaw(this, &xxx:fff); 50 * 获取时间控制器并且启动定时器 51 * 52 * Gworld->GetTimerManager().SetTimer(mlime, timeDele, 1.f, true); 53 * Gworld->GetTimerManager().PauseTimer(mTimer); //暂停 54 * Gworld->GetTimerManager().UnPauseTimer(mTimer); //唤醒 55 * Gworld->GetTimerManager().ClearTimer(mlimer); //清除计时器 56 */
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 void QuitGame(); 50 //进入游戏 51 void EnterGame(); 52 53 private: 54 //保存根节点,用来动态的修改SBox的大小 55 TSharedPtr<SBox> RootSizeBox; 56 //获取MenuStyle 57 const struct FSlAiMenuStyle* MenuStyle; 58 //保存标题 59 TSharedPtr<STextBlock> TitleText; 60 //用来保存垂直列表,所有的菜单按钮组件都会放置到这ContentBox中 61 TSharedPtr<SVerticalBox> ContentBox; 62 //保存菜单组 63 TMap<EMenuType::Type, TSharedPtr<MenuGroup>> MenuMap; 64 //游戏设置Widget的指引 65 TSharedPtr<SSlAiGameOptionWidget> GameOptionWidget; 66 //新游戏控件指针 67 TSharedPtr<SSlAiNewGameWidget> NewGameWidget; 68 //选择存档控件指针 69 TSharedPtr<SSlAiChooseRecordWidget> ChooseRecordWidget; 70 71 //动画播放器 72 FCurveSequence MenuAnimation; 73 //曲线控制器 74 FCurveHandle MenuCurve; 75 //用来保存新的长度 76 float CurrentHeight; 77 //是否已经显示Menu组件,显示为Ture 78 bool IsMenuShow; 79 //是否锁住按钮,锁住为Ture 80 bool ControlLocked; 81 //保存当前的动画状态 82 EMenuAnim::Type AnimState; 83 //保存当前的菜单 84 EMenuType::Type CurrentMenu; 85 };
实现相关逻辑
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" //一版会把SlateWidget的根组件设成SBox 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 #include "Kismet/GameplayStatics.h" 16 #include "GamePlay/SlAiMenuController.h" 17 18 19 /** 20 * 每一个结构体对应一个菜单*/ 21 struct MenuGroup 22 { 23 //菜单标题 24 FText MenuName; 25 //菜单高度 26 float MenuHeight; 27 //下属组件 28 TArray<TSharedPtr<SCompoundWidget>> ChildWidget; 29 30 //构造函数 31 MenuGroup(const FText Name, const float Height, TArray<TSharedPtr<SCompoundWidget>>* Children) 32 { 33 MenuName = Name; 34 MenuHeight = Height; 35 for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It(*Children); It; It++) 36 { 37 ChildWidget.Add(*It); 38 } 39 } 40 }; 41 42 43 BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 44 void SSlAiMenuWidget::Construct(const FArguments& InArgs) 45 { 46 //获取MenuStyle 47 MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle"); 48 49 //转换为中文 50 //SlAiDataHandle::Get()->ChangeLocalizationCulture(ECultureTeam::ZH); 51 52 //播放背景音乐 53 FSlateApplication::Get().PlaySound(MenuStyle->MenuBackgroudMusic); 54 55 ChildSlot 56 [ 57 /** 58 *没有Slot,没有Slot要么不能插入子组件,要么只能插入一个子组件,SizeBox 只能插入一个子组件 59 */ 60 SAssignNew(RootSizeBox, SBox) 61 [ 62 SNew(SOverlay) 63 64 +SOverlay::Slot() //主菜单背景 65 .HAlign(HAlign_Fill) 66 .VAlign(VAlign_Fill) 67 .Padding(FMargin(0.f, 50.f, 0.f, 0.f)) //FMargin 间隔(左 上 右 下) 68 [ 69 SNew(SImage) 70 .Image(&MenuStyle->MenuBackgroundBrush) 71 ] 72 73 +SOverlay::Slot() //菜单左侧图片 74 .HAlign(HAlign_Left) 75 .VAlign(VAlign_Center) 76 .Padding(FMargin(0.f, 25.f, 0.f, 0.f)) 77 [ 78 SNew(SImage).Image(&MenuStyle->LeftIconBrush) 79 ] 80 81 +SOverlay::Slot() //菜单右侧图片 82 .HAlign(HAlign_Right) 83 .VAlign(VAlign_Center) 84 .Padding(FMargin(0.f, 25.f, 0.f, 0.f)) 85 [ 86 SNew(SImage).Image(&MenuStyle->RightIconBrush) 87 ] 88 89 +SOverlay::Slot() //菜单标题图片 90 .HAlign(HAlign_Center) 91 .VAlign(VAlign_Top) 92 [ 93 SNew(SBox) 94 .WidthOverride(400.f) 95 .HeightOverride(100.f) 96 [ 97 SNew(SBorder) 98 .BorderImage(&MenuStyle->TitleBorderBrush) 99 .HAlign(HAlign_Center) 100 .VAlign(VAlign_Center) 101 [ 102 SAssignNew(TitleText, STextBlock) 103 .Font(SlAiStyle::Get().GetFontStyle("MenuItemFort")) 104 .Text(NSLOCTEXT("SlAiMenu", "Menu", "Menu")) 105 .Font(MenuStyle->Font_60) 106 ] 107 ] 108 ] 109 110 +SOverlay::Slot() //菜单按钮组件 111 .HAlign(HAlign_Center) 112 .VAlign(VAlign_Top) 113 .Padding(FMargin(0.f, 130.f, 0.f, 0.f)) 114 [ 115 //菜单组件创建到这里 116 SAssignNew(ContentBox, SVerticalBox) 117 ] 118 ] 119 ]; 120 InitializedMenuList(); 121 InitializedAnimation(); 122 } 123 124 void SSlAiMenuWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) 125 { 126 switch (AnimState) 127 { 128 case EMenuAnim::Stop: 129 break; 130 case EMenuAnim::Close: //如果菜单为关闭状态 131 if (MenuAnimation.IsPlaying()) 132 { 133 //MenuCurve.GetLerp() 动态修改Menu的大小,从1到0 134 ResetWidgetSize(MenuCurve.GetLerp() * 600.f, -1.f); 135 //Menu的大小在缩小了40%的时候不显示组件 136 if (MenuCurve.GetLerp() < 0.6f && IsMenuShow) ChooseWidget(EMenuType::None); 137 } 138 else 139 { 140 //动画播放完毕,GetLerp到0后,设置状态为打开 141 AnimState = EMenuAnim::Open; 142 //开始播放打开的动画, Play 0到1 143 MenuAnimation.Play(this->AsShared()); 144 } 145 break; 146 case EMenuAnim::Open: 147 //如果正在播放 148 if (MenuAnimation.IsPlaying()) 149 { 150 //实时修改Menu的大小 151 ResetWidgetSize(MenuCurve.GetLerp() * 600.f, CurrentHeight); 152 //打开60%后显示组件, 并且没有显示组件, 调用ChooseWidget(),WidgetType != EMenuType::None 时 IsMenuShow 将会变为True 153 if (MenuCurve.GetLerp() > 0.6f && !IsMenuShow) ChooseWidget(CurrentMenu); 154 } 155 if (MenuAnimation.IsAtEnd()) 156 { 157 //修改动画状态为停止 158 AnimState = EMenuAnim::Stop; 159 //解锁按钮 160 ControlLocked = false; 161 } 162 break; 163 } 164 } 165 166 END_SLATE_FUNCTION_BUILD_OPTIMIZATION 167 168 //按钮点击事件,实现界面跳转 169 void SSlAiMenuWidget::MenuItemOnClicked(EMenuItem::Type ItemType) 170 { 171 //如果锁住了,直接return 172 if (ControlLocked) return; 173 //设置锁住按钮,避免播放动画过程中操作其他按钮造成bug 174 ControlLocked = true; 175 176 switch (ItemType) 177 { 178 case EMenuItem::StartGame: 179 PlayClose(EMenuType::StartGame); 180 break; 181 case EMenuItem::GameOption: 182 PlayClose(EMenuType::GameOption); 183 break; 184 case EMenuItem::QuitGame: 185 //退出游戏,播放声音并且延时调用退出函数 186 SlAiHelper::PlayerSoundAndCall(UGameplayStatics::GetPlayerController(GWorld, 0)->GetWorld(), MenuStyle->ExitGameSound, this, &SSlAiMenuWidget::QuitGame); 187 break; 188 case EMenuItem::NewGame: 189 PlayClose(EMenuType::NewGame); 190 break; 191 case EMenuItem::LoadRecord: 192 PlayClose(EMenuType::ChooseRecord); 193 break; 194 case EMenuItem::StartGameGoBack: 195 PlayClose(EMenuType::MainMenu); 196 break; 197 case EMenuItem::GameOptionGoBack: 198 PlayClose(EMenuType::MainMenu); 199 break; 200 case EMenuItem::NewGameGoBack: 201 PlayClose(EMenuType::StartGame); 202 break; 203 case EMenuItem::ChooseRecordGoBack: 204 PlayClose(EMenuType::StartGame); 205 break; 206 case EMenuItem::EnterGame: 207 //进入游戏,播放声音并且延时调用进入游戏函数 208 SlAiHelper::PlayerSoundAndCall(UGameplayStatics::GetPlayerController(GWorld, 0)->GetWorld(), MenuStyle->StartGameSound, this, &SSlAiMenuWidget::EnterGame); 209 break; 210 case EMenuItem::EnterRecord: 211 //进入游戏,播放声音并且延时调用进入存档函数 212 SlAiHelper::PlayerSoundAndCall(UGameplayStatics::GetPlayerController(GWorld, 0)->GetWorld(), MenuStyle->StartGameSound, this, &SSlAiMenuWidget::EnterGame); 213 break; 214 } 215 } 216 217 void SSlAiMenuWidget::ChangeCulture(ECultureTeam Culture) 218 { 219 SlAiDataHandle::Get()->ChangeLocalizationCulture(Culture); 220 } 221 222 void SSlAiMenuWidget::ChangeVolume(const float MusicVolume, const float SoundVolume) 223 { 224 SlAiDataHandle::Get()->ResetMenuVolume(MusicVolume, SoundVolume); 225 } 226 227 /* 228 * 游戏运行后会调用到 InitializedMenuList() 实例化所有的界面组件 229 */ 230 void SSlAiMenuWidget::InitializedMenuList() 231 { 232 //实例化主界面 233 TArray<TSharedPtr<SCompoundWidget>> MainMenuList; 234 MainMenuList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "StartGame", "StartGame")).ItemType(EMenuItem::StartGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 235 MainMenuList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GameOption", "GameOption")).ItemType(EMenuItem::GameOption).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 236 MainMenuList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "QuitGame", "QuitGame")).ItemType(EMenuItem::QuitGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 237 238 MenuMap.Add(EMenuType::MainMenu, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "Menu", "Menu"), 510.f, &MainMenuList))); 239 240 //开始游戏界面 241 TArray<TSharedPtr<SCompoundWidget>> StartGameList; 242 StartGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame")).ItemType(EMenuItem::NewGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 243 StartGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "LoadRecord", "LoadRecord")).ItemType(EMenuItem::LoadRecord).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 244 StartGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::StartGameGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 245 246 MenuMap.Add(EMenuType::StartGame, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "StartGame", "StartGame"), 510.f, &StartGameList))); 247 248 //游戏设置界面 249 TArray<TSharedPtr<SCompoundWidget>> GameOptionList; 250 //实例化游戏设置Widget(语言和声音) 251 SAssignNew(GameOptionWidget, SSlAiGameOptionWidget).ChangeCulture(this, &SSlAiMenuWidget::ChangeCulture).ChangeVolume(this, &SSlAiMenuWidget::ChangeVolume); 252 GameOptionList.Add(GameOptionWidget); 253 GameOptionList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::GameOptionGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 254 255 MenuMap.Add(EMenuType::GameOption, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "GameOption", "GameOption"), 610.f, &GameOptionList))); 256 257 //新游戏界面 258 TArray<TSharedPtr<SCompoundWidget>> NewGameList; 259 SAssignNew(NewGameWidget, SSlAiNewGameWidget); 260 NewGameList.Add(NewGameWidget); 261 NewGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "EnterGame", "EnterGame")).ItemType(EMenuItem::EnterGame).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 262 NewGameList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::NewGameGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 263 264 MenuMap.Add(EMenuType::NewGame, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "NewGame", "NewGame"), 510.f, &NewGameList))); 265 266 //选择存档界面 267 TArray<TSharedPtr<SCompoundWidget>> ChooseRecordList; 268 SAssignNew(ChooseRecordWidget, SSlAiChooseRecordWidget); 269 ChooseRecordList.Add(ChooseRecordWidget); 270 ChooseRecordList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "EnterRecord", "EnterRecord")).ItemType(EMenuItem::EnterRecord).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 271 ChooseRecordList.Add(SNew(SSlAiMenuItemWidget).ItemText(NSLOCTEXT("SlAiMenu", "GoBack", "GoBack")).ItemType(EMenuItem::ChooseRecordGoBack).OnClicked(this, &SSlAiMenuWidget::MenuItemOnClicked)); 272 273 MenuMap.Add(EMenuType::ChooseRecord, MakeShareable(new MenuGroup(NSLOCTEXT("SlAiMenu", "LoadRecord", "LoadRecord"), 510.f, &ChooseRecordList))); 274 } 275 276 void SSlAiMenuWidget::ChooseWidget(EMenuType::Type WidgetType) 277 { 278 //是否已经显示菜单,显示菜单为True 279 IsMenuShow = WidgetType != EMenuType::None; 280 281 //移除所有组件 282 ContentBox->ClearChildren(); 283 284 if (WidgetType == EMenuType::None) return; 285 286 //添加菜单的下属组件 287 for (TArray<TSharedPtr<SCompoundWidget>>::TIterator It((*MenuMap.Find(WidgetType))->ChildWidget); It; It++) 288 { 289 ContentBox->AddSlot().AutoHeight()[(*It)->AsShared()]; 290 } 291 292 //更改标题 293 TitleText->SetText((*MenuMap.Find(WidgetType))->MenuName); 294 } 295 296 //如果不修改高度,NewHeight传入-1 297 void SSlAiMenuWidget::ResetWidgetSize(float NewWidget, float NewHeight) 298 { 299 RootSizeBox->SetWidthOverride(NewWidget); 300 if (NewHeight < 0) 301 { 302 return; 303 } 304 RootSizeBox->SetHeightOverride(NewHeight); 305 } 306 307 /** 308 * FCurveSequence 动画播放器 309 * FCurveHandle 是曲线控制器 310 * 我们会通过动画控制器 FCurveSequence 的一些方法 (如 Play Stop 等……) 让曲线 FCurveHandle 0到1、1到0实时的变化 311 * 我们可以实时的获取曲线 FCurveHandle 的值,获取曲线的值动态的修改界面的大小 312 */ 313 void SSlAiMenuWidget::InitializedAnimation() 314 { 315 //开始延时 316 const float StartDelay = 0.3f; 317 318 //持续时间 319 const float AnimDuration = 0.6f; 320 321 //将动画播放器实例化 322 MenuAnimation = FCurveSequence(); 323 324 //曲线控制器注册进动画播放器,参数:开始延时、持续时间、播放类型 325 MenuCurve = MenuAnimation.AddCurve(StartDelay, AnimDuration, ECurveEaseFunction::QuadInOut); 326 327 //初始设置Menu大小 328 ResetWidgetSize(600.f, 510.f); 329 330 //初始显示主界面 331 ChooseWidget(EMenuType::MainMenu); 332 333 //是否允许点击按钮 334 ControlLocked = false; 335 336 //设置动画状态为停止 337 AnimState = EMenuAnim::Stop; 338 339 //设置动画播放器跳到结尾 1 340 MenuAnimation.JumpToEnd(); 341 } 342 343 void SSlAiMenuWidget::PlayClose(EMenuType::Type NewMenu) 344 { 345 //设置新的界面 346 CurrentMenu = NewMenu; 347 //设置新高度 348 CurrentHeight = (*MenuMap.Find(CurrentMenu))->MenuHeight; 349 //设置播放状态是Close, 代表关闭Menu 350 AnimState = EMenuAnim::Close; 351 //播放反向动画,从1播放到0,动画播放器初始化时 MenuAnimation.JumpToEnd(); 设置到了1 352 MenuAnimation.PlayReverse(this->AsShared()); 353 //播放切换菜单音乐 354 FSlateApplication::Get().PlaySound(MenuStyle->MenuItemChangeSound); 355 } 356 357 void SSlAiMenuWidget::QuitGame() 358 { 359 //Cast 是一个类型转换的方法,控制台输入 quit 360 Cast<ASlAiMenuController>(UGameplayStatics::GetPlayerController(GWorld, 0))->ConsoleCommand("quit"); 361 } 362 363 void SSlAiMenuWidget::EnterGame() 364 { 365 SlAiHelper::Debug(FString("EnterGame"), 10.f); 366 ControlLocked = false; //测试用,免得解不了锁 367 }
到这里便可以正常播放音效
接下来写设置音量和音效大小的部分
先在 SlAiDataHandle.h 中声明声音相关变量
D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Public\Data\SlAiDataHandle.h
1 #include "SlAiTypes.h" 2 #include "CoreMinimal.h" 3 4 class USoundCue; 5 6 class SLAICOURSE_API SlAiDataHandle 7 { 8 public: 9 SlAiDataHandle(); 10 11 static void Initialize(); 12 13 static TSharedPtr<SlAiDataHandle> Get(); 14 15 //修改语言 16 void ChangeLocalizationCulture(ECultureTeam Culture); 17 //修改菜单音量大小 18 void ResetMenuVolume(float MusicVol, float SoundVol); 19 20 public: 21 /** 22 * 这里没有使用 GamePlay 框架的 GameInstance,变量直接写到C++类中除非你主动销毁,否则他会一直存在 23 */ 24 //语言状态 25 ECultureTeam CurrentCulture; 26 //音乐状态 27 float MusicVolume; 28 //音效状态 29 float SoundVolume; 30 //存档数据 31 TArray<FString> RecordDataList; 32 //存档名 33 FString RecordName; 34 35 private: 36 //创建单例 37 static TSharedRef<SlAiDataHandle> Create(); 38 39 //根据enum类型获取字符串(该方法只能用于UENUM()反射后的枚举,普通枚举不能这样搞) 40 template<typename TEnum> 41 FString GetEnumValueAsString(const FString& Name, TEnum Value); 42 43 //根据字符串获取enum值(该方法只能用于UENUM()反射后的枚举,普通枚举不能这样搞) 44 template<typename TEnum> 45 TEnum GetEnumValueFromString(const FString& Name, FString Value); 46 47 //初始化存档数据 48 void InitRecordData(); 49 50 //初始化 Menu 声音数据 51 void InitializedMenuAudio(); 52 53 private: 54 static TSharedPtr<SlAiDataHandle> DataInstance; 55 56 //保存 Menu 的声音 57 TMap<FString, TArray<USoundCue*>> MenuAudioResource; 58 59 //获取 MenuStyle 里边存放有声音文件 60 const struct FSlAiMenuStyle* MenuStyle; 61 62 };
实现相关功能
D:\UE4 Project\UE26.2\CourseProject\SlAiCourse\Source\SlAiCourse\Private\Data\SlAiDataHandle.cpp
1 #include "Data/SlAiDataHandle.h" 2 #include "Internationalization/Internationalization.h" 3 #include "Data/SlAiSingleton.h" 4 #include "Data/SlAiJsonHandle.h" 5 #include "Common/SlAiHelper.h" 6 #include "UI/Style/SlAiStyle.h" 7 #include "UI/Style/SlAiMenuWidgetStyle.h" 8 #include "Sound/SoundCue.h" 9 10 TSharedPtr<SlAiDataHandle> SlAiDataHandle::DataInstance = NULL; 11 12 SlAiDataHandle::SlAiDataHandle() 13 { 14 //初始化存档数据 15 InitRecordData(); 16 //初始化音乐数据 17 InitializedMenuAudio(); 18 19 } 20 21 void SlAiDataHandle::Initialize() 22 { 23 if (!DataInstance.IsValid()) 24 { 25 DataInstance = Create(); 26 } 27 } 28 29 TSharedPtr<SlAiDataHandle> SlAiDataHandle::Get() 30 { 31 Initialize(); 32 return DataInstance; 33 } 34 35 TSharedRef<SlAiDataHandle> SlAiDataHandle::Create() 36 { 37 /** 38 *MakeShareable 可以用来创建共享指针和共享引用 39 */ 40 TSharedRef<SlAiDataHandle> DataRef = MakeShareable(new SlAiDataHandle()); 41 return DataRef; 42 } 43 44 void SlAiDataHandle::ChangeLocalizationCulture(ECultureTeam Culture) 45 { 46 switch (Culture) 47 { 48 case ECultureTeam::EN: 49 FInternationalization::Get().SetCurrentCulture(TEXT("en")); 50 break; 51 case ECultureTeam::ZH: 52 FInternationalization::Get().SetCurrentCulture(TEXT("zh")); 53 break; 54 } 55 //赋值 56 CurrentCulture = Culture; 57 /* 58 更新存档数据*/ 59 SlAiSingleton<SlAiJsonHandle>::Get()->UpdateRecordData(GetEnumValueAsString<ECultureTeam>(FString("ECultureTeam"), CurrentCulture), MusicVolume, SoundVolume, &RecordDataList); 60 } 61 62 void SlAiDataHandle::ResetMenuVolume(float MusicVol, float SoundVol) 63 { 64 if (MusicVol > 0) 65 { 66 MusicVolume = MusicVol; 67 //循环设置所有背景音乐的音量 68 for (TArray<USoundCue*>::TIterator It(MenuAudioResource.Find(FString("Music"))->CreateIterator()); It; It++) 69 { 70 //设置音量 71 (*It)->VolumeMultiplier = MusicVolume; 72 } 73 } 74 if (SoundVol > 0) 75 { 76 SoundVolume = SoundVol; 77 //循环设置所有音效的音量 78 for (TArray<USoundCue*>::TIterator It(MenuAudioResource.Find(FString("Sound"))->CreateIterator()); It; It++) 79 { 80 //设置音量 81 (*It)->VolumeMultiplier = SoundVolume; 82 } 83 } 84 /* 85 更新存档数据*/ 86 SlAiSingleton<SlAiJsonHandle>::Get()->UpdateRecordData(GetEnumValueAsString<ECultureTeam>(FString("ECultureTeam"), CurrentCulture), MusicVolume, SoundVolume, &RecordDataList); 87 } 88 89 //根据枚举值返回字符串 90 template<typename TEnum> 91 FString SlAiDataHandle::GetEnumValueAsString(const FString& Name, TEnum Value) 92 { 93 //通过UE4的FindObject寻找所有ANY_PACKAGE反射到的Name,寻找到后实例化后返回 94 const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true); 95 96 if (!EnumPtr) 97 { 98 return FString("InValid"); 99 } 100 return EnumPtr->GetEnumName((int32)Value); 101 } 102 103 //根据字符串返回枚举值 104 template<typename TEnum> 105 TEnum SlAiDataHandle::GetEnumValueFromString(const FString& Name, FString Value) 106 { 107 const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *Name, true); 108 109 if (!EnumPtr) 110 { 111 return TEnum(0); 112 } 113 return (TEnum)EnumPtr->GetIndexByName(FName(*FString(Value))); 114 } 115 116 void SlAiDataHandle::InitRecordData() 117 { 118 //获取语言 119 FString Culture; 120 121 //使用单例读取存档数据 122 SlAiSingleton<SlAiJsonHandle>::Get()->RecordDataJsonRead(Culture, MusicVolume, SoundVolume, RecordDataList); 123 124 //初始化语言 125 ChangeLocalizationCulture(GetEnumValueFromString<ECultureTeam>(FString("ECultureTeam"), Culture)); 126 127 RecordName = FString(""); 128 } 129 130 void SlAiDataHandle::InitializedMenuAudio() 131 { 132 //获取 MenuStyle 133 MenuStyle = &SlAiStyle::Get().GetWidgetStyle<FSlAiMenuStyle>("BPSlAiMenuStyle"); 134 135 //添加资源文件到资源列表,添加前先转成USoundCue 136 TArray<USoundCue*> MusicList; 137 MusicList.Add(Cast<USoundCue>(MenuStyle->MenuBackgroudMusic.GetResourceObject())); 138 TArray<USoundCue*> SoundList; 139 SoundList.Add(Cast<USoundCue>(MenuStyle->StartGameSound.GetResourceObject())); 140 SoundList.Add(Cast<USoundCue>(MenuStyle->ExitGameSound.GetResourceObject())); 141 SoundList.Add(Cast<USoundCue>(MenuStyle->MenuItemChangeSound.GetResourceObject())); 142 143 //添加资源到Map 144 MenuAudioResource.Add(FString("Music"), MusicList); 145 MenuAudioResource.Add(FString("Sound"), SoundList); 146 147 //重制一下声音 148 ResetMenuVolume(MusicVolume, SoundVolume); 149 }
==============================================================================================================================