1. 程式人生 > 其它 >UE4 從無到有純 C++ & Slate 開發沙盒遊戲(十六) UI音樂播放與調節

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 }

==============================================================================================================================