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() constoverride { 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 }
==============================================================================================================================