From 3f2cd27c5dfb3b450245bf1a37fc1b3414031c7c Mon Sep 17 00:00:00 2001
From: yyl <yyl>
Date: 星期三, 11 二月 2026 11:03:58 +0800
Subject: [PATCH] 小游戏适配 资源系统改造
---
Main/System/TianziBillborad/TianziBillboradBossHead.cs | 47
Main/System/AssetVersion/DownLoadAndDiscompressHotTask.cs | 4
Main/System/InternalAffairs/GoldRushLeader.cs | 2
Main/System/KnapSack/Logic/CommonGetItemWin.cs | 2
Main/System/HappyXB/HeroCallResultWin.cs | 1
Main/ResModule/PlatformCacheHelper.cs | 86
Main/ResModule/UILoader.cs | 77
Main/System/Guild/GuildBossWin.cs | 4
Main/System/Message/RichText.cs | 22
Main/Utility/ComponentExtersion.cs | 91 +
Main/System/Main/HeroFightingCardCell.cs | 84
Main/System/HeroUI/HeroScenePosCell.cs | 1
Main/System/Arena/ArenaHeroHead.cs | 24
Main/System/Gubao/GubaoCallCell.cs | 4
Main/Tests/Main.Tests.asmdef | 28
Main/ResModule/ResourcePreloader.cs | 196 ++
Main/Utility/MaterialUtility.cs | 37
Main/System/InternalAffairs/AffairBaseWin.cs | 1
Main/System/Sound/SoundPlayer.cs | 29
Main/Tests/YooAssetServiceTests.cs | 211 ++
Main/Editor/WebGLBuildOptimizer.cs.meta | 11
Main/Tests/Main.Tests.asmdef.meta | 7
Main/System/Debug/DebugUtility.cs | 12
Main/System/HappyXB/HeroCallResultCell.cs | 1
Main/Tests/ResourcePreloaderTests.cs | 101 +
Main/Core/GameEngine/Launch/YooAssetInitTask.cs.meta | 11
Main/System/LineupRecommend/LineupRecommendItem.cs | 57
Main/Core/ResModule/ScriptableObjectLoader.cs | 36
Main/Editor/WebGLBuildOptimizer.cs | 78
Main/ResModule/IResourcePreloader.cs | 60
Main/System/Login/LoginWin.cs | 52
Main/System/Equip/EquipExchangeCell.cs | 2
Main/Tests/ResourceCacheManagerTests.cs.meta | 11
Main/System/Equip/EquipTipWin.cs | 1
Main/Utility/UIUtility.cs | 15
Main/System/Battle/AsyncResourceGuard.cs.meta | 11
Main/System/Attribute/TotalAttributeWin.cs | 2
Main/System/OtherPlayerDetail/OtherHeroFightingCardItem.cs | 18
Main/ResModule/YooAssetPackageConfig.cs | 184 ++
Main/System/HeroUI/HeroTrainWin.cs | 2
Main/System/BattleDetail/BattleDetailHeroInfoItem.cs | 45
Main/Core/GameEngine/Launch/BuiltInAssetCopyTask.cs | 3
Main/Manager/StageManager.cs | 119 +
Main/ResModule/BuiltInLoader.cs | 110 +
Main/System/Horse/HorseController.cs | 79
Main/System/Battle/UIComp/SkillTips.cs | 63
Main/System/Battle/BattleHUDWin.cs | 8
Main/Tests.meta | 8
Main/ResModule/RemoteServicesImpl.cs.meta | 11
Main/Core/GameEngine/Launch/LaunchInHot.cs | 7
Main/ResModule/AudioLoader.cs | 7
Main/System/HeroUI/HeroPosWin.cs | 2
Main/System/Guild/GuildBaseWin.cs | 1
Main/Utility/FontUtility.cs | 27
Main/Tests/YooAssetServiceTests.cs.meta | 11
Main/ResModule/YooAssetService.cs | 677 +++++++
Main/System/Battle/AsyncResourceGuard.cs | 141 +
Main/System/Battle/UIComp/TotalDamageDisplayer.cs | 74
Main/Core/GameEngine/Launch/YooAssetInitTask.cs | 96 +
Main/ResModule/IYooAssetService.cs | 164 +
Main/ResModule/ResManager.cs | 206 +
Main/System/Battle/BattleField/StoryBattleField.cs | 15
Main/ResModule/YooAssetPackageConfig.cs.meta | 11
Main/System/Battle/SkillEffect/SkillEffectFactory.cs | 2
Main/System/ItemTip/SmallTipWin.cs | 1
Main/System/OtherPlayerDetail/OtherHeroDetailWin.cs | 2
Main/ResModule/PlatformCacheHelper.cs.meta | 11
Main/System/OtherPlayerDetail/OtherEquipTipWin.cs | 1
Main/System/PhantasmPavilion/PhantasmPavilionManager.cs | 36
Main/Tests/ResourcePreloaderTests.cs.meta | 11
Main/System/Recharge/PrivilegeActiveCardWin.cs | 1
Main/Manager/UIManager.cs | 243 ++
Main/ResModule/ResourceCacheManager.cs | 292 +++
Main/System/HeroUI/HeroHeadBaseNoTrainCell.cs | 21
Main/ResModule/IResourcePreloader.cs.meta | 11
Main/System/Battle/BattleLoadingWin.cs | 165 +
Main/System/UIBase/UIBase.cs | 3
Main/System/HeroUI/HeroGiftWashWin.cs | 1
Main/System/Main/EquipOnMainUI.cs | 1
Main/System/PhantasmPavilion/PhantasmPavilionModelItem.cs | 45
Main/ResModule/IResourceCache.cs.meta | 11
Main/System/Battle/Sound/BattleSoundManager.cs | 19
Main/System/HeroUI/HeroHeadBaseCell.cs | 63
Main/System/NewBieGuidance/NewBieWin.cs | 3
Main/System/Battle/BattleLoadingWin.cs.meta | 11
Main/Core/GameEngine/Launch/AssetBundleInitTask.cs | 6
Main/System/Battle/UIComp/BossHeadCell.cs | 18
Main/System/OtherPlayerDetail/OtherNPCDetailWin.cs | 2
Main/Config/ConfigManager.cs | 42
Main/System/Battle/BattleObject/BattleObjectFactory.cs | 64
Main/System/Message/ImgAnalysis.cs | 41
Main/System/Equip/FloorItemCell.cs | 1
Main/System/InternalAffairs/GoldRushTentCell.cs | 1
Main/System/HeroUI/HeroConnectionHeadCell.cs | 39
Main/System/Gubao/GubaoCallWin.cs | 2
Main/System/Battle/BattleField/BattleField.cs | 45
Main/ResModule/IResourceCache.cs | 60
Main/System/Main/HomeWin.cs | 1
Main/ResModule/IYooAssetService.cs.meta | 11
Main/System/Guild/GuildManager.cs | 1
Main/Tests/ResourceCacheManagerTests.cs | 99 +
Main/System/Tip/ScrollTip.cs | 16
Main/System/Hero/UIHeroController.cs | 138 +
Main/System/Battle/BattleManager.cs | 25
Main/ResModule/ResourcePreloader.cs.meta | 11
Main/Main.asmdef | 4
Main/System/HeroUI/HeroBestWin.cs | 2
Main/ResModule/YooAssetService.cs.meta | 11
Main/ResModule/AssetBundle/AssetBundleUtility.cs | 5
Main/ResModule/ResourceCacheManager.cs.meta | 11
Main/ResModule/RemoteServicesImpl.cs | 90 +
Main/System/HeroUI/HeroSkillWin.cs | 2
Main/Editor.meta | 8
Main/System/Tip/ScrollTipWin.cs | 1
Main/Utility/ShaderUtility.cs | 16
115 files changed, 5,116 insertions(+), 119 deletions(-)
diff --git a/Main/Config/ConfigManager.cs b/Main/Config/ConfigManager.cs
index dfac8d5..1f6c3ba 100644
--- a/Main/Config/ConfigManager.cs
+++ b/Main/Config/ConfigManager.cs
@@ -22,7 +22,7 @@
public override void Init()
{
base.Init();
- InitConfigs();
+ InitConfigs().Forget();
}
public virtual async UniTask InitConfigs()
@@ -181,7 +181,9 @@
{
configName = configName.Substring(0, configName.Length - 6);
}
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, use LoadConfigByTypeAsync
string[] texts = ResManager.Instance.LoadConfig(configName);
+ #pragma warning restore CS0618
if (texts != null)
{
string[] lines = texts;
@@ -208,11 +210,47 @@
}
}
+ /// <summary>
+ /// US2: Async variant of LoadConfigByType. Uses UniTask-based config loading.
+ /// </summary>
+ public async UniTask LoadConfigByTypeAsync(Type configType)
+ {
+ string configName = configType.Name;
+ if (configName.EndsWith("Config"))
+ {
+ configName = configName.Substring(0, configName.Length - 6);
+ }
+ string[] texts = await ResManager.Instance.LoadConfigAsync(configName);
+ if (texts != null)
+ {
+ var methodInfo = configType.GetMethod("Init", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.FlattenHierarchy);
+ if (methodInfo != null)
+ {
+ methodInfo.Invoke(null, new object[] { texts });
+ var isInitField = configType.GetField("isInit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
+ if (isInitField != null)
+ {
+ isInitField.SetValue(null, true);
+ }
+ }
+ else
+ {
+ Debug.LogError($"閰嶇疆绫� {configType.Name} 娌℃湁闈欐�両nit鏂规硶");
+ }
+ }
+ else
+ {
+ Debug.LogError($"鎵句笉鍒伴厤缃枃浠�: {configName}");
+ }
+ }
+
private async UniTask LoadConfig<T>() where T : class
{
string configName = typeof(T).Name;
+ #pragma warning disable CS0618
string[] texts = ResManager.Instance.LoadConfig(configName);
+ #pragma warning restore CS0618
if (texts != null)
{
string[] lines = texts;
@@ -453,7 +491,9 @@
if (configName.EndsWith("Config"))
configName = configName.Substring(0, configName.Length - 6);
+ #pragma warning disable CS0618
string[] texts = ResManager.Instance.LoadConfig(configName);
+ #pragma warning restore CS0618
if (texts != null)
{
string[] lines = texts;
diff --git a/Main/Core/GameEngine/Launch/AssetBundleInitTask.cs b/Main/Core/GameEngine/Launch/AssetBundleInitTask.cs
index 6c9e2f0..65965b8 100644
--- a/Main/Core/GameEngine/Launch/AssetBundleInitTask.cs
+++ b/Main/Core/GameEngine/Launch/AssetBundleInitTask.cs
@@ -2,6 +2,12 @@
using System.Collections.Generic;
using System.IO;
using UnityEngine;
+
+/// <summary>
+/// [OBSOLETE] 宸茶 YooAssetInitTask 鏇夸唬銆�
+/// 姝ょ被涓嶅啀鍦ㄥ惎鍔ㄦ祦姘寸嚎涓娇鐢ㄣ�備繚鐣欎粎渚涘巻鍙插弬鑰冦��
+/// </summary>
+[Obsolete("Replaced by YooAssetInitTask. This class is no longer in the startup pipeline.")]
public class AssetBundleInitTask : LaunchTask
{
public override float expectTime
diff --git a/Main/Core/GameEngine/Launch/BuiltInAssetCopyTask.cs b/Main/Core/GameEngine/Launch/BuiltInAssetCopyTask.cs
index 19afb39..0a1e816 100644
--- a/Main/Core/GameEngine/Launch/BuiltInAssetCopyTask.cs
+++ b/Main/Core/GameEngine/Launch/BuiltInAssetCopyTask.cs
@@ -54,7 +54,8 @@
{
if (AssetSource.isUseAssetBundle)
{
- AssetBundleUtility.Instance.InitBuiltInAsset();
+ // YooAsset 宸插湪 Launch 闃舵鍒濆鍖栧唴缃祫婧愶紝涓嶅啀闇�瑕� AssetBundleUtility.InitBuiltInAsset()
+ // YooAssetInitializer.Instance.DefaultPackage 宸插寘鍚唴缃祫婧�
LaunchInHot.Instance.InitSystemMgr();
diff --git a/Main/Core/GameEngine/Launch/LaunchInHot.cs b/Main/Core/GameEngine/Launch/LaunchInHot.cs
index 740a69b..4c16094 100644
--- a/Main/Core/GameEngine/Launch/LaunchInHot.cs
+++ b/Main/Core/GameEngine/Launch/LaunchInHot.cs
@@ -48,7 +48,7 @@
var getVersionInfoTask = new GetVersionInfoTask();
var checkAssetValidTask = new CheckAssetValidTask();
var downLoadAssetTask = new DownLoadAssetTask();
- var assetBundleInitTask = new AssetBundleInitTask();
+ // AssetBundleInitTask removed 鈥� replaced by YooAssetInitTask
var configInitTask = new ConfigInitTask();
var launchFadeOutTask = new LaunchFadeOutTask();
@@ -85,7 +85,10 @@
tasks.Enqueue(checkAssetValidTask);
tasks.Enqueue(downLoadAssetTask);
- tasks.Enqueue(assetBundleInitTask);
+ // US1: Add YooAsset initialization task 鈥� replaces AssetBundleInitTask
+ var yooAssetInitTask = new YooAssetInitTask();
+ tasks.Enqueue(yooAssetInitTask);
+ // AssetBundleInitTask removed 鈥� YooAssetInitTask handles all resource system initialization
tasks.Enqueue(configInitTask);
tasks.Enqueue(launchFadeOutTask);
diff --git a/Main/Core/GameEngine/Launch/YooAssetInitTask.cs b/Main/Core/GameEngine/Launch/YooAssetInitTask.cs
new file mode 100644
index 0000000..a1f8d39
--- /dev/null
+++ b/Main/Core/GameEngine/Launch/YooAssetInitTask.cs
@@ -0,0 +1,96 @@
+// ============================================================================
+// YooAssetInitTask.cs 鈥� YooAsset 鍒濆鍖栧惎鍔ㄤ换鍔�
+// 鍦� LaunchInHot 鐨勫惎鍔ㄦ祦姘寸嚎涓垵濮嬪寲 YooAsset锛屼笌 AssetBundleInitTask 骞惰/鏇夸唬
+// T013: Register YooAssetService as IYooAssetBridge
+// ============================================================================
+
+using Cysharp.Threading.Tasks;
+using ProjSG.Resource;
+using UnityEngine;
+using YooAsset;
+
+public class YooAssetInitTask : LaunchTask
+{
+ private bool _initStarted = false;
+ private bool _initCompleted = false;
+
+ public override float expectTime
+ {
+ get { return LocalSave.GetFloat("YooAssetInitTask_ExpectTime", 1f); }
+ protected set { LocalSave.SetFloat("YooAssetInitTask_ExpectTime", value); }
+ }
+
+ public override void Begin()
+ {
+ LaunchInHot.m_CurrentStage = LaunchStage.AssetBundleInit;
+ duration = Mathf.Max(0.5f, expectTime);
+
+ if (!_initStarted)
+ {
+ _initStarted = true;
+ RunInitAsync().Forget();
+ }
+ }
+
+ private async UniTaskVoid RunInitAsync()
+ {
+ try
+ {
+ // Determine play mode based on AssetSource setting
+ EPlayMode playMode;
+ if (!AssetSource.isUseAssetBundle)
+ {
+#if UNITY_EDITOR
+ playMode = EPlayMode.EditorSimulateMode;
+#else
+ playMode = EPlayMode.OfflinePlayMode;
+#endif
+ }
+ else
+ {
+ playMode = EPlayMode.HostPlayMode;
+ }
+
+ // Initialize YooAssetService
+ await YooAssetService.Instance.InitializeAsync(playMode);
+
+ // Register as IYooAssetBridge for Launch assembly cross-assembly access
+ YooAssetBridgeHolder.Register(YooAssetService.Instance);
+
+ // US4 T042: Register default preload configs and execute StartupEssential preload
+ ResourcePreloader.Instance.RegisterDefaultConfigs();
+ await ResourcePreloader.Instance.PreloadAsync("StartupEssential");
+
+ Debug.Log("[YooAssetInitTask] YooAssetService initialized, bridge registered, StartupEssential preloaded.");
+ _initCompleted = true;
+ }
+ catch (System.Exception ex)
+ {
+ Debug.LogError($"[YooAssetInitTask] Failed: {ex}");
+ _initCompleted = true; // Mark done even on failure to avoid blocking pipeline
+ }
+ }
+
+ public override void Update()
+ {
+ timer += Time.deltaTime;
+
+ if (_initCompleted)
+ {
+ done = true;
+ progress = 1f;
+ }
+ else
+ {
+ progress = Mathf.Clamp01(timer / duration);
+ }
+
+ ExceptionReport();
+ }
+
+ public override void End()
+ {
+ expectTime = timer;
+ Debug.LogFormat("{0}鎵ц鏃堕暱锛歿1}锛�", GetType().Name, timer);
+ }
+}
diff --git a/Main/Core/GameEngine/Launch/YooAssetInitTask.cs.meta b/Main/Core/GameEngine/Launch/YooAssetInitTask.cs.meta
new file mode 100644
index 0000000..ff31a86
--- /dev/null
+++ b/Main/Core/GameEngine/Launch/YooAssetInitTask.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 05b7ea8c552919b4fa4463756adbd74c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Core/ResModule/ScriptableObjectLoader.cs b/Main/Core/ResModule/ScriptableObjectLoader.cs
index 805f15b..bf11edc 100644
--- a/Main/Core/ResModule/ScriptableObjectLoader.cs
+++ b/Main/Core/ResModule/ScriptableObjectLoader.cs
@@ -1,5 +1,7 @@
锘縰sing UnityEngine;
using System;
+using Cysharp.Threading.Tasks;
+using ProjSG.Resource;
#if UNITY_EDITOR
using UnityEditor;
@@ -32,8 +34,8 @@
else
{
var assetName = StringUtility.Concat(SoNewBieGuide_Suffix, _id.ToString());
- var assetInfo = new AssetInfo(bundleName, assetName);
- config = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo) as NewBieGuideScriptableObject;
+ var assetPath = StringUtility.Concat("Assets/ResourcesOut/ScriptableObject/NewBieGuide/", assetName);
+ config = YooAssetService.Instance.LoadAssetSync<NewBieGuideScriptableObject>(assetPath);
}
if (config == null)
@@ -44,5 +46,35 @@
return config;
}
+ public static async UniTask<NewBieGuideScriptableObject> LoadSoNewBieGuideStepAsync(int _id)
+ {
+ NewBieGuideScriptableObject config = null;
+ if (!AssetSource.isUseAssetBundle)
+ {
+#if UNITY_EDITOR
+ var resourcePath = StringUtility.Concat(ResourcesPath.ResourcesOutAssetPath,
+ "ScriptableObject/NewBieGuide/",
+ SoNewBieGuide_Suffix,
+ _id.ToString(),
+ ".asset");
+
+ config = AssetDatabase.LoadAssetAtPath<NewBieGuideScriptableObject>(resourcePath);
+#endif
+ }
+ else
+ {
+ var assetName = StringUtility.Concat(SoNewBieGuide_Suffix, _id.ToString());
+ var assetPath = StringUtility.Concat("Assets/ResourcesOut/ScriptableObject/NewBieGuide/", assetName);
+ config = await YooAssetService.Instance.LoadAssetAsync<NewBieGuideScriptableObject>(assetPath);
+ }
+
+ if (config == null)
+ {
+ Debug.LogErrorFormat("ScriptableObjectLoader.LoadSoNewBieGuideStepAsync() => 鍔犺浇涓嶅埌璧勬簮: {0}.", _id);
+ }
+
+ return config;
+ }
+
}
\ No newline at end of file
diff --git a/Main/Editor.meta b/Main/Editor.meta
new file mode 100644
index 0000000..cea5491
--- /dev/null
+++ b/Main/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7424e48c56909d94b813a858d3dc9091
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Editor/WebGLBuildOptimizer.cs b/Main/Editor/WebGLBuildOptimizer.cs
new file mode 100644
index 0000000..b211e18
--- /dev/null
+++ b/Main/Editor/WebGLBuildOptimizer.cs
@@ -0,0 +1,78 @@
+#if UNITY_EDITOR
+// ============================================================================
+// WebGLBuildOptimizer.cs 鈥� WebGL 鏋勫缓浼樺寲璁剧疆
+// US5 T048: 閰嶇疆 IL2CPP 瑁佸壀銆佸帇缂╃瓑浠ョ‘淇濋鍖� 鈮� 4MB
+// ============================================================================
+
+using UnityEditor;
+using UnityEngine;
+
+namespace ProjSG.Resource.Editor
+{
+ /// <summary>
+ /// WebGL 鏋勫缓浼樺寲閰嶇疆宸ュ叿銆�
+ /// 閫氳繃鑿滃崟 "ProjSG/Build/Apply WebGL Optimizations" 搴旂敤銆�
+ /// </summary>
+ public static class WebGLBuildOptimizer
+ {
+ [MenuItem("ProjSG/Build/Apply WebGL Optimizations")]
+ public static void ApplyOptimizations()
+ {
+ // IL2CPP code stripping
+ PlayerSettings.stripEngineCode = true;
+
+ // Managed stripping level 鈥� High for smallest build
+ PlayerSettings.SetManagedStrippingLevel(BuildTargetGroup.WebGL, ManagedStrippingLevel.High);
+
+ // WebGL compression 鈥� Brotli for smallest size
+ PlayerSettings.WebGL.compressionFormat = WebGLCompressionFormat.Brotli;
+
+ // Decompression fallback 鈥� enable for broader server compatibility
+ PlayerSettings.WebGL.decompressionFallback = true;
+
+ // Data caching 鈥� enable for faster subsequent loads
+ PlayerSettings.WebGL.dataCaching = true;
+
+ // Exception support 鈥� minimal for smaller build
+ PlayerSettings.WebGL.exceptionSupport = WebGLExceptionSupport.None;
+
+ // WebGL template 鈥� Minimal
+ PlayerSettings.WebGL.template = "APPLICATION:Minimal";
+
+ // Memory size 鈥� reasonable default (MB)
+ PlayerSettings.WebGL.memorySize = 256;
+
+ Debug.Log("[WebGLBuildOptimizer] Applied WebGL build optimizations for 鈮� 4MB first package.");
+ }
+
+ [MenuItem("ProjSG/Build/Validate First Package Size")]
+ public static void ValidateFirstPackageSize()
+ {
+ string buildPath = "Builds/WebGL";
+ if (!System.IO.Directory.Exists(buildPath))
+ {
+ Debug.LogWarning($"[WebGLBuildOptimizer] Build directory not found: {buildPath}. Build first, then validate.");
+ return;
+ }
+
+ long totalBytes = 0;
+ var files = System.IO.Directory.GetFiles(buildPath, "*", System.IO.SearchOption.AllDirectories);
+ foreach (var file in files)
+ {
+ var info = new System.IO.FileInfo(file);
+ totalBytes += info.Length;
+
+ // Log large files
+ if (info.Length > 500 * 1024) // > 500KB
+ {
+ Debug.Log($" Large file: {info.Name} = {info.Length / 1024f / 1024f:F2} MB");
+ }
+ }
+
+ float totalMB = totalBytes / 1024f / 1024f;
+ string status = totalMB <= 4f ? "PASS" : "FAIL";
+ Debug.Log($"[WebGLBuildOptimizer] First package total: {totalMB:F2} MB 鈥� {status} (target 鈮� 4MB)");
+ }
+ }
+}
+#endif
diff --git a/Main/Editor/WebGLBuildOptimizer.cs.meta b/Main/Editor/WebGLBuildOptimizer.cs.meta
new file mode 100644
index 0000000..9be0ba0
--- /dev/null
+++ b/Main/Editor/WebGLBuildOptimizer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 468b960b2f11c3e4e9fc575cd3653289
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Main.asmdef b/Main/Main.asmdef
index 21be3cf..fcbeea0 100644
--- a/Main/Main.asmdef
+++ b/Main/Main.asmdef
@@ -11,7 +11,9 @@
"GUID:05d41852e29aa5141a64e3d2d5339981",
"GUID:9ad05b610be6c974590152128a8b5b6e",
"GUID:d51b17ee17bf72443860693b4f9c20af",
- "GUID:04376767bc1f3b428aefa3d20743e819"
+ "GUID:04376767bc1f3b428aefa3d20743e819",
+ "GUID:e34a5702dd353724aa315fb8011f08c3",
+ "GUID:1278a46ce459c5a46b4eaeda148684ef"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Main/Manager/StageManager.cs b/Main/Manager/StageManager.cs
index 9b6e0a1..1f56223 100644
--- a/Main/Manager/StageManager.cs
+++ b/Main/Manager/StageManager.cs
@@ -3,6 +3,7 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;
+using ProjSG.Resource;
public enum StageName
{
@@ -37,14 +38,39 @@
{
UIManager.Instance.DestroyAllUI();
+ // US3: Show loading screen FIRST, then load resources with progress
+ LoadingWin loadingWin = UIManager.Instance.OpenWindow<LoadingWin>();
+ InitLoadingWinData(loadingWin);
+
+ // Phase 1 (0% ~ 30%): YooAsset resource preload
if (AssetSource.isUseAssetBundle)
{
- AssetBundleUtility.Instance.Sync_LoadAll("maps/Login");
+ loadingWin.SetProgress(0.05f);
+ await YooAssetService.Instance.LoadAllAssetsAsync<UnityEngine.Object>("Assets/ResourcesOut/maps/Login");
+ loadingWin.SetProgress(0.3f);
}
+ // Phase 2 (30% ~ 60%): Scene loading
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("Login");
+ asyncOperation.allowSceneActivation = false;
- await OnLoading(asyncOperation, ConfigManager.Instance.GetLoadingProgress, Main.InitManagers);
+ while (!asyncOperation.isDone)
+ {
+ if (asyncOperation.progress >= 0.9f)
+ {
+ asyncOperation.allowSceneActivation = true;
+ }
+ loadingWin.SetProgress(0.3f + asyncOperation.progress * 0.3f);
+ await UniTask.Yield();
+ }
+
+ // Phase 3 (60% ~ 100%): Manager initialization
+ await WaitForManagerProgress(loadingWin, 0.6f, 1.0f,
+ ConfigManager.Instance.GetLoadingProgress, Main.InitManagers);
+
+ loadingWin.SetProgress(1f, true);
+ await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
+ loadingWin.CloseWindow();
Main.OnSwitchToLoginScene();
@@ -52,7 +78,6 @@
UIManager.Instance.OpenWindow<LaunchBackGroundWin>();
UIManager.Instance.OpenWindow<LoginWin>();
- // SoundPlayer.Instance.StopBackGroundMusic();
if (VersionUtility.Instance.NeedDownAsset() && !AssetVersionUtility.hasDownLoadFullAsset)
{
@@ -111,15 +136,41 @@
BeforeLoadingGameScene?.Invoke();
- // ResManager.Instance.PrewarmResources();
+ // US3: Show loading screen FIRST, then load resources with progress
+ LoadingWin loadingWin = UIManager.Instance.OpenWindow<LoadingWin>();
+ InitLoadingWinData(loadingWin);
+
+ // Phase 1 (0% ~ 30%): YooAsset resource preload
if (AssetSource.isUseAssetBundle)
{
- AssetBundleUtility.Instance.Sync_LoadAll("maps/Game");
+ loadingWin.SetProgress(0.05f);
+ await YooAssetService.Instance.LoadAllAssetsAsync<UnityEngine.Object>("Assets/ResourcesOut/maps/Game");
+ loadingWin.SetProgress(0.3f);
}
- SoundPlayer.Instance.StopBackGroundMusic();
- AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("Game");
- await OnLoading(asyncOperation, () => (DTC0403_tagPlayerLoginLoadOK.finishedLogin ? .5f : 0f) + GetManagerRequestDataProgress() * .5f);
+ SoundPlayer.Instance.StopBackGroundMusic();
+
+ // Phase 2 (30% ~ 60%): Scene loading
+ AsyncOperation asyncOperation = SceneManager.LoadSceneAsync("Game");
+ asyncOperation.allowSceneActivation = false;
+
+ while (!asyncOperation.isDone)
+ {
+ if (asyncOperation.progress >= 0.9f)
+ {
+ asyncOperation.allowSceneActivation = true;
+ }
+ loadingWin.SetProgress(0.3f + asyncOperation.progress * 0.3f);
+ await UniTask.Yield();
+ }
+
+ // Phase 3 (60% ~ 100%): Manager data ready
+ await WaitForManagerProgress(loadingWin, 0.6f, 1.0f,
+ () => (DTC0403_tagPlayerLoginLoadOK.finishedLogin ? .5f : 0f) + GetManagerRequestDataProgress() * .5f);
+
+ loadingWin.SetProgress(1f, true);
+ await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
+ loadingWin.CloseWindow();
// 鍔犺浇鍒濆鍖栨暟鎹畬鎴�
currentStage = StageName.Game;
@@ -150,18 +201,7 @@
asyncOperation.allowSceneActivation = false;
LoadingWin loadingWin = UIManager.Instance.OpenWindow<LoadingWin>();
-
- LaunchWin launchWin = UIManager.Instance.GetUI<LaunchWin>();
- if (null != launchWin && launchWin.IsActive() && launchWinData == null)
- {
- launchWinData = launchWin.GetData();
- }
-
- if (null != launchWinData)
- {
- loadingWin.SetData(launchWinData);
- launchWinData = null;
- }
+ InitLoadingWinData(loadingWin);
while (!asyncOperation.isDone)
{
@@ -198,6 +238,45 @@
loadingWin.CloseWindow();
}
+ /// <summary>
+ /// US3: 绛夊緟Manager鍒濆鍖栬繘搴﹀苟鏇存柊LoadingWin銆�
+ /// </summary>
+ private async UniTask WaitForManagerProgress(LoadingWin loadingWin, float startPct, float endPct,
+ Func<float> getProgress, Func<UniTask> extraTask = null)
+ {
+ float managerProgress = getProgress();
+
+ while (managerProgress < 1f)
+ {
+ loadingWin.SetProgress(startPct + managerProgress * (endPct - startPct));
+ await UniTask.Yield();
+ managerProgress = getProgress();
+ }
+
+ if (extraTask != null)
+ {
+ await extraTask();
+ }
+ }
+
+ /// <summary>
+ /// US3: 鍒濆鍖� LoadingWin 鏁版嵁锛堜粠 LaunchWin 缁ф壙鑳屾櫙绛夛級銆�
+ /// </summary>
+ private void InitLoadingWinData(LoadingWin loadingWin)
+ {
+ LaunchWin launchWin = UIManager.Instance.GetUI<LaunchWin>();
+ if (launchWin != null && launchWin.IsActive() && launchWinData == null)
+ {
+ launchWinData = launchWin.GetData();
+ }
+
+ if (launchWinData != null)
+ {
+ loadingWin.SetData(launchWinData);
+ launchWinData = null;
+ }
+ }
+
private void OnCloseWindow(UIBase closeUI)
{
if (closeUI is LaunchWin)
diff --git a/Main/Manager/UIManager.cs b/Main/Manager/UIManager.cs
index 2343c08..b574a97 100644
--- a/Main/Manager/UIManager.cs
+++ b/Main/Manager/UIManager.cs
@@ -4,6 +4,7 @@
using UnityEngine;
using System.Linq;
using DG.Tweening;
+using Cysharp.Threading.Tasks;
/// <summary>
/// UI绠$悊鍣� - 璐熻矗绠$悊鎵�鏈塙I鐣岄潰鐨勬樉绀恒�侀殣钘忓拰灞傜骇
@@ -562,7 +563,9 @@
}
else
{
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, use LoadUIResourceAsync
prefab = ResManager.Instance.LoadAsset<GameObject>("UI", uiName);
+ #pragma warning restore CS0618
}
// 妫�鏌ラ鍒朵綋鏄惁鍔犺浇鎴愬姛
@@ -617,6 +620,246 @@
{
return LoadUIResource(uiName) as T;
}
+
+ // ====================================================================
+ // US2: Async variants 鈥� InitUIRootAsync, LoadUIResourceAsync, OpenWindowAsync
+ // ====================================================================
+
+ /// <summary>
+ /// US2: 寮傛鍒濆鍖� UI 鏍硅妭鐐广��
+ /// </summary>
+ public async UniTask InitUIRootAsync()
+ {
+ GameObject root = GameObject.Find("UIRoot");
+ if (root == null)
+ {
+ var prefab = await BuiltInLoader.LoadPrefabAsync("UIRoot");
+ root = GameObject.Instantiate(prefab);
+ root.name = "UIRoot";
+ if (root == null)
+ {
+ Debug.LogError("鏃犳硶鍔犺浇UI鏍硅妭鐐�");
+ return;
+ }
+ GameObject.DontDestroyOnLoad(root);
+ }
+ }
+
+ /// <summary>
+ /// US2: 寮傛鍔犺浇 UI 璧勬簮銆�
+ /// </summary>
+ private async UniTask<UIBase> LoadUIResourceAsync(string uiName)
+ {
+ GameObject prefab;
+ if (uiName == "LaunchWin" || uiName == "DownLoadWin" || uiName == "RequestSecretWin" || uiName == "GameAgeWarnWin")
+ {
+ prefab = await BuiltInLoader.LoadPrefabAsync(uiName);
+ }
+ else
+ {
+ prefab = await ResManager.Instance.LoadAssetAsync<GameObject>("UI", uiName);
+ }
+
+ if (prefab == null)
+ {
+ Debug.LogError($"鍔犺浇UI棰勫埗浣撳け璐�: {uiName}");
+ return null;
+ }
+
+ GameObject uiObject = GameObject.Instantiate(prefab);
+ uiObject.name = uiName;
+
+ Type uiType = Type.GetType(uiName);
+ if (uiType == null)
+ {
+ Debug.LogError($"鎵句笉鍒癠I绫诲瀷: {uiName}");
+ return null;
+ }
+
+ UIBase uiBase = uiObject.GetComponent(uiType) as UIBase;
+ if (uiBase == null)
+ {
+ Debug.LogError($"UI棰勫埗浣� {uiName} 娌℃湁 UIBase 缁勪欢鎴栫被鍨嬩笉鍖归厤");
+ return null;
+ }
+
+ uiBase.uiName = uiName;
+
+ Transform parentTrans = GetTransForLayer(uiBase.uiLayer);
+ uiObject.transform.SetParent(parentTrans, false);
+
+ int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer);
+ uiBase.SetSortingOrder(baseSortingOrder);
+
+ return uiBase;
+ }
+
+ /// <summary>
+ /// US2: 寮傛鎵撳紑绐楀彛銆�
+ /// </summary>
+ public async UniTask<UIBase> OpenWindowAsync(string uiName, int functionOrder = 0)
+ {
+ UIBase returnValue = null;
+ UIBase parentUI = null;
+
+ // Check closed cache
+ if (closedUIDict.TryGetValue(uiName, out var closedUIList) && closedUIList.Count > 0)
+ {
+ returnValue = closedUIList[0] as UIBase;
+ closedUIList.RemoveAt(0);
+ if (closedUIList.Count == 0)
+ {
+ closedUIDict.Remove(uiName);
+ }
+ }
+ else
+ {
+ // US3: Show loading indicator while loading UI prefab (auto-hide after load)
+ ShowLoadingIndicator();
+ try
+ {
+ returnValue = await LoadUIResourceAsync(uiName);
+ }
+ finally
+ {
+ HideLoadingIndicator();
+ }
+
+ if (returnValue == null)
+ {
+ Debug.LogError($"鎵撳紑UI澶辫触: {uiName}");
+ return null;
+ }
+ }
+
+ returnValue.gameObject.SetActive(true);
+
+ if (returnValue.supportParentChildRelation && uiStack.Count > 0 && !returnValue.isMainUI)
+ {
+ parentUI = GetLastSupportParentChildRelationUI();
+ }
+
+ if (parentUI != null)
+ {
+ returnValue.parentUI = parentUI;
+ if (parentUI.childrenUI == null)
+ {
+ parentUI.childrenUI = new List<UIBase>();
+ }
+ parentUI.childrenUI.Add(returnValue);
+ }
+
+ currentRound++;
+ returnValue.lastUsedRound = currentRound;
+ UpdateParentUIRounds(returnValue);
+
+ if (!uiDict.ContainsKey(uiName))
+ {
+ uiDict[uiName] = new List<UIBase>();
+ }
+ uiDict[uiName].Add(returnValue);
+
+ uiStack.Push(returnValue);
+ UpdateUISortingOrder();
+
+ returnValue.functionOrder = functionOrder;
+ returnValue.HandleOpen();
+ OnOpenWindow?.Invoke(returnValue);
+ CheckAndCloseIdleUI();
+
+ return returnValue;
+ }
+
+ /// <summary>
+ /// US2: 娉涘瀷 寮傛鎵撳紑绐楀彛銆�
+ /// </summary>
+ public async UniTask<T> OpenWindowAsync<T>(int functionOrder = 0) where T : UIBase
+ {
+ string uiName = typeof(T).Name;
+ var result = await OpenWindowAsync(uiName, functionOrder);
+ return result as T;
+ }
+
+ // ====================================================================
+ // US3: Loading indicator for async UI loading
+ // ====================================================================
+
+ private GameObject _loadingIndicatorGO;
+ private int _loadingRefCount;
+
+ /// <summary>
+ /// US3: 鏄剧ず鍔犺浇鎸囩ず鍣紙寮曠敤璁℃暟锛屾敮鎸侀噸鍏ワ級銆�
+ /// </summary>
+ public void ShowLoadingIndicator()
+ {
+ _loadingRefCount++;
+ if (_loadingRefCount == 1)
+ {
+ EnsureLoadingIndicator();
+ if (_loadingIndicatorGO != null)
+ {
+ _loadingIndicatorGO.SetActive(true);
+ }
+ }
+ }
+
+ /// <summary>
+ /// US3: 闅愯棌鍔犺浇鎸囩ず鍣ㄣ��
+ /// </summary>
+ public void HideLoadingIndicator()
+ {
+ _loadingRefCount = Mathf.Max(0, _loadingRefCount - 1);
+ if (_loadingRefCount == 0 && _loadingIndicatorGO != null)
+ {
+ _loadingIndicatorGO.SetActive(false);
+ }
+ }
+
+ private void EnsureLoadingIndicator()
+ {
+ if (_loadingIndicatorGO != null) return;
+
+ // 鍒涘缓绠�鏄撳姞杞芥寚绀哄櫒: 鍗婇�忔槑閬僵 + 鏃嬭浆鍥炬爣
+ var canvas = uiRoot != null ? uiRoot.GetComponentInChildren<Canvas>() : null;
+ if (canvas == null) return;
+
+ _loadingIndicatorGO = new GameObject("UILoadingIndicator");
+ _loadingIndicatorGO.transform.SetParent(canvas.transform, false);
+
+ // 鍏ㄥ睆鍗婇�忔槑閬僵
+ var maskRT = _loadingIndicatorGO.AddComponent<RectTransform>();
+ maskRT.anchorMin = Vector2.zero;
+ maskRT.anchorMax = Vector2.one;
+ maskRT.offsetMin = Vector2.zero;
+ maskRT.offsetMax = Vector2.zero;
+
+ var maskImage = _loadingIndicatorGO.AddComponent<UnityEngine.UI.Image>();
+ maskImage.color = new Color(0, 0, 0, 0.3f);
+ maskImage.raycastTarget = true; // 鎷︽埅鐐瑰嚮
+
+ // 鍔犺浇鎻愮ず鏂囧瓧
+ var textGO = new GameObject("LoadingText");
+ textGO.transform.SetParent(_loadingIndicatorGO.transform, false);
+ var textRT = textGO.AddComponent<RectTransform>();
+ textRT.anchorMin = new Vector2(0.5f, 0.5f);
+ textRT.anchorMax = new Vector2(0.5f, 0.5f);
+ textRT.sizeDelta = new Vector2(300, 60);
+
+ var text = textGO.AddComponent<UnityEngine.UI.Text>();
+ text.text = "Loading...";
+ text.alignment = TextAnchor.MiddleCenter;
+ text.fontSize = 28;
+ text.color = Color.white;
+ text.font = UnityEngine.Font.CreateDynamicFontFromOSFont("Arial", 28);
+
+ // 纭繚鍦ㄦ渶涓婂眰
+ var sortCanvas = _loadingIndicatorGO.AddComponent<Canvas>();
+ sortCanvas.overrideSorting = true;
+ sortCanvas.sortingOrder = 30000;
+ _loadingIndicatorGO.AddComponent<UnityEngine.UI.GraphicRaycaster>();
+
+ _loadingIndicatorGO.SetActive(false);
+ }
#endregion
diff --git a/Main/ResModule/AssetBundle/AssetBundleUtility.cs b/Main/ResModule/AssetBundle/AssetBundleUtility.cs
index eca08d2..6f8890b 100644
--- a/Main/ResModule/AssetBundle/AssetBundleUtility.cs
+++ b/Main/ResModule/AssetBundle/AssetBundleUtility.cs
@@ -5,6 +5,11 @@
using UnityEngine;
using Cysharp.Threading.Tasks;
+/// <summary>
+/// [Obsolete] US1: 宸茶 YooAssetService 鏇夸唬銆傚皢鍦� Phase 10 (T060) 鐗╃悊鍒犻櫎銆�
+/// 褰撳墠浠嶄繚鐣欎互鏀寔 AssetBundleInitTask 鐨勫惎鍔ㄥ吋瀹规�с��
+/// </summary>
+[System.Obsolete("Use ProjSG.Resource.YooAssetService instead. This class will be removed in Phase 10 (T060).")]
public class AssetBundleUtility : SingletonMonobehaviour<AssetBundleUtility>
{
private List<AssetBundleInfo> m_AssetBundleInfoList = new List<AssetBundleInfo>();
diff --git a/Main/ResModule/AudioLoader.cs b/Main/ResModule/AudioLoader.cs
index e6db7ab..af8bab3 100644
--- a/Main/ResModule/AudioLoader.cs
+++ b/Main/ResModule/AudioLoader.cs
@@ -1,6 +1,8 @@
锘縰sing UnityEngine;
using System.Collections;
using System;
+using Cysharp.Threading.Tasks;
+using System.Threading;
public class AudioLoader
{
@@ -14,5 +16,10 @@
ResManager.Instance.LoadAssetAsync<AudioClip>("Audio/" + _folderName, _clipName, _callBack, false);
}
+ // US2: Async UniTask variant
+ public static UniTask<AudioClip> LoadAudioAsync(string _folderName, string _clipName, CancellationToken ct = default)
+ {
+ return ResManager.Instance.LoadAssetAsync<AudioClip>("Audio/" + _folderName, _clipName, false, ct);
+ }
}
diff --git a/Main/ResModule/BuiltInLoader.cs b/Main/ResModule/BuiltInLoader.cs
index 9b8c8f3..2c69c14 100644
--- a/Main/ResModule/BuiltInLoader.cs
+++ b/Main/ResModule/BuiltInLoader.cs
@@ -2,6 +2,9 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;
+using Cysharp.Threading.Tasks;
+using System.Threading;
+using ProjSG.Resource;
public class BuiltInLoader
{
@@ -32,13 +35,13 @@
}
else
{
- //var assetInfo = new AssetInfo("builtin/sprites", "sprites");
- //var spriteAtlas = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo, typeof(SpriteAtlas)) as SpriteAtlas;
- //sprite = spriteAtlas?.GetSprite(name);
//if (sprite == null)
{
- var assetInfo = new AssetInfo("builtin/sprites", name);
- sprite = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo, typeof(Sprite)) as Sprite;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Sprites/", name, SPRITE_EXTENSION);
+ #pragma warning disable CS0612
+ sprite = YooAssetService.Instance.LoadAssetSync<Sprite>(path);
+ #pragma warning restore CS0612
}
}
@@ -62,8 +65,11 @@
}
else
{
- var assetInfo = new AssetInfo("builtin/prefabs", name);
- prefab = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo) as GameObject;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Prefabs/", name, PREFAB_EXTENSION);
+ #pragma warning disable CS0612
+ prefab = YooAssetService.Instance.LoadAssetSync<GameObject>(path);
+ #pragma warning restore CS0612
}
if (prefab == null)
@@ -76,10 +82,7 @@
public static void UnLoadPrefab(string name)
{
- if (AssetSource.isUseAssetBundle)
- {
- AssetBundleUtility.Instance.UnloadAsset("builtin/prefabs", name);
- }
+ // US1: No-op. YooAsset manages asset lifecycle via handle-based release.
}
public static AudioClip LoadMusic(string name)
@@ -94,8 +97,11 @@
}
else
{
- var assetInfo = new AssetInfo("builtin/musics", name);
- audioClip = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo) as AudioClip;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Musics/", name, ".mp3");
+ #pragma warning disable CS0612
+ audioClip = YooAssetService.Instance.LoadAssetSync<AudioClip>(path);
+ #pragma warning restore CS0612
}
if (audioClip == null)
@@ -118,8 +124,11 @@
}
else
{
- var assetInfo = new AssetInfo("builtin/animationclips", name);
- clip = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo) as AnimationClip;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/AnimationClips/", name, ".anim");
+ #pragma warning disable CS0612
+ clip = YooAssetService.Instance.LoadAssetSync<AnimationClip>(path);
+ #pragma warning restore CS0612
}
if (clip == null)
@@ -142,8 +151,11 @@
}
else
{
- var assetInfo = new AssetInfo("builtin/materials", name);
- material = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo) as Material;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Materials/", name, ".mat");
+ #pragma warning disable CS0612
+ material = YooAssetService.Instance.LoadAssetSync<Material>(path);
+ #pragma warning restore CS0612
}
if (material == null)
@@ -169,8 +181,12 @@
}
else
{
- var assetInfo = new AssetInfo("builtin/scriptableobjects", name);
- config = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo) as T;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat(ResourcesPath.ResourcesOutAssetPath,
+ "BuiltIn/ScriptableObjects/", name, ".asset");
+ #pragma warning disable CS0612
+ config = YooAssetService.Instance.LoadAssetSync<T>(path);
+ #pragma warning restore CS0612
}
if (config == null)
@@ -194,8 +210,12 @@
}
else
{
- var assetInfo = new AssetInfo("builtin/font", fontName);
- font = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo, typeof(Font)) as Font;
+ // US1: Route through YooAssetService sync wrapper
+ var path = StringUtility.Concat(ResourcesPath.ResourcesOutAssetPath,
+ "BuiltIn/Font/", fontName, ".ttf");
+ #pragma warning disable CS0612
+ font = YooAssetService.Instance.LoadAssetSync<Font>(path);
+ #pragma warning restore CS0612
}
if (font == null)
@@ -206,5 +226,53 @@
return font;
}
+ // ====================================================================
+ // US2: Async UniTask variants
+ // ====================================================================
+
+ public static async UniTask<Sprite> LoadSpriteAsync(string name, CancellationToken ct = default)
+ {
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Sprites/", name, SPRITE_EXTENSION);
+ return await YooAssetService.Instance.LoadAssetAsync<Sprite>(path, ct: ct);
+ }
+
+ public static async UniTask<GameObject> LoadPrefabAsync(string name, CancellationToken ct = default)
+ {
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Prefabs/", name, PREFAB_EXTENSION);
+ return await YooAssetService.Instance.LoadAssetAsync<GameObject>(path, ct: ct);
+ }
+
+ public static async UniTask<AudioClip> LoadMusicAsync(string name, CancellationToken ct = default)
+ {
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Musics/", name, ".mp3");
+ return await YooAssetService.Instance.LoadAssetAsync<AudioClip>(path, ct: ct);
+ }
+
+ public static async UniTask<AnimationClip> LoadAnimationClipAsync(string name, CancellationToken ct = default)
+ {
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/AnimationClips/", name, ".anim");
+ return await YooAssetService.Instance.LoadAssetAsync<AnimationClip>(path, ct: ct);
+ }
+
+ public static async UniTask<Material> LoadMaterialAsync(string name, CancellationToken ct = default)
+ {
+ var path = StringUtility.Concat("Assets/ResourcesOut/BuiltIn/Materials/", name, ".mat");
+ return await YooAssetService.Instance.LoadAssetAsync<Material>(path, ct: ct);
+ }
+
+ public static async UniTask<T> LoadScriptableObjectAsync<T>(string name, CancellationToken ct = default) where T : ScriptableObject
+ {
+ var path = StringUtility.Concat(ResourcesPath.ResourcesOutAssetPath,
+ "BuiltIn/ScriptableObjects/", name, ".asset");
+ return await YooAssetService.Instance.LoadAssetAsync<T>(path, ct: ct);
+ }
+
+ public static async UniTask<Font> LoadFontAsync(string fontName, CancellationToken ct = default)
+ {
+ var path = StringUtility.Concat(ResourcesPath.ResourcesOutAssetPath,
+ "BuiltIn/Font/", fontName, ".ttf");
+ return await YooAssetService.Instance.LoadAssetAsync<Font>(path, ct: ct);
+ }
+
}
diff --git a/Main/ResModule/IResourceCache.cs b/Main/ResModule/IResourceCache.cs
new file mode 100644
index 0000000..2717e8e
--- /dev/null
+++ b/Main/ResModule/IResourceCache.cs
@@ -0,0 +1,60 @@
+// ============================================================================
+// IResourceCache.cs 鈥� 璧勬簮缂撳瓨鏈嶅姟鎺ュ彛
+// Feature: 001-async-resource-loading
+// ============================================================================
+
+using System;
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// 鍏ㄥ眬璧勬簮缂撳瓨鏈嶅姟鎺ュ彛銆�
+ /// 鎻愪緵棰勫姞杞藉悗鐨勫悓姝ョ紦瀛樿幏鍙栬兘鍔涳紝浠ュ強寮傛鍔犺浇+鑷姩缂撳瓨鑳藉姏銆�
+ /// </summary>
+ public interface IResourceCache
+ {
+ /// <summary>
+ /// 缂撳瓨涓殑璧勬簮鏁伴噺
+ /// </summary>
+ int CachedCount { get; }
+
+ /// <summary>
+ /// 鍚屾鑾峰彇宸茬紦瀛樼殑璧勬簮銆�
+ /// 濡傛灉璧勬簮鏈湪缂撳瓨涓紝杩斿洖 null锛堜笉浼氳Е鍙戝姞杞斤級銆�
+ /// </summary>
+ T GetCached<T>(string location) where T : UnityEngine.Object;
+
+ /// <summary>
+ /// 妫�鏌ヨ祫婧愭槸鍚﹀凡鍦ㄧ紦瀛樹腑銆�
+ /// </summary>
+ bool IsCached(string location);
+
+ /// <summary>
+ /// 寮傛鑾峰彇璧勬簮銆傜紦瀛樺懡涓洿鎺ヨ繑鍥烇紝鏈懡涓垯鍔犺浇骞剁紦瀛樸��
+ /// 鍚屼竴璧勬簮鐨勫苟鍙戣姹傝嚜鍔ㄥ幓閲嶃��
+ /// </summary>
+ UniTask<T> GetOrLoadAsync<T>(string location) where T : UnityEngine.Object;
+
+ /// <summary>
+ /// 鎵归噺棰勫姞杞借祫婧愬埌缂撳瓨銆�
+ /// </summary>
+ UniTask PreloadAsync(string[] locations, bool permanent = false, IProgress<float> progress = null);
+
+ /// <summary>
+ /// 閲婃斁鎸囧畾璧勬簮鐨勭紦瀛樸�傚父椹昏祫婧愰渶 forceRelease=true銆�
+ /// </summary>
+ void Release(string location, bool forceRelease = false);
+
+ /// <summary>
+ /// 閲婃斁鎵�鏈夐潪甯搁┗缂撳瓨璧勬簮銆�
+ /// </summary>
+ void ReleaseAll();
+
+ /// <summary>
+ /// 閲婃斁鎵�鏈夎祫婧愶紙鍚父椹伙級銆�
+ /// </summary>
+ void ForceReleaseAll();
+ }
+}
diff --git a/Main/ResModule/IResourceCache.cs.meta b/Main/ResModule/IResourceCache.cs.meta
new file mode 100644
index 0000000..6581317
--- /dev/null
+++ b/Main/ResModule/IResourceCache.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6ce8cec774664004e85ea08f6557275c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/IResourcePreloader.cs b/Main/ResModule/IResourcePreloader.cs
new file mode 100644
index 0000000..657b7d1
--- /dev/null
+++ b/Main/ResModule/IResourcePreloader.cs
@@ -0,0 +1,60 @@
+// ============================================================================
+// IResourcePreloader.cs 鈥� 璧勬簮棰勫姞杞芥湇鍔℃帴鍙�
+// Feature: 001-async-resource-loading
+// ============================================================================
+
+using System;
+using Cysharp.Threading.Tasks;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// 璧勬簮棰勫姞杞芥湇鍔℃帴鍙c��
+ /// 鎸夊満鏅�/娴佺▼缁勭粐鎵归噺璧勬簮棰勫姞杞姐��
+ /// </summary>
+ public interface IResourcePreloader
+ {
+ /// <summary>
+ /// 娉ㄥ唽棰勫姞杞介厤缃��
+ /// </summary>
+ void RegisterConfig(PreloadConfig config);
+
+ /// <summary>
+ /// 鎵ц鎸囧畾棰勫姞杞介厤缃��
+ /// </summary>
+ UniTask PreloadAsync(string configName, IProgress<float> progress = null);
+
+ /// <summary>
+ /// 鎸夎祫婧愭爣绛炬壒閲忛鍔犺浇銆�
+ /// </summary>
+ UniTask PreloadByTagAsync(string tag, IProgress<float> progress = null);
+
+ /// <summary>
+ /// 鍗歌浇鎸囧畾閰嶇疆鐨勮祫婧愶紙甯搁┗閰嶇疆涓嶅嵏杞斤級銆�
+ /// </summary>
+ void UnloadConfig(string configName);
+
+ /// <summary>
+ /// 妫�鏌ラ厤缃槸鍚﹀凡棰勫姞杞藉畬鎴愩��
+ /// </summary>
+ bool IsConfigLoaded(string configName);
+ }
+
+ /// <summary>
+ /// 棰勫姞杞介厤缃暟鎹��
+ /// </summary>
+ public class PreloadConfig
+ {
+ /// <summary>閰嶇疆鍚嶇О</summary>
+ public string ConfigName { get; set; }
+
+ /// <summary>闇�瑕侀鍔犺浇鐨勮祫婧愬湴鍧�鍒楄〃</summary>
+ public string[] Locations { get; set; }
+
+ /// <summary>闇�瑕侀鍔犺浇鐨勮祫婧愭爣绛惧垪琛�</summary>
+ public string[] Tags { get; set; }
+
+ /// <summary>鏄惁涓哄父椹昏祫婧愶紙涓嶅厑璁稿嵏杞斤級</summary>
+ public bool IsPermanent { get; set; }
+ }
+}
diff --git a/Main/ResModule/IResourcePreloader.cs.meta b/Main/ResModule/IResourcePreloader.cs.meta
new file mode 100644
index 0000000..3df247a
--- /dev/null
+++ b/Main/ResModule/IResourcePreloader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2307c27da18d094469bdbb03f7be2734
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/IYooAssetService.cs b/Main/ResModule/IYooAssetService.cs
new file mode 100644
index 0000000..fe94c5f
--- /dev/null
+++ b/Main/ResModule/IYooAssetService.cs
@@ -0,0 +1,164 @@
+// ============================================================================
+// IYooAssetService.cs 鈥� YooAsset 璧勬簮鍔犺浇鏈嶅姟鎺ュ彛
+// 瀹氫箟鍦� Main 绋嬪簭闆嗕腑锛屼緵鎵�鏈変笟鍔$郴缁熶娇鐢�
+// ============================================================================
+
+using System;
+using System.Threading;
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using YooAsset;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// YooAsset 璧勬簮鍔犺浇鏈嶅姟鎺ュ彛銆�
+ /// 灏佽 YooAsset ResourcePackage 鐨勬牳蹇冨姞杞借兘鍔涳紝鎻愪緵 UniTask 寮傛 API銆�
+ /// </summary>
+ public interface IYooAssetService
+ {
+ /// <summary>
+ /// 鏈嶅姟鏄惁宸插垵濮嬪寲瀹屾垚
+ /// </summary>
+ bool IsInitialized { get; }
+
+ /// <summary>
+ /// 褰撳墠杩愯妯″紡
+ /// </summary>
+ EPlayMode PlayMode { get; }
+
+ // ====================================================================
+ // 鍒濆鍖�
+ // ====================================================================
+
+ /// <summary>
+ /// 鍒濆鍖� YooAsset 璧勬簮鏈嶅姟銆�
+ /// 鍦� Launch 闃舵鐢� YooAssetInitializer 璋冪敤銆�
+ /// </summary>
+ /// <param name="playMode">杩愯妯″紡</param>
+ /// <param name="remoteServices">CDN 杩滅▼鏈嶅姟锛圚ostPlayMode/WebPlayMode 蹇呴渶锛�</param>
+ /// <returns>鍒濆鍖栧畬鎴�</returns>
+ UniTask InitializeAsync(EPlayMode playMode, IRemoteServices remoteServices = null);
+
+ // ====================================================================
+ // 璧勬簮鍔犺浇 鈥� Asset
+ // ====================================================================
+
+ /// <summary>
+ /// 寮傛鍔犺浇鎸囧畾绫诲瀷鐨勮祫婧愩��
+ /// </summary>
+ /// <typeparam name="T">璧勬簮绫诲瀷锛圙ameObject, Sprite, AudioClip 绛夛級</typeparam>
+ /// <param name="location">YooAsset 璧勬簮鍦板潃</param>
+ /// <param name="priority">鍔犺浇浼樺厛绾э紙0 = 榛樿锛�</param>
+ /// <param name="ct">鍙栨秷浠ょ墝</param>
+ /// <returns>鍔犺浇瀹屾垚鐨勮祫婧愬璞★紝鍔犺浇澶辫触杩斿洖 null</returns>
+ UniTask<T> LoadAssetAsync<T>(string location, uint priority = 0,
+ CancellationToken ct = default) where T : UnityEngine.Object;
+
+ /// <summary>
+ /// 寮傛鍔犺浇鎸囧畾绫诲瀷鐨勮祫婧愶紙Type 鍙傛暟鐗堟湰锛夈��
+ /// </summary>
+ UniTask<UnityEngine.Object> LoadAssetAsync(string location, Type type, uint priority = 0,
+ CancellationToken ct = default);
+
+ /// <summary>
+ /// 寮傛鍔犺浇瀛愯祫婧愶紙濡� SpriteAtlas 涓殑 Sprite锛夈��
+ /// </summary>
+ UniTask<SubAssetsHandle> LoadSubAssetsAsync<T>(string location, uint priority = 0,
+ CancellationToken ct = default) where T : UnityEngine.Object;
+
+ /// <summary>
+ /// 寮傛鍔犺浇鍚屼竴 Bundle 涓嬬殑鎵�鏈夊悓绫诲瀷璧勬簮銆�
+ /// </summary>
+ UniTask<AllAssetsHandle> LoadAllAssetsAsync<T>(string location, uint priority = 0,
+ CancellationToken ct = default) where T : UnityEngine.Object;
+
+ // ====================================================================
+ // 璧勬簮鍔犺浇 鈥� RawFile
+ // ====================================================================
+
+ /// <summary>
+ /// 寮傛鍔犺浇鍘熷鏂囦欢骞惰繑鍥炴枃鏈唴瀹广��
+ /// 鐢ㄤ簬閰嶇疆鏂囦欢锛�.txt, .json, .csv 绛夛級鍔犺浇銆�
+ /// </summary>
+ UniTask<string> LoadRawFileTextAsync(string location, CancellationToken ct = default);
+
+ /// <summary>
+ /// 寮傛鍔犺浇鍘熷鏂囦欢骞惰繑鍥炲瓧鑺傛暟缁勩��
+ /// </summary>
+ UniTask<byte[]> LoadRawFileBytesAsync(string location, CancellationToken ct = default);
+
+ // ====================================================================
+ // 鍦烘櫙鍔犺浇
+ // ====================================================================
+
+ /// <summary>
+ /// 寮傛鍔犺浇鍦烘櫙銆�
+ /// </summary>
+ UniTask<SceneHandle> LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single,
+ LocalPhysicsMode physicsMode = LocalPhysicsMode.None, bool suspendLoad = false, uint priority = 0, CancellationToken ct = default);
+
+ // ====================================================================
+ // 璧勬簮淇℃伅鏌ヨ
+ // ====================================================================
+
+ /// <summary>
+ /// 妫�鏌ヨ祫婧愬湴鍧�鏄惁鏈夋晥銆�
+ /// </summary>
+ bool CheckLocationValid(string location);
+
+ /// <summary>
+ /// 鑾峰彇鎸囧畾鏍囩鐨勬墍鏈夎祫婧愪俊鎭��
+ /// </summary>
+ YooAsset.AssetInfo[] GetAssetInfosByTag(string tag);
+
+ /// <summary>
+ /// 妫�鏌ヨ祫婧愭槸鍚﹂渶瑕佷粠杩滅▼涓嬭浇銆�
+ /// </summary>
+ bool IsNeedDownloadFromRemote(string location);
+
+ // ====================================================================
+ // 璧勬簮涓嬭浇
+ // ====================================================================
+
+ /// <summary>
+ /// 鍒涘缓璧勬簮涓嬭浇鍣ㄥ苟寮�濮嬩笅杞姐��
+ /// </summary>
+ UniTask DownloadByTagsAsync(string[] tags, int downloadingMaxNumber = 10,
+ int failedTryAgain = 3, IProgress<float> progress = null, CancellationToken ct = default);
+
+ // ====================================================================
+ // 鐗堟湰绠$悊
+ // ====================================================================
+
+ /// <summary>
+ /// 璇锋眰鏈�鏂板寘瑁圭増鏈��
+ /// </summary>
+ UniTask<string> RequestPackageVersionAsync(CancellationToken ct = default);
+
+ /// <summary>
+ /// 鏇存柊鍖呰9 Manifest 鍒版寚瀹氱増鏈��
+ /// </summary>
+ UniTask UpdatePackageManifestAsync(string packageVersion, CancellationToken ct = default);
+
+ // ====================================================================
+ // 璧勬簮閲婃斁
+ // ====================================================================
+
+ /// <summary>
+ /// 閲婃斁璧勬簮鍙ユ焺锛堝紩鐢ㄨ鏁� -1锛夈��
+ /// </summary>
+ void ReleaseHandle(HandleBase handle);
+
+ /// <summary>
+ /// 鍗歌浇鎵�鏈夊紩鐢ㄨ鏁颁负闆剁殑璧勬簮銆�
+ /// </summary>
+ UniTask UnloadUnusedAssetsAsync();
+
+ /// <summary>
+ /// 寮哄埗鍗歌浇鎵�鏈夎祫婧愩��
+ /// </summary>
+ UniTask UnloadAllAssetsAsync();
+ }
+}
diff --git a/Main/ResModule/IYooAssetService.cs.meta b/Main/ResModule/IYooAssetService.cs.meta
new file mode 100644
index 0000000..c15df79
--- /dev/null
+++ b/Main/ResModule/IYooAssetService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3361511557042814db2923ee333359a2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/PlatformCacheHelper.cs b/Main/ResModule/PlatformCacheHelper.cs
new file mode 100644
index 0000000..137d48e
--- /dev/null
+++ b/Main/ResModule/PlatformCacheHelper.cs
@@ -0,0 +1,86 @@
+// ============================================================================
+// PlatformCacheHelper.cs 鈥� 骞冲彴鏂囦欢绯荤粺缂撳瓨杈呭姪
+// 涓哄悇灏忔父鎴忓钩鍙版彁渚涜祫婧愮紦瀛樿矾寰勫拰鏂囦欢绯荤粺鍙傛暟鍒涘缓
+// ============================================================================
+
+using UnityEngine;
+using YooAsset;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// 骞冲彴璧勬簮缂撳瓨杈呭姪宸ュ叿銆�
+ /// 涓轰笉鍚屽皬娓告垙骞冲彴鎻愪緵 YooAsset 鏂囦欢绯荤粺鍙傛暟鍒涘缓鍜岀紦瀛樿矾寰勮幏鍙栥��
+ /// </summary>
+ public static class PlatformCacheHelper
+ {
+ private const string CACHE_DIR_NAME = "__GAME_FILE_CACHE";
+
+ /// <summary>
+ /// 鑾峰彇褰撳墠骞冲彴鐨勮祫婧愮紦瀛樻牴璺緞銆�
+ /// </summary>
+ public static string GetCacheRootPath()
+ {
+#if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR
+ return $"{WeChatWASM.WX.env.USER_DATA_PATH}/{CACHE_DIR_NAME}";
+#elif UNITY_WEBGL && DOUYINMINIGAME && !UNITY_EDITOR
+ return $"{TTSDK.TTFileSystem.USER_DATA_PATH}/{CACHE_DIR_NAME}";
+#else
+ // Standalone / Editor / 鍏朵粬绉诲姩绔細浣跨敤 persistentDataPath
+ return $"{Application.persistentDataPath}/{CACHE_DIR_NAME}";
+#endif
+ }
+
+ /// <summary>
+ /// 涓哄綋鍓嶅钩鍙板垱寤� WebPlayMode 鐨� WebServerFileSystemParameters銆�
+ /// 鑷姩閫夋嫨寰俊/鎶栭煶/榛樿 Web 鏂囦欢绯荤粺銆�
+ /// </summary>
+ /// <param name="remoteServices">杩滅▼鏈嶅姟閰嶇疆</param>
+ /// <returns>鏂囦欢绯荤粺鍙傛暟</returns>
+ public static FileSystemParameters CreateWebFileSystemParameters(IRemoteServices remoteServices)
+ {
+#if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR
+ string packageRoot = GetCacheRootPath();
+ return WechatFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices);
+#elif UNITY_WEBGL && DOUYINMINIGAME && !UNITY_EDITOR
+ string packageRoot = GetCacheRootPath();
+ return TiktokFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices);
+#else
+ // 榛樿 WebGL 鏂囦欢绯荤粺
+ return FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
+#endif
+ }
+
+ /// <summary>
+ /// 鑾峰彇缂撳瓨澶у皬闄愬埗锛圡B锛夈��
+ /// 鍚勫钩鍙版湁涓嶅悓鐨勫瓨鍌ㄩ檺鍒躲��
+ /// </summary>
+ public static int GetCacheSizeLimitMB()
+ {
+ var platform = PlatformFactory.GetCurrent();
+ switch (platform.GetPlatformType())
+ {
+ case PlatformType.WeChat:
+ return 200; // 寰俊灏忔父鎴忕敤鎴锋暟鎹笂闄愮害 200MB
+ case PlatformType.Douyin:
+ return 200; // 鎶栭煶灏忔父鎴忕被浼奸檺鍒�
+ case PlatformType.Vivo:
+ return 100; // Vivo 闄愬埗杈冨皬
+ default:
+ return 1024; // Standalone 鏃犱弗鏍奸檺鍒�
+ }
+ }
+
+ /// <summary>
+ /// 妫�鏌ュ綋鍓嶅钩鍙版槸鍚︽敮鎸佹枃浠剁郴缁熺紦瀛樸��
+ /// </summary>
+ public static bool IsCacheSupported()
+ {
+#if UNITY_WEBGL
+ return true; // 鎵�鏈夊皬娓告垙骞冲彴閮芥敮鎸佺紦瀛�
+#else
+ return true; // 绉诲姩绔�/妗岄潰绔篃鏀寔
+#endif
+ }
+ }
+}
diff --git a/Main/ResModule/PlatformCacheHelper.cs.meta b/Main/ResModule/PlatformCacheHelper.cs.meta
new file mode 100644
index 0000000..4670466
--- /dev/null
+++ b/Main/ResModule/PlatformCacheHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 469fcba458bd2684681de1a15ba21655
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/RemoteServicesImpl.cs b/Main/ResModule/RemoteServicesImpl.cs
new file mode 100644
index 0000000..69c9aee
--- /dev/null
+++ b/Main/ResModule/RemoteServicesImpl.cs
@@ -0,0 +1,90 @@
+// ============================================================================
+// RemoteServicesImpl.cs 鈥� IRemoteServices 瀹炵幇
+// 閰嶅悎骞冲彴鎶借薄灞傛彁渚� CDN 鍦板潃锛屾敮鎸佸骞冲彴 CDN 璺敱
+// ============================================================================
+
+using YooAsset;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// YooAsset 杩滅▼鏈嶅姟瀹炵幇銆�
+ /// 鎻愪緵涓� CDN 鍜屽鐢� CDN 鐨勮祫婧愪笅杞藉湴鍧�鎷兼帴銆�
+ /// </summary>
+ public class RemoteServicesImpl : IRemoteServices
+ {
+ private readonly string _mainUrl;
+ private readonly string _fallbackUrl;
+
+ /// <summary>
+ /// 鍒涘缓杩滅▼鏈嶅姟閰嶇疆銆�
+ /// </summary>
+ /// <param name="mainUrl">涓� CDN 鏍瑰湴鍧�锛堝 https://cdn.example.com/bundles/锛�</param>
+ /// <param name="fallbackUrl">澶囩敤 CDN 鏍瑰湴鍧�</param>
+ public RemoteServicesImpl(string mainUrl, string fallbackUrl)
+ {
+ _mainUrl = mainUrl;
+ _fallbackUrl = fallbackUrl;
+ }
+
+ /// <summary>
+ /// 鑾峰彇涓� CDN 涓嬭浇鍦板潃銆�
+ /// </summary>
+ public string GetRemoteMainURL(string fileName)
+ {
+ return $"{_mainUrl}/{fileName}";
+ }
+
+ /// <summary>
+ /// 鑾峰彇澶囩敤 CDN 涓嬭浇鍦板潃銆�
+ /// </summary>
+ public string GetRemoteFallbackURL(string fileName)
+ {
+ return $"{_fallbackUrl}/{fileName}";
+ }
+
+ /// <summary>
+ /// 鏍规嵁褰撳墠骞冲彴鍒涘缓 RemoteServicesImpl銆�
+ /// 閫氳繃 IPlatformService + PlatformFactory 鑷姩璺敱鍒板搴斿钩鍙扮殑 CDN銆�
+ /// </summary>
+ /// <param name="baseCdnUrl">鍩虹 CDN 鍦板潃锛堝 https://cdn.example.com锛�</param>
+ /// <param name="fallbackCdnUrl">澶囩敤 CDN 鍦板潃锛堝彲閫夛紝榛樿鍚� baseCdnUrl锛�</param>
+ /// <returns>骞冲彴瀵瑰簲鐨� RemoteServicesImpl 瀹炰緥</returns>
+ public static RemoteServicesImpl CreateForCurrentPlatform(string baseCdnUrl, string fallbackCdnUrl = null)
+ {
+ fallbackCdnUrl = fallbackCdnUrl ?? baseCdnUrl;
+
+ var platform = PlatformFactory.GetCurrent();
+ var platformType = platform.GetPlatformType();
+
+ // 鎸夊钩鍙扮被鍨嬭矾鐢卞瓙璺緞
+ string platformPath = GetPlatformCdnPath(platformType);
+
+ string mainUrl = $"{baseCdnUrl}/{platformPath}";
+ string fallbackUrl = $"{fallbackCdnUrl}/{platformPath}";
+
+ UnityEngine.Debug.Log($"[RemoteServicesImpl] Platform={platformType}, CDN={mainUrl}");
+ return new RemoteServicesImpl(mainUrl, fallbackUrl);
+ }
+
+ /// <summary>
+ /// 鑾峰彇骞冲彴瀵瑰簲鐨� CDN 瀛愯矾寰勩��
+ /// </summary>
+ private static string GetPlatformCdnPath(PlatformType platformType)
+ {
+ switch (platformType)
+ {
+ case PlatformType.WeChat:
+ return "wechat/bundles";
+ case PlatformType.Douyin:
+ return "douyin/bundles";
+ case PlatformType.Vivo:
+ return "vivo/bundles";
+ // OPPO / Kuaishou 绛夊彲鍦ㄦ鎵╁睍
+ case PlatformType.Standalone:
+ default:
+ return "standalone/bundles";
+ }
+ }
+ }
+}
diff --git a/Main/ResModule/RemoteServicesImpl.cs.meta b/Main/ResModule/RemoteServicesImpl.cs.meta
new file mode 100644
index 0000000..7b1b3a6
--- /dev/null
+++ b/Main/ResModule/RemoteServicesImpl.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eb5c16e6b126a2045866004b0e7eacc6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/ResManager.cs b/Main/ResModule/ResManager.cs
index 48f61f6..bdf5de0 100644
--- a/Main/ResModule/ResManager.cs
+++ b/Main/ResModule/ResManager.cs
@@ -6,6 +6,9 @@
using UnityEngine.Video;
using Spine.Unity;
using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
+using System.Threading;
+using ProjSG.Resource;
@@ -127,11 +130,11 @@
#endif
//needExt 鏄惁闇�瑕佸嚱鏁板唴閮ㄦ坊鍔犲悗缂�
+ [System.Obsolete("US2: Use LoadAssetAsync<T>(directory, name, needExt) returning UniTask<T> instead.")]
public T LoadAsset<T>(string directory, string name, bool needExt = true) where T : UnityEngine.Object
{
directory = directory.Replace("\\", "/");
name = name.Replace("\\", "/");
- T asset = null;
// 鐗规畩澶勭悊 鍥犱负鏈変竴灞傚浘闆嗙殑鍏崇郴 directory瑕佷紶鍏ョ殑搴旇鏄痑tlas鐨勫悕瀛�
if (typeof(T) == typeof(Sprite))
{
@@ -165,34 +168,10 @@
}
else
{
- if (!needExt)
- {
- //澶栭儴鐢ㄥ埌鐨勮嚜宸卞姞鍚庣紑锛屽唴閮ㄧ粺涓�鍘婚櫎鍚庣紑鍚�
- name = name.Substring(0, name.LastIndexOf("."));
- }
- //TODO: 涓存椂鐗规畩澶勭悊鎵撳寘鍚庣殑璺緞璇诲彇
- if (directory == "UI" || directory == "UIComp" || directory.StartsWith("Sprite")
- || directory == "Battle/Prefabs" || directory == "Materials")
- {
- directory = "UI/" + directory;
- }
- else if (name == "Hero_001")
- {
- directory = "UI/Hero/SpineRes";
- }
-
- else if (directory.Contains("Texture"))
- {
- directory = "maps/" + name;
- }
- else if (directory.Contains("Shader"))
- {
- directory = "graphic/shader";
- }
-
-
- var assetInfo = new AssetInfo(directory.ToLower(), name.ToLower());
- asset = AssetBundleUtility.Instance.Sync_LoadAsset(assetInfo, typeof(T)) as T;
+ // US1: Route through YooAssetService sync wrapper (transitional)
+ #pragma warning disable CS0612, CS0618
+ asset = YooAssetService.Instance.LoadAssetSync<T>(path);
+ #pragma warning restore CS0612, CS0618
}
if (asset == null)
@@ -203,6 +182,7 @@
return asset;
}
+ [System.Obsolete("US2: Use LoadConfigAsync returning UniTask<string[]> instead.")]
public string[] LoadConfig(string name)
{
string path = string.Empty;
@@ -224,7 +204,9 @@
{
if (!AssetSource.isUseAssetBundle)
{
+ #pragma warning disable CS0618 // Obsolete 鈥� legacy sync fallback
SpriteAtlas atlas = LoadAsset<SpriteAtlas>("Sprite", atlasName.Replace("Sprite/", ""));
+ #pragma warning restore CS0618
if (null == atlas)
{
return null;
@@ -236,6 +218,7 @@
}
//needExt 鏄惁闇�瑕佸嚱鏁板唴閮ㄦ坊鍔犲悗缂�
+ [System.Obsolete("US2: Use LoadAssetAsync<T>(directory, name, needExt) returning UniTask<T> instead.")]
public void LoadAssetAsync<T>(string directory, string name, Action<bool, UnityEngine.Object> callBack, bool needExt = true) where T : UnityEngine.Object
{
directory = directory.Replace("\\", "/");
@@ -253,22 +236,19 @@
private void LoadSpriteAsync<T>(string atlasName, string spriteName, Action<bool, UnityEngine.Object> callBack) where T : UnityEngine.Object
{
-#if !UNITY_EDITOR
- LoadAssetAsync<SpriteAtlas>(atlasName, spriteName, (isLoaded, atlas) => {
- if (isLoaded)
+ if (!AssetSource.isUseAssetBundle)
+ {
+ // Editor 妯″紡涓嬪彲鐩存帴鍔犺浇 sprite
+ LoadAssetAsyncInternal<T>(atlasName, spriteName, callBack);
+ }
+ else
+ {
+ // AB 妯″紡涓嬬洿鎺ュ姞杞藉崟鐙殑 Sprite 鏂囦欢锛圷ooAsset 鑷姩澶勭悊 SpriteAtlas 渚濊禆锛�
+ LoadAssetAsyncInternal<Sprite>(atlasName, spriteName, (isLoaded, sprite) =>
{
- SpriteAtlas _atlas = atlas as SpriteAtlas;
- callBack?.Invoke(isLoaded, _atlas.GetSprite(spriteName));
- }
- else
- {
- callBack?.Invoke(false, null);
- }
- });
-#else
- // 缂栬緫鍣ㄤ笅鍙互鐩存帴鍔犺浇娌″暐闂
- LoadAssetAsyncInternal<T>(atlasName, spriteName, callBack);
-#endif
+ callBack?.Invoke(isLoaded, sprite);
+ });
+ }
}
private void LoadAssetAsyncInternal<T>(string directory, string name, Action<bool, UnityEngine.Object> callBack, bool needExt = true) where T : UnityEngine.Object
@@ -284,24 +264,36 @@
}
else
{
- var assetInfo = new AssetInfo(directory.ToLower(), name.ToLower());
- AssetBundleUtility.Instance.Co_LoadAsset(assetInfo, callBack);
+ // US1: Route through YooAssetService async
+ CoLoadViaYooAsset<T>(path, callBack).Forget();
}
}
- public void UnloadAsset(string assetBundleName, string assetName)
+ private async UniTaskVoid CoLoadViaYooAsset<T>(string path, Action<bool, UnityEngine.Object> callBack, CancellationToken ct = default) where T : UnityEngine.Object
{
- if (!AssetSource.isUseAssetBundle)
- return;
-
- AssetBundleUtility.Instance.UnloadAsset(assetBundleName, assetName);
+ try
+ {
+ var asset = await YooAssetService.Instance.LoadAssetAsync<T>(path, ct: ct);
+ callBack?.Invoke(asset != null, asset);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"[ResManager] Async load via YooAsset failed: {ex.Message}");
+ callBack?.Invoke(false, null);
+ }
}
+ [System.Obsolete("US1: Use YooAssetService.ReleaseHandle or UnloadUnusedAssetsAsync instead.")]
+ public void UnloadAsset(string assetBundleName, string assetName)
+ {
+ // US1: AssetBundleUtility unload no longer effective since assets loaded via YooAsset.
+ // Proper unload handled via YooAssetService handle-based release.
+ }
+
+ [System.Obsolete("US1: Use YooAssetService.UnloadUnusedAssetsAsync instead.")]
public void UnloadAssetBundle(string assetBundleName, bool unloadAllLoadedObjects, bool includeDependenice)
{
- if (!AssetSource.isUseAssetBundle)
- return;
- AssetBundleUtility.Instance.UnloadAssetBundle(assetBundleName, unloadAllLoadedObjects, includeDependenice);
+ // US1: AssetBundleUtility unload no longer effective since assets loaded via YooAsset.
}
public string GetAssetFilePath(string _assetKey)
@@ -315,5 +307,111 @@
return path;
}
+ // ====================================================================
+ // US1: New UniTask-based async variants
+ // ====================================================================
+
+ /// <summary>
+ /// 寮傛鍔犺浇璧勬簮锛圲niTask 鐗堟湰锛孶S1 鏂板锛夈��
+ /// </summary>
+ public async UniTask<T> LoadAssetAsync<T>(string directory, string name, bool needExt = true, CancellationToken ct = default) where T : UnityEngine.Object
+ {
+ directory = directory.Replace("\\", "/");
+ name = name.Replace("\\", "/");
+
+ if (typeof(T) == typeof(Sprite))
+ {
+ return await LoadSpriteAsyncUniTask(directory, name, ct) as T;
+ }
+
+ if (typeof(T) == typeof(SkeletonDataAsset))
+ {
+ if (name.Contains("/"))
+ {
+ directory += name.Substring(0, name.LastIndexOf("/"));
+ name = name.Substring(name.LastIndexOf("/") + 1);
+ }
+ }
+
+ var path = ($"Assets/ResourcesOut/{directory}/{name}" + (needExt ? GetExtension(typeof(T)) : ""))
+ .Replace("//", "/").Trim().Replace("\\", "/");
+
+ if (!AssetSource.isUseAssetBundle)
+ {
+#if UNITY_EDITOR
+ return UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path);
+#else
+ return null;
+#endif
+ }
+
+ return await YooAssetService.Instance.LoadAssetAsync<T>(path, ct: ct);
+ }
+
+ /// <summary>
+ /// US4: 寮傛鍔犺浇璧勬簮骞惰蛋缂撳瓨灞傦紙缂撳瓨鍛戒腑鐩存帴杩斿洖锛屾湭鍛戒腑鍒欏姞杞藉苟缂撳瓨锛夈��
+ /// </summary>
+ public async UniTask<T> LoadAssetCachedAsync<T>(string directory, string name, bool needExt = true, CancellationToken ct = default) where T : UnityEngine.Object
+ {
+ directory = directory.Replace("\\", "/");
+ name = name.Replace("\\", "/");
+
+ var path = ($"Assets/ResourcesOut/{directory}/{name}" + (needExt ? GetExtension(typeof(T)) : ""))
+ .Replace("//", "/").Trim().Replace("\\", "/");
+
+ if (!AssetSource.isUseAssetBundle)
+ {
+#if UNITY_EDITOR
+ return UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path);
+#else
+ return null;
+#endif
+ }
+
+ return await ResourceCacheManager.Instance.GetOrLoadAsync<T>(path);
+ }
+
+ private async UniTask<Sprite> LoadSpriteAsyncUniTask(string atlasName, string spriteName, CancellationToken ct = default)
+ {
+ if (!AssetSource.isUseAssetBundle)
+ {
+ var atlas = await LoadAssetAsync<SpriteAtlas>("Sprite", atlasName.Replace("Sprite/", ""), ct: ct);
+ return atlas?.GetSprite(spriteName);
+ }
+ else
+ {
+ var path = $"Assets/ResourcesOut/{atlasName}/{spriteName}.png"
+ .Replace("//", "/").Trim().Replace("\\", "/");
+ return await YooAssetService.Instance.LoadAssetAsync<Sprite>(path, ct: ct);
+ }
+ }
+
+ /// <summary>
+ /// 寮傛鍔犺浇閰嶇疆鏂囦欢锛圲niTask 鐗堟湰锛夈��
+ /// WebGL 骞冲彴浣跨敤 YooAsset RawFile 寮傛鍔犺浇锛屽叾浠栧钩鍙颁娇鐢ㄧ嚎绋嬫睜銆�
+ /// </summary>
+ public async UniTask<string[]> LoadConfigAsync(string name, CancellationToken ct = default)
+ {
+#if UNITY_WEBGL && !UNITY_EDITOR
+ // WebGL 涓嶆敮鎸佸绾跨▼鍜� File.ReadAllLines锛屼娇鐢� YooAsset RawFile
+ try
+ {
+ var text = await ProjSG.Resource.YooAssetService.Instance.LoadRawFileTextAsync($"config/{name}", ct);
+ if (!string.IsNullOrEmpty(text))
+ {
+ return text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.None);
+ }
+ }
+ catch (System.Exception ex)
+ {
+ UnityEngine.Debug.LogError($"[ResManager] LoadConfigAsync WebGL fallback failed for '{name}': {ex.Message}");
+ }
+ return System.Array.Empty<string>();
+#else
+ #pragma warning disable CS0618 // LoadConfig is obsolete 鈥� used here as thread-pool fallback for non-WebGL
+ return await UniTask.RunOnThreadPool(() => LoadConfig(name));
+ #pragma warning restore CS0618
+#endif
+ }
}
\ No newline at end of file
diff --git a/Main/ResModule/ResourceCacheManager.cs b/Main/ResModule/ResourceCacheManager.cs
new file mode 100644
index 0000000..b9d18a5
--- /dev/null
+++ b/Main/ResModule/ResourceCacheManager.cs
@@ -0,0 +1,292 @@
+// ============================================================================
+// ResourceCacheManager.cs 鈥� 鍏ㄥ眬璧勬簮缂撳瓨绠$悊鍣�
+// Feature: 001-async-resource-loading
+// ============================================================================
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Cysharp.Threading.Tasks;
+using YooAsset;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// 鍏ㄥ眬璧勬簮缂撳瓨绠$悊鍣ㄣ��
+ /// 鎻愪緵鍚屾缂撳瓨鑾峰彇銆佸紓姝ュ姞杞�+鍘婚噸銆丩RU 娣樻卑绛夎兘鍔涖��
+ /// </summary>
+ public class ResourceCacheManager : Singleton<ResourceCacheManager>, IResourceCache
+ {
+ /// <summary>
+ /// 缂撳瓨鏉$洰
+ /// </summary>
+ private class CachedResource
+ {
+ public UnityEngine.Object Asset;
+ public AssetHandle Handle;
+ public bool IsPermanent;
+ public float LastAccessTime;
+ }
+
+ /// <summary>宸茬紦瀛樼殑璧勬簮</summary>
+ private readonly Dictionary<string, CachedResource> _syncCache = new Dictionary<string, CachedResource>();
+
+ /// <summary>姝e湪鍔犺浇涓殑浠诲姟锛堢敤浜庡幓閲嶏級</summary>
+ private readonly Dictionary<string, UniTask<UnityEngine.Object>> _loadingTasks = new Dictionary<string, UniTask<UnityEngine.Object>>();
+
+ /// <summary>LRU 娣樻卑闃堝�硷紙闈炲父椹昏祫婧愭暟閲忚秴杩囨鍊兼椂瑙﹀彂娣樻卑锛�</summary>
+ private const int LRU_THRESHOLD = 200;
+
+ /// <summary>娣樻卑鍚庝繚鐣欑殑闈炲父椹昏祫婧愭暟閲�</summary>
+ private const int LRU_KEEP_COUNT = 150;
+
+ /// <inheritdoc/>
+ public int CachedCount => _syncCache.Count;
+
+ // ====================================================================
+ // 鍚屾鑾峰彇
+ // ====================================================================
+
+ /// <inheritdoc/>
+ public T GetCached<T>(string location) where T : UnityEngine.Object
+ {
+ if (string.IsNullOrEmpty(location)) return null;
+
+ if (_syncCache.TryGetValue(location, out var cached))
+ {
+ cached.LastAccessTime = Time.unscaledTime;
+ return cached.Asset as T;
+ }
+
+ return null;
+ }
+
+ /// <inheritdoc/>
+ public bool IsCached(string location)
+ {
+ return !string.IsNullOrEmpty(location) && _syncCache.ContainsKey(location);
+ }
+
+ // ====================================================================
+ // 寮傛鑾峰彇锛堢紦瀛樼┛閫� + 鍘婚噸锛�
+ // ====================================================================
+
+ /// <inheritdoc/>
+ public async UniTask<T> GetOrLoadAsync<T>(string location) where T : UnityEngine.Object
+ {
+ if (string.IsNullOrEmpty(location))
+ {
+ Debug.LogError("ResourceCacheManager.GetOrLoadAsync: location is null or empty");
+ return null;
+ }
+
+ // 1. 缂撳瓨鍛戒腑
+ if (_syncCache.TryGetValue(location, out var cached))
+ {
+ cached.LastAccessTime = Time.unscaledTime;
+ return cached.Asset as T;
+ }
+
+ // 2. 姝e湪鍔犺浇涓� 鈫� 绛夊緟宸叉湁浠诲姟锛堝幓閲嶏級
+ if (_loadingTasks.TryGetValue(location, out var loadingTask))
+ {
+ var result = await loadingTask;
+ return result as T;
+ }
+
+ // 3. 鍙戣捣鏂板姞杞�
+ var task = LoadAndCacheAsync(location, false);
+ _loadingTasks[location] = task;
+
+ try
+ {
+ var asset = await task;
+ return asset as T;
+ }
+ finally
+ {
+ _loadingTasks.Remove(location);
+ }
+ }
+
+ // ====================================================================
+ // 鎵归噺棰勫姞杞�
+ // ====================================================================
+
+ /// <inheritdoc/>
+ public async UniTask PreloadAsync(string[] locations, bool permanent = false, IProgress<float> progress = null)
+ {
+ if (locations == null || locations.Length == 0)
+ {
+ progress?.Report(1f);
+ return;
+ }
+
+ int completed = 0;
+ int total = locations.Length;
+
+ // 杩囨护宸茬紦瀛樼殑
+ var toLoad = new List<string>();
+ foreach (var loc in locations)
+ {
+ if (_syncCache.ContainsKey(loc))
+ {
+ // 宸茬紦瀛橈紝鏇存柊甯搁┗鏍囪
+ if (permanent)
+ {
+ _syncCache[loc].IsPermanent = true;
+ }
+ completed++;
+ }
+ else
+ {
+ toLoad.Add(loc);
+ }
+ }
+
+ progress?.Report((float)completed / total);
+
+ // 骞惰鍔犺浇锛堥檺鍒跺苟鍙戞暟锛�
+ const int maxConcurrency = 8;
+ for (int i = 0; i < toLoad.Count; i += maxConcurrency)
+ {
+ var batch = new List<UniTask>();
+ int batchEnd = Mathf.Min(i + maxConcurrency, toLoad.Count);
+
+ for (int j = i; j < batchEnd; j++)
+ {
+ var loc = toLoad[j];
+ batch.Add(LoadAndCacheAsync(loc, permanent).ContinueWith(_ =>
+ {
+ completed++;
+ progress?.Report((float)completed / total);
+ }));
+ }
+
+ await UniTask.WhenAll(batch);
+ }
+
+ progress?.Report(1f);
+ TryLRUEviction();
+ }
+
+ // ====================================================================
+ // 閲婃斁
+ // ====================================================================
+
+ /// <inheritdoc/>
+ public void Release(string location, bool forceRelease = false)
+ {
+ if (string.IsNullOrEmpty(location)) return;
+
+ if (_syncCache.TryGetValue(location, out var cached))
+ {
+ if (cached.IsPermanent && !forceRelease) return;
+
+ if (cached.Handle != null && cached.Handle.IsValid)
+ {
+ cached.Handle.Release();
+ }
+
+ _syncCache.Remove(location);
+ }
+ }
+
+ /// <inheritdoc/>
+ public void ReleaseAll()
+ {
+ var toRemove = new List<string>();
+
+ foreach (var kvp in _syncCache)
+ {
+ if (!kvp.Value.IsPermanent)
+ {
+ if (kvp.Value.Handle != null && kvp.Value.Handle.IsValid)
+ {
+ kvp.Value.Handle.Release();
+ }
+ toRemove.Add(kvp.Key);
+ }
+ }
+
+ foreach (var key in toRemove)
+ {
+ _syncCache.Remove(key);
+ }
+ }
+
+ /// <inheritdoc/>
+ public void ForceReleaseAll()
+ {
+ foreach (var kvp in _syncCache)
+ {
+ if (kvp.Value.Handle != null && kvp.Value.Handle.IsValid)
+ {
+ kvp.Value.Handle.Release();
+ }
+ }
+
+ _syncCache.Clear();
+ _loadingTasks.Clear();
+ }
+
+ // ====================================================================
+ // 鍐呴儴鏂规硶
+ // ====================================================================
+
+ private async UniTask<UnityEngine.Object> LoadAndCacheAsync(string location, bool permanent)
+ {
+ try
+ {
+ var asset = await YooAssetService.Instance.LoadAssetAsync<UnityEngine.Object>(location);
+
+ if (asset != null && !_syncCache.ContainsKey(location))
+ {
+ _syncCache[location] = new CachedResource
+ {
+ Asset = asset,
+ Handle = null, // Handle 鐢� YooAssetService 绠$悊
+ IsPermanent = permanent,
+ LastAccessTime = Time.unscaledTime,
+ };
+ }
+
+ return asset;
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"ResourceCacheManager.LoadAndCacheAsync failed for '{location}': {e.Message}");
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// LRU 娣樻卑锛氶潪甯搁┗璧勬簮瓒呰繃闃堝�兼椂锛屾寜璁块棶鏃堕棿娣樻卑鏈�鏃х殑璧勬簮銆�
+ /// </summary>
+ private void TryLRUEviction()
+ {
+ var nonPermanent = _syncCache
+ .Where(kvp => !kvp.Value.IsPermanent)
+ .ToList();
+
+ if (nonPermanent.Count <= LRU_THRESHOLD) return;
+
+ // 鎸夎闂椂闂存帓搴忥紝绉婚櫎鏈�鏃х殑
+ var sorted = nonPermanent
+ .OrderBy(kvp => kvp.Value.LastAccessTime)
+ .ToList();
+
+ int toRemove = sorted.Count - LRU_KEEP_COUNT;
+ for (int i = 0; i < toRemove; i++)
+ {
+ var kvp = sorted[i];
+ if (kvp.Value.Handle != null && kvp.Value.Handle.IsValid)
+ {
+ kvp.Value.Handle.Release();
+ }
+ _syncCache.Remove(kvp.Key);
+ }
+ }
+ }
+}
diff --git a/Main/ResModule/ResourceCacheManager.cs.meta b/Main/ResModule/ResourceCacheManager.cs.meta
new file mode 100644
index 0000000..26a3d48
--- /dev/null
+++ b/Main/ResModule/ResourceCacheManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 940d389e2ddcbe94ab0f851f56e988c9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/ResourcePreloader.cs b/Main/ResModule/ResourcePreloader.cs
new file mode 100644
index 0000000..5bd635e
--- /dev/null
+++ b/Main/ResModule/ResourcePreloader.cs
@@ -0,0 +1,196 @@
+// ============================================================================
+// ResourcePreloader.cs 鈥� 璧勬簮棰勫姞杞界鐞嗗櫒
+// Feature: 001-async-resource-loading
+// ============================================================================
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using Cysharp.Threading.Tasks;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// 璧勬簮棰勫姞杞界鐞嗗櫒銆�
+ /// 鎸夊満鏅�/娴佺▼缁勭粐棰勫姞杞介厤缃紝閫氳繃 ResourceCacheManager 鎵ц瀹為檯缂撳瓨銆�
+ /// </summary>
+ public class ResourcePreloader : Singleton<ResourcePreloader>, IResourcePreloader
+ {
+ private readonly Dictionary<string, PreloadConfig> _configs = new Dictionary<string, PreloadConfig>();
+ private readonly HashSet<string> _loadedConfigs = new HashSet<string>();
+
+ // ====================================================================
+ // 棰勮閰嶇疆
+ // ====================================================================
+
+ /// <summary>
+ /// 娉ㄥ唽榛樿棰勫姞杞介厤缃�傚簲鍦� YooAsset 鍒濆鍖栧悗璋冪敤銆�
+ /// </summary>
+ public void RegisterDefaultConfigs()
+ {
+ // 鍚姩蹇呴渶璧勬簮锛堝父椹伙級
+ RegisterConfig(new PreloadConfig
+ {
+ ConfigName = "StartupEssential",
+ Locations = new[]
+ {
+ "Assets/ResourcesOut/Shader", // Shader 鍏ㄩ儴
+ "Assets/ResourcesOut/Materials", // 閫氱敤 Material
+ "Assets/ResourcesOut/BuiltIn/Font", // 甯哥敤瀛椾綋
+ "Assets/ResourcesOut/BuiltIn/UIRoot", // UIRoot 棰勫埗浣�
+ "Assets/ResourcesOut/BuiltIn/SoundPlayer", // 闊抽鎾斁鍣�
+ },
+ Tags = null,
+ IsPermanent = true,
+ });
+
+ // 鎴樻枟鍦烘櫙璧勬簮锛堥潪甯搁┗锛屾垬鏂楃粨鏉熷悗鍙噴鏀撅級
+ RegisterConfig(new PreloadConfig
+ {
+ ConfigName = "BattleScene",
+ Locations = null,
+ Tags = new[] { "tag_battle_spine", "tag_battle_effect", "tag_battle_sound" },
+ IsPermanent = false,
+ });
+ }
+
+ // ====================================================================
+ // IResourcePreloader 瀹炵幇
+ // ====================================================================
+
+ /// <inheritdoc/>
+ public void RegisterConfig(PreloadConfig config)
+ {
+ if (config == null || string.IsNullOrEmpty(config.ConfigName))
+ {
+ Debug.LogError("ResourcePreloader.RegisterConfig: config or ConfigName is null");
+ return;
+ }
+
+ _configs[config.ConfigName] = config;
+ }
+
+ /// <inheritdoc/>
+ public async UniTask PreloadAsync(string configName, IProgress<float> progress = null)
+ {
+ if (string.IsNullOrEmpty(configName))
+ {
+ Debug.LogError("ResourcePreloader.PreloadAsync: configName is null or empty");
+ progress?.Report(1f);
+ return;
+ }
+
+ if (!_configs.TryGetValue(configName, out var config))
+ {
+ Debug.LogWarning($"ResourcePreloader.PreloadAsync: config '{configName}' not found");
+ progress?.Report(1f);
+ return;
+ }
+
+ if (_loadedConfigs.Contains(configName))
+ {
+ progress?.Report(1f);
+ return;
+ }
+
+ var cacheManager = ResourceCacheManager.Instance;
+ int totalSteps = 0;
+ int completedSteps = 0;
+
+ // 璁$畻鎬绘楠�
+ if (config.Locations != null) totalSteps += config.Locations.Length;
+ if (config.Tags != null) totalSteps += config.Tags.Length;
+
+ if (totalSteps == 0)
+ {
+ progress?.Report(1f);
+ _loadedConfigs.Add(configName);
+ return;
+ }
+
+ // 鍔犺浇 Locations
+ if (config.Locations != null && config.Locations.Length > 0)
+ {
+ var locationProgress = new Progress<float>(p =>
+ {
+ float locationWeight = (float)config.Locations.Length / totalSteps;
+ progress?.Report(p * locationWeight);
+ });
+
+ await cacheManager.PreloadAsync(config.Locations, config.IsPermanent, locationProgress);
+ completedSteps += config.Locations.Length;
+ }
+
+ // 鎸� Tag 鍔犺浇
+ if (config.Tags != null)
+ {
+ foreach (var tag in config.Tags)
+ {
+ await PreloadByTagAsync(tag);
+ completedSteps++;
+ progress?.Report((float)completedSteps / totalSteps);
+ }
+ }
+
+ _loadedConfigs.Add(configName);
+ progress?.Report(1f);
+ }
+
+ /// <inheritdoc/>
+ public async UniTask PreloadByTagAsync(string tag, IProgress<float> progress = null)
+ {
+ if (string.IsNullOrEmpty(tag))
+ {
+ progress?.Report(1f);
+ return;
+ }
+
+ try
+ {
+ // 浣跨敤 YooAssetService 鎸夋爣绛句笅杞�/缂撳瓨
+ await YooAssetService.Instance.DownloadByTagsAsync(
+ new[] { tag },
+ progress: progress);
+
+ progress?.Report(1f);
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"ResourcePreloader.PreloadByTagAsync failed for tag '{tag}': {e.Message}");
+ progress?.Report(1f);
+ }
+ }
+
+ /// <inheritdoc/>
+ public void UnloadConfig(string configName)
+ {
+ if (string.IsNullOrEmpty(configName)) return;
+
+ if (!_configs.TryGetValue(configName, out var config)) return;
+
+ // 甯搁┗閰嶇疆涓嶅嵏杞�
+ if (config.IsPermanent)
+ {
+ Debug.LogWarning($"ResourcePreloader.UnloadConfig: config '{configName}' is permanent, skipping unload");
+ return;
+ }
+
+ if (config.Locations != null)
+ {
+ var cacheManager = ResourceCacheManager.Instance;
+ foreach (var loc in config.Locations)
+ {
+ cacheManager.Release(loc);
+ }
+ }
+
+ _loadedConfigs.Remove(configName);
+ }
+
+ /// <inheritdoc/>
+ public bool IsConfigLoaded(string configName)
+ {
+ return _loadedConfigs.Contains(configName);
+ }
+ }
+}
diff --git a/Main/ResModule/ResourcePreloader.cs.meta b/Main/ResModule/ResourcePreloader.cs.meta
new file mode 100644
index 0000000..eac3db2
--- /dev/null
+++ b/Main/ResModule/ResourcePreloader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: da2fba186b20495479f006115e8cc8d4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/UILoader.cs b/Main/ResModule/UILoader.cs
index 4cdbc95..1c8f553 100644
--- a/Main/ResModule/UILoader.cs
+++ b/Main/ResModule/UILoader.cs
@@ -2,29 +2,40 @@
using System.Collections.Generic;
using UnityEngine;
using System;
-
+using Cysharp.Threading.Tasks;
+using System.Threading;
using System.IO;
using UnityEngine.U2D;
public class UILoader
{
+ [System.Obsolete("US2: Use LoadWindowAsync instead.")]
public static GameObject LoadWindow(string name)
{
+ #pragma warning disable CS0618
return ResManager.Instance.LoadAsset<GameObject>(ResourcesPath.UI_WINDOW_SUFFIX, name);
+ #pragma warning restore CS0618
}
+ [System.Obsolete("US2: Use LoadPrefabAsync instead.")]
public static GameObject LoadPrefab(string _name)
{
+ #pragma warning disable CS0618
return ResManager.Instance.LoadAsset<GameObject>(ResourcesPath.UI_PREFAB_SUFFIX, _name);
+ #pragma warning restore CS0618
}
+ [System.Obsolete("US2: Use YooAssetService.ReleaseHandle or UnloadUnusedAssetsAsync instead.")]
public static void UnLoadPrefab(string _assetName)
{
+ #pragma warning disable CS0618
ResManager.Instance.UnloadAsset(ResourcesPath.UI_PREFAB_SUFFIX, _assetName);
+ #pragma warning restore CS0618
}
//閫氳繃ICON琛ㄥ姞杞�
+ [System.Obsolete("US2: Use LoadSpriteAsync instead.")]
public static Sprite LoadSprite(string _iconKey)
{
var iconConfig = IconConfig.Get(_iconKey);
@@ -36,31 +47,44 @@
return LoadSprite(iconConfig.folder, iconConfig.sprite);
}
+ [System.Obsolete("US2: Use LoadSpriteAsync instead.")]
public static Sprite LoadSprite(string _folder, string _file)
{
+ #pragma warning disable CS0618
return ResManager.Instance.LoadAsset<Sprite>(StringUtility.Concat(ResourcesPath.UI_SPRITE_SUFFIX, "/", _folder), _file);
+ #pragma warning restore CS0618
}
+ [System.Obsolete("US2: Use YooAssetService.ReleaseHandle instead.")]
public static void UnLoadSprite(string _iconKey)
{
var iconConfig = IconConfig.Get(_iconKey);
if (iconConfig != null)
{
var bundleName = StringUtility.Concat(ResourcesPath.UI_SPRITE_SUFFIX, "/", iconConfig.folder);
+ #pragma warning disable CS0618
ResManager.Instance.UnloadAsset(bundleName, iconConfig.sprite);
+ #pragma warning restore CS0618
}
}
+ [System.Obsolete("US2: Use LoadFontAsync instead.")]
public static Font LoadFont(string _fontName)
{
+ #pragma warning disable CS0618
return ResManager.Instance.LoadAsset<Font>(ResourcesPath.UI_FONT_SUFFIX, _fontName);
+ #pragma warning restore CS0618
}
+ [System.Obsolete("US2: Use YooAssetService.ReleaseHandle instead.")]
public static void UnLoadFont(string _fontName)
{
+ #pragma warning disable CS0618
ResManager.Instance.UnloadAsset(ResourcesPath.UI_FONT_SUFFIX, _fontName);
+ #pragma warning restore CS0618
}
+ [System.Obsolete("US2: Use LoadTexture2DAsync instead.")]
public static Texture2D LoadTexture2D(string _iconKey)
{
var iconConfig = IconConfig.Get(_iconKey);
@@ -68,11 +92,62 @@
{
return null;
}
+ #pragma warning disable CS0618
return ResManager.Instance.LoadAsset<Texture2D>(StringUtility.Concat(ResourcesPath.UI_TEXTURE_SUFFIX, "/" + iconConfig.folder), iconConfig.sprite);
+ #pragma warning restore CS0618
}
+ [System.Obsolete("US2: Use LoadTexture2DPNGAsync instead.")]
public static Texture2D LoadTexture2DPNG(string name)
{
+ #pragma warning disable CS0618
return ResManager.Instance.LoadAsset<Texture2D>(StringUtility.Concat(ResourcesPath.UI_TEXTURE_SUFFIX, "/FullScreenBg"), name + ".png", false);
+ #pragma warning restore CS0618
+ }
+
+ // ====================================================================
+ // US2: Async UniTask variants
+ // ====================================================================
+
+ public static UniTask<GameObject> LoadWindowAsync(string name, CancellationToken ct = default)
+ {
+ return ResManager.Instance.LoadAssetAsync<GameObject>(ResourcesPath.UI_WINDOW_SUFFIX, name, ct: ct);
+ }
+
+ public static UniTask<GameObject> LoadPrefabAsync(string _name, CancellationToken ct = default)
+ {
+ return ResManager.Instance.LoadAssetAsync<GameObject>(ResourcesPath.UI_PREFAB_SUFFIX, _name, ct: ct);
+ }
+
+ public static async UniTask<Sprite> LoadSpriteAsync(string _iconKey, CancellationToken ct = default)
+ {
+ var iconConfig = IconConfig.Get(_iconKey);
+ if (iconConfig == null) return null;
+ return await LoadSpriteAsync(iconConfig.folder, iconConfig.sprite, ct);
+ }
+
+ public static UniTask<Sprite> LoadSpriteAsync(string _folder, string _file, CancellationToken ct = default)
+ {
+ return ResManager.Instance.LoadAssetAsync<Sprite>(
+ StringUtility.Concat(ResourcesPath.UI_SPRITE_SUFFIX, "/", _folder), _file, ct: ct);
+ }
+
+ public static UniTask<Font> LoadFontAsync(string _fontName, CancellationToken ct = default)
+ {
+ return ResManager.Instance.LoadAssetAsync<Font>(ResourcesPath.UI_FONT_SUFFIX, _fontName, ct: ct);
+ }
+
+ public static async UniTask<Texture2D> LoadTexture2DAsync(string _iconKey, CancellationToken ct = default)
+ {
+ var iconConfig = IconConfig.Get(_iconKey);
+ if (iconConfig == null) return null;
+ return await ResManager.Instance.LoadAssetAsync<Texture2D>(
+ StringUtility.Concat(ResourcesPath.UI_TEXTURE_SUFFIX, "/" + iconConfig.folder), iconConfig.sprite, ct: ct);
+ }
+
+ public static UniTask<Texture2D> LoadTexture2DPNGAsync(string name, CancellationToken ct = default)
+ {
+ return ResManager.Instance.LoadAssetAsync<Texture2D>(
+ StringUtility.Concat(ResourcesPath.UI_TEXTURE_SUFFIX, "/FullScreenBg"), name + ".png", false, ct);
}
}
diff --git a/Main/ResModule/YooAssetPackageConfig.cs b/Main/ResModule/YooAssetPackageConfig.cs
new file mode 100644
index 0000000..01d6da5
--- /dev/null
+++ b/Main/ResModule/YooAssetPackageConfig.cs
@@ -0,0 +1,184 @@
+// ============================================================================
+// YooAssetPackageConfig.cs 鈥� YooAsset 澶� Package 鏋舵瀯閰嶇疆
+// 瀹氫箟 Package 鍚嶇О甯搁噺鍜岃祫婧愮洰褰曗啋Package 璺敱琛�
+// ============================================================================
+//
+// 鍦� YooAsset Editor (YooAsset 鈫� AssetBundle Collector) 涓厤缃細
+// 姣忎釜 Package 瀵瑰簲涓�涓嫭绔嬬殑鏋勫缓浜х墿锛屽彲鐙珛涓嬭浇鍜屾洿鏂般��
+//
+// 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� Package 鈹� 鏀堕泦璺緞 (CollectPath) 鈹� 閮ㄧ讲鏂瑰紡 鈹�
+// 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� UI 鈹� Assets/ResourcesOut/UI 鈹� Remote 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/UIComp 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Sprite 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Texture 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Font 鈹� 鈹�
+// 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� Prefab 鈹� Assets/ResourcesOut/BuiltIn 鈹� Remote 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Shader 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Materials 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/ScriptableObject 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Scenes 鈹� 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Config 鈹� 鈹�
+// 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� UIEffect 鈹� Assets/ResourcesOut/UIEffect (宸叉湁) 鈹� Remote 鈹�
+// 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� Dll 鈹� HybridCLR 鐑洿 DLL 鈹� Remote 鈹�
+// 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� Battle 鈹� Assets/ResourcesOut/Hero 鈹� Remote 鈹�
+// 鈹� 鈹� Assets/ResourcesOut/Battle 鈹� 鈹�
+// 鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+// 鈹� Audio 鈹� Assets/ResourcesOut/Audio 鈹� Remote 鈹�
+// 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+//
+// ============================================================================
+
+using System.Collections.Generic;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// YooAsset Package 鍚嶇О甯搁噺銆�
+ /// 蹇呴』涓� AssetBundleCollectorSetting.asset 涓殑 PackageName 涓�鑷淬��
+ /// </summary>
+ public static class YooAssetPackageConfig
+ {
+ // ====================================================================
+ // Package 鍚嶇О锛堜笌 Collector 瀹屽叏涓�鑷达級
+ // ====================================================================
+
+ /// <summary>UI 璧勬簮鍖咃細UI 绐楀彛銆乁IComp銆丼prite銆乀exture銆丗ont</summary>
+ public const string UI = "UI";
+
+ /// <summary>閫氱敤棰勫埗浣撳寘锛欱uiltIn銆丼hader銆丮aterials銆丼criptableObject銆丼cenes銆丆onfig</summary>
+ public const string Prefab = "Prefab";
+
+ /// <summary>UI 鐗规晥鍖咃細UIEffect 鐩綍</summary>
+ public const string UIEffect = "UIEffect";
+
+ /// <summary>HybridCLR 鐑洿 DLL 鍖�</summary>
+ public const string Dll = "Dll";
+
+ /// <summary>鎴樻枟璧勬簮鍖咃細Hero Spine銆丅attle Prefabs</summary>
+ public const string Battle = "Battle";
+
+ /// <summary>闊抽璧勬簮鍖咃細Audio 鐩綍</summary>
+ public const string Audio = "Audio";
+
+ /// <summary>
+ /// 涓诲寘鍚� 鈥� 鍦� YooAssetService/YooAssetInitializer 涓綔涓� DefaultPackage銆�
+ /// 閫夋嫨 Prefab 鍖咃紝鍥犱负瀹冨寘鍚� BuiltIn/Shader/Materials 绛夊惎鍔ㄥ繀闇�璧勬簮銆�
+ /// </summary>
+ public const string DefaultPackage = Prefab;
+
+ /// <summary>
+ /// 鎵�鏈夐渶瑕佸垵濮嬪寲鐨� Package锛堜笉鍚� Dll锛孌ll 鍖呯敱鐑洿娴佺▼鍗曠嫭绠$悊锛夈��
+ /// </summary>
+ public static readonly string[] AllPackages = new[]
+ {
+ Prefab, // 鍏堝垵濮嬪寲榛樿鍖�
+ UI,
+ UIEffect,
+ Battle,
+ Audio,
+ };
+
+ // ====================================================================
+ // 璧勬簮鐩綍 鈫� Package 璺敱琛�
+ // ====================================================================
+
+ /// <summary>
+ /// 璧勬簮鐩綍鍓嶇紑鍒� Package 鍚嶇О鐨勬槧灏勩��
+ /// key 鏄� ResourcesOut 涓嬬殑涓�绾х洰褰曞悕锛堝皬鍐欙級锛寁alue 鏄� Package 鍚嶇О銆�
+ /// </summary>
+ private static readonly Dictionary<string, string> _directoryToPackage = new Dictionary<string, string>
+ {
+ // UI Package
+ { "ui", UI },
+ { "uicomp", UI },
+ { "sprite", UI },
+ { "texture", UI },
+ { "font", UI },
+
+ // Prefab Package (default 鈥� BuiltIn, Shader, Materials, etc.)
+ { "builtin", Prefab },
+ { "shader", Prefab },
+ { "materials", Prefab },
+ { "graphic", Prefab }, // 鏃ц矾寰勫吋瀹� "Graphic/Shader", "Graphic/Material"
+ { "scriptableobject", Prefab },
+ { "scenes", Prefab },
+ { "config", Prefab },
+ { "prefab", Prefab },
+
+ // UIEffect Package
+ { "uieffect", UIEffect },
+ { "effect", UIEffect },
+
+ // Battle Package
+ { "hero", Battle },
+ { "battle", Battle },
+
+ // Audio Package
+ { "audio", Audio },
+ };
+
+ /// <summary>
+ /// 鏍规嵁瀹屾暣璧勬簮璺緞纭畾搴斾娇鐢ㄧ殑 Package 鍚嶇О銆�
+ /// 渚嬪 "Assets/ResourcesOut/UI/LoginWin.prefab" 鈫� "UI"
+ /// 渚嬪 "Assets/ResourcesOut/BuiltIn/Font/MainFont.ttf" 鈫� "Prefab"
+ /// </summary>
+ /// <param name="location">璧勬簮璺緞锛堝畬鏁磋矾寰勬垨鐩稿璺緞锛�</param>
+ /// <returns>Package 鍚嶇О锛屽尮閰嶄笉鍒拌繑鍥� DefaultPackage</returns>
+ public static string GetPackageForLocation(string location)
+ {
+ if (string.IsNullOrEmpty(location))
+ return DefaultPackage;
+
+ // 鍘绘帀 "Assets/ResourcesOut/" 鍓嶇紑
+ string relativePath = location;
+ const string PREFIX = "Assets/ResourcesOut/";
+ if (relativePath.StartsWith(PREFIX))
+ {
+ relativePath = relativePath.Substring(PREFIX.Length);
+ }
+
+ // 鍙栦竴绾х洰褰曞悕
+ int slashIndex = relativePath.IndexOf('/');
+ string topDir = slashIndex >= 0
+ ? relativePath.Substring(0, slashIndex)
+ : relativePath;
+
+ if (_directoryToPackage.TryGetValue(topDir.ToLower(), out string packageName))
+ {
+ return packageName;
+ }
+
+ return DefaultPackage;
+ }
+ }
+
+ /// <summary>
+ /// YooAsset 璧勬簮鏍囩甯搁噺銆�
+ /// 鐢ㄤ簬缁嗙矑搴﹁祫婧愬垎缁勫拰鎸夐渶涓嬭浇銆�
+ /// </summary>
+ public static class YooAssetTagConfig
+ {
+ // 鎴樻枟鐩稿叧
+ public const string BattleSpine = "tag_battle_spine";
+ public const string BattleEffect = "tag_battle_effect";
+ public const string BattleSound = "tag_battle_sound";
+ public const string BattleMap = "tag_battle_map";
+
+ // UI 鐩稿叧
+ public const string UIMain = "tag_ui_main";
+ public const string UIShop = "tag_ui_shop";
+ public const string UIBattle = "tag_ui_battle";
+ public const string UIHero = "tag_ui_hero";
+
+ // 閫氱敤
+ public const string Shader = "tag_shader";
+ public const string Font = "tag_font";
+ public const string Material = "tag_material";
+ }
+}
diff --git a/Main/ResModule/YooAssetPackageConfig.cs.meta b/Main/ResModule/YooAssetPackageConfig.cs.meta
new file mode 100644
index 0000000..c949e13
--- /dev/null
+++ b/Main/ResModule/YooAssetPackageConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f90c77034c46bdc4784d80b3f2a96715
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/ResModule/YooAssetService.cs b/Main/ResModule/YooAssetService.cs
new file mode 100644
index 0000000..beddec3
--- /dev/null
+++ b/Main/ResModule/YooAssetService.cs
@@ -0,0 +1,677 @@
+// ============================================================================
+// YooAssetService.cs 鈥� YooAsset 灏佽鏈嶅姟
+// 瀹炵幇 IYooAssetService 鍜� IYooAssetBridge锛屾浛浠� AssetBundleUtility
+// ============================================================================
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using YooAsset;
+
+namespace ProjSG.Resource
+{
+ /// <summary>
+ /// YooAsset 璧勬簮鍔犺浇鏈嶅姟鍗曚緥銆�
+ /// 灏佽 YooAsset ResourcePackage 鐨勬牳蹇冨姞杞借兘鍔涳紝鎻愪緵 UniTask 寮傛 API銆�
+ /// 鍚屾椂瀹炵幇 IYooAssetBridge 渚� Launch 绋嬪簭闆嗚法绋嬪簭闆嗚皟鐢ㄣ��
+ /// </summary>
+ public class YooAssetService : Singleton<YooAssetService>, IYooAssetService, IYooAssetBridge
+ {
+ private readonly Dictionary<string, ResourcePackage> _packages = new Dictionary<string, ResourcePackage>();
+ private ResourcePackage _defaultPackage;
+ private IRemoteServices _remoteServices;
+ private bool _isInitialized;
+ private EPlayMode _playMode;
+
+ // ====================================================================
+ // IYooAssetService Properties
+ // ====================================================================
+
+ /// <inheritdoc />
+ public bool IsInitialized => _isInitialized;
+
+ /// <inheritdoc />
+ public EPlayMode PlayMode => _playMode;
+
+ // ====================================================================
+ // IYooAssetBridge Properties
+ // ====================================================================
+
+ bool IYooAssetBridge.IsRegistered => _isInitialized;
+
+ // ====================================================================
+ // Initialization
+ // ====================================================================
+
+ /// <inheritdoc />
+ public async UniTask InitializeAsync(EPlayMode playMode, IRemoteServices remoteServices = null)
+ {
+ if (_isInitialized)
+ {
+ Debug.LogWarning("[YooAssetService] Already initialized.");
+ return;
+ }
+
+ _playMode = playMode;
+ _remoteServices = remoteServices;
+
+ // YooAsset 鍏ㄥ眬鍒濆鍖栵紙骞傜瓑鎿嶄綔锛�
+ YooAssets.Initialize();
+
+ // 鍒濆鍖栨墍鏈夐厤缃腑鐨勫寘瑁�
+ foreach (var pkgName in YooAssetPackageConfig.AllPackages)
+ {
+ try
+ {
+ // 浼樺厛澶嶇敤 Launch 闃舵宸插垱寤虹殑鍖呰9
+ var package = YooAssets.TryGetPackage(pkgName);
+ if (package != null)
+ {
+ Debug.Log($"[YooAssetService] Reusing existing package '{pkgName}' from YooAssetInitializer");
+ }
+ else
+ {
+ // 鑷鍒涘缓骞跺垵濮嬪寲锛堥娆″惎鍔ㄦ垨璇ュ寘鏈湪 Launch 闃舵鍒涘缓锛�
+ package = YooAssets.CreatePackage(pkgName);
+ var initParams = CreateInitParameters(playMode, remoteServices, pkgName);
+ var initOp = package.InitializeAsync(initParams);
+ await initOp.ToUniTask();
+
+ if (initOp.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogWarning($"[YooAssetService] Package '{pkgName}' init failed: {initOp.Error}");
+ continue;
+ }
+
+ Debug.Log($"[YooAssetService] Package '{pkgName}' newly initialized.");
+ }
+
+ _packages[pkgName] = package;
+
+ // 璁剧疆榛樿鍖�
+ if (_defaultPackage == null || pkgName == YooAssetPackageConfig.DefaultPackage)
+ {
+ _defaultPackage = package;
+ YooAssets.SetDefaultPackage(package);
+ }
+ }
+ catch (Exception ex)
+ {
+ // EditorSimulateMode 涓嬪寘涓嶅湪 Collector 涓細鎶涘紓甯革紝璺宠繃
+ Debug.LogWarning($"[YooAssetService] Package '{pkgName}' init exception (skipped): {ex.Message}");
+ }
+ }
+
+ if (_defaultPackage == null)
+ {
+ Debug.LogError("[YooAssetService] No packages initialized successfully!");
+ throw new InvalidOperationException("YooAsset initialization failed: no packages available.");
+ }
+
+ _isInitialized = true;
+ Debug.Log($"[YooAssetService] Initialized {_packages.Count}/{YooAssetPackageConfig.AllPackages.Length} packages with PlayMode={playMode}");
+ }
+
+ /// <summary>
+ /// 鍒濆鍖栨寚瀹氬悕绉扮殑棰濆璧勬簮鍖呰9銆�
+ /// </summary>
+ public async UniTask InitializePackageAsync(string packageName, EPlayMode playMode,
+ IRemoteServices remoteServices = null)
+ {
+ if (_packages.ContainsKey(packageName))
+ {
+ Debug.LogWarning($"[YooAssetService] Package '{packageName}' already initialized.");
+ return;
+ }
+
+ // 浼樺厛澶嶇敤宸插瓨鍦ㄧ殑鍖呰9锛堝彲鑳界敱 Launch 闃舵鍒涘缓锛�
+ var package = YooAssets.TryGetPackage(packageName);
+ if (package == null)
+ {
+ package = YooAssets.CreatePackage(packageName);
+ var initParams = CreateInitParameters(playMode, remoteServices ?? _remoteServices, packageName);
+ var initOp = package.InitializeAsync(initParams);
+ await initOp.ToUniTask();
+
+ if (initOp.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] Initialize package '{packageName}' failed: {initOp.Error}");
+ throw new InvalidOperationException($"YooAsset package '{packageName}' initialization failed: {initOp.Error}");
+ }
+ }
+
+ _packages[packageName] = package;
+ Debug.Log($"[YooAssetService] Package '{packageName}' initialized.");
+ }
+
+ private InitializeParameters CreateInitParameters(EPlayMode playMode, IRemoteServices remoteServices, string packageName)
+ {
+ switch (playMode)
+ {
+ case EPlayMode.EditorSimulateMode:
+ {
+#if UNITY_EDITOR
+ var simulateResult = EditorSimulateModeHelper.SimulateBuild(packageName);
+ return new EditorSimulateModeParameters
+ {
+ EditorFileSystemParameters = FileSystemParameters
+ .CreateDefaultEditorFileSystemParameters(simulateResult.PackageRootDirectory)
+ };
+#else
+ throw new InvalidOperationException("EditorSimulateMode is only available in Unity Editor.");
+#endif
+ }
+ case EPlayMode.HostPlayMode:
+ {
+ return new HostPlayModeParameters
+ {
+ BuildinFileSystemParameters = FileSystemParameters
+ .CreateDefaultBuildinFileSystemParameters(),
+ CacheFileSystemParameters = FileSystemParameters
+ .CreateDefaultCacheFileSystemParameters(remoteServices)
+ };
+ }
+ case EPlayMode.OfflinePlayMode:
+ {
+ return new OfflinePlayModeParameters
+ {
+ BuildinFileSystemParameters = FileSystemParameters
+ .CreateDefaultBuildinFileSystemParameters()
+ };
+ }
+ case EPlayMode.WebPlayMode:
+ {
+ var webParams = new WebPlayModeParameters();
+#if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR
+ string packageRoot = $"{WeChatWASM.WX.env.USER_DATA_PATH}/__GAME_FILE_CACHE";
+ webParams.WebServerFileSystemParameters = WechatFileSystemCreater
+ .CreateFileSystemParameters(packageRoot, remoteServices);
+#elif UNITY_WEBGL && DOUYINMINIGAME && !UNITY_EDITOR
+ string packageRoot = TTSDK.TTFileSystem.USER_DATA_PATH + "/__GAME_FILE_CACHE";
+ webParams.WebServerFileSystemParameters = TiktokFileSystemCreater
+ .CreateFileSystemParameters(packageRoot, remoteServices);
+#else
+ webParams.WebServerFileSystemParameters = FileSystemParameters
+ .CreateDefaultWebServerFileSystemParameters();
+ if (remoteServices != null)
+ {
+ webParams.WebRemoteFileSystemParameters = FileSystemParameters
+ .CreateDefaultWebRemoteFileSystemParameters(remoteServices);
+ }
+#endif
+ return webParams;
+ }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(playMode), playMode, "Unsupported PlayMode.");
+ }
+ }
+
+ // ====================================================================
+ // Asset Loading
+ // ====================================================================
+
+ /// <summary>
+ /// 璧勬簮鍔犺浇閲嶈瘯閰嶇疆
+ /// </summary>
+ private const int MAX_RETRY_COUNT = 3;
+ private const int BASE_RETRY_DELAY_MS = 500; // 500ms, 1000ms, 2000ms (exponential)
+
+ /// <summary>
+ /// 甯﹂噸璇曠殑寮傛鎿嶄綔鎵ц鍣ㄣ��
+ /// 浣跨敤鎸囨暟閫�閬跨瓥鐣ワ紙500ms 鈫� 1000ms 鈫� 2000ms锛夈��
+ /// </summary>
+ /// <param name="operation">瑕佹墽琛岀殑寮傛鎿嶄綔</param>
+ /// <param name="operationName">鎿嶄綔鍚嶇О锛堢敤浜庢棩蹇楋級</param>
+ /// <param name="ct">鍙栨秷浠ょ墝</param>
+ /// <returns>鎿嶄綔缁撴灉</returns>
+ private async UniTask<T> ExecuteWithRetryAsync<T>(
+ Func<UniTask<T>> operation,
+ string operationName,
+ CancellationToken ct = default)
+ {
+ Exception lastException = null;
+
+ for (int attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++)
+ {
+ try
+ {
+ ct.ThrowIfCancellationRequested();
+ return await operation();
+ }
+ catch (OperationCanceledException)
+ {
+ throw; // Don't retry cancellations
+ }
+ catch (Exception ex)
+ {
+ lastException = ex;
+ if (attempt < MAX_RETRY_COUNT)
+ {
+ int delayMs = BASE_RETRY_DELAY_MS * (1 << attempt); // Exponential backoff
+ Debug.LogWarning($"[YooAssetService] {operationName} failed (attempt {attempt + 1}/{MAX_RETRY_COUNT + 1}), retrying in {delayMs}ms: {ex.Message}");
+ await UniTask.Delay(delayMs, cancellationToken: ct);
+ }
+ }
+ }
+
+ Debug.LogError($"[YooAssetService] {operationName} failed after {MAX_RETRY_COUNT + 1} attempts: {lastException?.Message}");
+ return default;
+ }
+
+ private void ThrowIfNotInitialized()
+ {
+ if (!_isInitialized)
+ throw new InvalidOperationException("[YooAssetService] Service not initialized. Call InitializeAsync first.");
+ }
+
+ /// <summary>
+ /// 鏍规嵁璧勬簮璺緞鏌ユ壘搴斾娇鐢ㄧ殑 ResourcePackage銆�
+ /// 浣跨敤 YooAssetPackageConfig 璺敱琛ㄧ‘瀹氱洰鏍囧寘锛屾壘涓嶅埌鍒欏洖閫�鍒伴粯璁ゅ寘銆�
+ /// </summary>
+ private ResourcePackage FindPackageForAsset(string location)
+ {
+ var packageName = YooAssetPackageConfig.GetPackageForLocation(location);
+ if (_packages.TryGetValue(packageName, out var package))
+ return package;
+
+ // 璺敱鍒扮殑鍖呭皻鏈垵濮嬪寲锛屽洖閫�鍒伴粯璁ゅ寘
+ return _defaultPackage;
+ }
+
+ /// <inheritdoc />
+ public async UniTask<T> LoadAssetAsync<T>(string location, uint priority = 0,
+ CancellationToken ct = default) where T : UnityEngine.Object
+ {
+ ThrowIfNotInitialized();
+
+ if (string.IsNullOrEmpty(location))
+ {
+ Debug.LogError("[YooAssetService] LoadAssetAsync: location is null or empty.");
+ return null;
+ }
+
+ var package = FindPackageForAsset(location);
+ return await ExecuteWithRetryAsync(async () =>
+ {
+ var handle = package.LoadAssetAsync<T>(location, priority);
+ await handle.ToUniTask(cancellationToken: ct);
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ throw new InvalidOperationException($"LoadAssetAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle.GetAssetObject<T>();
+ }, $"LoadAssetAsync<{typeof(T).Name}>('{location}')", ct);
+ }
+
+ /// <inheritdoc />
+ public async UniTask<UnityEngine.Object> LoadAssetAsync(string location, Type type, uint priority = 0,
+ CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ if (string.IsNullOrEmpty(location))
+ {
+ Debug.LogError("[YooAssetService] LoadAssetAsync: location is null or empty.");
+ return null;
+ }
+
+ var package = FindPackageForAsset(location);
+ return await ExecuteWithRetryAsync(async () =>
+ {
+ var handle = package.LoadAssetAsync(location, type, priority);
+ await handle.ToUniTask(cancellationToken: ct);
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ throw new InvalidOperationException($"LoadAssetAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle.AssetObject;
+ }, $"LoadAssetAsync('{location}', {type.Name})", ct);
+ }
+
+ /// <summary>
+ /// 鍚屾鍔犺浇璧勪骇锛堜粎鍦ㄩ潪 WebGL 骞冲彴杩囨浮鏈熶娇鐢級銆�
+ /// </summary>
+ [System.Obsolete("Use LoadAssetAsync instead. Sync loading will be removed in US2.")]
+ public T LoadAssetSync<T>(string location) where T : UnityEngine.Object
+ {
+ ThrowIfNotInitialized();
+
+ if (string.IsNullOrEmpty(location))
+ {
+ Debug.LogError("[YooAssetService] LoadAssetSync: location is null or empty.");
+ return null;
+ }
+
+ var package = FindPackageForAsset(location);
+ var handle = package.LoadAssetSync<T>(location);
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] LoadAssetSync failed for '{location}': {handle.LastError}");
+ return null;
+ }
+
+ return handle.GetAssetObject<T>();
+ }
+
+ /// <inheritdoc />
+ public async UniTask<SubAssetsHandle> LoadSubAssetsAsync<T>(string location, uint priority = 0,
+ CancellationToken ct = default) where T : UnityEngine.Object
+ {
+ ThrowIfNotInitialized();
+
+ var package = FindPackageForAsset(location);
+ var handle = package.LoadSubAssetsAsync<T>(location, priority);
+ await handle.ToUniTask();
+ ct.ThrowIfCancellationRequested();
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] LoadSubAssetsAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle;
+ }
+
+ /// <inheritdoc />
+ public async UniTask<AllAssetsHandle> LoadAllAssetsAsync<T>(string location, uint priority = 0,
+ CancellationToken ct = default) where T : UnityEngine.Object
+ {
+ ThrowIfNotInitialized();
+
+ var package = FindPackageForAsset(location);
+ var handle = package.LoadAllAssetsAsync<T>(location, priority);
+ await handle.ToUniTask();
+ ct.ThrowIfCancellationRequested();
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] LoadAllAssetsAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle;
+ }
+
+ // ====================================================================
+ // RawFile Loading
+ // ====================================================================
+
+ /// <inheritdoc />
+ public async UniTask<string> LoadRawFileTextAsync(string location, CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ var rawPackage = FindPackageForAsset(location);
+ return await ExecuteWithRetryAsync(async () =>
+ {
+ var handle = rawPackage.LoadRawFileAsync(location);
+ await handle.ToUniTask(cancellationToken: ct);
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ throw new InvalidOperationException($"LoadRawFileTextAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle.GetRawFileText();
+ }, $"LoadRawFileTextAsync('{location}')", ct);
+ }
+
+ /// <inheritdoc />
+ public async UniTask<byte[]> LoadRawFileBytesAsync(string location, CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ var rawPackage = FindPackageForAsset(location);
+ return await ExecuteWithRetryAsync(async () =>
+ {
+ var handle = rawPackage.LoadRawFileAsync(location);
+ await handle.ToUniTask(cancellationToken: ct);
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ throw new InvalidOperationException($"LoadRawFileBytesAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle.GetRawFileData();
+ }, $"LoadRawFileBytesAsync('{location}')", ct);
+ }
+
+ // ====================================================================
+ // Scene Loading
+ // ====================================================================
+
+ /// <inheritdoc />
+ public async UniTask<SceneHandle> LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single,
+ LocalPhysicsMode physicsMode = LocalPhysicsMode.None, bool suspendLoad = false, uint priority = 0, CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ var package = FindPackageForAsset(location);
+ var handle = package.LoadSceneAsync(location, sceneMode, physicsMode, suspendLoad, priority);
+ await handle.ToUniTask();
+ ct.ThrowIfCancellationRequested();
+
+ if (handle.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] LoadSceneAsync failed for '{location}': {handle.LastError}");
+ }
+
+ return handle;
+ }
+
+ // ====================================================================
+ // Query
+ // ====================================================================
+
+ /// <inheritdoc />
+ public bool CheckLocationValid(string location)
+ {
+ ThrowIfNotInitialized();
+ // 鍏堢敤璺敱鍖呮鏌ワ紝鎵句笉鍒板垯閬嶅巻鎵�鏈夊寘
+ var package = FindPackageForAsset(location);
+ if (package.CheckLocationValid(location))
+ return true;
+ foreach (var kvp in _packages)
+ {
+ if (kvp.Value != package && kvp.Value.CheckLocationValid(location))
+ return true;
+ }
+ return false;
+ }
+
+ /// <inheritdoc />
+ public YooAsset.AssetInfo[] GetAssetInfosByTag(string tag)
+ {
+ ThrowIfNotInitialized();
+ // 浠庢墍鏈夊寘鏀堕泦鎸囧畾鏍囩鐨勮祫婧愪俊鎭�
+ var allInfos = new List<YooAsset.AssetInfo>();
+ foreach (var kvp in _packages)
+ {
+ var infos = kvp.Value.GetAssetInfos(tag);
+ if (infos != null && infos.Length > 0)
+ allInfos.AddRange(infos);
+ }
+ return allInfos.ToArray();
+ }
+
+ /// <inheritdoc />
+ public bool IsNeedDownloadFromRemote(string location)
+ {
+ ThrowIfNotInitialized();
+ var package = FindPackageForAsset(location);
+ return package.IsNeedDownloadFromRemote(location);
+ }
+
+ // ====================================================================
+ // Download
+ // ====================================================================
+
+ /// <inheritdoc />
+ public async UniTask DownloadByTagsAsync(string[] tags, int downloadingMaxNumber = 10,
+ int failedTryAgain = 3, IProgress<float> progress = null, CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ foreach (var tag in tags)
+ {
+ // 瀵规墍鏈夊寘鎸夋爣绛惧垱寤轰笅杞藉櫒
+ foreach (var kvp in _packages)
+ {
+ var downloader = kvp.Value.CreateResourceDownloader(tag, downloadingMaxNumber, failedTryAgain);
+ if (downloader.TotalDownloadCount == 0)
+ continue;
+
+ downloader.BeginDownload();
+ while (!downloader.IsDone)
+ {
+ ct.ThrowIfCancellationRequested();
+ progress?.Report(downloader.Progress);
+ await UniTask.Yield();
+ }
+
+ if (downloader.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] Download tag '{tag}' from package '{kvp.Key}' failed: {downloader.Error}");
+ throw new InvalidOperationException($"Resource download failed for tag '{tag}': {downloader.Error}");
+ }
+ }
+ }
+
+ progress?.Report(1f);
+ }
+
+ // ====================================================================
+ // Version Management
+ // ====================================================================
+
+ /// <inheritdoc />
+ public async UniTask<string> RequestPackageVersionAsync(CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ var op = _defaultPackage.RequestPackageVersionAsync();
+ await op.ToUniTask();
+ ct.ThrowIfCancellationRequested();
+
+ if (op.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] RequestPackageVersion failed: {op.Error}");
+ throw new InvalidOperationException($"Request package version failed: {op.Error}");
+ }
+
+ return op.PackageVersion;
+ }
+
+ /// <inheritdoc />
+ public async UniTask UpdatePackageManifestAsync(string packageVersion, CancellationToken ct = default)
+ {
+ ThrowIfNotInitialized();
+
+ var op = _defaultPackage.UpdatePackageManifestAsync(packageVersion);
+ await op.ToUniTask();
+ ct.ThrowIfCancellationRequested();
+
+ if (op.Status != EOperationStatus.Succeed)
+ {
+ Debug.LogError($"[YooAssetService] UpdatePackageManifest failed: {op.Error}");
+ throw new InvalidOperationException($"Update package manifest failed: {op.Error}");
+ }
+ }
+
+ // ====================================================================
+ // Release
+ // ====================================================================
+
+ /// <inheritdoc />
+ public void ReleaseHandle(HandleBase handle)
+ {
+ if (handle == null) return;
+ handle.Release();
+ }
+
+ /// <inheritdoc />
+ public async UniTask UnloadUnusedAssetsAsync()
+ {
+ ThrowIfNotInitialized();
+ // 瀵规墍鏈夊寘鎵ц鍗歌浇
+ foreach (var kvp in _packages)
+ {
+ var op = kvp.Value.UnloadUnusedAssetsAsync();
+ await op.ToUniTask();
+ }
+ }
+
+ /// <inheritdoc />
+ public async UniTask UnloadAllAssetsAsync()
+ {
+ ThrowIfNotInitialized();
+ // 瀵规墍鏈夊寘鎵ц鍗歌浇
+ foreach (var kvp in _packages)
+ {
+ var op = kvp.Value.UnloadAllAssetsAsync();
+ await op.ToUniTask();
+ }
+ }
+
+ // ====================================================================
+ // IYooAssetBridge Implementation
+ // ====================================================================
+
+ async UniTask<T> IYooAssetBridge.LoadAssetAsync<T>(string location)
+ {
+ return await LoadAssetAsync<T>(location);
+ }
+
+ async UniTask<string> IYooAssetBridge.LoadRawFileTextAsync(string location)
+ {
+ return await LoadRawFileTextAsync(location);
+ }
+
+ async UniTask<byte[]> IYooAssetBridge.LoadRawFileBytesAsync(string location)
+ {
+ return await LoadRawFileBytesAsync(location);
+ }
+
+ async UniTask IYooAssetBridge.PreloadAsync(string[] locations)
+ {
+ // 鎵归噺棰勫姞杞斤紝浣跨敤 UniTask.WhenAll 骞惰
+ var tasks = new List<UniTask>(locations.Length);
+ foreach (var loc in locations)
+ {
+ tasks.Add(LoadAssetAsync<UnityEngine.Object>(loc).AsUniTask());
+ }
+ await UniTask.WhenAll(tasks);
+ }
+
+ T IYooAssetBridge.GetCached<T>(string location)
+ {
+ // 濮旀墭缁� ResourceCacheManager锛圲S4 宸查泦鎴愶級
+ if (ProjSG.Resource.ResourceCacheManager.IsValid())
+ {
+ return ProjSG.Resource.ResourceCacheManager.Instance.GetCached<T>(location);
+ }
+ return null;
+ }
+
+ // ====================================================================
+ // Sync Wrappers (Transitional 鈥� removed in US2)
+ // ====================================================================
+
+ /// <summary>
+ /// 鍚屾鍔犺浇鎵�鏈夊悓绫诲瀷璧勬簮锛堣繃娓℃湡浣跨敤锛夈��
+ /// </summary>
+ [System.Obsolete("Use LoadAllAssetsAsync instead. Sync loading will be removed in US2.")]
+ public AllAssetsHandle LoadAllAssetsSync<T>(string location) where T : UnityEngine.Object
+ {
+ ThrowIfNotInitialized();
+ var package = FindPackageForAsset(location);
+ return package.LoadAllAssetsSync<T>(location);
+ }
+ }
+}
diff --git a/Main/ResModule/YooAssetService.cs.meta b/Main/ResModule/YooAssetService.cs.meta
new file mode 100644
index 0000000..120171d
--- /dev/null
+++ b/Main/ResModule/YooAssetService.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ad3881ecd3a99ea439a411fd1866ed41
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Arena/ArenaHeroHead.cs b/Main/System/Arena/ArenaHeroHead.cs
index 1895e49..01e96eb 100644
--- a/Main/System/Arena/ArenaHeroHead.cs
+++ b/Main/System/Arena/ArenaHeroHead.cs
@@ -1,4 +1,5 @@
using UnityEngine;
+using Cysharp.Threading.Tasks;
public class ArenaHeroHead : MonoBehaviour
{
@@ -27,4 +28,27 @@
txtHeroLv.text = Language.Get("Arena22", heroLv);
}
+
+ public async UniTask DisplayAsync(int heroID, int skinID, int heroLv)
+ {
+ if (!HeroConfig.HasKey(heroID) || !HeroSkinConfig.HasKey(skinID))
+ return;
+ var heroConfig = HeroConfig.Get(heroID);
+ var heroSkinConfig = HeroSkinConfig.Get(skinID);
+ imgQuality.SetSprite("heroheadBG" + heroConfig.Quality);
+
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", heroSkinConfig.SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ imgHeadIcon.SetSprite("herohead_default");
+ }
+ else
+ {
+ imgHeadIcon.overrideSprite = sprite;
+ }
+
+ txtHeroLv.text = Language.Get("Arena22", heroLv);
+ }
}
diff --git a/Main/System/AssetVersion/DownLoadAndDiscompressHotTask.cs b/Main/System/AssetVersion/DownLoadAndDiscompressHotTask.cs
index 97f8daf..bf430c0 100644
--- a/Main/System/AssetVersion/DownLoadAndDiscompressHotTask.cs
+++ b/Main/System/AssetVersion/DownLoadAndDiscompressHotTask.cs
@@ -144,7 +144,9 @@
{
if (reinitedBuiltInAsset)
{
- AssetBundleUtility.Instance.ReInitBuiltInAsset();
+ // YooAsset 璧勬簮鏇存柊鍚庤嚜鍔ㄧ敓鏁堬紝涓嶅啀闇�瑕� AssetBundleUtility.ReInitBuiltInAsset()
+ // 濡傞渶涓诲姩鍒锋柊鍙�氳繃 YooAsset 鐨� manifest 鏇存柊鏈哄埗澶勭悊
+ Debug.Log("[DownLoadAndDiscompressHotTask] BuiltIn asset refresh skipped 鈥� YooAsset handles resource versioning");
}
}
catch (System.Exception ex)
diff --git a/Main/System/Attribute/TotalAttributeWin.cs b/Main/System/Attribute/TotalAttributeWin.cs
index 5e47032..7bdebba 100644
--- a/Main/System/Attribute/TotalAttributeWin.cs
+++ b/Main/System/Attribute/TotalAttributeWin.cs
@@ -121,6 +121,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
var layouts = allContent.GetComponentsInChildren<LayoutGroup>(true);
foreach (var layout in layouts)
@@ -128,6 +129,7 @@
LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
}
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
foreach (var layout in layouts)
{
diff --git a/Main/System/Battle/AsyncResourceGuard.cs b/Main/System/Battle/AsyncResourceGuard.cs
new file mode 100644
index 0000000..414d43e
--- /dev/null
+++ b/Main/System/Battle/AsyncResourceGuard.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using Cysharp.Threading.Tasks;
+
+/// <summary>
+/// US3: 寮傛璧勬簮瀹堝崼锛屽欢杩熺壒鏁�/Spine鎾斁鐩村埌璧勬簮灏辩华銆�
+/// 鍦ㄨ祫婧愭湭鍔犺浇瀹屾垚鏃舵帓闃熸挱鏀捐姹傦紝鍔犺浇瀹屾垚鍚庢寜椤哄簭鎵ц銆�
+/// </summary>
+public class AsyncResourceGuard
+{
+ private bool _isReady;
+ private readonly Queue<Action> _pendingActions = new Queue<Action>();
+ private UniTaskCompletionSource _readySource;
+
+ public bool IsReady => _isReady;
+
+ public AsyncResourceGuard()
+ {
+ _isReady = false;
+ _readySource = new UniTaskCompletionSource();
+ }
+
+ /// <summary>
+ /// 鏍囪璧勬簮宸插氨缁紝鎵ц鎵�鏈夋帓闃熺殑鍔ㄤ綔銆�
+ /// </summary>
+ public void SetReady()
+ {
+ if (_isReady) return;
+
+ _isReady = true;
+ _readySource.TrySetResult();
+
+ // 鎵ц鎵�鏈夋帓闃熺殑鍔ㄤ綔
+ while (_pendingActions.Count > 0)
+ {
+ var action = _pendingActions.Dequeue();
+ try
+ {
+ action?.Invoke();
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ }
+ }
+ }
+
+ /// <summary>
+ /// 鎺掗槦涓�涓姩浣滐細濡傛灉璧勬簮宸插氨缁垯绔嬪嵆鎵ц锛屽惁鍒欐帓闃熺瓑寰呫��
+ /// </summary>
+ public void EnqueueOrExecute(Action action)
+ {
+ if (action == null) return;
+
+ if (_isReady)
+ {
+ action.Invoke();
+ }
+ else
+ {
+ _pendingActions.Enqueue(action);
+ }
+ }
+
+ /// <summary>
+ /// 绛夊緟璧勬簮灏辩华锛堝彲鐢ㄤ簬 await锛夈��
+ /// </summary>
+ public UniTask WaitUntilReady()
+ {
+ if (_isReady) return UniTask.CompletedTask;
+ return _readySource.Task;
+ }
+
+ /// <summary>
+ /// 閲嶇疆鐘舵�侊紝鐢ㄤ簬璧勬簮閲嶆柊鍔犺浇銆�
+ /// </summary>
+ public void Reset()
+ {
+ _isReady = false;
+ _pendingActions.Clear();
+ _readySource = new UniTaskCompletionSource();
+ }
+
+ /// <summary>
+ /// 娓呯┖鎵�鏈夋帓闃熺殑鍔ㄤ綔锛堝鍦烘櫙鍒囨崲鏃讹級銆�
+ /// </summary>
+ public void ClearPending()
+ {
+ _pendingActions.Clear();
+ }
+}
+
+/// <summary>
+/// US3: MonoBehaviour 鐗堝紓姝ヨ祫婧愬畧鍗紝鍙寕杞藉埌闇�瑕佺瓑寰呰祫婧愮殑 GameObject 涓娿��
+/// 鍏稿瀷鐢ㄦ硶锛氬湪 BattleField 涓婄鐞嗙壒鏁�/Spine 鐨勫欢杩熸挱鏀俱��
+/// </summary>
+public class AsyncResourceGuardBehaviour : MonoBehaviour
+{
+ private readonly AsyncResourceGuard _guard = new AsyncResourceGuard();
+
+ public bool IsReady => _guard.IsReady;
+
+ /// <summary>
+ /// 鏍囪璧勬簮灏辩华锛岃Е鍙戞墍鏈夋帓闃熸挱鏀俱��
+ /// </summary>
+ public void SetReady()
+ {
+ _guard.SetReady();
+ }
+
+ /// <summary>
+ /// 鎻愪氦涓�涓挱鏀捐姹傦細璧勬簮灏辩华鍒欑珛鍗虫墽琛岋紝鍚﹀垯鎺掗槦銆�
+ /// </summary>
+ public void Play(Action playAction)
+ {
+ if (this == null) return;
+ _guard.EnqueueOrExecute(playAction);
+ }
+
+ /// <summary>
+ /// 绛夊緟璧勬簮灏辩华銆�
+ /// </summary>
+ public UniTask WaitUntilReady()
+ {
+ return _guard.WaitUntilReady();
+ }
+
+ /// <summary>
+ /// 閲嶇疆瀹堝崼鐘舵�併��
+ /// </summary>
+ public void ResetGuard()
+ {
+ _guard.Reset();
+ }
+
+ private void OnDestroy()
+ {
+ _guard.ClearPending();
+ }
+}
diff --git a/Main/System/Battle/AsyncResourceGuard.cs.meta b/Main/System/Battle/AsyncResourceGuard.cs.meta
new file mode 100644
index 0000000..4fade2a
--- /dev/null
+++ b/Main/System/Battle/AsyncResourceGuard.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f604b53f3abcb5544b8ec2b68d0146aa
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleField/BattleField.cs b/Main/System/Battle/BattleField/BattleField.cs
index 0fd882a..b0b24ca 100644
--- a/Main/System/Battle/BattleField/BattleField.cs
+++ b/Main/System/Battle/BattleField/BattleField.cs
@@ -5,6 +5,7 @@
using DG.Tweening;
using System.IO;
using System.Linq;
+using Cysharp.Threading.Tasks;
public class BattleField
@@ -87,7 +88,9 @@
{
guid = _guid;
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy constructor, use CreateAsync for new code
GameObject go = ResManager.Instance.LoadAsset<GameObject>("Battle/Prefabs", "BattleRootNode");
+ #pragma warning restore CS0618
GameObject battleRootNodeGO = GameObject.Instantiate(go);
battleRootNode = battleRootNodeGO.GetComponent<BattleRootNode>();
battleRootNodeGO.name = this.GetType().Name;
@@ -98,6 +101,33 @@
recordPlayer = new RecordPlayer();
soundManager = new BattleSoundManager(this);
+ processingDeathObjIds = new HashSet<uint>();
+ }
+
+ /// <summary>
+ /// US2: Static async factory method. Creates BattleField with async resource loading.
+ /// </summary>
+ public static async UniTask<BattleField> CreateAsync(string _guid)
+ {
+ var field = new BattleField(_guid, skipResourceLoad: true);
+ GameObject go = await ResManager.Instance.LoadAssetAsync<GameObject>("Battle/Prefabs", "BattleRootNode");
+ GameObject battleRootNodeGO = GameObject.Instantiate(go);
+ field.battleRootNode = battleRootNodeGO.GetComponent<BattleRootNode>();
+ battleRootNodeGO.name = field.GetType().Name;
+ return field;
+ }
+
+ /// <summary>
+ /// US2: Protected constructor without resource loading (for async factory).
+ /// </summary>
+ protected BattleField(string _guid, bool skipResourceLoad)
+ {
+ guid = _guid;
+ battleObjMgr = new BattleObjMgr();
+ battleEffectMgr = new BattleEffectMgr();
+ battleTweenMgr = new BattleTweenMgr();
+ recordPlayer = new RecordPlayer();
+ soundManager = new BattleSoundManager(this);
processingDeathObjIds = new HashSet<uint>();
}
@@ -302,7 +332,22 @@
BattleMapConfig battleMapConfig = BattleMapConfig.Get(mapID);
if (battleMapConfig != null)
{
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, use LoadMapAsync
Texture texture = ResManager.Instance.LoadAsset<Texture>("Texture/FullScreenBg", battleMapConfig.MapBg);
+ #pragma warning restore CS0618
+ battleRootNode.SetBackground(texture);
+ }
+ }
+
+ /// <summary>
+ /// US2: Async map loading.
+ /// </summary>
+ protected virtual async UniTask LoadMapAsync(int mapID)
+ {
+ BattleMapConfig battleMapConfig = BattleMapConfig.Get(mapID);
+ if (battleMapConfig != null)
+ {
+ Texture texture = await ResManager.Instance.LoadAssetAsync<Texture>("Texture/FullScreenBg", battleMapConfig.MapBg);
battleRootNode.SetBackground(texture);
}
}
diff --git a/Main/System/Battle/BattleField/StoryBattleField.cs b/Main/System/Battle/BattleField/StoryBattleField.cs
index 262cc9a..c64f4a6 100644
--- a/Main/System/Battle/BattleField/StoryBattleField.cs
+++ b/Main/System/Battle/BattleField/StoryBattleField.cs
@@ -2,6 +2,7 @@
using LitJson;
using UnityEngine;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
// 銆愪富绾挎垬鏂楁祦绋嬨��
// 鍙戦�� B413 锛圧eqType 涓� 2 鎴� 3锛�
@@ -77,7 +78,21 @@
{
if (chapterConfig != null)
{
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, use LoadMapAsync
Texture texture = ResManager.Instance.LoadAsset<Texture>("Texture/FullScreenBg", chapterConfig.MapBG);
+ #pragma warning restore CS0618
+ battleRootNode.SetBackground(texture);
+ }
+ }
+
+ /// <summary>
+ /// US2: Async map loading.
+ /// </summary>
+ protected override async UniTask LoadMapAsync(int mapID)
+ {
+ if (chapterConfig != null)
+ {
+ Texture texture = await ResManager.Instance.LoadAssetAsync<Texture>("Texture/FullScreenBg", chapterConfig.MapBG);
battleRootNode.SetBackground(texture);
}
}
diff --git a/Main/System/Battle/BattleHUDWin.cs b/Main/System/Battle/BattleHUDWin.cs
index 60770af..8bc9877 100644
--- a/Main/System/Battle/BattleHUDWin.cs
+++ b/Main/System/Battle/BattleHUDWin.cs
@@ -78,7 +78,15 @@
private void InitializePools()
{
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, use InitializePoolsAsync
damagePrefabPool = GameObjectPoolManager.Instance.GetPool(UILoader.LoadPrefab("DamageContent"));
+ #pragma warning restore CS0618
+ }
+
+ private async UniTask InitializePoolsAsync()
+ {
+ damagePrefabPool = GameObjectPoolManager.Instance.GetPool(await UILoader.LoadPrefabAsync("DamageContent"));
+ if (this == null) return;
}
public void SetBattleField(BattleField _battleField)
diff --git a/Main/System/Battle/BattleLoadingWin.cs b/Main/System/Battle/BattleLoadingWin.cs
new file mode 100644
index 0000000..34486f0
--- /dev/null
+++ b/Main/System/Battle/BattleLoadingWin.cs
@@ -0,0 +1,165 @@
+using UnityEngine;
+using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
+using System;
+using System.Threading;
+
+/// <summary>
+/// 鎴樻枟鍔犺浇鐣岄潰锛屾樉绀烘垬鏂楄祫婧愬姞杞借繘搴︺��
+/// 鍦ㄦ垬鏂楄祫婧愬紓姝ュ姞杞芥湡闂村睍绀猴紝鍔犺浇瀹屾垚鍚庤嚜鍔ㄥ叧闂��
+/// </summary>
+public class BattleLoadingWin : UIBase
+{
+ [SerializeField] private Slider m_ProgressSlider;
+ [SerializeField] private Text m_ProgressText;
+ [SerializeField] private Text m_TipText;
+
+ private float _currentProgress;
+ private float _targetProgress;
+ private bool _isComplete;
+
+ /// <summary>
+ /// 鍒涘缓 IProgress<float> 閫傞厤鍣紝鐢ㄤ簬闆嗘垚寮傛鍔犺浇鐨勮繘搴﹀洖鎶ャ��
+ /// </summary>
+ public IProgress<float> CreateProgressReporter()
+ {
+ return new BattleLoadingProgress(this);
+ }
+
+ protected override void InitComponent()
+ {
+ base.InitComponent();
+ _currentProgress = 0f;
+ _targetProgress = 0f;
+ _isComplete = false;
+ }
+
+ protected override void OnPreOpen()
+ {
+ base.OnPreOpen();
+ _currentProgress = 0f;
+ _targetProgress = 0f;
+ _isComplete = false;
+ UpdateUI();
+ RefreshTip();
+ }
+
+ /// <summary>
+ /// 璁剧疆鍔犺浇杩涘害 (0.0 ~ 1.0)銆�
+ /// </summary>
+ public void SetProgress(float progress)
+ {
+ _targetProgress = Mathf.Clamp01(progress);
+ if (_targetProgress >= 1f)
+ {
+ _isComplete = true;
+ }
+ }
+
+ /// <summary>
+ /// 鐩存帴璁剧疆杩涘害骞跺埛鏂� UI锛堟棤骞虫粦杩囨浮锛夈��
+ /// </summary>
+ public void SetProgressDirectly(float progress)
+ {
+ _targetProgress = Mathf.Clamp01(progress);
+ _currentProgress = _targetProgress;
+ UpdateUI();
+
+ if (_targetProgress >= 1f)
+ {
+ _isComplete = true;
+ }
+ }
+
+ private void LateUpdate()
+ {
+ if (Mathf.Abs(_currentProgress - _targetProgress) > 0.001f)
+ {
+ _currentProgress = Mathf.Lerp(_currentProgress, _targetProgress, Time.deltaTime * 5f);
+
+ // 鎺ヨ繎鐩爣鏃剁洿鎺ュ榻愶紝閬垮厤鏃犻檺瓒嬭繎
+ if (Mathf.Abs(_currentProgress - _targetProgress) < 0.005f)
+ {
+ _currentProgress = _targetProgress;
+ }
+
+ UpdateUI();
+ }
+
+ if (_isComplete && _currentProgress >= 0.99f)
+ {
+ _currentProgress = 1f;
+ UpdateUI();
+ OnLoadComplete();
+ }
+ }
+
+ private void UpdateUI()
+ {
+ if (m_ProgressSlider != null)
+ {
+ m_ProgressSlider.value = _currentProgress;
+ }
+
+ if (m_ProgressText != null)
+ {
+ m_ProgressText.text = $"{(int)(_currentProgress * 100)}%";
+ }
+ }
+
+ private void RefreshTip()
+ {
+ if (m_TipText != null && GeneralDefine.loadingTips != null && GeneralDefine.loadingTips.Length > 0)
+ {
+ var randomIndex = UnityEngine.Random.Range(0, GeneralDefine.loadingTips.Length);
+ m_TipText.text = Language.Get(GeneralDefine.loadingTips[randomIndex]);
+ }
+ }
+
+ private void OnLoadComplete()
+ {
+ // 寤惰繜鍏抽棴锛岃鐜╁鐪嬪埌 100% 鐘舵��
+ CompleteAndCloseAsync().Forget();
+ }
+
+ private async UniTask CompleteAndCloseAsync()
+ {
+ await UniTask.Delay(300, cancellationToken: this.GetCancellationTokenOnDestroy());
+
+ if (this != null)
+ {
+ UIManager.Instance.CloseWindow(this);
+ }
+ }
+
+ /// <summary>
+ /// 鏄剧ず鎴樻枟鍔犺浇鐣岄潰骞惰繑鍥炶繘搴﹀洖鎶ュ櫒銆�
+ /// 鐢ㄦ硶: var progress = await BattleLoadingWin.ShowAsync();
+ /// </summary>
+ public static async UniTask<BattleLoadingWin> ShowAsync()
+ {
+ var win = await UIManager.Instance.OpenWindowAsync("BattleLoadingWin") as BattleLoadingWin;
+ return win;
+ }
+
+ /// <summary>
+ /// IProgress 閫傞厤鍣ㄥ疄鐜般��
+ /// </summary>
+ private class BattleLoadingProgress : IProgress<float>
+ {
+ private readonly BattleLoadingWin _win;
+
+ public BattleLoadingProgress(BattleLoadingWin win)
+ {
+ _win = win;
+ }
+
+ public void Report(float value)
+ {
+ if (_win != null)
+ {
+ _win.SetProgress(value);
+ }
+ }
+ }
+}
diff --git a/Main/System/Battle/BattleLoadingWin.cs.meta b/Main/System/Battle/BattleLoadingWin.cs.meta
new file mode 100644
index 0000000..fe8418f
--- /dev/null
+++ b/Main/System/Battle/BattleLoadingWin.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5a91908ec288e6544943af9b9bdbbf03
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/System/Battle/BattleManager.cs b/Main/System/Battle/BattleManager.cs
index 1719158..b961f00 100644
--- a/Main/System/Battle/BattleManager.cs
+++ b/Main/System/Battle/BattleManager.cs
@@ -3,6 +3,8 @@
using LitJson;
using System;
using System.Linq;
+using Cysharp.Threading.Tasks;
+using ProjSG.Resource;
public class BattleManager : GameSystemManager<BattleManager>
{
@@ -502,6 +504,12 @@
int MapID = (int)vNetData.MapID;
int FuncLineID = (int)vNetData.FuncLineID;
+ // US4 T043: Trigger BattleScene preload before creating battle field
+ if (!ResourcePreloader.Instance.IsConfigLoaded("BattleScene"))
+ {
+ PreloadBattleResourcesAsync().Forget();
+ }
+
bool isCreate = true;
if (battleFields.TryGetValue(guid, out battleField))
{
@@ -548,6 +556,23 @@
return battleField;
}
+ /// <summary>
+ /// US4 T043: 寮傛棰勫姞杞芥垬鏂楄祫婧愩��
+ /// </summary>
+ public async UniTask PreloadBattleResourcesAsync(IProgress<float> progress = null)
+ {
+ if (ResourcePreloader.Instance.IsConfigLoaded("BattleScene")) return;
+ await ResourcePreloader.Instance.PreloadAsync("BattleScene", progress);
+ }
+
+ /// <summary>
+ /// US4 T043: 鍗歌浇鎴樻枟棰勫姞杞借祫婧愶紙鎵�鏈夋垬鏂楃粨鏉熸椂璋冪敤锛夈��
+ /// </summary>
+ public void UnloadBattleResources()
+ {
+ ResourcePreloader.Instance.UnloadConfig("BattleScene");
+ }
+
public void DestroyBattleField(BattleField battleField)
{
if (battleField == null)
diff --git a/Main/System/Battle/BattleObject/BattleObjectFactory.cs b/Main/System/Battle/BattleObject/BattleObjectFactory.cs
index 7101554..0cb40f5 100644
--- a/Main/System/Battle/BattleObject/BattleObjectFactory.cs
+++ b/Main/System/Battle/BattleObject/BattleObjectFactory.cs
@@ -2,6 +2,7 @@
using System;
using UnityEngine;
using Spine.Unity;
+using Cysharp.Threading.Tasks;
public class BattleObjectFactory
{
@@ -32,10 +33,12 @@
}
// ===== 鐩存帴鍔犺浇璧勬簮锛堥潪棰勫姞杞界殑璧勬簮涓嶈蛋缂撳瓨绯荤粺锛�=====
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, async path exists in CreateBattleObjectAsync
SkeletonDataAsset skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>(
"Hero/SpineRes/",
skinCfg.SpineRes
);
+ #pragma warning restore CS0618
if (skeletonDataAsset == null)
{
@@ -44,7 +47,9 @@
}
// ==============================================
+ #pragma warning disable CS0618
GameObject battleGO = ResManager.Instance.LoadAsset<GameObject>("Hero/SpineRes", "Hero_001"/*skinCfg.SpineRes*/);
+ #pragma warning restore CS0618
GameObject goParent = posNodeList[teamHero.positionNum];
BattleObject battleObject = Produce(teamHero.positionNum, _battleField);
@@ -93,6 +98,65 @@
return battleObject;
}
+ /// <summary>
+ /// US2: Async version of CreateBattleObject.
+ /// </summary>
+ public static async UniTask<BattleObject> CreateBattleObjectAsync(BattleField _battleField, List<GameObject> posNodeList, TeamHero teamHero, BattleCamp _Camp)
+ {
+ var skinCfg = HeroSkinConfig.Get(teamHero.SkinID);
+ if (skinCfg == null)
+ {
+ Debug.LogError($"BattleObjectFactory: skinCfg is null for SkinID {teamHero.SkinID}");
+ return null;
+ }
+
+ SkeletonDataAsset skeletonDataAsset = await ResManager.Instance.LoadAssetAsync<SkeletonDataAsset>(
+ "Hero/SpineRes/",
+ skinCfg.SpineRes
+ );
+
+ if (skeletonDataAsset == null)
+ {
+ Debug.LogError($"BattleObjectFactory: Failed to load SkeletonDataAsset for {skinCfg.SpineRes}");
+ return null;
+ }
+
+ GameObject battleGO = await ResManager.Instance.LoadAssetAsync<GameObject>("Hero/SpineRes", "Hero_001");
+
+ GameObject goParent = posNodeList[teamHero.positionNum];
+ BattleObject battleObject = Produce(teamHero.positionNum, _battleField);
+ battleObject.ObjID = teamHero.ObjID;
+
+ GameObject realGO = GameObject.Instantiate(battleGO, goParent.transform);
+ SkeletonAnimation skeletonAnimation = realGO.GetComponentInChildren<SkeletonAnimation>(true);
+
+ float finalScaleRate = modelScaleRate * teamHero.modelScale;
+
+ skeletonAnimation.initialSkinName = skinCfg.InitialSkinName;
+ skeletonAnimation.skeletonDataAsset = skeletonDataAsset;
+ skeletonAnimation.Initialize(true);
+
+ if (!string.IsNullOrEmpty(skinCfg.InitialSkinName))
+ {
+ var skeleton = skeletonAnimation.Skeleton;
+ skeleton.SetSkin(skinCfg.InitialSkinName);
+ skeleton.SetSlotsToSetupPose();
+ skeletonAnimation.Update(0);
+ }
+
+ realGO.name = battleObject.ObjID.ToString();
+ realGO.transform.localScale = new Vector3(finalScaleRate, finalScaleRate, finalScaleRate);
+ RectTransform rectTrans = realGO.GetComponent<RectTransform>();
+ rectTrans.anchoredPosition = Vector2.zero;
+
+ if (battleObject is HeroBattleObject heroBattleObject)
+ {
+ heroBattleObject.Init(realGO, teamHero, _Camp);
+ }
+
+ return battleObject;
+ }
+
public static BattleObject Produce(int positionNum, BattleField battleField)
{
if (positionNum >= 0)
diff --git a/Main/System/Battle/SkillEffect/SkillEffectFactory.cs b/Main/System/Battle/SkillEffect/SkillEffectFactory.cs
index 65ac7ad..7175020 100644
--- a/Main/System/Battle/SkillEffect/SkillEffectFactory.cs
+++ b/Main/System/Battle/SkillEffect/SkillEffectFactory.cs
@@ -25,8 +25,6 @@
default:
UnityEngine.Debug.LogError("Unknown Skill Effect Type " + skillConfig.effectType + " skill id is " + skillConfig.SkillID);
return new NoEffect(skillBase, skillConfig, caster, tagUseSkillAttack);
- break;
}
- return null;
}
}
\ No newline at end of file
diff --git a/Main/System/Battle/Sound/BattleSoundManager.cs b/Main/System/Battle/Sound/BattleSoundManager.cs
index 33f3439..e52c232 100644
--- a/Main/System/Battle/Sound/BattleSoundManager.cs
+++ b/Main/System/Battle/Sound/BattleSoundManager.cs
@@ -1,5 +1,6 @@
锘縰sing UnityEngine;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
/// <summary>
/// 鎴樻枟闊虫晥绠$悊鍣�
@@ -236,14 +237,32 @@
if (config == null)
return null;
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback, use LoadAudioClipAsync
AudioClip audioClip = ResManager.Instance.LoadAsset<AudioClip>(
"Audio/" + config.Folder,
config.Audio,
false
);
+ #pragma warning restore CS0618
return audioClip;
}
+
+ /// <summary>
+ /// US2: Async audio clip loading.
+ /// </summary>
+ private async UniTask<AudioClip> LoadAudioClipAsync(int audioId)
+ {
+ var config = AudioConfig.Get(audioId);
+ if (config == null)
+ return null;
+
+ return await ResManager.Instance.LoadAssetAsync<AudioClip>(
+ "Audio/" + config.Folder,
+ config.Audio,
+ false
+ );
+ }
/// <summary>
/// 鑾峰彇鍙敤鐨勯煶棰戞簮
diff --git a/Main/System/Battle/UIComp/BossHeadCell.cs b/Main/System/Battle/UIComp/BossHeadCell.cs
index 6041171..9d2caea 100644
--- a/Main/System/Battle/UIComp/BossHeadCell.cs
+++ b/Main/System/Battle/UIComp/BossHeadCell.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
public class BossHeadCell : MonoBehaviour
{
@@ -28,6 +29,23 @@
// TODO YYL
// imgFrame璺焛mgDecoration绛夊够澧冮榿瀹屾垚涔嬪悗鍐嶆潵鍋�
}
+
+ public async UniTask SetTeamHeroAsync(TeamHero teamHero)
+ {
+ if (null == teamHero)
+ {
+ SetDefault();
+ return;
+ }
+
+ HeroSkinConfig heroSkinConfig = teamHero.skinConfig;
+ imgIcon.sprite = await UILoader.LoadSpriteAsync("HeroHead", heroSkinConfig.SquareIcon);
+ if (this == null) return;
+ txtLv.text = Language.Get("Arena22", teamHero.level);
+
+ // TODO YYL
+ // imgFrame璺焛mgDecoration绛夊够澧冮榿瀹屾垚涔嬪悗鍐嶆潵鍋�
+ }
public void SetDefault()
{
diff --git a/Main/System/Battle/UIComp/SkillTips.cs b/Main/System/Battle/UIComp/SkillTips.cs
index 429bc1c..d9648fb 100644
--- a/Main/System/Battle/UIComp/SkillTips.cs
+++ b/Main/System/Battle/UIComp/SkillTips.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine.UI;
using UnityEngine;
@@ -81,6 +82,68 @@
});
battleField.battleTweenMgr.OnPlayTween(tween1);
}
+
+ public async UniTask PlayMotionAsync(BattleField battleField, bool isRed, TeamHero teamHero, SkillConfig skillConfig)
+ {
+ if (teamHero == null || skillConfig == null)
+ {
+ return;
+ }
+
+ if (skillConfig.FuncType != 2)
+ return;
+
+ KillAllTweens();
+
+ imgIcon.sprite = await UILoader.LoadSpriteAsync("HeroHead", teamHero.skinConfig.SquareIcon);
+ if (this == null) return;
+ imgSkillName.sprite = await UILoader.LoadSpriteAsync("SkillNameIcon", skillConfig.SkillTipsName);
+ if (this == null) return;
+ imgSkillName.SetNativeSize();
+ // 淇濊瘉寮�濮嬫椂鎵�鏈夊浘鐗囦负鍙锛坅lpha=1锛�
+ if (imageBg != null) { var c = imageBg.color; c.a = 1f; imageBg.color = c; }
+ if (imgIcon != null) { var c = imgIcon.color; c.a = 1f; imgIcon.color = c; }
+ if (imgSkillName != null) { var c = imgSkillName.color; c.a = 1f; imgSkillName.color = c; }
+
+ gameObject.SetActive(true);
+ float posY = transform.localPosition.y;
+ transform.localPosition = isRed ? new Vector3(-beginingX, posY, 0f) : new Vector3(beginingX, posY, 0f);
+ tween1 = transform.DOLocalMoveX(0, tweenDuration / battleField.speedRatio, false).SetEase(Ease.Linear).OnComplete(() =>
+ {
+ tween1 = null;
+ tween3 = DOVirtual.DelayedCall(delayDuration / battleField.speedRatio, () =>
+ {
+ tween3 = null;
+
+ // tween2 鏀逛负鍋氬噺娣★紙瀵� imageBg銆乮mgIcon銆乮mgSkillName 鍚屾娣″嚭锛�
+ float fadeDuration = tweenDuration / battleField.speedRatio;
+ Sequence seq = DOTween.Sequence();
+ if (imageBg != null)
+ seq.Join(imageBg.DOFade(0f, fadeDuration).SetEase(Ease.InQuad));
+ if (imgIcon != null)
+ seq.Join(imgIcon.DOFade(0f, fadeDuration).SetEase(Ease.InQuad));
+ if (imgSkillName != null)
+ seq.Join(imgSkillName.DOFade(0f, fadeDuration).SetEase(Ease.InQuad));
+
+ seq.OnComplete(() =>
+ {
+ tween2 = null;
+ // 鎭㈠鍥剧墖 alpha锛屼繚璇佷笅娆℃樉绀烘椂鍙
+ if (imageBg != null) { var cc = imageBg.color; cc.a = 1f; imageBg.color = cc; }
+ if (imgIcon != null) { var cc = imgIcon.color; cc.a = 1f; imgIcon.color = cc; }
+ if (imgSkillName != null) { var cc = imgSkillName.color; cc.a = 1f; imgSkillName.color = cc; }
+
+ transform.localPosition = isRed ? new Vector3(-beginingX, posY, 0f) : new Vector3(beginingX, posY, 0f);
+ gameObject.SetActive(false);
+ });
+
+ tween2 = seq;
+ battleField.battleTweenMgr.OnPlayTween(tween2);
+ });
+ battleField.battleTweenMgr.OnPlayTween(tween3);
+ });
+ battleField.battleTweenMgr.OnPlayTween(tween1);
+ }
public void KillAllTweens()
{
diff --git a/Main/System/Battle/UIComp/TotalDamageDisplayer.cs b/Main/System/Battle/UIComp/TotalDamageDisplayer.cs
index 4abc05c..24a077b 100644
--- a/Main/System/Battle/UIComp/TotalDamageDisplayer.cs
+++ b/Main/System/Battle/UIComp/TotalDamageDisplayer.cs
@@ -97,4 +97,78 @@
battleField.battleTweenMgr.OnPlayTween(punchTween);
}
+
+ public async UniTask SetDamageAsync(BattleDmgInfo dmgInfo)
+ {
+ var battleField = BattleManager.Instance.GetBattleField(dmgInfo.battleFieldGuid);
+
+ if (!gameObject.activeInHierarchy)
+ gameObject.SetActive(true);
+
+ if (dmgInfo == null)
+ return;
+
+ if (dmgInfo.isFirstHit)
+ {
+ damage = 0;
+ heal = 0;
+ }
+
+ if (dmgInfo.IsType(DamageType.Recovery))
+ {
+ // 淇濇寔鍘熸湁澶勭悊閫昏緫浣嶇疆
+ foreach (var h in dmgInfo.damageList)
+ {
+ heal += h;
+ }
+ textDamage.text = BattleUtility.DisplayDamageNum(heal, BattleConst.BattleTotalRecoverType);
+ damageBackground.sprite = await UILoader.LoadSpriteAsync("Fight", "Fight1_img_83");
+ if (this == null) return;
+ imgTotalDesc.sprite = await UILoader.LoadSpriteAsync("Fight", "Fight1_img_80");
+ if (this == null) return;
+ }
+ else if (dmgInfo.IsType(DamageType.Damage) || dmgInfo.IsType(DamageType.Realdamage))
+ {
+ // 淇濇寔鍘熸湁澶勭悊閫昏緫浣嶇疆
+ foreach (var d in dmgInfo.damageList)
+ {
+ damage += d;
+ }
+ textDamage.text = BattleUtility.DisplayDamageNum(damage, BattleConst.BattleTotalDamageType);
+ imgTotalDesc.sprite = await UILoader.LoadSpriteAsync("Fight", "Fight1_img_85");
+ if (this == null) return;
+ damageBackground.sprite = await UILoader.LoadSpriteAsync("Fight", "Fight1_img_88");
+ if (this == null) return;
+ }
+ else
+ {
+ gameObject.SetActive(false);
+ return;
+ }
+
+ if (punchTween != null && punchTween.IsActive())
+ {
+ battleField.battleTweenMgr.OnKillTween(punchTween);
+ textDamage.transform.localScale = Vector3.one;
+ punchTween = null;
+ }
+
+ punchTween = DOTween.Sequence();
+ var tween1 = textDamage.transform.DOPunchScale(scalePunch, scaleDuration / battleField.speedRatio, 1);
+ punchTween.Append(tween1);
+ // 鎾斁缁撴潫鍚� 寤惰繜1.5绉掑啀娑堝け
+ var tween2 = DOVirtual.DelayedCall(delayCloseDuration / battleField.speedRatio, () => { });
+ punchTween.Append(tween2);
+ punchTween.OnComplete(() =>
+ {
+ textDamage.transform.localScale = Vector3.one;
+ if (dmgInfo.isLastHit)
+ {
+ gameObject.SetActive(false);
+ }
+ });
+
+ battleField.battleTweenMgr.OnPlayTween(punchTween);
+
+ }
}
diff --git a/Main/System/BattleDetail/BattleDetailHeroInfoItem.cs b/Main/System/BattleDetail/BattleDetailHeroInfoItem.cs
index 49e13b5..1556664 100644
--- a/Main/System/BattleDetail/BattleDetailHeroInfoItem.cs
+++ b/Main/System/BattleDetail/BattleDetailHeroInfoItem.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
public class BattleDetailHeroInfoItem : MonoBehaviour
{
@@ -66,6 +67,50 @@
DisplaySlider(imgCureHP, txtCureHP, (ulong)info.CureHP, data.maxCure);
}
+ public async UniTask DisplayAsync(BattleDetailHeroInfoItemData data)
+ {
+ if (data == null || data.info == null)
+ return;
+ BattleDetailHeroInfo info = data.info;
+
+ int heroID = info.HeroID;
+ if (!HeroConfig.HasKey(heroID))
+ return;
+ HeroConfig heroConfig = HeroConfig.Get(heroID);
+
+
+ int skinID = info.Skin;
+ if (!HeroSkinConfig.HasKey(skinID))
+ return;
+ HeroSkinConfig skinConfig = HeroSkinConfig.Get(skinID);
+
+ bool isDead = info.Dead == 1;
+ imgMask.SetActive(isDead);
+ imgMVP.SetActive(data.index == data.mvpIndex);
+
+ imgHeadBg.SetSprite("heroheadBG" + heroConfig.Quality);
+
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", skinConfig.SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ imgHead.SetSprite("herohead_default");
+ }
+ else
+ {
+ imgHead.overrideSprite = sprite;
+ }
+
+ imgCountry.SetSprite(HeroUIManager.Instance.GetCountryIconName(heroConfig.Country));
+ txtHeroName.text = heroConfig.Name;
+ txtLV.text = StringUtility.Concat(Language.Get("L1094"), info.LV.ToString());
+ DisplayStars(info.Star);
+
+ DisplaySlider(imgAtkHurt, txtAtkHurt, (ulong)info.AtkHurt, data.maxAtk);
+ DisplaySlider(imgDefHurt, txtDefHurt, (ulong)info.DefHurt, data.maxDef);
+ DisplaySlider(imgCureHP, txtCureHP, (ulong)info.CureHP, data.maxCure);
+ }
+
private void DisplaySlider(ImageEx imgSlider, TextEx txtSlider, ulong value, ulong maxValue)
diff --git a/Main/System/Debug/DebugUtility.cs b/Main/System/Debug/DebugUtility.cs
index 01feb3f..33953f7 100644
--- a/Main/System/Debug/DebugUtility.cs
+++ b/Main/System/Debug/DebugUtility.cs
@@ -4,6 +4,7 @@
using LitJson;
using System.IO;
using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
public class DebugUtility : Singleton<DebugUtility>
@@ -97,6 +98,17 @@
}
}
+ public async UniTask CreateDebugRootAsync()
+ {
+ if (debugRoot == null)
+ {
+ var prefab = await BuiltInLoader.LoadPrefabAsync("UIRootDebug");
+ debugRoot = GameObject.Instantiate(prefab);
+ MonoBehaviour.DontDestroyOnLoad(debugRoot);
+ debugRoot.name = "UIRootDebug";
+ }
+ }
+
public class DebugBranch
{
diff --git a/Main/System/Equip/EquipExchangeCell.cs b/Main/System/Equip/EquipExchangeCell.cs
index bdbe329..9c7d65e 100644
--- a/Main/System/Equip/EquipExchangeCell.cs
+++ b/Main/System/Equip/EquipExchangeCell.cs
@@ -175,6 +175,7 @@
async UniTask RefreshEffect(ItemModel equip)
{
await UniTask.DelayFrame(1);
+ if (this == null) return; // destroyed during await
int effectID = EquipModel.Instance.equipUIEffects[Math.Min(equip.config.ItemColor, EquipModel.Instance.equipUIEffects.Length) - 1];
if (effectID == 0)
{
@@ -191,6 +192,7 @@
//浜屾澶勭悊鏀惧ぇ鏁堟灉
await UniTask.Delay(100);
+ if (this == null) return; // destroyed during await
if (effectID == 0)
{
uieffect.Stop();
diff --git a/Main/System/Equip/EquipTipWin.cs b/Main/System/Equip/EquipTipWin.cs
index c12924e..d198f32 100644
--- a/Main/System/Equip/EquipTipWin.cs
+++ b/Main/System/Equip/EquipTipWin.cs
@@ -200,6 +200,7 @@
async UniTask RefreshEffect(int itemColor)
{
await UniTask.DelayFrame(3);
+ if (this == null) return; // destroyed during await
int effectID = EquipModel.Instance.equipUIEffects[Math.Min(itemColor, EquipModel.Instance.equipUIEffects.Length) - 1];
if (effectID == 0)
{
diff --git a/Main/System/Equip/FloorItemCell.cs b/Main/System/Equip/FloorItemCell.cs
index aecc834..9156dc7 100644
--- a/Main/System/Equip/FloorItemCell.cs
+++ b/Main/System/Equip/FloorItemCell.cs
@@ -25,6 +25,7 @@
public async UniTask Display(int index, bool isAnimate, Vector3 position)
{
await UniTask.Delay(300);
+ if (this == null) return; // destroyed during await
itemIndex = index;
float duration = 0.5f / AutoFightModel.Instance.fightSpeed; //鎺夎惤鏃堕棿
var item = PackManager.Instance.GetItemByIndex(PackType.DropItem, index);
diff --git a/Main/System/Gubao/GubaoCallCell.cs b/Main/System/Gubao/GubaoCallCell.cs
index 2419170..24b5950 100644
--- a/Main/System/Gubao/GubaoCallCell.cs
+++ b/Main/System/Gubao/GubaoCallCell.cs
@@ -96,14 +96,18 @@
int delay = isSkip ? 0 : index * 100; // delay 姣
await UniTask.Delay(delay);
+ if (this == null) return; // destroyed during await
rotationTween.Play();
await UniTask.Delay(300);
+ if (this == null) return; // destroyed during await
rotationTween.Stop();
openEffect.Play();
await UniTask.Delay(200);
+ if (this == null) return; // destroyed during await
canImage.SetActive(false);
await UniTask.Delay(400);
+ if (this == null) return; // destroyed during await
showEffect.effectId = GetShowEffectID(result.itemId, result.count);
showEffect.PlayByArrIndex(Math.Max(itemCfg.ItemColor - 1, 0));
itemIcon.SetActive(true);
diff --git a/Main/System/Gubao/GubaoCallWin.cs b/Main/System/Gubao/GubaoCallWin.cs
index d7f9bdb..5db6c15 100644
--- a/Main/System/Gubao/GubaoCallWin.cs
+++ b/Main/System/Gubao/GubaoCallWin.cs
@@ -100,6 +100,7 @@
await UniTask.Delay((int)(delay*1000*0.6));
+ if (this == null) return; // destroyed during await
if (quality >= 10)
{
@@ -114,6 +115,7 @@
roleModel.Play(GubaoManager.Instance.emojiGBDict[quality]);
}
await UniTask.Delay((int)(delay*1000*0.4));
+ if (this == null) return; // destroyed during await
if (delay != 0)
{
opObj.SetActive(true);
diff --git a/Main/System/Guild/GuildBaseWin.cs b/Main/System/Guild/GuildBaseWin.cs
index 1b3a829..c0d66c1 100644
--- a/Main/System/Guild/GuildBaseWin.cs
+++ b/Main/System/Guild/GuildBaseWin.cs
@@ -212,6 +212,7 @@
async UniTask Talk(int index)
{
await UniTask.Delay(5000);
+ if (this == null) return; // destroyed during await
talkRects[index].SetActive(false);
var npc = funcNPCs[index].GetModel();
npc.PlayAnimation("idle", true);
diff --git a/Main/System/Guild/GuildBossWin.cs b/Main/System/Guild/GuildBossWin.cs
index 5b8e74f..079429a 100644
--- a/Main/System/Guild/GuildBossWin.cs
+++ b/Main/System/Guild/GuildBossWin.cs
@@ -413,10 +413,13 @@
atkBtn.SetColorful(null, false);
atkCDText.text = "3";
await UniTask.Delay(1000);
+ if (this == null) return; // destroyed during await
atkCDText.text = "2";
await UniTask.Delay(1000);
+ if (this == null) return; // destroyed during await
atkCDText.text = "1";
await UniTask.Delay(1000);
+ if (this == null) return; // destroyed during await
atkBtn.SetColorful(null, true);
isCD = false;
@@ -485,6 +488,7 @@
};
hurtValues[i].text = BattleUtility.DisplayDamageNum(dmg);
await UniTask.Delay(atkValueShowCD);
+ if (this == null) return; // destroyed during await
}
else
{
diff --git a/Main/System/Guild/GuildManager.cs b/Main/System/Guild/GuildManager.cs
index 8336836..9dd2295 100644
--- a/Main/System/Guild/GuildManager.cs
+++ b/Main/System/Guild/GuildManager.cs
@@ -233,7 +233,6 @@
config = FuncConfigConfig.Get("FamilyBillboardSet");
rankShowMaxCnt = int.Parse(config.Numerical1);
pageCnt = int.Parse(config.Numerical2);
- queryPointNum = int.Parse(config.Numerical3);
}
diff --git a/Main/System/HappyXB/HeroCallResultCell.cs b/Main/System/HappyXB/HeroCallResultCell.cs
index 8a58e8b..96680e6 100644
--- a/Main/System/HappyXB/HeroCallResultCell.cs
+++ b/Main/System/HappyXB/HeroCallResultCell.cs
@@ -30,6 +30,7 @@
int delaytime = LocalSave.GetBool(HeroUIManager.skipKey + PlayerDatas.Instance.baseData.PlayerID, false) ? 50 * index : 100 * index;
await UniTask.Delay(delaytime);
+ if (this == null) return; // destroyed during await
this.transform.localScale = Vector3.one;
//鍏堟樉绀哄彴瀛愶紝鍐嶆樉绀哄皬浜�
heroModel.SetActive(false);
diff --git a/Main/System/HappyXB/HeroCallResultWin.cs b/Main/System/HappyXB/HeroCallResultWin.cs
index f29f37f..5830d55 100644
--- a/Main/System/HappyXB/HeroCallResultWin.cs
+++ b/Main/System/HappyXB/HeroCallResultWin.cs
@@ -143,6 +143,7 @@
}
await UniTask.Delay(resultState == ResultState.singleStart ? 800 : 1500);
+ if (this == null) return; // destroyed during await
resultState = ResultState.Lihui;
try
diff --git a/Main/System/Hero/UIHeroController.cs b/Main/System/Hero/UIHeroController.cs
index dd7ae75..c60a66c 100644
--- a/Main/System/Hero/UIHeroController.cs
+++ b/Main/System/Hero/UIHeroController.cs
@@ -1,5 +1,6 @@
using System;
+using Cysharp.Threading.Tasks;
using Spine.Unity;
using UnityEngine;
using UnityEngine.UI;
@@ -150,6 +151,143 @@
spineAnimationState.Complete += OnAnimationComplete;
}
+ public async UniTask CreateAsync(int _skinID, float scale = 0.8f, Action _onComplete = null, string motionName = "idle", bool isLh = false)
+ {
+ if (skinID == _skinID)
+ {
+ //閬垮厤閲嶅鍒涘缓
+
+ if (skeletonGraphic != null)
+ {
+ SetMaterialNone();
+ if (isLh)
+ {
+ var skinConfigTmp = HeroSkinConfig.Get(skinID);
+ if (skinConfigTmp != null && skinConfigTmp.Tachie.Contains("SkeletonData"))
+ {
+ skeletonGraphic.enabled = true;
+ }
+ }
+ else
+ {
+ skeletonGraphic.enabled = true;
+ }
+ }
+ return;
+ }
+
+ skinID = _skinID;
+ var skinConfig = HeroSkinConfig.Get(skinID);
+ if (isLh)
+ {
+
+ //X杞村亸绉伙紝Y杞村亸绉伙紝缂╂斁锛屾槸鍚︽按骞崇炕杞紙0鍚�1鏄級
+ if (skinConfig.TachieParam.Length == 4)
+ {
+ this.transform.localPosition = new Vector3(skinConfig.TachieParam[0], skinConfig.TachieParam[1], 0);
+ this.transform.localScale = Vector3.one * skinConfig.TachieParam[2];
+ this.transform.localRotation = Quaternion.Euler(0, skinConfig.TachieParam[3] == 0 ? 0 : 180, 0);
+ }
+ else
+ {
+ this.transform.localPosition = Vector3.zero;
+ this.transform.localScale = Vector3.one;
+ this.transform.localRotation = Quaternion.identity;
+ }
+
+ //绔嬬粯鐗规畩澶勭悊锛屾病鏈塻pine鍔ㄧ敾鐨勬敼鐢ㄥ浘鐗�
+ var lhImg = this.AddMissingComponent<RawImage>();
+ if (!skinConfig.Tachie.Contains("SkeletonData"))
+ {
+ //鍥剧墖鏇挎崲
+ lhImg.SetTexture2DPNG(skinConfig.Tachie);
+ lhImg.SetNativeSize();
+ if (skeletonGraphic != null)
+ {
+ skeletonGraphic.enabled = false;
+ }
+ lhImg.enabled = true;
+ lhImg.raycastTarget = false;
+ return;
+ }
+ else
+ {
+ if (skeletonGraphic != null)
+ {
+ skeletonGraphic.enabled = true;
+ }
+ lhImg.enabled = false;
+ }
+ }
+ else
+ {
+ this.transform.localScale = Vector3.one * scale;
+ }
+
+ onComplete = _onComplete;
+ pool = GameObjectPoolManager.Instance.GetPool(await UILoader.LoadPrefabAsync("UIHero"));
+ if (this == null) return;
+
+ if (!transform.gameObject.activeSelf)
+ {
+ transform.SetActive(true);
+ }
+ if (instanceGO == null)
+ {
+ instanceGO = pool.Request();
+ instanceGO.transform.SetParent(transform);
+ //transform 鐨凱ivot Y鏄�0锛岃instanceGO 灞呬腑
+ instanceGO.transform.localPosition = new Vector3(0, instanceGO.GetComponent<RectTransform>().sizeDelta.y * 0.5f);
+
+ //instanceGO.transform.localPosition = Vector3.zero;
+ instanceGO.transform.localScale = Vector3.one;
+ instanceGO.transform.localRotation = Quaternion.identity;
+ }
+
+ skeletonGraphic = instanceGO.GetComponentInChildren<SkeletonGraphic>(true);
+ if (isLh)
+ {
+ skeletonGraphic.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("Hero/SpineRes/", skinConfig.Tachie);
+ }
+ else
+ {
+ skeletonGraphic.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("Hero/SpineRes/", skinConfig.SpineRes);
+ }
+ if (skeletonGraphic.skeletonDataAsset == null)
+ {
+
+ transform.SetActive(false);
+ if (pool != null)
+ pool.Release(instanceGO);
+ skeletonGraphic = null;
+ Destroy(instanceGO);
+ Debug.LogError("鏈厤缃畇pine");
+ return;
+ }
+ skeletonGraphic.initialSkinName = skinConfig.InitialSkinName;
+ skeletonGraphic.Initialize(true);
+ // 鍒濆鍖栧畬鎴愬悗璁剧疆鐨偆
+ if (!string.IsNullOrEmpty(skinConfig.InitialSkinName))
+ {
+ var skeleton = skeletonGraphic.Skeleton;
+ skeleton.SetSkin(skinConfig.InitialSkinName);
+ skeleton.SetSlotsToSetupPose();
+ skeletonGraphic.Update(0);
+ }
+
+ skeletonGraphic.enabled = true;
+ SetMaterialNone();
+
+ spineAnimationState = skeletonGraphic.AnimationState;
+ spineAnimationState.Data.DefaultMix = 0f;
+ if (motionName == "")
+ motionName = GetFistSpineAnim();
+
+ PlayAnimation(motionName, true);
+ spineAnimationState.Complete -= OnAnimationComplete;
+ spineAnimationState.Complete += OnAnimationComplete;
+ }
+
diff --git a/Main/System/HeroUI/HeroBestWin.cs b/Main/System/HeroUI/HeroBestWin.cs
index 6659dd0..71c98ef 100644
--- a/Main/System/HeroUI/HeroBestWin.cs
+++ b/Main/System/HeroUI/HeroBestWin.cs
@@ -171,6 +171,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true);
foreach (var layout in layouts)
@@ -178,6 +179,7 @@
LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
}
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
foreach (var layout in layouts)
{
diff --git a/Main/System/HeroUI/HeroConnectionHeadCell.cs b/Main/System/HeroUI/HeroConnectionHeadCell.cs
index 77de465..498a80a 100644
--- a/Main/System/HeroUI/HeroConnectionHeadCell.cs
+++ b/Main/System/HeroUI/HeroConnectionHeadCell.cs
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
//缇佺粖涓殑姝﹀皢
public class HeroConnectionHeadCell : MonoBehaviour
@@ -52,5 +53,43 @@
connMarkImg.SetActive(index != 0);
}
+
+ public async UniTask DisplayAsync(int heroID, int index, bool showCollect = false, int _skinID = 0)
+ {
+ int skinID = 0;
+ HeroConfig heroConfig = HeroConfig.Get(heroID);
+ if (_skinID != 0)
+ {
+ skinID = _skinID;
+ }
+ else
+ {
+ skinID = heroConfig.SkinIDList[0]; //榛樿绗竴涓浘閴村睍绀�
+
+ }
+
+ nameText.text = heroConfig.Name;
+ qualityImg.SetSprite("heroheadBG" + heroConfig.Quality);
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", HeroSkinConfig.Get(skinID).SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ heroIcon.SetSprite("herohead_default");
+ }
+ else
+ {
+ heroIcon.overrideSprite = sprite;
+ }
+
+ if (showCollect)
+ {
+ //鏈幏寰楁灏嗚缃伆
+ heroIcon.gray = !HeroManager.Instance.HasHero(heroID);
+
+ }
+
+ connMarkImg.SetActive(index != 0);
+ }
}
diff --git a/Main/System/HeroUI/HeroGiftWashWin.cs b/Main/System/HeroUI/HeroGiftWashWin.cs
index d736dd8..f0be3bd 100644
--- a/Main/System/HeroUI/HeroGiftWashWin.cs
+++ b/Main/System/HeroUI/HeroGiftWashWin.cs
@@ -150,6 +150,7 @@
{
//寤惰繜0.5绉掑彂鍖�
await UniTask.Delay(500);
+ if (this == null) return; // destroyed during await
var hero = HeroManager.Instance.GetHero(HeroUIManager.Instance.selectWashHeroGUID);
if (hero == null)
{
diff --git a/Main/System/HeroUI/HeroHeadBaseCell.cs b/Main/System/HeroUI/HeroHeadBaseCell.cs
index 0a705bd..2104662 100644
--- a/Main/System/HeroUI/HeroHeadBaseCell.cs
+++ b/Main/System/HeroUI/HeroHeadBaseCell.cs
@@ -2,6 +2,7 @@
using UnityEngine.UI;
using UnityEngine.Events;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
public class HeroHeadBaseCell : MonoBehaviour
{
@@ -197,6 +198,68 @@
}
+ // 姝﹀皢灏忓ご鍍� Async鐗堟湰
+ public async UniTask InitAsync(int heroID, int skinID, int star = 0, int awakelv = 0, int lv = 0, UnityAction onclick = null)
+ {
+ LoadPrefab(); //瀛樺湪琚嵏杞界殑鍙兘锛岄噸鏂板姞杞�
+ if (onclick != null)
+ {
+ clickBtn.AddListener(onclick);
+ }
+ var heroConfig = HeroConfig.Get(heroID);
+ qualityBG.SetSprite("heroheadBG" + heroConfig.Quality);
+ // int skinID = 0;
+ // if (heroGuid != "")
+ // {
+ // skinID = HeroManager.Instance.GetHero(heroGuid).SkinID;
+ // }
+ // else
+ // {
+ // skinID = heroConfig.SkinIDList[0];
+ // }
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", HeroSkinConfig.Get(skinID).SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ heroIcon.SetSprite("herohead_default");
+ }
+ else
+ {
+ heroIcon.overrideSprite = sprite;
+ }
+
+
+ if (star == 0)
+ {
+ starRect.SetActive(false);
+ }
+ else
+ {
+ starRect.SetActive(true);
+ for (int i = 0; i < starsImg.Count; i++)
+ {
+ if ((star - 1) % starsImg.Count >= i)
+ {
+ starsImg[i].SetActive(true);
+ starsImg[i].SetSprite("herostar" + (((star - 1) / starsImg.Count) + 1) * starsImg.Count);
+ }
+ else
+ {
+ starsImg[i].SetActive(false);
+ }
+ }
+ }
+
+ countryImg.SetSprite(HeroUIManager.Instance.GetCountryIconName(heroConfig.Country));
+ lvText.text = lv == 0 ? "" : Language.Get("L1094") + lv;
+
+ awakeLvRect.SetActive(awakelv > 0);
+ awakeLvText.text = awakelv.ToString();
+
+
+ }
+
GameObject cellContainer;
protected void LoadPrefab()
{
diff --git a/Main/System/HeroUI/HeroHeadBaseNoTrainCell.cs b/Main/System/HeroUI/HeroHeadBaseNoTrainCell.cs
index 3126d22..5168d96 100644
--- a/Main/System/HeroUI/HeroHeadBaseNoTrainCell.cs
+++ b/Main/System/HeroUI/HeroHeadBaseNoTrainCell.cs
@@ -2,6 +2,7 @@
using UnityEngine.UI;
using UnityEngine.Events;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
public class HeroHeadBaseNoTrainCell : MonoBehaviour
{
@@ -111,6 +112,26 @@
}
+ public async UniTask InitAsync(int heroID, bool _gray = false, UnityAction onclick = null)
+ {
+ LoadPrefab(); //瀛樺湪琚嵏杞界殑鍙兘锛岄噸鏂板姞杞�
+ clickBtn.AddListener(onclick);
+ var heroConfig = HeroConfig.Get(heroID);
+ qualityBG.SetSprite("heroheadBG" + heroConfig.Quality);
+
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", HeroSkinConfig.Get(heroConfig.SkinIDList[0]).SquareIcon);
+ if (this == null) return;
+ heroIcon.overrideSprite = sprite;
+ heroIcon.gray = _gray;
+ qualityBG.gray = _gray;
+
+ countryImg.SetSprite(HeroUIManager.Instance.GetCountryIconName(heroConfig.Country));
+ jobImg.SetSprite(HeroUIManager.Instance.GetJobIconName(heroConfig.Class));
+ nameText.text = heroConfig.Name;
+
+
+ }
+
GameObject cellContainer;
protected void LoadPrefab()
{
diff --git a/Main/System/HeroUI/HeroPosWin.cs b/Main/System/HeroUI/HeroPosWin.cs
index 7ed586b..c79fbf2 100644
--- a/Main/System/HeroUI/HeroPosWin.cs
+++ b/Main/System/HeroUI/HeroPosWin.cs
@@ -394,6 +394,7 @@
while (showConnectTipQueue.Count > 0)
{
await UniTask.Delay(300, cancellationToken: token);
+ if (this == null) return; // destroyed during await
showConnectTipQueue.TryDequeue(out int fetterID);
if (fetterID == 0)
{
@@ -403,6 +404,7 @@
connetionForm.Display(fetterID);
//鏄剧ず1.5绉掑悗鍏抽棴
await UniTask.Delay(1500, cancellationToken: token);
+ if (this == null) return; // destroyed during await
connetionForm.SetActive(false);
}
diff --git a/Main/System/HeroUI/HeroScenePosCell.cs b/Main/System/HeroUI/HeroScenePosCell.cs
index 79090b5..48740bc 100644
--- a/Main/System/HeroUI/HeroScenePosCell.cs
+++ b/Main/System/HeroUI/HeroScenePosCell.cs
@@ -101,6 +101,7 @@
{
//寤惰繜0.5绉掓樉绀�
await UniTask.Delay(TimeSpan.FromSeconds(HeroUIManager.clickFlyPosTime));
+ if (this == null) return; // destroyed during await
objForfly.SetActive(true);
}
}
diff --git a/Main/System/HeroUI/HeroSkillWin.cs b/Main/System/HeroUI/HeroSkillWin.cs
index cc83ae1..248c9ea 100644
--- a/Main/System/HeroUI/HeroSkillWin.cs
+++ b/Main/System/HeroUI/HeroSkillWin.cs
@@ -57,9 +57,11 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
LayoutRebuilder.ForceRebuildLayoutImmediate(bg);
// 鍒锋柊鎵�鏈塋ayout缁勪欢
await UniTask.Delay(100);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
LayoutRebuilder.ForceRebuildLayoutImmediate(bg);
}
diff --git a/Main/System/HeroUI/HeroTrainWin.cs b/Main/System/HeroUI/HeroTrainWin.cs
index 07fd7f1..dae5d18 100644
--- a/Main/System/HeroUI/HeroTrainWin.cs
+++ b/Main/System/HeroUI/HeroTrainWin.cs
@@ -320,6 +320,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true);
foreach (var layout in layouts)
@@ -327,6 +328,7 @@
LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
}
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
foreach (var layout in layouts)
{
diff --git a/Main/System/Horse/HorseController.cs b/Main/System/Horse/HorseController.cs
index a805522..ae9ab44 100644
--- a/Main/System/Horse/HorseController.cs
+++ b/Main/System/Horse/HorseController.cs
@@ -1,5 +1,6 @@
using System;
+using Cysharp.Threading.Tasks;
using Spine.Unity;
using UnityEngine;
using UnityEngine.UI;
@@ -96,6 +97,84 @@
spineAnimationState.Complete += OnAnimationComplete;
}
+ // 鍒涘缓鍧愰獞寮傛鐗堟湰
+ public async UniTask CreateAsync(int _skinID, int _heroSkinID = 0, float scale = 1f, Action _onComplete = null, string motionName = "idle")
+ {
+ pool = GameObjectPoolManager.Instance.GetPool(await UILoader.LoadPrefabAsync("UIHorse"));
+ if (this == null) return;
+ if (instanceGO == null)
+ {
+ instanceGO = pool.Request();
+ instanceGO.transform.SetParent(transform);
+ //transform 鐨凱ivot Y鏄�0锛岃instanceGO 灞呬腑
+ instanceGO.transform.localPosition = new Vector3(0, instanceGO.GetComponent<RectTransform>().sizeDelta.y * 0.5f);
+
+ //instanceGO.transform.localPosition = Vector3.zero;
+ instanceGO.transform.localScale = Vector3.one;
+ instanceGO.transform.localRotation = Quaternion.identity;
+ }
+ skeletonGraphic = instanceGO.transform.Find("Horse").GetComponent<SkeletonGraphic>();
+
+ if (skinID == _skinID)
+ {
+ if (skinID == 0)
+ {
+ skeletonGraphic.enabled = false;
+ }
+ CreateHero(_heroSkinID, scale);
+ //閬垮厤閲嶅鍒涘缓
+ return;
+ }
+
+ skinID = _skinID;
+ var skinConfig = HorseSkinConfig.Get(skinID);
+
+ this.transform.localScale = Vector3.one * scale;
+
+ onComplete = _onComplete;
+
+ if (!transform.gameObject.activeSelf)
+ {
+ transform.SetActive(true);
+ }
+
+
+ if (skinConfig == null || string.IsNullOrEmpty(skinConfig.Spine))
+ {
+ //鍗镐笅鍧愰獞鐨勬儏鍐�
+ skeletonGraphic.enabled = false;
+ spineAnimationState = null;
+ CreateHero(_heroSkinID, scale);
+ return;
+ }
+
+ skeletonGraphic.skeletonDataAsset = ResManager.Instance.LoadAsset<SkeletonDataAsset>("UIEffect/Spine/Horse", skinConfig.Spine);
+ if (skeletonGraphic.skeletonDataAsset == null)
+ {
+
+ transform.SetActive(false);
+ if (pool != null)
+ pool.Release(instanceGO);
+ skeletonGraphic = null;
+ Destroy(instanceGO);
+ Debug.LogError("鏈厤缃畇pine");
+ return;
+ }
+ skeletonGraphic.enabled = true;
+ skeletonGraphic.Initialize(true);
+
+ skeletonGraphic.transform.localPosition = new Vector3(skinConfig.Poses[0], skinConfig.Poses[1], 0);
+ isHeroShowBefore = skinConfig.heroFirst == 1;
+ spineAnimationState = skeletonGraphic.AnimationState;
+ spineAnimationState.Data.DefaultMix = 0f;
+ if (motionName == "")
+ motionName = GetFistSpineAnim();
+ PlayAnimation(motionName, true);
+ CreateHero(_heroSkinID, scale);
+ spineAnimationState.Complete -= OnAnimationComplete;
+ spineAnimationState.Complete += OnAnimationComplete;
+ }
+
public void CreateHero(int heroSkinID, float _scale)
{
if (instanceGO == null)
diff --git a/Main/System/InternalAffairs/AffairBaseWin.cs b/Main/System/InternalAffairs/AffairBaseWin.cs
index e9d19ed..e1d7be3 100644
--- a/Main/System/InternalAffairs/AffairBaseWin.cs
+++ b/Main/System/InternalAffairs/AffairBaseWin.cs
@@ -188,6 +188,7 @@
async UniTask Talk(int index)
{
await UniTask.Delay(5000);
+ if (this == null) return; // destroyed during await
talkRects[index].SetActive(false);
var npc = funcNPCs[index].GetModel();
npc.PlayAnimation("idle", true);
diff --git a/Main/System/InternalAffairs/GoldRushLeader.cs b/Main/System/InternalAffairs/GoldRushLeader.cs
index a7f346e..da73f07 100644
--- a/Main/System/InternalAffairs/GoldRushLeader.cs
+++ b/Main/System/InternalAffairs/GoldRushLeader.cs
@@ -46,6 +46,7 @@
{
int delayTime = Math.Max(1, (int)(waitTime * 1000));
await UniTask.Delay(delayTime);
+ if (this == null) return; // destroyed during await
StartLeaderMove(isBack);
}
@@ -218,6 +219,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
LayoutRebuilder.ForceRebuildLayoutImmediate(leaderWord.GetComponent<RectTransform>());
}
}
diff --git a/Main/System/InternalAffairs/GoldRushTentCell.cs b/Main/System/InternalAffairs/GoldRushTentCell.cs
index a4e98d4..39b64f6 100644
--- a/Main/System/InternalAffairs/GoldRushTentCell.cs
+++ b/Main/System/InternalAffairs/GoldRushTentCell.cs
@@ -440,6 +440,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
foreach (var word in wordArr)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(word.GetComponent<RectTransform>());
diff --git a/Main/System/ItemTip/SmallTipWin.cs b/Main/System/ItemTip/SmallTipWin.cs
index 7e35643..1fc442d 100644
--- a/Main/System/ItemTip/SmallTipWin.cs
+++ b/Main/System/ItemTip/SmallTipWin.cs
@@ -39,6 +39,7 @@
async UniTask UpdatePos()
{
await UniTask.DelayFrame(3);
+ if (this == null) return; // destroyed during await
// 闄愬埗鍦ㄥ睆骞曡寖鍥村唴
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
diff --git a/Main/System/KnapSack/Logic/CommonGetItemWin.cs b/Main/System/KnapSack/Logic/CommonGetItemWin.cs
index 87f7aba..ce19880 100644
--- a/Main/System/KnapSack/Logic/CommonGetItemWin.cs
+++ b/Main/System/KnapSack/Logic/CommonGetItemWin.cs
@@ -105,6 +105,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
var layouts = bg.GetComponentsInChildren<LayoutGroup>(true);
foreach (var layout in layouts)
@@ -112,6 +113,7 @@
LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
}
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
foreach (var layout in layouts)
{
diff --git a/Main/System/LineupRecommend/LineupRecommendItem.cs b/Main/System/LineupRecommend/LineupRecommendItem.cs
index ba28e61..26c3629 100644
--- a/Main/System/LineupRecommend/LineupRecommendItem.cs
+++ b/Main/System/LineupRecommend/LineupRecommendItem.cs
@@ -1,4 +1,5 @@
using UnityEngine;
+using Cysharp.Threading.Tasks;
public class LineupRecommendItem : MonoBehaviour
{
@@ -72,5 +73,61 @@
}
+ public async UniTask DisplayAsync(int recommendID, int index)
+ {
+ if (!manager.TryGetHeroConfigByIndex(recommendID, index, out HeroConfig heroConfig))
+ return;
+
+ if (!manager.TryGetMoneyInfo(recommendID, index, out int moneyType, out int moneyNeedCnt))
+ return;
+
+ int heroID = heroConfig.HeroID;
+ if (!manager.TryGetHeroSkinConfig(heroID, out HeroSkinConfig heroSkinConfig))
+ return;
+
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", heroSkinConfig.SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ imgHeroHead.SetSprite("herohead_default");
+ }
+ else
+ {
+ imgHeroHead.overrideSprite = sprite;
+ }
+
+ imgSquareIcon.SetSprite("heroheadBG" + heroConfig.Quality);
+ imgCountry.SetSprite(HeroUIManager.Instance.GetCountryIconName(heroConfig.Country));
+ txtName.text = heroConfig.Name;
+ txtDesc.text = heroConfig.Desc;
+ imgJob.SetSprite(HeroUIManager.Instance.GetJobIconName(heroConfig.Class));
+
+ LineupRecommendHeroState heroState = manager.GetHeroState(recommendID, index);
+ imgMask.SetActive(heroState != LineupRecommendHeroState.ActivateAndHave);
+ txtNoHave.SetActive(heroState == LineupRecommendHeroState.ActivateButNoHave);
+ imgMoney.SetActive(heroState == LineupRecommendHeroState.NoActivate || heroState == LineupRecommendHeroState.CanActivate);
+ txtMoney.SetActive(heroState == LineupRecommendHeroState.NoActivate || heroState == LineupRecommendHeroState.CanActivate);
+ imgRed.SetActive(heroState == LineupRecommendHeroState.CanActivate);
+
+ imgMoney.SetIconWithMoneyType(moneyType);
+ imgMoney.gray = heroState == LineupRecommendHeroState.NoActivate;
+ txtMoney.text = moneyNeedCnt.ToString();
+ txtMoney.color = heroState == LineupRecommendHeroState.NoActivate ? colMoneyNoActivate : colMoneyCanActivate;
+
+ btnClick.SetListener(() =>
+ {
+ if (heroState == LineupRecommendHeroState.CanActivate)
+ {
+ manager.SendGetReward(recommendID, index);
+ }
+ else
+ {
+ HeroUIManager.Instance.selectForPreviewHeroID = heroConfig.HeroID;
+ UIManager.Instance.OpenWindow<HeroBestWin>();
+ }
+ });
+
+ }
+
}
\ No newline at end of file
diff --git a/Main/System/Login/LoginWin.cs b/Main/System/Login/LoginWin.cs
index e81f412..d72f6fb 100644
--- a/Main/System/Login/LoginWin.cs
+++ b/Main/System/Login/LoginWin.cs
@@ -1,6 +1,7 @@
using UnityEngine;
using UnityEngine.UI;
using System.IO;
+using Cysharp.Threading.Tasks;
public class LoginWin : UIBase, ICanvasRaycastFilter
{
@@ -186,6 +187,57 @@
checkRead.isOn = LocalSave.GetBool("secretToggleStart5");
}
+ public async UniTask RefreshAsync()
+ {
+ base.Refresh();
+ Debug.Log("鍒锋柊鐧诲綍绐楀彛");
+
+ //鎵撳寘鐗堟湰 + 鍔熻兘鐗堟湰 + 璇█ID
+ verInfo.text = LoginManager.Instance.GetVersionStr();
+
+ var sprite = await BuiltInLoader.LoadSpriteAsync("TB_DL_Logo");
+ if (this == null) return;
+ m_Logo.overrideSprite = sprite;
+ m_Logo.SetNativeSize();
+ m_Logo.rectTransform.anchoredPosition = VersionConfig.Get().logoPosition;
+ m_Notice.SetActive(GameNotice.HasNotice());
+
+
+ bool hasNotice = GameNotice.HasNotice();
+
+ // 璐﹀彿鍒囨崲
+ m_SwitchAccount.SetActive(false);
+ // 鐢ㄦ埛甯姪
+ // TODO YYL
+ var appId = VersionConfig.Get().appId;
+ var branch = VersionConfig.Get().branch;
+ // m_UserHelp.SetActive(ContactConfig.GetConfig(appId, branch) != null);
+
+ // 鏄惁宸茬粡鑾峰彇鍒版湇鍔″櫒鍒楄〃
+ bool isGetServerList = ServerListCenter.Instance.serverListGot;
+ m_WaitServerList.SetActive(!isGetServerList);
+
+ m_ContainerEnterGame.SetActive(isGetServerList);
+ m_ContainerAccount.SetActive(isGetServerList
+ && (VersionConfig.Get().versionAuthority == VersionAuthority.InterTest || VersionConfig.Get().isBanShu));
+ m_EnterGame.SetActive(isGetServerList);
+
+ if (isGetServerList)
+ {
+ ChangeServerInfo(ServerListCenter.Instance.currentServer);
+ }
+
+
+ ChangeUserInfo(LoginManager.Instance.localSaveAccountName);
+
+ m_EnterGame.SetActive(true);
+ //m_QQLogin.SetActive(false);
+ //m_WXLogin.SetActive(false);
+
+ // 鐢ㄦ埛鍗忚 todo
+ checkRead.isOn = LocalSave.GetBool("secretToggleStart5");
+ }
+
private void OnLoginOk(SDKUtils.FP_LoginOk arg0)
{
}
diff --git a/Main/System/Main/EquipOnMainUI.cs b/Main/System/Main/EquipOnMainUI.cs
index 501f06b..2434434 100644
--- a/Main/System/Main/EquipOnMainUI.cs
+++ b/Main/System/Main/EquipOnMainUI.cs
@@ -118,6 +118,7 @@
while (UIManager.Instance.IsOpened<EquipExchangeWin>())
{
await UniTask.Yield();
+ if (this == null) return; // destroyed during await
}
diff --git a/Main/System/Main/HeroFightingCardCell.cs b/Main/System/Main/HeroFightingCardCell.cs
index 08a8e9f..bb27300 100644
--- a/Main/System/Main/HeroFightingCardCell.cs
+++ b/Main/System/Main/HeroFightingCardCell.cs
@@ -1,6 +1,7 @@
锘縰sing UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
//涓荤晫闈㈠崱鐗�
public class HeroFightingCardCell : MonoBehaviour
@@ -118,6 +119,89 @@
}
+ public async UniTask DisplayAsync(int index, List<TeamHero> heros)
+ {
+ TeamHero teamHero = null;
+ if (index < heros.Count)
+ {
+ teamHero = heros[index];
+ }
+ guid = teamHero != null ? teamHero.guid : "";
+ if (guid == "")
+ {
+ clickHeroBtn.SetActive(false);
+ clickEmptyBtn.SetActive(true);
+ clickEmptyBtn.AddListener(ClickEmpty);
+ emptyLockImg.SetActive(false);
+ redPointImg.SetActive(false);
+
+ int lockCnt = HeroUIManager.Instance.lockIndexList.Count;
+ //鏍规嵁閿佹暟閲� 鍊掑簭鍒ゆ柇閿佷綇
+ if (lockCnt > 0)
+ {
+ lockIndex = lockCnt - (TeamConst.MaxTeamHeroCount - 1 - index) - 1;
+ if (lockIndex >= 0 && lockIndex < lockCnt)
+ {
+ emptyLockImg.SetActive(true);
+ redPointImg.SetActive(HeroUIManager.Instance.CanUnLock(HeroUIManager.Instance.lockIndexList[lockIndex]));
+ }
+ }
+
+ return;
+ }
+ else
+ {
+ clickHeroBtn.SetActive(true);
+ clickEmptyBtn.SetActive(false);
+ }
+
+ var hero = HeroManager.Instance.GetHero(guid);
+ var heroID = hero.heroId;
+ var star = hero.heroStar;
+ clickHeroBtn.AddListener(ClickHero);
+ var heroConfig = HeroConfig.Get(heroID);
+ qualityBG.SetSprite("herocBG" + heroConfig.Quality);
+
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", HeroSkinConfig.Get(hero.SkinID).RectangleIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ heroIcon.SetSprite("herohead_big_default");
+ }
+ else
+ {
+ heroIcon.overrideSprite = sprite;
+ }
+
+ if (star == 0)
+ {
+ starRect.SetActive(false);
+ }
+ else
+ {
+ starRect.SetActive(true);
+ for (int i = 0; i < starsImg.Count; i++)
+ {
+ if ((star - 1) % starsImg.Count >= i)
+ {
+ starsImg[i].SetActive(true);
+ starsImg[i].SetSprite("herostar" + (((star - 1) / starsImg.Count) + 1) * starsImg.Count);
+ }
+ else
+ {
+ starsImg[i].SetActive(false);
+ }
+ }
+ }
+
+ countryImg.SetSprite(HeroUIManager.Instance.GetCountryIconName(heroConfig.Country));
+ lvText.text = hero.heroLevel == 0 ? "" : Language.Get("L1094") + hero.heroLevel;
+
+ // RefreshFightIng(false);
+
+ }
+
void ClickHero()
{
diff --git a/Main/System/Main/HomeWin.cs b/Main/System/Main/HomeWin.cs
index 734d192..74c21df 100644
--- a/Main/System/Main/HomeWin.cs
+++ b/Main/System/Main/HomeWin.cs
@@ -451,6 +451,7 @@
async UniTask DelayPlayMusic()
{
await UniTask.Delay(1200);
+ if (this == null) return; // destroyed during await
if (!SoundPlayer.Instance.IsPlayBackGroundMuisic())
SoundPlayer.Instance.PlayBackGroundMusic(38);
}
diff --git a/Main/System/Message/ImgAnalysis.cs b/Main/System/Message/ImgAnalysis.cs
index 05c8164..b678705 100644
--- a/Main/System/Message/ImgAnalysis.cs
+++ b/Main/System/Message/ImgAnalysis.cs
@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
+using Cysharp.Threading.Tasks;
using UnityEngine;
public class ImgAnalysis : TRichAnalysis<ImgAnalysis>
@@ -229,6 +230,46 @@
}
}
+ private async UniTask LoadSpriteAsync()
+ {
+ if (presentImgInfo.IsFace) return;
+ if (IconConfig.isInit)
+ {
+ if (!string.IsNullOrEmpty(presentImgInfo.folderName))
+ {
+ presentImgInfo.sprite = await UILoader.LoadSpriteAsync(presentImgInfo.folderName, presentImgInfo.spriteName);
+ }
+ else
+ {
+ presentImgInfo.sprite = await UILoader.LoadSpriteAsync(presentImgInfo.spriteName);
+ }
+ }
+
+ if (presentImgInfo.sprite != null)
+ {
+ RichText text = RichTextMgr.Inst.presentRichText;
+ if (text != null)
+ {
+ if (text.LockImgSize)
+ {
+ presentImgInfo.width = presentImgInfo.height = text.fontSize;
+ return;
+ }
+ else if (text.ModifyImgSiez)
+ {
+ presentImgInfo.width = text.ModifyImgWidth;
+ presentImgInfo.height = text.ModifyImgHeight;
+ return;
+ }
+ }
+ if (presentImgInfo.scale != 1f)
+ {
+ presentImgInfo.width = presentImgInfo.sprite.rect.width * presentImgInfo.scale;
+ presentImgInfo.height = presentImgInfo.sprite.rect.height * presentImgInfo.scale;
+ }
+ }
+ }
+
private const string FACE_REPLACE = @"#~([0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z])";
public static Regex FaceRegex = new Regex(FACE_REPLACE, RegexOptions.Singleline);
public static string ReplaceFace(string msg)
diff --git a/Main/System/Message/RichText.cs b/Main/System/Message/RichText.cs
index 360a4da..9b4fc8f 100644
--- a/Main/System/Message/RichText.cs
+++ b/Main/System/Message/RichText.cs
@@ -7,6 +7,7 @@
using System;
using System.Linq;
using System.Text;
+using Cysharp.Threading.Tasks;
public class RichText : Text, IPointerClickHandler
{
/// <summary>
@@ -191,6 +192,27 @@
}
}
+ public async UniTask AwakeAsync()
+ {
+#if UNITY_EDITOR
+ if (UnityEditor.PrefabUtility.GetPrefabType(this) == UnityEditor.PrefabType.Prefab)
+ {
+ return;
+ }
+#endif
+ unline = transform.GetComponentInChildren<TextUnline>();
+ if (unline == null)
+ {
+ GameObject obj = await BuiltInLoader.LoadPrefabAsync("TextUnline");
+ if (this == null) return;
+ obj = Instantiate(obj);
+ obj.transform.SetParent(transform);
+ obj.transform.localScale = Vector3.one;
+ unline = obj.GetComponent<TextUnline>();
+ unline.raycastTarget = false;
+ }
+ }
+
protected override void OnEnable()
{
base.OnEnable();
diff --git a/Main/System/NewBieGuidance/NewBieWin.cs b/Main/System/NewBieGuidance/NewBieWin.cs
index 4b601cc..407cdcc 100644
--- a/Main/System/NewBieGuidance/NewBieWin.cs
+++ b/Main/System/NewBieGuidance/NewBieWin.cs
@@ -213,6 +213,7 @@
async UniTask DelayDisplay()
{
await UniTask.Delay((tryGuideCount + 1) * 100);
+ if (this == null) return; // destroyed during await
tryGuideCount++;
Display();
}
@@ -275,6 +276,7 @@
async UniTask Co_FunctionUnLockDelay()
{
await UniTask.Delay(1300);
+ if (this == null) return; // destroyed during await
m_ContainerFunctionBg.SetActive(false);
m_FunctionName.SetActive(false);
@@ -330,6 +332,7 @@
async UniTask DelayShowClickEffect()
{
await UniTask.Delay(stepConfig.delayTime);
+ if (this == null) return; // destroyed during await
m_ClickEffect.SetActive(true);
m_ClickEffect.Play();
m_ClickEffect.transform.position = m_ClickTarget.position;
diff --git a/Main/System/OtherPlayerDetail/OtherEquipTipWin.cs b/Main/System/OtherPlayerDetail/OtherEquipTipWin.cs
index 7f3ae37..ff91121 100644
--- a/Main/System/OtherPlayerDetail/OtherEquipTipWin.cs
+++ b/Main/System/OtherPlayerDetail/OtherEquipTipWin.cs
@@ -117,6 +117,7 @@
async UniTask RefreshEffect(int itemColor)
{
await UniTask.DelayFrame(3);
+ if (this == null) return; // destroyed during await
int effectID = EquipModel.Instance.equipUIEffects[Math.Min(itemColor, EquipModel.Instance.equipUIEffects.Length) - 1];
if (effectID == 0)
{
diff --git a/Main/System/OtherPlayerDetail/OtherHeroDetailWin.cs b/Main/System/OtherPlayerDetail/OtherHeroDetailWin.cs
index dcd9aed..5fdc57b 100644
--- a/Main/System/OtherPlayerDetail/OtherHeroDetailWin.cs
+++ b/Main/System/OtherPlayerDetail/OtherHeroDetailWin.cs
@@ -597,6 +597,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true);
foreach (var layout in layouts)
@@ -604,6 +605,7 @@
LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
}
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
foreach (var layout in layouts)
{
diff --git a/Main/System/OtherPlayerDetail/OtherHeroFightingCardItem.cs b/Main/System/OtherPlayerDetail/OtherHeroFightingCardItem.cs
index 8dc72bf..388fe5c 100644
--- a/Main/System/OtherPlayerDetail/OtherHeroFightingCardItem.cs
+++ b/Main/System/OtherPlayerDetail/OtherHeroFightingCardItem.cs
@@ -1,6 +1,7 @@
锘縰sing UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
public class OtherHeroFightingCardItem : MonoBehaviour
{
@@ -40,7 +41,24 @@
void DisplayHero(OtherPlayerDetailManager.RolePlusData.HeroData heroData)
{
+#pragma warning disable CS0618
var sprite = UILoader.LoadSprite("HeroHead", HeroSkinConfig.Get(heroData.SkinID).RectangleIcon);
+#pragma warning restore CS0618
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ imgHero.SetSprite("herohead_big_default");
+ }
+ else
+ {
+ imgHero.overrideSprite = sprite;
+ }
+ }
+
+ async UniTask DisplayHeroAsync(OtherPlayerDetailManager.RolePlusData.HeroData heroData)
+ {
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", HeroSkinConfig.Get(heroData.SkinID).RectangleIcon);
+ if (this == null) return;
if (sprite == null)
{
// 鍐呯綉鏈厤缃椂
diff --git a/Main/System/OtherPlayerDetail/OtherNPCDetailWin.cs b/Main/System/OtherPlayerDetail/OtherNPCDetailWin.cs
index f335b27..777e954 100644
--- a/Main/System/OtherPlayerDetail/OtherNPCDetailWin.cs
+++ b/Main/System/OtherPlayerDetail/OtherNPCDetailWin.cs
@@ -306,6 +306,7 @@
async UniTask ForceRefreshLayout()
{
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true);
foreach (var layout in layouts)
@@ -313,6 +314,7 @@
LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
}
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
// 鍒锋柊鎵�鏈塋ayout缁勪欢
foreach (var layout in layouts)
{
diff --git a/Main/System/PhantasmPavilion/PhantasmPavilionManager.cs b/Main/System/PhantasmPavilion/PhantasmPavilionManager.cs
index 52ce53f..984f6ca 100644
--- a/Main/System/PhantasmPavilion/PhantasmPavilionManager.cs
+++ b/Main/System/PhantasmPavilion/PhantasmPavilionManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using UnityEngine;
+using Cysharp.Threading.Tasks;
public partial class PhantasmPavilionManager : GameSystemManager<PhantasmPavilionManager>
{
@@ -228,6 +229,41 @@
}
+ public async UniTask ShowFaceAsync(ImageEx imgFace, UIEffectPlayer spine, UIFrame uiFrame, EllipseMask ellipseMask, int id)
+ {
+ PhantasmPavilionType type = PhantasmPavilionType.Face;
+ int UnlockWay = GetUnlockWay(type, id);
+ int unlockValue = GetUnlockValue(type, id);
+ int resourceType = GetResourceType(type, id);
+ string resourceValue = GetResourceValue(type, id);
+ if (UnlockWay == 3 && resourceValue == "")
+ {
+ int heroID = unlockValue;
+ if (!HeroConfig.HasKey(heroID))
+ return;
+ HeroConfig heroConfig = HeroConfig.Get(heroID);
+ int skinID = heroConfig.SkinIDList[0];
+ if (!HeroSkinConfig.HasKey(skinID))
+ return;
+ HeroSkinConfig skinConfig = HeroSkinConfig.Get(skinID);
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", skinConfig.SquareIcon);
+ if (sprite == null)
+ {
+ Show(imgFace, spine, uiFrame, resourceType, "herohead_default", null, ellipseMask);
+ }
+ else
+ {
+ Show(imgFace, spine, uiFrame, resourceType, string.Empty, sprite, ellipseMask);
+ }
+ }
+ else
+ {
+ resourceValue = GetResourceValue(type, id);
+ Show(imgFace, spine, uiFrame, resourceType, resourceValue, null, ellipseMask);
+ }
+
+ }
+
public void Show(ImageEx imgFace, UIEffectPlayer spine, UIFrame uiFrame, int resourceType, string resourceValue, Sprite sprite = null, EllipseMask ellipseMask = null)
{
spine.Stop();
diff --git a/Main/System/PhantasmPavilion/PhantasmPavilionModelItem.cs b/Main/System/PhantasmPavilion/PhantasmPavilionModelItem.cs
index 87cbd41..76c099f 100644
--- a/Main/System/PhantasmPavilion/PhantasmPavilionModelItem.cs
+++ b/Main/System/PhantasmPavilion/PhantasmPavilionModelItem.cs
@@ -1,5 +1,6 @@
锘縰sing UnityEngine;
using UnityEngine.UI;
+using Cysharp.Threading.Tasks;
public class PhantasmPavilionModelItem : MonoBehaviour
{
@@ -60,4 +61,48 @@
manager.UpdateItemRedPoint(imgRed, type, id);
}
+
+ public async UniTask DisplayAsync(int id)
+ {
+ this.id = id;
+ btnChoose.SetListener(() =>
+ {
+ manager.selectId = id;
+ });
+
+ PhantasmPavilionState state = manager.GetUnLockState(type, id);
+ bool isLimitedTime = manager.IsLimitTime(type, id);
+ bool isUsing = manager.IsUsing(type, id);
+ imgChoose.SetActive(manager.selectId == id);
+ imgLimit.SetActive(state == PhantasmPavilionState.Activated && isLimitedTime);
+ imgLock.SetActive(state != PhantasmPavilionState.Activated);
+ imgCanUnlock.SetActive(state == PhantasmPavilionState.CanActivate);
+ txtUsing.SetActive(state == PhantasmPavilionState.Activated && isUsing);
+
+ if (!ModelConfig.HasKey(id))
+ return;
+ ModelConfig model = ModelConfig.Get(id);
+ int skinID = model.SkinID;
+ if (!HeroSkinConfig.HasKey(skinID))
+ return;
+ HeroSkinConfig skinConfig = HeroSkinConfig.Get(skinID);
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", skinConfig.SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ imgFace.SetSprite("herohead_default");
+ }
+ else
+ {
+ imgFace.overrideSprite = sprite;
+ }
+
+ int resourceType = manager.GetResourceType(type, id);
+ string resourceValue = manager.GetResourceValue(type, id);
+
+ imgBg.SetSprite(manager.GetModelBgColorStr(id));
+
+ manager.UpdateItemRedPoint(imgRed, type, id);
+ }
}
\ No newline at end of file
diff --git a/Main/System/Recharge/PrivilegeActiveCardWin.cs b/Main/System/Recharge/PrivilegeActiveCardWin.cs
index 8046174..79788ad 100644
--- a/Main/System/Recharge/PrivilegeActiveCardWin.cs
+++ b/Main/System/Recharge/PrivilegeActiveCardWin.cs
@@ -44,6 +44,7 @@
{
LayoutRebuilder.ForceRebuildLayoutImmediate(bg);
await UniTask.DelayFrame(2);
+ if (this == null) return; // destroyed during await
LayoutRebuilder.ForceRebuildLayoutImmediate(bg);
}
}
diff --git a/Main/System/Sound/SoundPlayer.cs b/Main/System/Sound/SoundPlayer.cs
index 25c8175..5d7c388 100644
--- a/Main/System/Sound/SoundPlayer.cs
+++ b/Main/System/Sound/SoundPlayer.cs
@@ -92,6 +92,22 @@
Debug.Log("CreateSoundPlayer");
}
+ public static async UniTask CreateSoundPlayerAsync()
+ {
+ if (m_Instance != null)
+ {
+ return;
+ }
+
+ var prefab = await BuiltInLoader.LoadPrefabAsync("SoundPlayer");
+ var gameObject = GameObject.Instantiate(prefab);
+ m_Instance = gameObject.GetComponent<SoundPlayer>();
+ m_Instance.name = "SoundPlayer";
+ m_Instance.SetActive(true);
+ DontDestroyOnLoad(gameObject);
+ Debug.Log("CreateSoundPlayer");
+ }
+
public void PlayBackGroundMusic(int _audioId)
{
if (_audioId <= 0)
@@ -177,7 +193,9 @@
if (key != _exclude)
{
var config = AudioConfig.Get(key);
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy unload
ResManager.Instance.UnloadAsset("Audio/" + config.Folder, config.Audio);
+ #pragma warning restore CS0618
}
}
@@ -196,6 +214,7 @@
public async UniTask PlayUIAudioDelay(int _audioId)
{
await UniTask.Delay(1);
+ if (this == null) return; // destroyed during await
PlayUIAudio(_audioId);
}
@@ -371,6 +390,16 @@
}
}
+ public async UniTask PlayLoginMusicAsync()
+ {
+ var loginMusic = await BuiltInLoader.LoadMusicAsync("login");
+ if (this == null) return;
+ if (!m_MusicAudioSource.isPlaying || m_MusicAudioSource.clip != loginMusic)
+ {
+ StartCoroutine(Co_BackGroundMusicFadeOutIn(loginMusic, false));
+ }
+ }
+
//private void LateUpdate()
//{
// if (CameraController.Instance != null && CameraController.Instance.CameraObject != null)
diff --git a/Main/System/TianziBillborad/TianziBillboradBossHead.cs b/Main/System/TianziBillborad/TianziBillboradBossHead.cs
index 38c00ce..44a28fb 100644
--- a/Main/System/TianziBillborad/TianziBillboradBossHead.cs
+++ b/Main/System/TianziBillborad/TianziBillboradBossHead.cs
@@ -1,5 +1,6 @@
using System;
using UnityEngine;
+using Cysharp.Threading.Tasks;
public class TianziBillboradBossHead : MonoBehaviour
{
@@ -57,6 +58,52 @@
}
}
+ public async UniTask DisplayAsync(int bossId)
+ {
+ if (!NPCConfig.HasKey(bossId))
+ return;
+ NPCConfig npcConfig = NPCConfig.Get(bossId);
+ int heroID = npcConfig.RelatedHeroID;
+ if (!HeroConfig.HasKey(heroID))
+ return;
+ var heroConfig = HeroConfig.Get(heroID);
+ int skinID = heroConfig.SkinIDList[0];
+ if (!HeroSkinConfig.HasKey(skinID))
+ return;
+ if (!model.TryGetBossConfig(model.DataMapID, model.todayLineID, out DungeonConfig dungeonConfig, out NPCLineupConfig npcLineupConfig, out NPCConfig npcConfigToday))
+ return;
+
+ isTodayBoss = npcConfigToday.NPCID == bossId;
+
+ // --- 璁剧疆灏哄 ---
+ imgQuality.rectTransform.sizeDelta = isTodayBoss ? new Vector2(104, 104) : new Vector2(94, 94);
+ rectTransform.sizeDelta = isTodayBoss ? new Vector2(104, 104) : new Vector2(94, 94);
+
+ // --- 璁剧疆鍥惧儚鍜岀姸鎬� ---
+ var heroSkinConfig = HeroSkinConfig.Get(skinID);
+ imgQuality.SetSprite("heroheadBG" + heroConfig.Quality);
+ imgQuality.gray = !isTodayBoss;
+
+ var sprite = await UILoader.LoadSpriteAsync("HeroHead", heroSkinConfig.SquareIcon);
+ if (this == null) return;
+ if (sprite == null)
+ {
+ // 鍐呯綉鏈厤缃椂
+ imgHeadIcon.SetSprite("herohead_default");
+ }
+ else
+ {
+ imgHeadIcon.overrideSprite = sprite;
+ }
+ imgHeadIcon.gray = !isTodayBoss;
+
+ txtTime.SetActive(isTodayBoss);
+ if (isTodayBoss)
+ {
+ UpdateTimer();
+ }
+ }
+
public void UpdateTimer()
{
if (!isTodayBoss)
diff --git a/Main/System/Tip/ScrollTip.cs b/Main/System/Tip/ScrollTip.cs
index dbb20af..f5103cd 100644
--- a/Main/System/Tip/ScrollTip.cs
+++ b/Main/System/Tip/ScrollTip.cs
@@ -1,6 +1,7 @@
锘縰sing System;
using System.Collections;
using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
using UnityEngine;
public class ScrollTip
{
@@ -83,6 +84,21 @@
return tip;
}
+ public static async UniTask<ScrollTipDetail> RequestAsync()
+ {
+ ScrollTipDetail tip = null;
+ if (pool == null)
+ {
+ var _prefab = await UILoader.LoadPrefabAsync("Tip");
+ pool = GameObjectPoolManager.Instance.GetPool(_prefab);
+ }
+ if (pool != null)
+ {
+ tip = pool.Request().AddMissingComponent<ScrollTipDetail>();
+ }
+ return tip;
+ }
+
public static void Release(ScrollTipDetail tip, bool next = true)
{
if (m_ActiveTips.Contains(tip))
diff --git a/Main/System/Tip/ScrollTipWin.cs b/Main/System/Tip/ScrollTipWin.cs
index 709758d..d576ef5 100644
--- a/Main/System/Tip/ScrollTipWin.cs
+++ b/Main/System/Tip/ScrollTipWin.cs
@@ -75,6 +75,7 @@
{
OnTipReceiveEvent();
await UniTask.Delay(100);
+ if (this == null) return; // destroyed during await
}
}
finally
diff --git a/Main/System/UIBase/UIBase.cs b/Main/System/UIBase/UIBase.cs
index 6d1fddb..6514d5e 100644
--- a/Main/System/UIBase/UIBase.cs
+++ b/Main/System/UIBase/UIBase.cs
@@ -176,6 +176,7 @@
{
//寤惰繜x甯у悗鍙偣鍑�,闃叉鐐瑰嚮杩囧揩绔嬪嵆鍏抽棴浜�
await UniTask.Delay(200);
+ if (this == null) return; // destroyed during await
btnClickEmptyClose.enabled = true;
}
}
@@ -208,6 +209,7 @@
protected async void ExecuteNextFrame(Action _action)
{
await UniTask.DelayFrame(1);
+ if (this == null) return; // destroyed during await
_action?.Invoke();
}
@@ -422,6 +424,7 @@
public async UniTask DelayCloseWindow(int delayTime = 30)
{
await UniTask.Delay(delayTime);
+ if (this == null) return; // destroyed during await
CloseWindow();
}
diff --git a/Main/Tests.meta b/Main/Tests.meta
new file mode 100644
index 0000000..c3a8b7c
--- /dev/null
+++ b/Main/Tests.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3557e951d4d91304da2928693eb783e1
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Tests/Main.Tests.asmdef b/Main/Tests/Main.Tests.asmdef
new file mode 100644
index 0000000..b72b0e5
--- /dev/null
+++ b/Main/Tests/Main.Tests.asmdef
@@ -0,0 +1,28 @@
+{
+ "name": "Main.Tests",
+ "rootNamespace": "",
+ "references": [
+ "GUID:eac12500b66aea340bf118070ac14bec",
+ "GUID:3ffa07c58a98b0445a7a34376b165fd1",
+ "GUID:f51ebe6a0ceec4240a699833d6309b23",
+ "GUID:e34a5702dd353724aa315fb8011f08c3",
+ "GUID:1278a46ce459c5a46b4eaeda148684ef",
+ "GUID:27619889b8ba8c24980f86f011571974",
+ "GUID:0acc523941302664db1f4e527237feb3"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": true,
+ "precompiledReferences": [
+ "nunit.framework.dll"
+ ],
+ "autoReferenced": false,
+ "defineConstraints": [
+ "UNITY_INCLUDE_TESTS"
+ ],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
diff --git a/Main/Tests/Main.Tests.asmdef.meta b/Main/Tests/Main.Tests.asmdef.meta
new file mode 100644
index 0000000..5b29703
--- /dev/null
+++ b/Main/Tests/Main.Tests.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3b1d5c2a5a8a8ac4d90faec3a9bbe7e5
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Tests/ResourceCacheManagerTests.cs b/Main/Tests/ResourceCacheManagerTests.cs
new file mode 100644
index 0000000..4bb6122
--- /dev/null
+++ b/Main/Tests/ResourceCacheManagerTests.cs
@@ -0,0 +1,99 @@
+// ============================================================================
+// ResourceCacheManagerTests.cs 鈥� 缂撳瓨绠$悊鍣ㄥ崟鍏冩祴璇�
+// Feature: 001-async-resource-loading
+// ============================================================================
+
+using System.Collections;
+using NUnit.Framework;
+using UnityEngine.TestTools;
+using ProjSG.Resource;
+
+[TestFixture]
+public class ResourceCacheManagerTests
+{
+ private ResourceCacheManager _cacheManager;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _cacheManager = ResourceCacheManager.Instance;
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _cacheManager.ForceReleaseAll();
+ }
+
+ // ====================================================================
+ // 缂撳瓨鍛戒腑 / 鏈懡涓�
+ // ====================================================================
+
+ [Test]
+ public void GetCached_ReturnsNull_WhenNotCached()
+ {
+ var result = _cacheManager.GetCached<UnityEngine.Texture2D>("nonexistent/path");
+ Assert.IsNull(result);
+ }
+
+ [Test]
+ public void IsCached_ReturnsFalse_WhenNotCached()
+ {
+ Assert.IsFalse(_cacheManager.IsCached("nonexistent/path"));
+ }
+
+ [Test]
+ public void GetCached_ReturnsNull_WhenNullLocation()
+ {
+ var result = _cacheManager.GetCached<UnityEngine.Texture2D>(null);
+ Assert.IsNull(result);
+ }
+
+ [Test]
+ public void GetCached_ReturnsNull_WhenEmptyLocation()
+ {
+ var result = _cacheManager.GetCached<UnityEngine.Texture2D>("");
+ Assert.IsNull(result);
+ }
+
+ [Test]
+ public void IsCached_ReturnsFalse_WhenNullOrEmpty()
+ {
+ Assert.IsFalse(_cacheManager.IsCached(null));
+ Assert.IsFalse(_cacheManager.IsCached(""));
+ }
+
+ // ====================================================================
+ // 閲婃斁
+ // ====================================================================
+
+ [Test]
+ public void Release_DoesNotThrow_WhenLocationNotCached()
+ {
+ Assert.DoesNotThrow(() => _cacheManager.Release("nonexistent/path"));
+ }
+
+ [Test]
+ public void ReleaseAll_DoesNotThrow_WhenEmpty()
+ {
+ Assert.DoesNotThrow(() => _cacheManager.ReleaseAll());
+ }
+
+ [Test]
+ public void ForceReleaseAll_ResetsCachedCount_ToZero()
+ {
+ _cacheManager.ForceReleaseAll();
+ Assert.AreEqual(0, _cacheManager.CachedCount);
+ }
+
+ // ====================================================================
+ // CachedCount
+ // ====================================================================
+
+ [Test]
+ public void CachedCount_IsZero_WhenEmpty()
+ {
+ _cacheManager.ForceReleaseAll();
+ Assert.AreEqual(0, _cacheManager.CachedCount);
+ }
+}
diff --git a/Main/Tests/ResourceCacheManagerTests.cs.meta b/Main/Tests/ResourceCacheManagerTests.cs.meta
new file mode 100644
index 0000000..d2be980
--- /dev/null
+++ b/Main/Tests/ResourceCacheManagerTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d6066e660ee423e4f95f22d8d1276a36
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Tests/ResourcePreloaderTests.cs b/Main/Tests/ResourcePreloaderTests.cs
new file mode 100644
index 0000000..d9e1b3f
--- /dev/null
+++ b/Main/Tests/ResourcePreloaderTests.cs
@@ -0,0 +1,101 @@
+// ============================================================================
+// ResourcePreloaderTests.cs 鈥� 棰勫姞杞界鐞嗗櫒鍗曞厓娴嬭瘯
+// Feature: 001-async-resource-loading
+// ============================================================================
+
+using NUnit.Framework;
+using ProjSG.Resource;
+
+[TestFixture]
+public class ResourcePreloaderTests
+{
+ private ResourcePreloader _preloader;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _preloader = ResourcePreloader.Instance;
+ }
+
+ // ====================================================================
+ // 閰嶇疆娉ㄥ唽
+ // ====================================================================
+
+ [Test]
+ public void RegisterConfig_AcceptsValidConfig()
+ {
+ var config = new PreloadConfig
+ {
+ ConfigName = "TestConfig",
+ Locations = new[] { "Assets/Test/a.png" },
+ IsPermanent = false,
+ };
+
+ Assert.DoesNotThrow(() => _preloader.RegisterConfig(config));
+ }
+
+ [Test]
+ public void RegisterConfig_RejectsNullConfig()
+ {
+ // Should log error but not throw
+ Assert.DoesNotThrow(() => _preloader.RegisterConfig(null));
+ }
+
+ [Test]
+ public void RegisterConfig_RejectsEmptyName()
+ {
+ var config = new PreloadConfig
+ {
+ ConfigName = "",
+ Locations = new[] { "Assets/Test/a.png" },
+ };
+
+ Assert.DoesNotThrow(() => _preloader.RegisterConfig(config));
+ }
+
+ // ====================================================================
+ // IsConfigLoaded
+ // ====================================================================
+
+ [Test]
+ public void IsConfigLoaded_ReturnsFalse_WhenNotLoaded()
+ {
+ Assert.IsFalse(_preloader.IsConfigLoaded("NotRegistered"));
+ }
+
+ [Test]
+ public void IsConfigLoaded_ReturnsFalse_ForNewConfig()
+ {
+ _preloader.RegisterConfig(new PreloadConfig
+ {
+ ConfigName = "NewConfig",
+ Locations = new[] { "Assets/Test/x.png" },
+ });
+
+ Assert.IsFalse(_preloader.IsConfigLoaded("NewConfig"));
+ }
+
+ // ====================================================================
+ // UnloadConfig
+ // ====================================================================
+
+ [Test]
+ public void UnloadConfig_DoesNotThrow_WhenNotRegistered()
+ {
+ Assert.DoesNotThrow(() => _preloader.UnloadConfig("NonExistent"));
+ }
+
+ [Test]
+ public void UnloadConfig_SkipsPermanentConfig()
+ {
+ _preloader.RegisterConfig(new PreloadConfig
+ {
+ ConfigName = "PermanentTest",
+ Locations = new[] { "Assets/Test/p.png" },
+ IsPermanent = true,
+ });
+
+ // Should not throw, should log warning
+ Assert.DoesNotThrow(() => _preloader.UnloadConfig("PermanentTest"));
+ }
+}
diff --git a/Main/Tests/ResourcePreloaderTests.cs.meta b/Main/Tests/ResourcePreloaderTests.cs.meta
new file mode 100644
index 0000000..e8f9106
--- /dev/null
+++ b/Main/Tests/ResourcePreloaderTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e835caf82b8e5cc49a15766510c3d0cb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Tests/YooAssetServiceTests.cs b/Main/Tests/YooAssetServiceTests.cs
new file mode 100644
index 0000000..5ed6ee2
--- /dev/null
+++ b/Main/Tests/YooAssetServiceTests.cs
@@ -0,0 +1,211 @@
+// ============================================================================
+// YooAssetServiceTests.cs 鈥� YooAssetService 鏍稿績閫昏緫鍗曞厓娴嬭瘯
+// 瑕嗙洊锛氬垵濮嬪寲鐘舵�佹満銆丩oadAssetAsync null/invalid 澶勭悊銆丷eleaseHandle 骞傜瓑鎬�
+// ============================================================================
+
+using System;
+using NUnit.Framework;
+using ProjSG.Resource;
+
+namespace ProjSG.Resource.Tests
+{
+ [TestFixture]
+ public class YooAssetServiceTests
+ {
+ private YooAssetService _service;
+
+ [SetUp]
+ public void SetUp()
+ {
+ // 纭繚姣忎釜娴嬭瘯寮�濮嬫椂閿�姣佹棫瀹炰緥
+ if (YooAssetService.IsValid())
+ {
+ YooAssetService.Destroy();
+ }
+ _service = YooAssetService.Instance;
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ if (YooAssetService.IsValid())
+ {
+ YooAssetService.Destroy();
+ }
+ }
+
+ // ====================================================================
+ // 鍒濆鍖栫姸鎬佹満娴嬭瘯
+ // ====================================================================
+
+ [Test]
+ public void IsInitialized_BeforeInit_ReturnsFalse()
+ {
+ Assert.IsFalse(_service.IsInitialized);
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_LoadAssetAsync()
+ {
+ // 鏈垵濮嬪寲鏃惰皟鐢ㄥ姞杞芥柟娉曞簲鎶涘嚭 InvalidOperationException
+ // ThrowIfNotInitialized 鍦ㄥ紓姝ユ柟娉曞悓姝ラ儴鍒嗘姏鍑猴紝GetAwaiter().GetResult() 浼氱珛鍗抽噸鏂版姏鍑�
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.LoadAssetAsync<UnityEngine.Object>("test_location").GetAwaiter().GetResult();
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_LoadAssetAsyncByType()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.LoadAssetAsync("test_location", typeof(UnityEngine.Object)).GetAwaiter().GetResult();
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_LoadRawFileTextAsync()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.LoadRawFileTextAsync("test_location").GetAwaiter().GetResult();
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_LoadRawFileBytesAsync()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.LoadRawFileBytesAsync("test_location").GetAwaiter().GetResult();
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_CheckLocationValid()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.CheckLocationValid("test_location");
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_GetAssetInfosByTag()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.GetAssetInfosByTag("test_tag");
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_IsNeedDownloadFromRemote()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.IsNeedDownloadFromRemote("test_location");
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_UnloadUnusedAssetsAsync()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.UnloadUnusedAssetsAsync().GetAwaiter().GetResult();
+ });
+ }
+
+ [Test]
+ public void ThrowsIfNotInitialized_UnloadAllAssetsAsync()
+ {
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ _service.UnloadAllAssetsAsync().GetAwaiter().GetResult();
+ });
+ }
+
+ // ====================================================================
+ // ReleaseHandle 骞傜瓑鎬ф祴璇�
+ // ====================================================================
+
+ [Test]
+ public void ReleaseHandle_WithNull_DoesNotThrow()
+ {
+ // ReleaseHandle(null) 搴斿畨鍏ㄨ繑鍥烇紝涓嶆姏寮傚父
+ Assert.DoesNotThrow(() =>
+ {
+ _service.ReleaseHandle(null);
+ });
+ }
+
+ [Test]
+ public void ReleaseHandle_CalledTwiceWithNull_StillDoesNotThrow()
+ {
+ // 澶氭璋冪敤 null 浠嶅畨鍏�
+ Assert.DoesNotThrow(() =>
+ {
+ _service.ReleaseHandle(null);
+ _service.ReleaseHandle(null);
+ });
+ }
+
+ // ====================================================================
+ // Singleton 琛屼负娴嬭瘯
+ // ====================================================================
+
+ [Test]
+ public void Singleton_Instance_ReturnsSameInstance()
+ {
+ var instance1 = YooAssetService.Instance;
+ var instance2 = YooAssetService.Instance;
+ Assert.AreSame(instance1, instance2);
+ }
+
+ [Test]
+ public void Singleton_IsValid_ReturnsTrue()
+ {
+ _ = YooAssetService.Instance;
+ Assert.IsTrue(YooAssetService.IsValid());
+ }
+
+ [Test]
+ public void Singleton_AfterDestroy_IsValidReturnsFalse()
+ {
+ _ = YooAssetService.Instance;
+ YooAssetService.Destroy();
+ Assert.IsFalse(YooAssetService.IsValid());
+ }
+
+ [Test]
+ public void Singleton_AfterDestroy_NewInstanceCreated()
+ {
+ var instance1 = YooAssetService.Instance;
+ YooAssetService.Destroy();
+ var instance2 = YooAssetService.Instance;
+ Assert.AreNotSame(instance1, instance2);
+ }
+
+ // ====================================================================
+ // IYooAssetBridge 鎺ュ彛琛屼负娴嬭瘯
+ // ====================================================================
+
+ [Test]
+ public void IYooAssetBridge_GetCached_ReturnsNull_BeforeUS4()
+ {
+ // GetCached 鍦� US4 闆嗘垚鍓嶈繑鍥� null
+ IYooAssetBridge bridge = _service;
+ var result = bridge.GetCached<UnityEngine.Object>("test_location");
+ Assert.IsNull(result);
+ }
+
+ [Test]
+ public void IYooAssetBridge_IsRegistered_ReturnsFalse_BeforeInit()
+ {
+ IYooAssetBridge bridge = _service;
+ Assert.IsFalse(bridge.IsRegistered);
+ }
+ }
+}
diff --git a/Main/Tests/YooAssetServiceTests.cs.meta b/Main/Tests/YooAssetServiceTests.cs.meta
new file mode 100644
index 0000000..9548f29
--- /dev/null
+++ b/Main/Tests/YooAssetServiceTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f0b053bf25c64a34c972bbf4796e2923
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Main/Utility/ComponentExtersion.cs b/Main/Utility/ComponentExtersion.cs
index c3b8882..3f25b7c 100644
--- a/Main/Utility/ComponentExtersion.cs
+++ b/Main/Utility/ComponentExtersion.cs
@@ -2,6 +2,7 @@
using UnityEngine.UI;
using UnityEngine.Events;
using System;
+using Cysharp.Threading.Tasks;
public static class ComponentExtersion
@@ -333,6 +334,24 @@
_image.overrideSprite = sprite;
}
+ public static async UniTask SetSpriteAsync(this Image _image, string _id)
+ {
+ if (_image == null)
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(_id))
+ {
+ Debug.LogError("Image SetSpriteAsync id is null or empty " + _id);
+ return;
+ }
+
+ var sprite = await UILoader.LoadSpriteAsync(_id);
+ if (_image != null)
+ _image.overrideSprite = sprite;
+ }
+
public static void SetSprite(this TextImage _textImage, string _id)
{
@@ -349,6 +368,24 @@
var sprite = UILoader.LoadSprite(_id);
_textImage.sprite = sprite;
+ }
+
+ public static async UniTask SetSpriteAsync(this TextImage _textImage, string _id)
+ {
+ if (_textImage == null)
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(_id))
+ {
+ Debug.LogError("TextImage SetSpriteAsync id is null or empty " + _id);
+ return;
+ }
+
+ var sprite = await UILoader.LoadSpriteAsync(_id);
+ if (_textImage != null)
+ _textImage.sprite = sprite;
}
//閫氳繃鍥剧墖鍚嶅姞杞�, 濡傜墿鍝佽〃 鎶�鑳借〃绛夛紝鑺傜渷鍦↖con琛ㄥ仛澶氫綑閰嶇疆
@@ -368,6 +405,24 @@
var sprite = UILoader.LoadSprite(folderName, iconName);
if (null == sprite) return;
_image.overrideSprite = sprite;
+ }
+
+ public static async UniTask SetOrgSpriteAsync(this Image _image, string iconName, string folderName = "icon")
+ {
+ if (_image == null)
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(iconName))
+ {
+ Debug.LogError("SetOrgSpriteAsync iconName is null or empty " + iconName);
+ return;
+ }
+
+ var sprite = await UILoader.LoadSpriteAsync(folderName, iconName);
+ if (_image != null && sprite != null)
+ _image.overrideSprite = sprite;
}
public static void SetItemSprite(this Image _image, int itemID)
@@ -417,6 +472,30 @@
_image.overrideSprite = sprite;
}
+ public static async UniTask SetSkillSpriteAsync(this Image _image, int skillID)
+ {
+ if (_image == null)
+ {
+ return;
+ }
+
+ var skillConfig = SkillConfig.Get(skillID);
+ if (skillConfig == null)
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(skillConfig.IconName))
+ {
+ Debug.LogError("SetSkillSpriteAsync IconName is null or empty for skillID " + skillID);
+ return;
+ }
+
+ var sprite = await UILoader.LoadSpriteAsync("SkillIcon", skillConfig.IconName);
+ if (_image != null)
+ _image.overrideSprite = sprite;
+ }
+
public static void SetActive(this Component compoent, bool active)
{
if (compoent != null)
@@ -444,6 +523,18 @@
_image.texture = texture;
}
+ public static async UniTask SetTexture2DAsync(this RawImage _image, string _id)
+ {
+ if (_image == null)
+ {
+ return;
+ }
+
+ var texture = await UILoader.LoadTexture2DAsync(_id);
+ if (_image != null)
+ _image.texture = texture;
+ }
+
public static void SetTexture2DPNG(this RawImage _image, string _id)
{
diff --git a/Main/Utility/FontUtility.cs b/Main/Utility/FontUtility.cs
index 09d455f..d612580 100644
--- a/Main/Utility/FontUtility.cs
+++ b/Main/Utility/FontUtility.cs
@@ -2,17 +2,32 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
+using Cysharp.Threading.Tasks;
+using ProjSG.Resource;
public class FontUtility
{
- static Font m_Preferred;
- public static Font preferred {
- get { return m_Preferred ?? (m_Preferred = ResManager.Instance.LoadAsset<Font>("Font", "GameFont1")); }
+ // T044: Fonts must be pre-loaded via StartupEssential preload config
+ // (location: "Assets/ResourcesOut/BuiltIn/Font")
+
+ public static Font preferred
+ {
+ get { return ResourceCacheManager.Instance.GetCached<Font>("Assets/ResourcesOut/BuiltIn/Font/GameFont1.ttf"); }
}
- static Font m_Secondary;
- public static Font secondary {
- get { return m_Secondary ?? (m_Secondary = ResManager.Instance.LoadAsset<Font>("Font", "GameFont2")); }
+ public static Font secondary
+ {
+ get { return ResourceCacheManager.Instance.GetCached<Font>("Assets/ResourcesOut/BuiltIn/Font/GameFont2.ttf"); }
+ }
+
+ /// <summary>
+ /// US2: Async initialization 鈥� fallback API if preload is not yet available.
+ /// </summary>
+ public static async UniTask InitAsync()
+ {
+ // Fallback: async load and cache via ResManager
+ await ResManager.Instance.LoadAssetAsync<Font>("Font", "GameFont1");
+ await ResManager.Instance.LoadAssetAsync<Font>("Font", "GameFont2");
}
}
diff --git a/Main/Utility/MaterialUtility.cs b/Main/Utility/MaterialUtility.cs
index 2249278..d5bf793 100644
--- a/Main/Utility/MaterialUtility.cs
+++ b/Main/Utility/MaterialUtility.cs
@@ -1,13 +1,17 @@
锘縰sing UnityEngine;
using System;
+using Cysharp.Threading.Tasks;
+using ProjSG.Resource;
public static class MaterialUtility
{
+ // T044: Materials must be pre-loaded via StartupEssential preload config
+ // (location: "Assets/ResourcesOut/BuiltIn/Materials")
public static Material GetDefaultSpriteGrayMaterial()
{
- return ResManager.Instance.LoadAsset<Material>("BuiltIn/Materials", "SpriteGray");
+ return ResourceCacheManager.Instance.GetCached<Material>("Assets/ResourcesOut/BuiltIn/Materials/SpriteGray.mat");
}
public static Material GetInstantiatedSpriteGrayMaterial()
@@ -18,12 +22,12 @@
public static Material GetSmoothMaskGrayMaterial()
{
- return ResManager.Instance.LoadAsset<Material>("BuiltIn/Materials", "SmoothMaskGray");
+ return ResourceCacheManager.Instance.GetCached<Material>("Assets/ResourcesOut/BuiltIn/Materials/SmoothMaskGray.mat");
}
public static Material GetInstantiatedSpriteTwinkleMaterial()
{
- var material = ResManager.Instance.LoadAsset<Material>("BuiltIn/Materials", "Flash");
+ var material = ResourceCacheManager.Instance.GetCached<Material>("Assets/ResourcesOut/BuiltIn/Materials/Flash.mat");
return new Material(material);
}
@@ -34,7 +38,32 @@
public static Material GetGUIRenderTextureMaterial()
{
- return ResManager.Instance.LoadAsset<Material>("BuiltIn/Materials", "UI_RenderTexture");
+ return ResourceCacheManager.Instance.GetCached<Material>("Assets/ResourcesOut/BuiltIn/Materials/UI_RenderTexture.mat");
+ }
+
+ // ====================================================================
+ // US2: Async variants 鈥� temporary API. Replaced by preload+cache in T044.
+ // ====================================================================
+
+ public static UniTask<Material> GetDefaultSpriteGrayMaterialAsync()
+ {
+ return ResManager.Instance.LoadAssetAsync<Material>("BuiltIn/Materials", "SpriteGray");
+ }
+
+ public static UniTask<Material> GetSmoothMaskGrayMaterialAsync()
+ {
+ return ResManager.Instance.LoadAssetAsync<Material>("BuiltIn/Materials", "SmoothMaskGray");
+ }
+
+ public static async UniTask<Material> GetInstantiatedSpriteTwinkleMaterialAsync()
+ {
+ var material = await ResManager.Instance.LoadAssetAsync<Material>("BuiltIn/Materials", "Flash");
+ return new Material(material);
+ }
+
+ public static UniTask<Material> GetGUIRenderTextureMaterialAsync()
+ {
+ return ResManager.Instance.LoadAssetAsync<Material>("BuiltIn/Materials", "UI_RenderTexture");
}
public static void SetRenderSortingOrder(this GameObject root, int sortingOrder, bool includeChildren)
diff --git a/Main/Utility/ShaderUtility.cs b/Main/Utility/ShaderUtility.cs
index f19e29a..3e11810 100644
--- a/Main/Utility/ShaderUtility.cs
+++ b/Main/Utility/ShaderUtility.cs
@@ -1,6 +1,8 @@
锘縰sing System.Collections;
using System.Collections.Generic;
using UnityEngine;
+using Cysharp.Threading.Tasks;
+using ProjSG.Resource;
public class ShaderUtility
{
@@ -15,13 +17,23 @@
Shader.SetGlobalColor("_Gbl_Wat", new Color(1, 1, 1, 1));
}
+ [System.Obsolete("US2: Use WarmUpAllAsync. Sync loading removed. Final preload+cache pattern in T044.")]
public static void WarmUpAll()
+ {
+ // US2: Sync AB loading removed. Use WarmUpAllAsync instead.
+ Shader.WarmupAllShaders();
+ }
+
+ /// <summary>
+ /// US2: Async shader warm up via YooAsset. Temporary API 鈥� will be replaced by preload+cache in T044.
+ /// </summary>
+ public static async UniTask WarmUpAllAsync()
{
if (AssetSource.isUseAssetBundle)
{
- AssetBundleUtility.Instance.Sync_LoadAllAssets("Graphic/Shader");
- Shader.WarmupAllShaders();
+ await YooAssetService.Instance.LoadAllAssetsAsync<Shader>("Assets/ResourcesOut/Shader");
}
+ Shader.WarmupAllShaders();
}
diff --git a/Main/Utility/UIUtility.cs b/Main/Utility/UIUtility.cs
index 5c24afd..7096f2f 100644
--- a/Main/Utility/UIUtility.cs
+++ b/Main/Utility/UIUtility.cs
@@ -12,7 +12,22 @@
public static GameObject CreateWidget(string _sourceName, string _name)
{
+ #pragma warning disable CS0618 // Obsolete 鈥� sync legacy fallback
var prefab = UILoader.LoadPrefab(_sourceName);
+ #pragma warning restore CS0618
+ if (prefab == null)
+ {
+ return null;
+ }
+
+ var instance = GameObject.Instantiate(prefab);
+ instance.name = string.IsNullOrEmpty(_name) ? _sourceName : _name;
+ return instance;
+ }
+
+ public static async UniTask<GameObject> CreateWidgetAsync(string _sourceName, string _name)
+ {
+ var prefab = await UILoader.LoadPrefabAsync(_sourceName);
if (prefab == null)
{
return null;
--
Gitblit v1.8.0