WwiseBrowserHelpers.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /*******************************************************************************
  2. The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
  3. Technology released in source code form as part of the game integration package.
  4. The content of this file may not be used without valid licenses to the
  5. AUDIOKINETIC Wwise Technology.
  6. Note that the use of the game engine is subject to the Unreal(R) Engine End User
  7. License Agreement at https://www.unrealengine.com/en-US/eula/unreal
  8. License Usage
  9. Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
  10. this file in accordance with the end user license agreement provided with the
  11. software or, alternatively, in accordance with the terms contained
  12. in a written agreement between you and Audiokinetic Inc.
  13. Copyright (c) 2023 Audiokinetic Inc.
  14. *******************************************************************************/
  15. #include "WwiseBrowser/WwiseBrowserHelpers.h"
  16. #include "WwiseUEFeatures.h"
  17. #include "AkAcousticTexture.h"
  18. #include "AkAudioEvent.h"
  19. #include "AkAuxBus.h"
  20. #include "AkRtpc.h"
  21. #include "AkStateValue.h"
  22. #include "AkSwitchValue.h"
  23. #include "AkTrigger.h"
  24. #include "AkEffectShareSet.h"
  25. #include "AkInitBank.h"
  26. #include "WwiseUnrealHelper.h"
  27. #include "AkAssetFactories.h"
  28. #include "AssetManagement/AkAssetDatabase.h"
  29. #include "IAudiokineticTools.h"
  30. #include "AssetRegistry/AssetRegistryModule.h"
  31. #include "AssetToolsModule.h"
  32. #include "Async/Async.h"
  33. #include "Engine/ObjectReferencer.h"
  34. #include "FileHelpers.h"
  35. #include "ObjectTools.h"
  36. #include "PackageTools.h"
  37. #define LOCTEXT_NAMESPACE "AkAudio"
  38. EWwiseItemType::Type WwiseBrowserHelpers::GetTypeFromClass(UClass* Class)
  39. {
  40. if (Class == UAkAudioEvent::StaticClass())
  41. {
  42. return EWwiseItemType::Event;
  43. }
  44. if (Class == UAkAcousticTexture::StaticClass())
  45. {
  46. return EWwiseItemType::AcousticTexture;
  47. }
  48. if (Class == UAkRtpc::StaticClass())
  49. {
  50. return EWwiseItemType::GameParameter;
  51. }
  52. if (Class == UAkStateValue::StaticClass())
  53. {
  54. return EWwiseItemType::State;
  55. }
  56. if (Class == UAkSwitchValue::StaticClass())
  57. {
  58. return EWwiseItemType::Switch;
  59. }
  60. if (Class == UAkTrigger::StaticClass())
  61. {
  62. return EWwiseItemType::Trigger;
  63. }
  64. if (Class == UAkEffectShareSet::StaticClass())
  65. {
  66. return EWwiseItemType::EffectShareSet;
  67. }
  68. if (Class == UAkAuxBus::StaticClass())
  69. {
  70. return EWwiseItemType::AuxBus;
  71. }
  72. if(Class == UAkInitBank::StaticClass())
  73. {
  74. return EWwiseItemType::InitBank;
  75. }
  76. return EWwiseItemType::None;
  77. }
  78. void WwiseBrowserHelpers::FindOrCreateAssetsRecursive(const FWwiseTreeItemPtr& WwiseTreeItem,
  79. TArray<WwiseBrowserAssetPayload>& InOutBrowserAssetPayloads, TSet<FGuid>& InOutKnownGuids,
  80. const EAssetCreationMode AssetCreationMode, const FString& PackagePath, const FString& CurrentRelativePath)
  81. {
  82. FString Name;
  83. UClass* WwiseAssetClass = nullptr;
  84. FString CurrentRelativePackagePath = PackagePath / CurrentRelativePath;
  85. if (WwiseTreeItem->ItemId.IsValid() && InOutKnownGuids.Contains(WwiseTreeItem->ItemId))
  86. {
  87. return;
  88. }
  89. if (WwiseTreeItem->ItemType == EWwiseItemType::Event)
  90. {
  91. Name = WwiseTreeItem->DisplayName;
  92. WwiseAssetClass = UAkAudioEvent::StaticClass();
  93. }
  94. if (WwiseTreeItem->ItemType == EWwiseItemType::AcousticTexture)
  95. {
  96. Name = WwiseTreeItem->DisplayName;
  97. WwiseAssetClass = UAkAcousticTexture::StaticClass();
  98. }
  99. else if (WwiseTreeItem->ItemType == EWwiseItemType::AuxBus)
  100. {
  101. Name = WwiseTreeItem->DisplayName;
  102. WwiseAssetClass = UAkAuxBus::StaticClass();
  103. for (FWwiseTreeItemPtr Child : WwiseTreeItem->GetChildren())
  104. {
  105. FString NewRelativePath = CurrentRelativePath / WwiseTreeItem->DisplayName;
  106. FindOrCreateAssetsRecursive(Child, InOutBrowserAssetPayloads, InOutKnownGuids, AssetCreationMode, PackagePath, NewRelativePath);
  107. }
  108. }
  109. else if (WwiseTreeItem->ItemType == EWwiseItemType::GameParameter)
  110. {
  111. Name = WwiseTreeItem->DisplayName;
  112. WwiseAssetClass = UAkRtpc::StaticClass();
  113. }
  114. else if (WwiseTreeItem->ItemType == EWwiseItemType::State)
  115. {
  116. Name = WwiseTreeItem->GetSwitchAssetName();
  117. WwiseAssetClass = UAkStateValue::StaticClass();
  118. }
  119. else if (WwiseTreeItem->ItemType == EWwiseItemType::Switch)
  120. {
  121. Name = WwiseTreeItem->GetSwitchAssetName();
  122. WwiseAssetClass = UAkSwitchValue::StaticClass();
  123. }
  124. else if (WwiseTreeItem->ItemType == EWwiseItemType::Trigger)
  125. {
  126. Name = WwiseTreeItem->DisplayName;
  127. WwiseAssetClass = UAkTrigger::StaticClass();
  128. }
  129. else if (WwiseTreeItem->ItemType == EWwiseItemType::EffectShareSet)
  130. {
  131. Name = WwiseTreeItem->DisplayName;
  132. WwiseAssetClass = UAkEffectShareSet::StaticClass();
  133. }
  134. else if (WwiseTreeItem->IsFolder())
  135. {
  136. //Add object to prevent Drag and Drop in the world.
  137. WwiseAssetClass = UAkDragDropBlocker::StaticClass();
  138. Name = WwiseTreeItem->DisplayName;
  139. for (FWwiseTreeItemPtr Child : WwiseTreeItem->GetChildren())
  140. {
  141. FString NewRelativePath = CurrentRelativePath / Name;
  142. FindOrCreateAssetsRecursive(Child, InOutBrowserAssetPayloads, InOutKnownGuids, AssetCreationMode, PackagePath, NewRelativePath);
  143. }
  144. }
  145. if (WwiseAssetClass)
  146. {
  147. TArray<FAssetData> SearchResults;
  148. WwiseBrowserAssetPayload Payload;
  149. AkAssetDatabase::Get().FindAssetsByGuidAndClass(WwiseTreeItem->ItemId, WwiseAssetClass, Payload.ExistingAssets);
  150. if (Payload.ExistingAssets.Num() == 0)
  151. {
  152. Payload.CreatedAsset = CreateBrowserAsset(Name, WwiseTreeItem, WwiseAssetClass, AssetCreationMode, PackagePath / CurrentRelativePath);
  153. }
  154. Payload.Name = UPackageTools::SanitizePackageName(Name);
  155. Payload.RelativePackagePath = UPackageTools::SanitizePackageName(CurrentRelativePath);
  156. Payload.WwiseObjectGuid = WwiseTreeItem->ItemId;
  157. InOutBrowserAssetPayloads.Add(Payload);
  158. InOutKnownGuids.Add(WwiseTreeItem->ItemId);
  159. }
  160. }
  161. FAssetData WwiseBrowserHelpers::CreateBrowserAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, const EAssetCreationMode AssetCreationMode, const FString& PackagePath)
  162. {
  163. //We shouldn't call NewObject outside of the game thread
  164. if (!IsInGameThread())
  165. {
  166. AsyncTask(ENamedThreads::GameThread, [AssetName, WwiseTreeItem, AssetClass, AssetCreationMode, PackagePath]
  167. {
  168. CreateBrowserAssetTask(AssetName, WwiseTreeItem, AssetClass, AssetCreationMode, PackagePath);
  169. });
  170. // Spoof the FAssetData for the asset that will be created asynchronously
  171. const FString SanitizedName = UPackageTools::SanitizePackageName(AssetName);
  172. //Folder asset are always created in the transient package
  173. if (AssetCreationMode == EAssetCreationMode::Transient || WwiseTreeItem->IsFolder())
  174. {
  175. FString AssetPackage = UPackageTools::SanitizePackageName(GetTransientPackage()->GetPathName() / AssetClass->GetName());
  176. #if UE_5_1_OR_LATER
  177. return FAssetData(FName(AssetPackage), FName(GetTransientPackage()->GetPathName()), FName(*SanitizedName), AssetClass->GetClassPathName());
  178. #else
  179. return FAssetData(FName(AssetPackage), FName(GetTransientPackage()->GetPathName()), FName(*SanitizedName), AssetClass->GetFName());
  180. #endif
  181. }
  182. else if (AssetCreationMode == EAssetCreationMode::InPackage)
  183. {
  184. FString AssetPackage = UPackageTools::SanitizePackageName(PackagePath / AssetName);
  185. #if UE_5_1_OR_LATER
  186. return FAssetData(FName(AssetPackage), FName(PackagePath), FName(*SanitizedName), AssetClass->GetClassPathName());
  187. #else
  188. return FAssetData(FName(AssetPackage), FName(PackagePath), FName(*SanitizedName), AssetClass->GetFName());
  189. #endif
  190. }
  191. }
  192. return CreateBrowserAssetTask(AssetName, WwiseTreeItem, AssetClass, AssetCreationMode, PackagePath);
  193. }
  194. FAssetData WwiseBrowserHelpers::CreateBrowserAssetTask(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, const EAssetCreationMode AssetCreationMode, const FString& PackagePath)
  195. {
  196. if (!ensureMsgf(IsInGameThread(), TEXT("WwiseBrowserHelpers::CreateBrowserAsset : Not in the Game thread. Assets will not be created.")))
  197. {
  198. return {};
  199. }
  200. //Folder asset are always created in the transient package
  201. if (AssetCreationMode == EAssetCreationMode::Transient || WwiseTreeItem->IsFolder())
  202. {
  203. return CreateTransientAsset(AssetName, WwiseTreeItem, AssetClass);
  204. }
  205. else //if (AssetCreationMode == EAssetCreationMode::InPackage)
  206. {
  207. return CreateAssetInPackage(AssetName, WwiseTreeItem, PackagePath, AssetClass);
  208. }
  209. }
  210. FAssetData WwiseBrowserHelpers::CreateTransientAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass)
  211. {
  212. //Create a sub-package in transient to avoid asset name collisions for different wwise object type
  213. const FString PackageName = UPackageTools::SanitizePackageName(GetTransientPackage()->GetPathName() / AssetClass->GetName());
  214. UPackage* Pkg = CreatePackage(*PackageName);
  215. UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Creating new temporary %s asset for Drag operation in '%s' in '%s'."), *AssetClass->GetName(), *AssetName, *PackageName);
  216. return CreateAsset(AssetName, WwiseTreeItem, AssetClass, Pkg);
  217. }
  218. FAssetData WwiseBrowserHelpers::CreateAssetInPackage(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, const FString& PackagePath, UClass* AssetClass)
  219. {
  220. const FString PackageName = UPackageTools::SanitizePackageName(PackagePath / AssetName);
  221. UPackage* Pkg = CreatePackage(*PackageName);
  222. UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Creating new %s asset '%s' in '%s'."), *AssetClass->GetName(), *AssetName, *PackageName);
  223. return CreateAsset(AssetName, WwiseTreeItem, AssetClass, Pkg);
  224. }
  225. FAssetData WwiseBrowserHelpers::CreateAsset(const FString& AssetName, const FWwiseTreeItemPtr& WwiseTreeItem, UClass* AssetClass, UPackage* Pkg)
  226. {
  227. // Verify the asset class
  228. if (!ensureMsgf(AssetClass, TEXT("The new asset '%s' wasn't created due to a problem finding the appropriate class for the new asset.")))
  229. {
  230. return nullptr;
  231. }
  232. const auto Factory = GetAssetFactory(WwiseTreeItem);
  233. const FString SanitizedName = UPackageTools::SanitizePackageName(AssetName);
  234. UObject* NewObj = nullptr;
  235. EObjectFlags Flags = RF_Public | RF_Transactional | RF_Standalone;
  236. if (Factory)
  237. {
  238. NewObj = Factory->FactoryCreateNew(AssetClass, Pkg, FName(*SanitizedName), Flags, nullptr, GWarn);
  239. }
  240. else if (AssetClass)
  241. {
  242. NewObj = NewObject<UObject>(Pkg, AssetClass, FName(*SanitizedName), Flags);
  243. }
  244. return FAssetData(NewObj);
  245. }
  246. void WwiseBrowserHelpers::SaveSelectedAssets(TArray<WwiseBrowserAssetPayload> Assets, const FString& RootPackagePath, const EAssetCreationMode AssetCreationMode, const EAssetDuplicationMode AssetDuplicationMode)
  247. {
  248. //It is probably dangerous to manipulate UObjects (rename/delete/duplicate) outside of the game thread
  249. if (!IsInGameThread())
  250. {
  251. AsyncTask(ENamedThreads::GameThread, [Assets, RootPackagePath, AssetCreationMode, AssetDuplicationMode]
  252. {
  253. SaveSelectedAssetsTask(Assets, RootPackagePath, AssetCreationMode, AssetDuplicationMode);
  254. });
  255. return;
  256. }
  257. SaveSelectedAssetsTask(Assets, RootPackagePath, AssetCreationMode, AssetDuplicationMode);
  258. }
  259. void WwiseBrowserHelpers::SaveSelectedAssetsTask(TArray<WwiseBrowserAssetPayload> Assets, const FString& RootPackagePath, const EAssetCreationMode AssetCreationMode, const EAssetDuplicationMode AssetDuplicationMode)
  260. {
  261. if (!ensureMsgf(IsInGameThread(), TEXT("WwiseBrowserHelpers::SaveSelectedAssets : Not in the Game thread. Assets will not be saved or moved.")))
  262. {
  263. return;
  264. }
  265. auto AssetToolsModule = &FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
  266. TArray<FAssetRenameData> AssetsToRename;
  267. TArray<FAssetData> AssetsToDelete;
  268. TArray<FAssetData> RenamedTransientAssets;
  269. TArray<UPackage*> PackagesToSave;
  270. for (WwiseBrowserAssetPayload& AssetResult : Assets)
  271. {
  272. const bool bPreExisting = AssetResult.ExistingAssets.Num() > 0;
  273. FString Path = AssetResult.RelativePackagePath;
  274. FString PackagePath = RootPackagePath;
  275. if (!Path.IsEmpty())
  276. {
  277. PackagePath = UPackageTools::SanitizePackageName(PackagePath / Path);
  278. }
  279. FString NewAssetPath = PackagePath / AssetResult.Name;
  280. if (bPreExisting)
  281. {
  282. if (AssetDuplicationMode == EAssetDuplicationMode::NoDuplication)
  283. {
  284. continue;
  285. }
  286. //Make sure none of the existing assets would be overwritten by the new asset we want to duplicate
  287. bool bMatch = false;
  288. for (auto ExistingAsset : AssetResult.ExistingAssets)
  289. {
  290. if (ExistingAsset.PackageName.ToString() == NewAssetPath)
  291. {
  292. bMatch = true;
  293. break;
  294. }
  295. }
  296. if (bMatch)
  297. {
  298. //Asset already exists, nothing to do
  299. continue;
  300. }
  301. UE_LOG(LogAudiokineticTools, Log, TEXT("Wwise Browser: Duplicating existing asset '%s' into '%s'."), *AssetResult.ExistingAssets[0].GetFullName(), *PackagePath);
  302. auto NewAsset = AssetToolsModule->Get().DuplicateAsset(AssetResult.Name, PackagePath, AssetResult.ExistingAssets[0].GetAsset());
  303. if (NewAsset)
  304. {
  305. PackagesToSave.Add(NewAsset->GetPackage());
  306. }
  307. }
  308. else
  309. {
  310. UObject* NewAsset = AssetResult.CreatedAsset.GetAsset();
  311. if (IsValid(NewAsset))
  312. {
  313. if (NewAsset->IsA<UAkDragDropBlocker>())
  314. {
  315. AssetsToDelete.Add(AssetResult.CreatedAsset);
  316. continue;
  317. }
  318. }
  319. if (AssetCreationMode == EAssetCreationMode::Transient)
  320. {
  321. //Drag/Drop assets are created in transient package, and we need to move them to the drop location (or the default folder)
  322. FAssetRenameData NewAssetRenameData(NewAsset, PackagePath, AssetResult.Name);
  323. AssetsToRename.Add(NewAssetRenameData);
  324. RenamedTransientAssets.Add(AssetResult.CreatedAsset);
  325. UE_LOG(LogAudiokineticTools, Verbose, TEXT("Wwise Browser: Temporary asset '%s' will be moved to '%s'."), *NewAsset->GetFullName(), *PackagePath);
  326. }
  327. else if (AssetCreationMode == EAssetCreationMode::InPackage)
  328. {
  329. UE_LOG(LogAudiokineticTools, Verbose, TEXT("Wwise Browser: Saving new asset '%s'."), *NewAsset->GetFullName());
  330. //Assets were created in the destination package but we still need to save them
  331. PackagesToSave.Add(NewAsset->GetPackage());
  332. }
  333. }
  334. }
  335. if (AssetsToRename.Num() > 0)
  336. {
  337. bool bRenameSuccess = AssetToolsModule->Get().RenameAssets(AssetsToRename);
  338. //We really don't want to leave assets hanging around in the transient package
  339. if (!bRenameSuccess && AssetCreationMode == EAssetCreationMode::Transient)
  340. {
  341. TArray<UObject*> ObjectsToDelete;
  342. for (auto Asset : RenamedTransientAssets)
  343. {
  344. if (auto TransientObject = Asset.GetAsset())
  345. {
  346. ObjectsToDelete.Add(TransientObject);
  347. UE_LOG(LogAudiokineticTools, Warning, TEXT("Wwise Browser: Failed to rename temporary asset '%s' created in Drag/Drop, it will be deleted."), *Asset.GetFullName());
  348. }
  349. }
  350. ObjectTools::ForceDeleteObjects(ObjectsToDelete);
  351. }
  352. }
  353. if (AssetsToDelete.Num() > 0)
  354. {
  355. UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Deleting '%d' temporary packages."), PackagesToSave.Num());
  356. ObjectTools::DeleteAssets(AssetsToDelete, false);
  357. }
  358. if (PackagesToSave.Num() > 0)
  359. {
  360. UE_LOG(LogAudiokineticTools, VeryVerbose, TEXT("Wwise Browser: Saving '%d' new packages."), PackagesToSave.Num());
  361. UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true);
  362. }
  363. }
  364. UAkAssetFactory* WwiseBrowserHelpers::GetAssetFactory(const FWwiseTreeItemPtr& WwiseTreeItem)
  365. {
  366. UFactory* Factory = nullptr;
  367. switch (WwiseTreeItem->ItemType)
  368. {
  369. case EWwiseItemType::Event:
  370. Factory = UAkAudioEventFactory::StaticClass()->GetDefaultObject<UFactory>();
  371. break;
  372. case EWwiseItemType::AcousticTexture:
  373. Factory = UAkAcousticTextureFactory::StaticClass()->GetDefaultObject<UFactory>();
  374. break;
  375. case EWwiseItemType::AuxBus:
  376. Factory = UAkAuxBusFactory::StaticClass()->GetDefaultObject<UFactory>();
  377. break;
  378. case EWwiseItemType::GameParameter:
  379. Factory = UAkRtpcFactory::StaticClass()->GetDefaultObject<UFactory>();
  380. break;
  381. case EWwiseItemType::Switch:
  382. Factory = UAkSwitchValueFactory::StaticClass()->GetDefaultObject<UFactory>();
  383. break;
  384. case EWwiseItemType::State:
  385. Factory = UAkStateValueFactory::StaticClass()->GetDefaultObject<UFactory>();
  386. break;
  387. case EWwiseItemType::Trigger:
  388. Factory = UAkTriggerFactory::StaticClass()->GetDefaultObject<UFactory>();
  389. break;
  390. case EWwiseItemType::EffectShareSet:
  391. Factory = UAkEffectShareSetFactory::StaticClass()->GetDefaultObject<UFactory>();
  392. break;
  393. default:
  394. return nullptr;
  395. }
  396. if (Factory)
  397. {
  398. if (auto AkAssetFactory = Cast<UAkAssetFactory>(Factory))
  399. {
  400. AkAssetFactory->AssetID = WwiseTreeItem->ItemId;
  401. AkAssetFactory->WwiseObjectName = WwiseTreeItem->DisplayName;
  402. return AkAssetFactory;
  403. }
  404. }
  405. return nullptr;
  406. }
  407. bool WwiseBrowserHelpers::CanCreateAsset(const FWwiseTreeItemPtr& Item)
  408. {
  409. return !Item->IsOfType({ EWwiseItemType::Sound });
  410. }
  411. FLinearColor WwiseBrowserHelpers::GetTextColor(bool bUpToDate)
  412. {
  413. FColor Color;
  414. return bUpToDate ? FLinearColor::Gray : FLinearColor(1.f, 0.33f, 0);
  415. }
  416. #undef LOCTEXT_NAMESPACE