| | |
| | | public override void Init()
|
| | | {
|
| | | base.Init();
|
| | | InitConfigs();
|
| | | InitConfigs().Forget();
|
| | | }
|
| | |
|
| | | public virtual async UniTask InitConfigs()
|
| | |
| | | {
|
| | | 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;
|
| | |
| | | }
|
| | | }
|
| | |
|
| | | /// <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} 没有静态Init方法");
|
| | | }
|
| | | }
|
| | | 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;
|
| | |
| | | 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;
|
| | |
| | | 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 |
| | |
| | | { |
| | | if (AssetSource.isUseAssetBundle) |
| | | { |
| | | AssetBundleUtility.Instance.InitBuiltInAsset(); |
| | | // YooAsset 已在 Launch 阶段初始化内置资源,不再需要 AssetBundleUtility.InitBuiltInAsset() |
| | | // YooAssetInitializer.Instance.DefaultPackage 已包含内置资源 |
| | | |
| | | LaunchInHot.Instance.InitSystemMgr(); |
| | | |
| | |
| | | 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(); |
| | | |
| | |
| | | |
| | | 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); |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 05b7ea8c552919b4fa4463756adbd74c |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | using UnityEngine; |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | using ProjSG.Resource; |
| | | |
| | | #if UNITY_EDITOR |
| | | using UnityEditor; |
| | |
| | | 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) |
| | |
| | | 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; |
| | | } |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 7424e48c56909d94b813a858d3dc9091 |
| | | folderAsset: yes |
| | | DefaultImporter: |
| | | externalObjects: {} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | #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 |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 468b960b2f11c3e4e9fc575cd3653289 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | "GUID:05d41852e29aa5141a64e3d2d5339981", |
| | | "GUID:9ad05b610be6c974590152128a8b5b6e", |
| | | "GUID:d51b17ee17bf72443860693b4f9c20af", |
| | | "GUID:04376767bc1f3b428aefa3d20743e819" |
| | | "GUID:04376767bc1f3b428aefa3d20743e819", |
| | | "GUID:e34a5702dd353724aa315fb8011f08c3", |
| | | "GUID:1278a46ce459c5a46b4eaeda148684ef" |
| | | ], |
| | | "includePlatforms": [], |
| | | "excludePlatforms": [], |
| | |
| | | using Cysharp.Threading.Tasks;
|
| | | using UnityEngine;
|
| | | using UnityEngine.SceneManagement;
|
| | | using ProjSG.Resource;
|
| | |
|
| | | public enum StageName
|
| | | {
|
| | |
| | | {
|
| | | 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();
|
| | |
|
| | |
| | |
|
| | | UIManager.Instance.OpenWindow<LaunchBackGroundWin>();
|
| | | UIManager.Instance.OpenWindow<LoginWin>();
|
| | | // SoundPlayer.Instance.StopBackGroundMusic();
|
| | |
|
| | | if (VersionUtility.Instance.NeedDownAsset() && !AssetVersionUtility.hasDownLoadFullAsset)
|
| | | {
|
| | |
| | |
|
| | | 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;
|
| | |
| | | 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)
|
| | | {
|
| | |
| | | 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)
|
| | |
| | | using UnityEngine; |
| | | using System.Linq; |
| | | using DG.Tweening; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | /// <summary> |
| | | /// UI管理器 - 负责管理所有UI界面的显示、隐藏和层级 |
| | |
| | | } |
| | | else |
| | | { |
| | | #pragma warning disable CS0618 // Obsolete — sync legacy fallback, use LoadUIResourceAsync |
| | | prefab = ResManager.Instance.LoadAsset<GameObject>("UI", uiName); |
| | | #pragma warning restore CS0618 |
| | | } |
| | | |
| | | // 检查预制体是否加载成功 |
| | |
| | | { |
| | | 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($"找不到UI类型: {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 |
| | | |
| | |
| | | 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>(); |
| | |
| | | using UnityEngine; |
| | | using System.Collections; |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | using System.Threading; |
| | | |
| | | public class AudioLoader |
| | | { |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | using UnityEngine.U2D; |
| | | using Cysharp.Threading.Tasks; |
| | | using System.Threading; |
| | | using ProjSG.Resource; |
| | | |
| | | public class BuiltInLoader |
| | | { |
| | |
| | | } |
| | | 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 |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | 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) |
| | |
| | | |
| | | 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) |
| | |
| | | } |
| | | 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) |
| | |
| | | } |
| | | 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) |
| | |
| | | } |
| | | 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) |
| | |
| | | } |
| | | 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) |
| | |
| | | } |
| | | 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) |
| | |
| | | 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); |
| | | } |
| | | |
| | | |
| | | } |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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(); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 6ce8cec774664004e85ea08f6557275c |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // IResourcePreloader.cs — 资源预加载服务接口 |
| | | // Feature: 001-async-resource-loading |
| | | // ============================================================================ |
| | | |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | namespace ProjSG.Resource |
| | | { |
| | | /// <summary> |
| | | /// 资源预加载服务接口。 |
| | | /// 按场景/流程组织批量资源预加载。 |
| | | /// </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; } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 2307c27da18d094469bdbb03f7be2734 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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 远程服务(HostPlayMode/WebPlayMode 必需)</param> |
| | | /// <returns>初始化完成</returns> |
| | | UniTask InitializeAsync(EPlayMode playMode, IRemoteServices remoteServices = null); |
| | | |
| | | // ==================================================================== |
| | | // 资源加载 — Asset |
| | | // ==================================================================== |
| | | |
| | | /// <summary> |
| | | /// 异步加载指定类型的资源。 |
| | | /// </summary> |
| | | /// <typeparam name="T">资源类型(GameObject, 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> |
| | | /// 更新包裹 Manifest 到指定版本。 |
| | | /// </summary> |
| | | UniTask UpdatePackageManifestAsync(string packageVersion, CancellationToken ct = default); |
| | | |
| | | // ==================================================================== |
| | | // 资源释放 |
| | | // ==================================================================== |
| | | |
| | | /// <summary> |
| | | /// 释放资源句柄(引用计数 -1)。 |
| | | /// </summary> |
| | | void ReleaseHandle(HandleBase handle); |
| | | |
| | | /// <summary> |
| | | /// 卸载所有引用计数为零的资源。 |
| | | /// </summary> |
| | | UniTask UnloadUnusedAssetsAsync(); |
| | | |
| | | /// <summary> |
| | | /// 强制卸载所有资源。 |
| | | /// </summary> |
| | | UniTask UnloadAllAssetsAsync(); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 3361511557042814db2923ee333359a2 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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> |
| | | /// 获取缓存大小限制(MB)。 |
| | | /// 各平台有不同的存储限制。 |
| | | /// </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 |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 469fcba458bd2684681de1a15ba21655 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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"; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: eb5c16e6b126a2045866004b0e7eacc6 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | using UnityEngine.Video; |
| | | using Spine.Unity; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | using System.Threading; |
| | | using ProjSG.Resource; |
| | | |
| | | |
| | | |
| | |
| | | #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要传入的应该是atlas的名字 |
| | | if (typeof(T) == typeof(Sprite)) |
| | | { |
| | |
| | | } |
| | | 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) |
| | |
| | | return asset; |
| | | } |
| | | |
| | | [System.Obsolete("US2: Use LoadConfigAsync returning UniTask<string[]> instead.")] |
| | | public string[] LoadConfig(string name) |
| | | { |
| | | string path = string.Empty; |
| | |
| | | { |
| | | 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; |
| | |
| | | } |
| | | |
| | | //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("\\", "/"); |
| | |
| | | |
| | | 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 文件(YooAsset 自动处理 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 |
| | |
| | | } |
| | | 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) |
| | |
| | | return path; |
| | | } |
| | | |
| | | // ==================================================================== |
| | | // US1: New UniTask-based async variants |
| | | // ==================================================================== |
| | | |
| | | /// <summary> |
| | | /// 异步加载资源(UniTask 版本,US1 新增)。 |
| | | /// </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> |
| | | /// 异步加载配置文件(UniTask 版本)。 |
| | | /// 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 |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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> |
| | | /// 全局资源缓存管理器。 |
| | | /// 提供同步缓存获取、异步加载+去重、LRU 淘汰等能力。 |
| | | /// </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>正在加载中的任务(用于去重)</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. 正在加载中 → 等待已有任务(去重) |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 940d389e2ddcbe94ab0f851f56e988c9 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: da2fba186b20495479f006115e8cc8d4 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | { |
| | | 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); |
| | | } |
| | | } |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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 窗口、UIComp、Sprite、Texture、Font</summary> |
| | | public const string UI = "UI"; |
| | | |
| | | /// <summary>通用预制体包:BuiltIn、Shader、Materials、ScriptableObject、Scenes、Config</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、Battle 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,Dll 包由热更流程单独管理)。 |
| | | /// </summary> |
| | | public static readonly string[] AllPackages = new[] |
| | | { |
| | | Prefab, // 先初始化默认包 |
| | | UI, |
| | | UIEffect, |
| | | Battle, |
| | | Audio, |
| | | }; |
| | | |
| | | // ==================================================================== |
| | | // 资源目录 → Package 路由表 |
| | | // ==================================================================== |
| | | |
| | | /// <summary> |
| | | /// 资源目录前缀到 Package 名称的映射。 |
| | | /// key 是 ResourcesOut 下的一级目录名(小写),value 是 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"; |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: f90c77034c46bdc4784d80b3f2a96715 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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 阶段已创建的包裹 |
| | | 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> |
| | | /// 初始化指定名称的额外资源包裹。 |
| | | /// </summary> |
| | | public async UniTask InitializePackageAsync(string packageName, EPlayMode playMode, |
| | | IRemoteServices remoteServices = null) |
| | | { |
| | | if (_packages.ContainsKey(packageName)) |
| | | { |
| | | Debug.LogWarning($"[YooAssetService] Package '{packageName}' already initialized."); |
| | | return; |
| | | } |
| | | |
| | | // 优先复用已存在的包裹(可能由 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(US4 已集成) |
| | | 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); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: ad3881ecd3a99ea439a411fd1866ed41 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | using UnityEngine; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class ArenaHeroHead : MonoBehaviour |
| | | { |
| | |
| | | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | { |
| | | 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) |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | var layouts = allContent.GetComponentsInChildren<LayoutGroup>(true); |
| | | foreach (var layout in layouts) |
| | |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>()); |
| | | } |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | foreach (var layout in layouts) |
| | | { |
| New file |
| | |
| | | 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(); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: f604b53f3abcb5544b8ec2b68d0146aa |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | using DG.Tweening; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | |
| | | public class BattleField |
| | |
| | | { |
| | | 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; |
| | |
| | | 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>(); |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | using LitJson; |
| | | using UnityEngine; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | // 【主线战斗流程】 |
| | | // 发送 B413 (ReqType 为 2 或 3) |
| | |
| | | { |
| | | 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); |
| | | } |
| | | } |
| | |
| | | |
| | | 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) |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 5a91908ec288e6544943af9b9bdbbf03 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | using LitJson; |
| | | using System; |
| | | using System.Linq; |
| | | using Cysharp.Threading.Tasks; |
| | | using ProjSG.Resource; |
| | | |
| | | public class BattleManager : GameSystemManager<BattleManager> |
| | | { |
| | |
| | | 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)) |
| | | { |
| | |
| | | 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) |
| | |
| | | using System; |
| | | using UnityEngine; |
| | | using Spine.Unity; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class BattleObjectFactory |
| | | { |
| | |
| | | } |
| | | |
| | | // ===== 直接加载资源(非预加载的资源不走缓存系统)===== |
| | | #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) |
| | | { |
| | |
| | | } |
| | | // ============================================== |
| | | |
| | | #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); |
| | |
| | | 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) |
| | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | using UnityEngine; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | /// <summary> |
| | | /// 战斗音效管理器 |
| | |
| | | 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> |
| | | /// 获取可用的音频源 |
| | |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class BossHeadCell : MonoBehaviour |
| | | { |
| | |
| | | // TODO YYL |
| | | // imgFrame跟imgDecoration等幻境阁完成之后再来做 |
| | | } |
| | | |
| | | 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跟imgDecoration等幻境阁完成之后再来做 |
| | | } |
| | | |
| | | public void SetDefault() |
| | | { |
| | |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | using DG.Tweening; |
| | | using UnityEngine.UI; |
| | | using UnityEngine; |
| | |
| | | }); |
| | | 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(); |
| | | // 保证开始时所有图片为可见(alpha=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、imgIcon、imgSkillName 同步淡出) |
| | | 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() |
| | | { |
| | |
| | | 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); |
| | | |
| | | } |
| | | } |
| | |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class BattleDetailHeroInfoItem : MonoBehaviour |
| | | { |
| | |
| | | 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) |
| | |
| | | using LitJson; |
| | | using System.IO; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | |
| | | public class DebugUtility : Singleton<DebugUtility> |
| | |
| | | } |
| | | } |
| | | |
| | | 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 |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | |
| | | //二次处理放大效果 |
| | | await UniTask.Delay(100); |
| | | if (this == null) return; // destroyed during await |
| | | if (effectID == 0) |
| | | { |
| | | uieffect.Stop(); |
| | |
| | | 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)
|
| | | {
|
| | |
| | | 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);
|
| | |
| | | |
| | | 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); |
| | |
| | | |
| | | |
| | | await UniTask.Delay((int)(delay*1000*0.6)); |
| | | if (this == null) return; // destroyed during await |
| | | |
| | | if (quality >= 10) |
| | | { |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | }; |
| | | hurtValues[i].text = BattleUtility.DisplayDamageNum(dmg); |
| | | await UniTask.Delay(atkValueShowCD); |
| | | if (this == null) return; // destroyed during await |
| | | } |
| | | else |
| | | { |
| | |
| | | config = FuncConfigConfig.Get("FamilyBillboardSet");
|
| | | rankShowMaxCnt = int.Parse(config.Numerical1);
|
| | | pageCnt = int.Parse(config.Numerical2);
|
| | | queryPointNum = int.Parse(config.Numerical3);
|
| | | }
|
| | |
|
| | |
|
| | |
| | | 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); |
| | |
| | | } |
| | | |
| | | await UniTask.Delay(resultState == ResultState.singleStart ? 800 : 1500); |
| | | if (this == null) return; // destroyed during await |
| | | |
| | | resultState = ResultState.Lihui; |
| | | try |
| | |
| | | |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | using Spine.Unity; |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | |
| | | 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; |
| | | } |
| | | |
| | | //立绘特殊处理,没有spine动画的改用图片 |
| | | 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 的Pivot 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("未配置spine"); |
| | | 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; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true); |
| | | foreach (var layout in layouts) |
| | |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>()); |
| | | } |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | foreach (var layout in layouts) |
| | | { |
| | |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | //羁绊中的武将 |
| | | public class HeroConnectionHeadCell : MonoBehaviour |
| | |
| | | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | { |
| | | //延迟0.5秒发包 |
| | | await UniTask.Delay(500); |
| | | if (this == null) return; // destroyed during await |
| | | var hero = HeroManager.Instance.GetHero(HeroUIManager.Instance.selectWashHeroGUID); |
| | | if (hero == null) |
| | | { |
| | |
| | | using UnityEngine.UI; |
| | | using UnityEngine.Events; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class HeroHeadBaseCell : MonoBehaviour |
| | | { |
| | |
| | | |
| | | } |
| | | |
| | | // 武将小头像 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() |
| | | { |
| | |
| | | using UnityEngine.UI; |
| | | using UnityEngine.Events; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class HeroHeadBaseNoTrainCell : MonoBehaviour |
| | | { |
| | |
| | | |
| | | } |
| | | |
| | | 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() |
| | | { |
| | |
| | | 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) |
| | | { |
| | |
| | | connetionForm.Display(fetterID); |
| | | //显示1.5秒后关闭 |
| | | await UniTask.Delay(1500, cancellationToken: token); |
| | | if (this == null) return; // destroyed during await |
| | | connetionForm.SetActive(false); |
| | | } |
| | | |
| | |
| | | { |
| | | //延迟0.5秒显示 |
| | | await UniTask.Delay(TimeSpan.FromSeconds(HeroUIManager.clickFlyPosTime)); |
| | | if (this == null) return; // destroyed during await |
| | | objForfly.SetActive(true); |
| | | } |
| | | } |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(bg); |
| | | // 刷新所有Layout组件 |
| | | await UniTask.Delay(100); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(bg); |
| | | } |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true); |
| | | foreach (var layout in layouts) |
| | |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>()); |
| | | } |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | foreach (var layout in layouts) |
| | | { |
| | |
| | | |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | using Spine.Unity; |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | |
| | | 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 的Pivot 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("未配置spine"); |
| | | 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) |
| | |
| | | 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); |
| | |
| | | { |
| | | int delayTime = Math.Max(1, (int)(waitTime * 1000)); |
| | | await UniTask.Delay(delayTime); |
| | | if (this == null) return; // destroyed during await |
| | | StartLeaderMove(isBack); |
| | | } |
| | | |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(leaderWord.GetComponent<RectTransform>()); |
| | | } |
| | | } |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | foreach (var word in wordArr) |
| | | { |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(word.GetComponent<RectTransform>()); |
| | |
| | | async UniTask UpdatePos() |
| | | { |
| | | await UniTask.DelayFrame(3); |
| | | if (this == null) return; // destroyed during await |
| | | // 限制在屏幕范围内 |
| | | Vector3[] corners = new Vector3[4]; |
| | | rectTransform.GetWorldCorners(corners); |
| | |
| | | async UniTask ForceRefreshLayout()
|
| | | {
|
| | | await UniTask.DelayFrame(2);
|
| | | if (this == null) return; // destroyed during await
|
| | | // 刷新所有Layout组件
|
| | | var layouts = bg.GetComponentsInChildren<LayoutGroup>(true);
|
| | | foreach (var layout in layouts)
|
| | |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>());
|
| | | }
|
| | | await UniTask.DelayFrame(2);
|
| | | if (this == null) return; // destroyed during await
|
| | | // 刷新所有Layout组件
|
| | | foreach (var layout in layouts)
|
| | | {
|
| | |
| | | using UnityEngine; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class LineupRecommendItem : MonoBehaviour |
| | | { |
| | |
| | | |
| | | } |
| | | |
| | | 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>(); |
| | | } |
| | | }); |
| | | |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using System.IO; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class LoginWin : UIBase, ICanvasRaycastFilter |
| | | { |
| | |
| | | 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) |
| | | { |
| | | } |
| | |
| | | while (UIManager.Instance.IsOpened<EquipExchangeWin>())
|
| | | {
|
| | | await UniTask.Yield();
|
| | | if (this == null) return; // destroyed during await
|
| | | }
|
| | |
|
| | |
|
| | |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | //主界面卡牌 |
| | | public class HeroFightingCardCell : MonoBehaviour |
| | |
| | | |
| | | } |
| | | |
| | | 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() |
| | | { |
| | |
| | | async UniTask DelayPlayMusic() |
| | | { |
| | | await UniTask.Delay(1200); |
| | | if (this == null) return; // destroyed during await |
| | | if (!SoundPlayer.Instance.IsPlayBackGroundMuisic()) |
| | | SoundPlayer.Instance.PlayBackGroundMusic(38); |
| | | } |
| | |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using System.Text.RegularExpressions; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | using UnityEngine; |
| | | public class ImgAnalysis : TRichAnalysis<ImgAnalysis> |
| | |
| | | } |
| | | } |
| | | |
| | | 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) |
| | |
| | | using System; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using Cysharp.Threading.Tasks; |
| | | public class RichText : Text, IPointerClickHandler |
| | | { |
| | | /// <summary> |
| | |
| | | } |
| | | } |
| | | |
| | | 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(); |
| | |
| | | async UniTask DelayDisplay()
|
| | | {
|
| | | await UniTask.Delay((tryGuideCount + 1) * 100);
|
| | | if (this == null) return; // destroyed during await
|
| | | tryGuideCount++;
|
| | | Display();
|
| | | }
|
| | |
| | | async UniTask Co_FunctionUnLockDelay()
|
| | | {
|
| | | await UniTask.Delay(1300);
|
| | | if (this == null) return; // destroyed during await
|
| | |
|
| | | m_ContainerFunctionBg.SetActive(false);
|
| | | m_FunctionName.SetActive(false);
|
| | |
| | | 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;
|
| | |
| | | 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) |
| | | { |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true); |
| | | foreach (var layout in layouts) |
| | |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>()); |
| | | } |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | foreach (var layout in layouts) |
| | | { |
| | |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class OtherHeroFightingCardItem : MonoBehaviour |
| | | { |
| | |
| | | |
| | | 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) |
| | | { |
| | | // 内网未配置时 |
| | |
| | | async UniTask ForceRefreshLayout() |
| | | { |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | var layouts = allAttrScroll.GetComponentsInChildren<LayoutGroup>(true); |
| | | foreach (var layout in layouts) |
| | |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(layout.GetComponent<RectTransform>()); |
| | | } |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | // 刷新所有Layout组件 |
| | | foreach (var layout in layouts) |
| | | { |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public partial class PhantasmPavilionManager : GameSystemManager<PhantasmPavilionManager> |
| | | { |
| | |
| | | |
| | | } |
| | | |
| | | 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(); |
| | |
| | | using UnityEngine; |
| | | using UnityEngine.UI; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class PhantasmPavilionModelItem : MonoBehaviour |
| | | { |
| | |
| | | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | { |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(bg); |
| | | await UniTask.DelayFrame(2); |
| | | if (this == null) return; // destroyed during await |
| | | LayoutRebuilder.ForceRebuildLayoutImmediate(bg); |
| | | } |
| | | } |
| | |
| | | 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) |
| | |
| | | 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 |
| | | } |
| | | } |
| | | |
| | |
| | | public async UniTask PlayUIAudioDelay(int _audioId) |
| | | { |
| | | await UniTask.Delay(1); |
| | | if (this == null) return; // destroyed during await |
| | | PlayUIAudio(_audioId); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | 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) |
| | |
| | | using System; |
| | | using UnityEngine; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | public class TianziBillboradBossHead : MonoBehaviour |
| | | { |
| | |
| | | } |
| | | } |
| | | |
| | | 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) |
| | |
| | | using System; |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using Cysharp.Threading.Tasks; |
| | | using UnityEngine; |
| | | public class ScrollTip |
| | | { |
| | |
| | | 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)) |
| | |
| | | { |
| | | OnTipReceiveEvent(); |
| | | await UniTask.Delay(100); |
| | | if (this == null) return; // destroyed during await |
| | | } |
| | | } |
| | | finally |
| | |
| | | { |
| | | //延迟x帧后可点击,防止点击过快立即关闭了 |
| | | await UniTask.Delay(200); |
| | | if (this == null) return; // destroyed during await |
| | | btnClickEmptyClose.enabled = true; |
| | | } |
| | | } |
| | |
| | | protected async void ExecuteNextFrame(Action _action) |
| | | { |
| | | await UniTask.DelayFrame(1); |
| | | if (this == null) return; // destroyed during await |
| | | _action?.Invoke(); |
| | | } |
| | | |
| | |
| | | public async UniTask DelayCloseWindow(int delayTime = 30) |
| | | { |
| | | await UniTask.Delay(delayTime); |
| | | if (this == null) return; // destroyed during await |
| | | CloseWindow(); |
| | | } |
| | | |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 3557e951d4d91304da2928693eb783e1 |
| | | folderAsset: yes |
| | | DefaultImporter: |
| | | externalObjects: {} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | { |
| | | "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 |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: 3b1d5c2a5a8a8ac4d90faec3a9bbe7e5 |
| | | AssemblyDefinitionImporter: |
| | | externalObjects: {} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: d6066e660ee423e4f95f22d8d1276a36 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // 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")); |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: e835caf82b8e5cc49a15766510c3d0cb |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| New file |
| | |
| | | // ============================================================================ |
| | | // YooAssetServiceTests.cs — YooAssetService 核心逻辑单元测试 |
| | | // 覆盖:初始化状态机、LoadAssetAsync null/invalid 处理、ReleaseHandle 幂等性 |
| | | // ============================================================================ |
| | | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | fileFormatVersion: 2 |
| | | guid: f0b053bf25c64a34c972bbf4796e2923 |
| | | MonoImporter: |
| | | externalObjects: {} |
| | | serializedVersion: 2 |
| | | defaultReferences: [] |
| | | executionOrder: 0 |
| | | icon: {instanceID: 0} |
| | | userData: |
| | | assetBundleName: |
| | | assetBundleVariant: |
| | |
| | | using UnityEngine.UI; |
| | | using UnityEngine.Events; |
| | | using System; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | |
| | | public static class ComponentExtersion |
| | |
| | | _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) |
| | | { |
| | |
| | | |
| | | 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; |
| | | } |
| | | |
| | | //通过图片名加载, 如物品表 技能表等,节省在Icon表做多余配置 |
| | |
| | | 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) |
| | |
| | | _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) |
| | |
| | | _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) |
| | | { |
| | |
| | | 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"); |
| | | } |
| | | |
| | | } |
| | |
| | | using 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() |
| | |
| | | |
| | | 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); |
| | | } |
| | | |
| | |
| | | |
| | | 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) |
| | |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | using Cysharp.Threading.Tasks; |
| | | using ProjSG.Resource; |
| | | |
| | | public class ShaderUtility |
| | | { |
| | |
| | | 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(); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | 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; |