第9章 内存优化 发表于 2026-01-03 更新于 2026-01-03
第9章 内存优化 AssetBundle内存模型 AssetBundle内存结构 AssetBundle在内存中的结构是理解内存优化的基础。一个AssetBundle在加载到内存后会占用多个内存区域:
Bundle数据区 :存储原始的压缩数据
解压缓冲区 :存储解压后的数据
对象引用区 :存储对Asset对象的引用
类型信息区 :存储类型定义和元数据
AssetBundle内存分析工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class AssetBundleMemoryAnalyzer : MonoBehaviour { [System.Serializable ] public class MemorySnapshot { public string bundleName; public long bundleSize; public long loadedSize; public int objectCount; public List<AssetMemoryInfo> assets; public DateTime timestamp; } [System.Serializable ] public class AssetMemoryInfo { public string assetName; public string assetType; public long size; public int referenceCount; public bool isLoaded; } private Dictionary<string , MemorySnapshot> memorySnapshots = new Dictionary<string , MemorySnapshot>(); private List<string > loadedBundles = new List<string >(); public async System.Threading.Tasks.Task<MemorySnapshot> GetBundleMemorySnapshot (string bundleAddress ) { var snapshot = new MemorySnapshot { bundleName = bundleAddress, timestamp = DateTime.Now, assets = new List<AssetMemoryInfo>() }; try { var handle = Addressables.LoadAssetAsync<GameObject>(bundleAddress); await handle.Task; if (handle.Status == AsyncOperationStatus.Succeeded) { var loadedAsset = handle.Result; snapshot.loadedSize = EstimateAssetSize(loadedAsset); snapshot.objectCount = CountObjectsInAsset(loadedAsset); Addressables.Release(handle); } } catch (Exception e) { Debug.LogError($"获取内存快照失败: {e.Message} " ); } return snapshot; } private long EstimateAssetSize (UnityEngine.Object asset ) { if (asset is GameObject go) { long size = 0 ; var components = go.GetComponents<Component>(); size += components.Length * 1024 ; size += CountChildObjects(go.transform) * 512 ; return size; } return 1024 * 1024 ; } private int CountObjectsInAsset (UnityEngine.Object asset ) { if (asset is GameObject go) { return 1 + CountChildObjects(go.transform); } return 1 ; } private int CountChildObjects (Transform parent ) { int count = 0 ; foreach (Transform child in parent) { count++; count += CountChildObjects(child); } return count; } public void RecordMemoryUsage (string bundleAddress, long size ) { var snapshot = new MemorySnapshot { bundleName = bundleAddress, bundleSize = size, timestamp = DateTime.Now, assets = new List<AssetMemoryInfo>() }; memorySnapshots[bundleAddress] = snapshot; loadedBundles.Add(bundleAddress); Debug.Log($"记录内存使用: {bundleAddress} , 大小: {FormatFileSize(size)} " ); } public string GetMemoryUsageStats () { long totalSize = 0 ; foreach (var snapshot in memorySnapshots.Values) { totalSize += snapshot.bundleSize; } return $"已加载Bundle数: {loadedBundles.Count} , 总内存使用: {FormatFileSize(totalSize)} " ; } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public void ClearMemorySnapshots () { memorySnapshots.Clear(); loadedBundles.Clear(); } }
内存分配模式分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 using System;using System.Collections.Generic;using UnityEngine;public class MemoryAllocationAnalyzer { [System.Serializable ] public class AllocationPattern { public string assetType; public int allocationCount; public long totalAllocated; public long peakUsage; public float averageSize; public DateTime firstAllocation; public DateTime lastAllocation; } private Dictionary<string , AllocationPattern> allocationPatterns = new Dictionary<string , AllocationPattern>(); private List<long > memoryUsageHistory = new List<long >(); private long peakMemoryUsage = 0 ; public void RecordAllocation (string assetType, long size ) { if (!allocationPatterns.ContainsKey(assetType)) { allocationPatterns[assetType] = new AllocationPattern { assetType = assetType, firstAllocation = DateTime.Now }; } var pattern = allocationPatterns[assetType]; pattern.allocationCount++; pattern.totalAllocated += size; pattern.averageSize = (float )pattern.totalAllocated / pattern.allocationCount; pattern.lastAllocation = DateTime.Now; if (size > pattern.peakUsage) { pattern.peakUsage = size; } memoryUsageHistory.Add(size); if (size > peakMemoryUsage) { peakMemoryUsage = size; } Debug.Log($"内存分配记录: {assetType} , 大小: {FormatFileSize(size)} , " + $"类型总计: {pattern.allocationCount} 次, {FormatFileSize(pattern.totalAllocated)} " ); } public string GetAllocationAnalysis () { var analysis = new System.Text.StringBuilder(); analysis.AppendLine("=== 内存分配模式分析 ===" ); foreach (var pattern in allocationPatterns.Values) { analysis.AppendLine($"{pattern.assetType} :" ); analysis.AppendLine($" 分配次数: {pattern.allocationCount} " ); analysis.AppendLine($" 总分配量: {FormatFileSize(pattern.totalAllocated)} " ); analysis.AppendLine($" 平均大小: {FormatFileSize((long )pattern.averageSize)} " ); analysis.AppendLine($" 峰值使用: {FormatFileSize(pattern.peakUsage)} " ); analysis.AppendLine($" 分配时间: {pattern.firstAllocation:HH:mm:ss} - {pattern.lastAllocation:HH:mm:ss} " ); analysis.AppendLine(); } analysis.AppendLine($"总体峰值内存使用: {FormatFileSize(peakMemoryUsage)} " ); analysis.AppendLine($"总分配记录数: {memoryUsageHistory.Count} " ); return analysis.ToString(); } public List<string > GetMemoryOptimizationSuggestions () { var suggestions = new List<string >(); foreach (var pattern in allocationPatterns.Values) { if (pattern.averageSize > 10 * 1024 * 1024 ) { suggestions.Add($"大对象类型 {pattern.assetType} 平均大小: {FormatFileSize((long )pattern.averageSize)} , " + $"考虑资源分块或流式加载" ); } if (pattern.allocationCount > 100 && pattern.assetType == "Texture" ) { suggestions.Add($"纹理类型 {pattern.assetType} 分配频繁 ({pattern.allocationCount} 次), " + $"考虑纹理图集或对象池" ); } } long totalAllocated = 0 ; foreach (var pattern in allocationPatterns.Values) { totalAllocated += pattern.totalAllocated; } if (totalAllocated > 100 * 1024 * 1024 ) { suggestions.Add($"总内存分配量过大: {FormatFileSize(totalAllocated)} , " + $"考虑资源卸载策略和缓存管理" ); } return suggestions; } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public void ResetAnalysis () { allocationPatterns.Clear(); memoryUsageHistory.Clear(); peakMemoryUsage = 0 ; } }
Shared Dependencies处理 共享依赖识别与管理 在Addressables系统中,多个资源可能共享相同的依赖项(如纹理、材质、音频等)。正确处理共享依赖是内存优化的关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;public class SharedDependencyManager { [System.Serializable ] public class SharedDependencyInfo { public string dependencyAddress; public List<string > dependents; public int referenceCount; public long size; public bool isShared; public DateTime lastAccessed; } private Dictionary<string , SharedDependencyInfo> sharedDependencies = new Dictionary<string , SharedDependencyInfo>(); private Dictionary<string , List<string >> dependentMap = new Dictionary<string , List<string >>(); public void AnalyzeDependencies (string [] assetAddresses ) { foreach (string assetAddress in assetAddresses) { AnalyzeSingleAssetDependencies(assetAddress); } IdentifySharedDependencies(); Debug.Log($"依赖分析完成,发现 {sharedDependencies.Count} 个共享依赖" ); } private void AnalyzeSingleAssetDependencies (string assetAddress ) { var dependencies = GetAssetDependencies(assetAddress); foreach (string dependency in dependencies) { if (!sharedDependencies.ContainsKey(dependency)) { sharedDependencies[dependency] = new SharedDependencyInfo { dependencyAddress = dependency, dependents = new List<string >(), referenceCount = 0 }; } var depInfo = sharedDependencies[dependency]; if (!depInfo.dependents.Contains(assetAddress)) { depInfo.dependents.Add(assetAddress); depInfo.referenceCount++; } if (!dependentMap.ContainsKey(assetAddress)) { dependentMap[assetAddress] = new List<string >(); } if (!dependentMap[assetAddress].Contains(dependency)) { dependentMap[assetAddress].Add(dependency); } } } private List<string > GetAssetDependencies (string assetAddress ) { var dependencies = new List<string >(); if (assetAddress.Contains("Character" )) { dependencies.Add("Assets/Textures/CharacterTexture.png" ); dependencies.Add("Assets/Materials/CharacterMaterial.mat" ); } else if (assetAddress.Contains("Weapon" )) { dependencies.Add("Assets/Textures/WeaponTexture.png" ); dependencies.Add("Assets/Materials/WeaponMaterial.mat" ); } return dependencies; } private void IdentifySharedDependencies () { foreach (var pair in sharedDependencies) { pair.Value.isShared = pair.Value.referenceCount > 1 ; } } public SharedDependencyInfo GetSharedDependencyInfo (string dependencyAddress ) { if (sharedDependencies.ContainsKey(dependencyAddress)) { return sharedDependencies[dependencyAddress]; } return null ; } public List <string > GetAssetDependenciesList (string assetAddress ) { if (dependentMap.ContainsKey(assetAddress)) { return dependentMap[assetAddress]; } return new List<string >(); } public List<string > DetectSharedDependencyIssues () { var issues = new List<string >(); foreach (var pair in sharedDependencies) { var depInfo = pair.Value; if (depInfo.referenceCount > 50 ) { issues.Add($"资源 {depInfo.dependencyAddress} 被 {depInfo.referenceCount} 个资源共享," + $"可能导致内存占用过大" ); } if (depInfo.size > 10 * 1024 * 1024 && depInfo.isShared) { issues.Add($"大尺寸共享资源 {depInfo.dependencyAddress} 大小: {FormatFileSize(depInfo.size)} , " + $"被 {depInfo.referenceCount} 个资源共享" ); } } return issues; } public void OptimizeSharedDependencies () { var issues = DetectSharedDependencyIssues(); foreach (var issue in issues) { Debug.LogWarning($"共享依赖问题: {issue} " ); } var suggestions = GetOptimizationSuggestions(); foreach (var suggestion in suggestions) { Debug.Log($"优化建议: {suggestion} " ); } } private List<string > GetOptimizationSuggestions () { var suggestions = new List<string >(); foreach (var pair in sharedDependencies) { var depInfo = pair.Value; if (depInfo.referenceCount > 50 ) { suggestions.Add($"考虑将 {depInfo.dependencyAddress} 分解为多个专用资源以减少共享" ); } else if (depInfo.size > 10 * 1024 * 1024 && depInfo.isShared) { suggestions.Add($"考虑对大尺寸共享资源 {depInfo.dependencyAddress} 进行压缩或优化" ); } } return suggestions; } public string GetSharedDependencyStats () { int totalDependencies = sharedDependencies.Count; int sharedCount = 0 ; int totalReferences = 0 ; long totalSize = 0 ; foreach (var depInfo in sharedDependencies.Values) { if (depInfo.isShared) sharedCount++; totalReferences += depInfo.referenceCount; totalSize += depInfo.size; } float avgReferences = totalDependencies > 0 ? (float )totalReferences / totalDependencies : 0 ; var stats = new System.Text.StringBuilder(); stats.AppendLine("=== 共享依赖统计 ===" ); stats.AppendLine($"总依赖数: {totalDependencies} " ); stats.AppendLine($"共享依赖数: {sharedCount} " ); stats.AppendLine($"总引用数: {totalReferences} " ); stats.AppendLine($"平均引用数: {avgReferences:F2} " ); stats.AppendLine($"总大小: {FormatFileSize(totalSize)} " ); return stats.ToString(); } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public void ClearAnalysisData () { sharedDependencies.Clear(); dependentMap.Clear(); } }
共享依赖缓存策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class SharedDependencyCache { [System.Serializable ] public class CachedAsset { public UnityEngine.Object asset; public AsyncOperationHandle handle; public int referenceCount; public DateTime lastAccessed; public bool isPinned; } private Dictionary<string , CachedAsset> assetCache = new Dictionary<string , CachedAsset>(); private Queue<string > accessOrder = new Queue<string >(); private int maxCacheSize = 100 ; private long maxCacheMemory = 50 * 1024 * 1024 ; private long currentCacheMemory = 0 ; public SharedDependencyCache (int maxCacheSize = 100 , long maxMemory = 50 * 1024 * 1024 ) { this .maxCacheSize = maxCacheSize; this .maxCacheMemory = maxMemory; } public async System.Threading.Tasks.Task<UnityEngine.Object> GetSharedAsset(string address) { if (assetCache.ContainsKey(address)) { var cachedAsset = assetCache[address]; cachedAsset.referenceCount++; cachedAsset.lastAccessed = DateTime.Now; accessOrder.Enqueue(address); Debug.Log($"从缓存获取共享资源: {address} , 引用计数: {cachedAsset.referenceCount} " ); return cachedAsset.asset; } var handle = Addressables.LoadAssetAsync<UnityEngine.Object>(address); var asset = await handle.Task; if (asset != null ) { if (assetCache.Count >= maxCacheSize || currentCacheMemory >= maxCacheMemory) { CleanupCache(); } var cachedAsset = new CachedAsset { asset = asset, handle = handle, referenceCount = 1 , lastAccessed = DateTime.Now, isPinned = false }; assetCache[address] = cachedAsset; accessOrder.Enqueue(address); long assetSize = EstimateAssetSize(asset); currentCacheMemory += assetSize; Debug.Log($"加载并缓存共享资源: {address} , 内存使用: {FormatFileSize(assetSize)} " ); } return asset; } public void ReleaseSharedAsset (string address ) { if (assetCache.ContainsKey(address)) { var cachedAsset = assetCache[address]; cachedAsset.referenceCount--; Debug.Log($"释放共享资源引用: {address} , 剩余引用: {cachedAsset.referenceCount} " ); if (cachedAsset.referenceCount <= 0 && !cachedAsset.isPinned) { RemoveFromCache(address); } } } public void PinAsset (string address ) { if (assetCache.ContainsKey(address)) { assetCache[address].isPinned = true ; Debug.Log($"固定缓存资源: {address} " ); } } public void UnpinAsset (string address ) { if (assetCache.ContainsKey(address)) { assetCache[address].isPinned = false ; Debug.Log($"取消固定缓存资源: {address} " ); } } private void RemoveFromCache (string address ) { if (assetCache.ContainsKey(address)) { var cachedAsset = assetCache[address]; Addressables.Release(cachedAsset.handle); long assetSize = EstimateAssetSize(cachedAsset.asset); currentCacheMemory -= assetSize; assetCache.Remove(address); Debug.Log($"从缓存移除资源: {address} , 释放内存: {FormatFileSize(assetSize)} " ); } } private void CleanupCache () { while (assetCache.Count > maxCacheSize * 0.8 || currentCacheMemory > maxCacheMemory * 0.8 ) { if (accessOrder.Count == 0 ) break ; string oldestAddress = accessOrder.Dequeue(); if (assetCache.ContainsKey(oldestAddress)) { var cachedAsset = assetCache[oldestAddress]; if (!cachedAsset.isPinned && cachedAsset.referenceCount <= 0 ) { RemoveFromCache(oldestAddress); } else { accessOrder.Enqueue(oldestAddress); } } } Debug.Log($"缓存清理完成,当前缓存数: {assetCache.Count} , 内存使用: {FormatFileSize(currentCacheMemory)} " ); } private long EstimateAssetSize (UnityEngine.Object asset ) { if (asset is Texture2D texture) { return texture.width * texture.height * 4 ; } else if (asset is AudioClip audio) { return (long )(audio.length * audio.frequency * audio.channels * 2 ); } else if (asset is GameObject go) { var components = go.GetComponents<Component>(); return components.Length * 1024 ; } return 1024 * 1024 ; } public string GetCacheStats () { int pinnedCount = 0 ; int totalRefCount = 0 ; foreach (var pair in assetCache) { if (pair.Value.isPinned) pinnedCount++; totalRefCount += pair.Value.referenceCount; } return $"缓存资源数: {assetCache.Count} , 固定资源: {pinnedCount} , " + $"总引用数: {totalRefCount} , 内存使用: {FormatFileSize(currentCacheMemory)} / {FormatFileSize(maxCacheMemory)} " ; } public void ClearCache () { foreach (var pair in assetCache) { Addressables.Release(pair.Value.handle); } assetCache.Clear(); accessOrder.Clear(); currentCacheMemory = 0 ; Debug.Log("共享依赖缓存已清理" ); } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } }
重复资源检测与消除 重复资源检测器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 using System;using System.Collections.Generic;using System.Security.Cryptography;using UnityEngine;using UnityEngine.AddressableAssets;public class DuplicateResourceDetector { [System.Serializable ] public class ResourceHash { public string assetAddress; public string hash; public long size; public string assetType; public DateTime lastModified; } [System.Serializable ] public class DuplicateGroup { public string representativeAsset; public List<string > duplicates; public string hash; public long totalWastedSpace; } private List<ResourceHash> resourceHashes = new List<ResourceHash>(); private Dictionary<string , string > hashToAssetMap = new Dictionary<string , string >(); private Dictionary<string , List<string >> duplicateGroups = new Dictionary<string , List<string >>(); public void ScanForDuplicates (string [] assetAddresses ) { Debug.Log($"开始扫描 {assetAddresses.Length} 个资源以检测重复项" ); resourceHashes.Clear(); hashToAssetMap.Clear(); duplicateGroups.Clear(); foreach (string address in assetAddresses) { var hashInfo = CalculateAssetHash(address); if (hashInfo != null ) { resourceHashes.Add(hashInfo); if (hashToAssetMap.ContainsKey(hashInfo.hash)) { string firstAsset = hashToAssetMap[hashInfo.hash]; if (!duplicateGroups.ContainsKey(firstAsset)) { duplicateGroups[firstAsset] = new List<string >(); } duplicateGroups[firstAsset].Add(address); Debug.Log($"发现重复资源: {address} 与 {firstAsset} 重复" ); } else { hashToAssetMap[hashInfo.hash] = address; } } } Debug.Log($"扫描完成,发现 {GetDuplicateCount()} 组重复资源" ); } private ResourceHash CalculateAssetHash (string assetAddress ) { try { string assetPath = GetAssetPathFromAddress(assetAddress); if (string .IsNullOrEmpty(assetPath)) return null ; byte [] fileBytes = System.IO.File.ReadAllBytes(assetPath); byte [] hashBytes = SHA256.Create().ComputeHash(fileBytes); string hash = BitConverter.ToString(hashBytes).Replace("-" , "" ).ToLower(); var fileInfo = new System.IO.FileInfo(assetPath); return new ResourceHash { assetAddress = assetAddress, hash = hash, size = fileInfo.Length, assetType = GetAssetType(assetPath), lastModified = fileInfo.LastWriteTime }; } catch (Exception e) { Debug.LogError($"计算资源哈希失败 {assetAddress} : {e.Message} " ); return null ; } } private string GetAssetPathFromAddress (string address ) { if (address.StartsWith("Assets/" )) { return System.IO.Path.Combine(UnityEngine.Application.dataPath, address.Substring("Assets/" .Length)); } string guid = UnityEditor.AssetDatabase.AssetPathToGUID(address); if (!string .IsNullOrEmpty(guid)) { return UnityEditor.AssetDatabase.GUIDToAssetPath(guid); } return "" ; } private string GetAssetType (string assetPath ) { string extension = System.IO.Path.GetExtension(assetPath).ToLower(); switch (extension) { case ".prefab" : return "Prefab" ; case ".png" : case ".jpg" : case ".jpeg" : return "Texture" ; case ".fbx" : case ".obj" : return "Model" ; case ".wav" : case ".mp3" : case ".ogg" : return "Audio" ; case ".mat" : return "Material" ; case ".shader" : return "Shader" ; default : return "Other" ; } } public List<DuplicateGroup> GetDuplicateGroups () { var groups = new List<DuplicateGroup>(); foreach (var pair in duplicateGroups) { var hashInfo = GetResourceHashInfo(pair.Key); if (hashInfo != null ) { var group = new DuplicateGroup { representativeAsset = pair.Key, duplicates = pair.Value, hash = hashInfo.hash, totalWastedSpace = (hashInfo.size * (pair.Value.Count + 1 )) - hashInfo.size }; groups.Add(group ); } } return groups; } private ResourceHash GetResourceHashInfo (string assetAddress ) { return resourceHashes.Find(h => h.assetAddress == assetAddress); } public string GetDuplicateStats () { int duplicateGroupsCount = duplicateGroups.Count; long totalWastedSpace = 0 ; int totalDuplicates = 0 ; foreach (var group in GetDuplicateGroups ()) { totalWastedSpace += group .totalWastedSpace; totalDuplicates += group .duplicates.Count; } var stats = new System.Text.StringBuilder(); stats.AppendLine("=== 重复资源统计 ===" ); stats.AppendLine($"重复组数: {duplicateGroupsCount} " ); stats.AppendLine($"总重复资源数: {totalDuplicates} " ); stats.AppendLine($"浪费空间: {FormatFileSize(totalWastedSpace)} " ); return stats.ToString(); } public string GenerateDuplicateReport () { var report = new System.Text.StringBuilder(); report.AppendLine("=== 重复资源检测报告 ===" ); report.AppendLine(); var groups = GetDuplicateGroups(); if (groups.Count == 0 ) { report.AppendLine("未发现重复资源" ); return report.ToString(); } foreach (var group in groups) { var representativeHash = GetResourceHashInfo(group .representativeAsset); report.AppendLine($"重复组 (哈希: {group .hash.Substring(0 , 8 )} ...):" ); report.AppendLine($" 代表资源: {group .representativeAsset} " ); report.AppendLine($" 重复数量: {group .duplicates.Count} " ); report.AppendLine($" 资源大小: {FormatFileSize(representativeHash?.size ?? 0 )} " ); report.AppendLine($" 浪费空间: {FormatFileSize(group .totalWastedSpace)} " ); report.AppendLine(" 重复资源列表:" ); foreach (string duplicate in group .duplicates) { report.AppendLine($" - {duplicate} " ); } report.AppendLine(); } report.AppendLine(GetDuplicateStats()); return report.ToString(); } private int GetDuplicateCount () { int count = 0 ; foreach (var group in duplicateGroups.Values) { count += group .Count; } return count; } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public void ClearDetectionData () { resourceHashes.Clear(); hashToAssetMap.Clear(); duplicateGroups.Clear(); } }
重复资源消除策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 using System;using System.Collections.Generic;using UnityEngine;using UnityEditor;public class DuplicateResourceEliminator { private DuplicateResourceDetector detector; public DuplicateResourceEliminator (DuplicateResourceDetector detector ) { this .detector = detector; } public void ApplyEliminationStrategy () { var duplicateGroups = detector.GetDuplicateGroups(); Debug.Log($"开始应用重复资源消除策略,共 {duplicateGroups.Count} 组重复资源" ); foreach (var group in duplicateGroups) { ProcessDuplicateGroup(group ); } Debug.Log("重复资源消除策略应用完成" ); } private void ProcessDuplicateGroup (DuplicateGroup group ) { Debug.Log($"处理重复组: {group .representativeAsset} , 重复数: {group .duplicates.Count} " ); string bestAsset = FindBestAssetToKeep(group ); UpdateReferences(bestAsset, group .duplicates); } private string FindBestAssetToKeep (DuplicateGroup group ) { string bestAsset = group .representativeAsset; DateTime bestTime = DateTime.MinValue; var representativeHash = GetResourceHashInfo(group .representativeAsset); if (representativeHash != null && representativeHash.lastModified > bestTime) { bestTime = representativeHash.lastModified; bestAsset = group .representativeAsset; } foreach (string duplicate in group .duplicates) { var duplicateHash = GetResourceHashInfo(duplicate); if (duplicateHash != null && duplicateHash.lastModified > bestTime) { bestTime = duplicateHash.lastModified; bestAsset = duplicate; } } return bestAsset; } private DuplicateResourceDetector.ResourceHash GetResourceHashInfo (string assetAddress ) { return null ; } private void UpdateReferences (string bestAsset, List<string > duplicates ) { Debug.Log($"更新引用: 将 {duplicates.Count} 个重复资源的引用指向 {bestAsset} " ); foreach (string duplicate in duplicates) { FindAndReplaceReferences(duplicate, bestAsset); } } private void FindAndReplaceReferences (string oldAsset, string newAsset ) { string [] guids = AssetDatabase.FindAssets($"r:{oldAsset} " ); Debug.Log($"找到 {guids.Length} 个引用 {oldAsset} 的资源" ); foreach (string guid in guids) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); if (assetPath.EndsWith(".unity" )) { UpdateSceneReferences(assetPath, oldAsset, newAsset); } else if (assetPath.EndsWith(".prefab" )) { UpdatePrefabReferences(assetPath, oldAsset, newAsset); } } } private void UpdateSceneReferences (string scenePath, string oldAsset, string newAsset ) { Debug.Log($"更新场景引用: {scenePath} , {oldAsset} -> {newAsset} " ); } private void UpdatePrefabReferences (string prefabPath, string oldAsset, string newAsset ) { Debug.Log($"更新Prefab引用: {prefabPath} , {oldAsset} -> {newAsset} " ); } private void DeleteDuplicateAssets (List<string > duplicates, string keepAsset ) { Debug.LogWarning("删除重复资源功能已禁用,以防止意外删除重要资源" ); Debug.Log("如需删除重复资源,请确保已备份项目并确认引用已正确更新" ); } public string GenerateEliminationReport () { var report = new System.Text.StringBuilder(); report.AppendLine("=== 重复资源消除建议报告 ===" ); report.AppendLine(); var duplicateGroups = detector.GetDuplicateGroups(); if (duplicateGroups.Count == 0 ) { report.AppendLine("未发现重复资源" ); return report.ToString(); } long totalSavings = 0 ; foreach (var group in duplicateGroups) { report.AppendLine($"资源组: {group .representativeAsset} " ); report.AppendLine($" 重复数量: {group .duplicates.Count} " ); report.AppendLine($" 可节省空间: {DuplicateResourceDetector.FormatFileSize(group .totalWastedSpace)} " ); report.AppendLine($" 建议保留: {FindBestAssetToKeep(group )} " ); report.AppendLine(); totalSavings += group .totalWastedSpace; } report.AppendLine($"总计可节省空间: {DuplicateResourceDetector.FormatFileSize(totalSavings)} " ); report.AppendLine(); report.AppendLine("操作建议:" ); report.AppendLine("1. 备份项目" ); report.AppendLine("2. 运行引用更新" ); report.AppendLine("3. 验证所有引用正确更新" ); report.AppendLine("4. 删除重复资源(可选)" ); return report.ToString(); } }
Sprite Atlas在Addressables中的应用 Sprite Atlas优化策略 Sprite Atlas(精灵图集)是Unity中用于优化2D精灵渲染性能的重要工具。在Addressables系统中正确使用Sprite Atlas可以显著减少Draw Call并优化内存使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;using UnityEngine.U2D;public class SpriteAtlasOptimizer : MonoBehaviour { [System.Serializable ] public class AtlasInfo { public string atlasAddress; public int spriteCount; public long atlasSize; public float packingEfficiency; public List<string > includedSprites; public DateTime lastOptimized; } private Dictionary<string , AtlasInfo> atlasInfos = new Dictionary<string , AtlasInfo>(); private Dictionary<string , SpriteAtlas> loadedAtlases = new Dictionary<string , SpriteAtlas>(); public async System.Threading.Tasks.Task<SpriteAtlas> LoadSpriteAtlas (string atlasAddress ) { if (loadedAtlases.ContainsKey(atlasAddress)) { return loadedAtlases[atlasAddress]; } var handle = Addressables.LoadAssetAsync<SpriteAtlas>(atlasAddress); var atlas = await handle.Task; if (atlas != null ) { loadedAtlases[atlasAddress] = atlas; var atlasInfo = new AtlasInfo { atlasAddress = atlasAddress, spriteCount = atlas.spriteCount, atlasSize = EstimateAtlasSize(atlas), includedSprites = GetAtlasSprites(atlas), lastOptimized = DateTime.Now }; atlasInfos[atlasAddress] = atlasInfo; Debug.Log($"加载Sprite Atlas: {atlasAddress} , " + $"精灵数量: {atlas.spriteCount} , 大小: {FormatFileSize(atlasInfo.atlasSize)} " ); } Addressables.Release(handle); return atlas; } private long EstimateAtlasSize (SpriteAtlas atlas ) { var texture = atlas.texture; if (texture != null ) { int bytesPerPixel = 4 ; return (long )texture.width * texture.height * bytesPerPixel; } return 1024 * 1024 ; } private List<string > GetAtlasSprites (SpriteAtlas atlas ) { var sprites = new List<string >(); var tempSprites = new Sprite[atlas.spriteCount]; atlas.GetSprites(tempSprites); foreach (var sprite in tempSprites) { if (sprite != null ) { sprites.Add(sprite.name); } } return sprites; } public Sprite GetSpriteFromAtlas (string atlasAddress, string spriteName ) { if (loadedAtlases.ContainsKey(atlasAddress)) { var atlas = loadedAtlases[atlasAddress]; return atlas.GetSprite(spriteName); } Debug.LogWarning($"Atlas未加载: {atlasAddress} " ); return null ; } public void OptimizeAtlasConfiguration (SpriteAtlas atlas ) { if (atlas == null ) return ; Debug.Log($"优化Sprite Atlas配置: {atlas.name} " ); SetOptimalAtlasSize(atlas); OptimizeTextureCompression(atlas); ConfigurePackingOptions(atlas); } private void SetOptimalAtlasSize (SpriteAtlas atlas ) { int spriteCount = atlas.spriteCount; if (spriteCount < 10 ) { } else if (spriteCount < 50 ) { } else { } } private void OptimizeTextureCompression (SpriteAtlas atlas ) { Debug.Log($"优化纹理压缩: {atlas.name} " ); } private void ConfigurePackingOptions (SpriteAtlas atlas ) { Debug.Log($"配置打包选项: {atlas.name} " ); } public string AnalyzeAtlasEfficiency () { var analysis = new System.Text.StringBuilder(); analysis.AppendLine("=== Sprite Atlas效率分析 ===" ); long totalAtlasSize = 0 ; int totalSprites = 0 ; int totalAtlases = atlasInfos.Count; foreach (var atlasInfo in atlasInfos.Values) { totalAtlasSize += atlasInfo.atlasSize; totalSprites += atlasInfo.spriteCount; analysis.AppendLine($"{atlasInfo.atlasAddress} :" ); analysis.AppendLine($" 精灵数量: {atlasInfo.spriteCount} " ); analysis.AppendLine($" 图集大小: {FormatFileSize(atlasInfo.atlasSize)} " ); analysis.AppendLine($" 包含精灵: {string .Join(", " , atlasInfo.includedSprites.GetRange(0 , Math.Min(5 , atlasInfo.includedSprites.Count)))} ..." ); analysis.AppendLine(); } if (totalAtlases > 0 ) { float avgSpritesPerAtlas = (float )totalSprites / totalAtlases; analysis.AppendLine($"总计: {totalAtlases} 个图集, {totalSprites} 个精灵" ); analysis.AppendLine($"平均每图集精灵数: {avgSpritesPerAtlas:F2} " ); analysis.AppendLine($"总内存使用: {FormatFileSize(totalAtlasSize)} " ); } return analysis.ToString(); } public List<string > GenerateAtlasOptimizationSuggestions () { var suggestions = new List<string >(); foreach (var atlasInfo in atlasInfos.Values) { if (atlasInfo.spriteCount < 5 ) { suggestions.Add($"图集 {atlasInfo.atlasAddress} 精灵数量过少 ({atlasInfo.spriteCount} 个)," + $"考虑合并到其他图集以提高效率" ); } if (atlasInfo.atlasSize > 32 * 1024 * 1024 ) { suggestions.Add($"图集 {atlasInfo.atlasAddress} 过大 ({FormatFileSize(atlasInfo.atlasSize)} )," + $"考虑拆分为多个较小的图集" ); } } var currentlyUsedAtlases = new HashSet<string >(); return suggestions; } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public void UnloadSpriteAtlas (string atlasAddress ) { if (loadedAtlases.ContainsKey(atlasAddress)) { var atlas = loadedAtlases[atlasAddress]; loadedAtlases.Remove(atlasAddress); Debug.Log($"卸载Sprite Atlas: {atlasAddress} " ); } } public void ClearAllAtlases () { loadedAtlases.Clear(); atlasInfos.Clear(); Debug.Log("所有Sprite Atlas已清理" ); } void OnDestroy () { ClearAllAtlases(); } }
Sprite Atlas管理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;using UnityEngine.U2D;public class SpriteAtlasManager : MonoBehaviour { [System.Serializable ] public class AtlasGroup { public string groupName; public string [] atlasAddresses; public bool preloadOnStart; public int priority; } [Header("Atlas配置" ) ] public AtlasGroup[] atlasGroups; [Header("缓存设置" ) ] public int maxConcurrentLoads = 3 ; public float preloadDelay = 0.1f ; private Dictionary<string , SpriteAtlas> loadedAtlases = new Dictionary<string , SpriteAtlas>(); private Dictionary<string , AtlasGroup> groupMap = new Dictionary<string , AtlasGroup>(); private Queue<string > loadQueue = new Queue<string >(); private List<string > loadingAtlases = new List<string >(); void Start () { foreach (var group in atlasGroups) { groupMap[group .groupName] = group ; if (group .preloadOnStart) { StartCoroutine(PreloadAtlasGroup(group .groupName)); } } } private System.Collections.IEnumerator PreloadAtlasGroup (string groupName ) { if (!groupMap.ContainsKey(groupName)) yield break ; var group = groupMap[groupName]; Debug.Log($"预加载Atlas组: {groupName} , 数量: {group .atlasAddresses.Length} " ); foreach (string atlasAddress in group .atlasAddresses) { yield return StartCoroutine (LoadAtlasWithDelay(atlasAddress, preloadDelay )) ; } Debug.Log($"Atlas组预加载完成: {groupName} " ); } private System.Collections.IEnumerator LoadAtlasWithDelay (string atlasAddress, float delay ) { yield return new WaitForSeconds (delay ) ; yield return LoadAtlasAsync (atlasAddress ) ; } public System.Collections.IEnumerator LoadAtlasAsync (string atlasAddress ) { if (loadedAtlases.ContainsKey(atlasAddress)) { yield break ; } if (loadingAtlases.Contains(atlasAddress)) { yield return new WaitUntil (( ) => !loadingAtlases.Contains(atlasAddress)); yield break ; } if (loadingAtlases.Count >= maxConcurrentLoads) { loadQueue.Enqueue(atlasAddress); yield return new WaitUntil (( ) => loadingAtlases.Count < maxConcurrentLoads && loadQueue.Peek() == atlasAddress); loadQueue.Dequeue(); } loadingAtlases.Add(atlasAddress); var handle = Addressables.LoadAssetAsync<SpriteAtlas>(atlasAddress); yield return handle; if (handle.Status == AsyncOperationStatus.Succeeded) { loadedAtlases[atlasAddress] = handle.Result; Debug.Log($"Atlas加载成功: {atlasAddress} " ); } else { Debug.LogError($"Atlas加载失败: {atlasAddress} , 错误: {handle.OperationException} " ); } loadingAtlases.Remove(atlasAddress); Addressables.Release(handle); } public Sprite GetSprite (string atlasAddress, string spriteName ) { if (loadedAtlases.ContainsKey(atlasAddress)) { var atlas = loadedAtlases[atlasAddress]; return atlas.GetSprite(spriteName); } Debug.LogWarning($"Atlas未加载: {atlasAddress} " ); return null ; } public async System.Threading.Tasks.Task LoadMultipleAtlases (string [] atlasAddresses ) { var tasks = new List<System.Threading.Tasks.Task>(); foreach (string address in atlasAddresses) { var task = System.Threading.Tasks.Task.Run(async () => { await System.Threading.Tasks.Task.Delay(100 ); StartCoroutine(LoadAtlasAsync(address)); }); tasks.Add(task); } await System.Threading.Tasks.Task.WhenAll(tasks); } public void UnloadAtlas (string atlasAddress ) { if (loadedAtlases.ContainsKey(atlasAddress)) { loadedAtlases.Remove(atlasAddress); Debug.Log($"Atlas已卸载: {atlasAddress} " ); } } public void UnloadAtlasGroup (string groupName ) { if (groupMap.ContainsKey(groupName)) { var group = groupMap[groupName]; foreach (string atlasAddress in group .atlasAddresses) { UnloadAtlas(atlasAddress); } Debug.Log($"Atlas组已卸载: {groupName} " ); } } public string GetAtlasStats () { return $"已加载Atlas: {loadedAtlases.Count} , 加载中: {loadingAtlases.Count} , 队列中: {loadQueue.Count} " ; } public void ClearAllAtlases () { loadedAtlases.Clear(); loadingAtlases.Clear(); loadQueue.Clear(); Debug.Log("所有Atlas已清理" ); } void OnDestroy () { ClearAllAtlases(); } }
Shader Variant收集 Shader变体管理 Shader变体收集是Unity中优化构建大小和加载时间的重要技术。在Addressables系统中,正确管理Shader变体可以显著减少包大小和内存使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.Rendering;public class ShaderVariantCollector { [System.Serializable ] public class ShaderVariantInfo { public string shaderName; public string [] keywords; public int variantCount; public long estimatedSize; public DateTime lastCollected; public bool isUsedInBuild; } private Dictionary<string , ShaderVariantInfo> collectedVariants = new Dictionary<string , ShaderVariantInfo>(); private List<Shader> shadersToCollect = new List<Shader>(); public void CollectShaderVariants (Shader shader ) { if (shader == null ) return ; Debug.Log($"收集Shader变体: {shader.name} " ); var variantInfo = new ShaderVariantInfo { shaderName = shader.name, lastCollected = DateTime.Now, isUsedInBuild = true }; var keywords = GetShaderKeywords(shader); variantInfo.keywords = keywords.ToArray(); variantInfo.variantCount = keywords.Count; variantInfo.estimatedSize = EstimateShaderVariantSize(shader, keywords.Count); collectedVariants[shader.name] = variantInfo; Debug.Log($"收集完成: {shader.name} , 变体数: {variantInfo.variantCount} , " + $"估算大小: {FormatFileSize(variantInfo.estimatedSize)} " ); } private List<string > GetShaderKeywords (Shader shader ) { var keywords = new List<string >(); if (shader != null ) { var shaderKeywords = shader.GetShaderKeywords(); keywords.AddRange(shaderKeywords); } return keywords; } private long EstimateShaderVariantSize (Shader shader, int variantCount ) { long baseSize = 1024 * 1024 ; long perVariantSize = 100 * 1024 ; return baseSize + (variantCount * perVariantSize); } public void CollectSceneShaderVariants () { Debug.Log("开始收集场景中的Shader变体" ); var renderers = UnityEngine.Object.FindObjectsOfType<Renderer>(); foreach (var renderer in renderers) { foreach (var material in renderer.sharedMaterials) { if (material != null && material.shader != null ) { CollectShaderVariants(material.shader); } } } var graphics = UnityEngine.Object.FindObjectsOfType<UnityEngine.UI.Graphic>(); foreach (var graphic in graphics) { if (graphic.material != null && graphic.material.shader != null ) { CollectShaderVariants(graphic.material.shader); } } Debug.Log($"场景Shader变体收集完成,共收集 {collectedVariants.Count} 个Shader" ); } public async System.Threading.Tasks.Task CollectAddressableShaderVariants (string [] assetAddresses ) { Debug.Log($"开始收集Addressable资源中的Shader变体,资源数: {assetAddresses.Length} " ); foreach (string address in assetAddresses) { await CollectAssetShaderVariants(address); } Debug.Log($"Addressable Shader变体收集完成" ); } private async System.Threading.Tasks.Task CollectAssetShaderVariants (string address ) { try { var handle = Addressables.LoadAssetAsync<UnityEngine.Object>(address); var asset = await handle.Task; if (asset != null ) { if (asset is GameObject go) { CollectGameObjectShaderVariants(go); } else if (asset is Material material) { CollectMaterialShaderVariants(material); } else if (asset is UnityEngine.UI.Graphic graphic) { CollectGraphicShaderVariants(graphic); } } Addressables.Release(handle); } catch (Exception e) { Debug.LogError($"收集资源Shader变体失败 {address} : {e.Message} " ); } } private void CollectGameObjectShaderVariants (GameObject go ) { var renderers = go.GetComponentsInChildren<Renderer>(true ); foreach (var renderer in renderers) { foreach (var material in renderer.sharedMaterials) { if (material != null && material.shader != null ) { CollectShaderVariants(material.shader); } } } } private void CollectMaterialShaderVariants (Material material ) { if (material.shader != null ) { CollectShaderVariants(material.shader); } } private void CollectGraphicShaderVariants (UnityEngine.UI.Graphic graphic ) { if (graphic.material != null && graphic.material.shader != null ) { CollectShaderVariants(graphic.material.shader); } } public string GenerateShaderVariantReport () { var report = new System.Text.StringBuilder(); report.AppendLine("=== Shader变体收集报告 ===" ); report.AppendLine(); long totalSize = 0 ; int totalVariants = 0 ; foreach (var variantInfo in collectedVariants.Values) { report.AppendLine($"Shader: {variantInfo.shaderName} " ); report.AppendLine($" 变体数量: {variantInfo.variantCount} " ); report.AppendLine($" 关键词: {string .Join(", " , variantInfo.keywords)} " ); report.AppendLine($" 估算大小: {FormatFileSize(variantInfo.estimatedSize)} " ); report.AppendLine($" 收集时间: {variantInfo.lastCollected:yyyy-MM-dd HH:mm:ss} " ); report.AppendLine(); totalSize += variantInfo.estimatedSize; totalVariants += variantInfo.variantCount; } report.AppendLine("=== 总计 ===" ); report.AppendLine($"Shader数量: {collectedVariants.Count} " ); report.AppendLine($"总变体数: {totalVariants} " ); report.AppendLine($"总估算大小: {FormatFileSize(totalSize)} " ); return report.ToString(); } public List<string > GetOptimizationSuggestions () { var suggestions = new List<string >(); foreach (var variantInfo in collectedVariants.Values) { if (variantInfo.variantCount > 50 ) { suggestions.Add($"Shader {variantInfo.shaderName} 变体数量过多 ({variantInfo.variantCount} 个)," + $"考虑减少关键词或使用更简单的Shader" ); } if (variantInfo.estimatedSize > 10 * 1024 * 1024 ) { suggestions.Add($"Shader {variantInfo.shaderName} 大小过大 ({FormatFileSize(variantInfo.estimatedSize)} )," + $"考虑优化Shader代码或减少变体" ); } } var allShaders = Resources.FindObjectsOfTypeAll<Shader>(); foreach (var shader in allShaders) { if (!collectedVariants.ContainsKey(shader.name)) { } } return suggestions; } public void ExportShaderVariantList (string filePath ) { var content = new System.Text.StringBuilder(); content.AppendLine("# Shader变体列表" ); content.AppendLine(); foreach (var variantInfo in collectedVariants.Values) { content.AppendLine($"## {variantInfo.shaderName} " ); content.AppendLine($"变体数: {variantInfo.variantCount} " ); content.AppendLine($"大小: {FormatFileSize(variantInfo.estimatedSize)} " ); content.AppendLine($"关键词:" ); foreach (var keyword in variantInfo.keywords) { content.AppendLine($" - {keyword} " ); } content.AppendLine(); } System.IO.File.WriteAllText(filePath, content.ToString()); Debug.Log($"Shader变体列表已导出到: {filePath} " ); } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public void ClearCollectedData () { collectedVariants.Clear(); shadersToCollect.Clear(); } public string GetCollectionStats () { int totalShaders = collectedVariants.Count; long totalSize = 0 ; int totalVariants = 0 ; foreach (var info in collectedVariants.Values) { totalSize += info.estimatedSize; totalVariants += info.variantCount; } return $"已收集Shader: {totalShaders} 个, 总变体: {totalVariants} 个, 总大小: {FormatFileSize(totalSize)} " ; } }
Shader变体优化工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 using System;using System.Collections.Generic;using UnityEngine;using UnityEditor;using UnityEditor.Rendering;public class ShaderVariantOptimizer { public static Dictionary<string , int > AnalyzeShaderVariantUsage () { var usageCount = new Dictionary<string , int >(); var materials = Resources.FindObjectsOfTypeAll<Material>(); foreach (var material in materials) { if (material.shader != null ) { string shaderName = material.shader.name; if (usageCount.ContainsKey(shaderName)) { usageCount[shaderName]++; } else { usageCount[shaderName] = 1 ; } var keywords = material.shaderKeywords; } } return usageCount; } public static List<string > GenerateShaderOptimizationSuggestions () { var suggestions = new List<string >(); var usageCount = AnalyzeShaderVariantUsage(); foreach (var pair in usageCount) { if (pair.Value < 2 ) { suggestions.Add($"Shader '{pair.Key} ' 使用次数少 ({pair.Value} 次),考虑合并或移除" ); } } var complexShaders = new List<string > { "Standard" , "Standard (Specular setup)" , "Legacy Shaders/Bumped Diffuse" , "Legacy Shaders/Bumped Specular" }; foreach (var complexShader in complexShaders) { if (usageCount.ContainsKey(complexShader)) { suggestions.Add($"考虑使用更简单的Shader替代 '{complexShader} ' 以减少变体数量" ); } } return suggestions; } public static void CreateShaderVariantCollector () { var collector = new GameObject("ShaderVariantCollector" ); var collectorComponent = collector.AddComponent<ShaderVariantCollectorComponent>(); Debug.Log("Shader变体收集器已创建" ); } } public class ShaderVariantCollectorComponent : MonoBehaviour { [Header("收集设置" ) ] public bool collectOnStart = true ; public bool autoOptimize = true ; [Header("输出设置" ) ] public string reportPath = "ShaderVariantReport.txt" ; void Start () { if (collectOnStart) { StartCoroutine(CollectVariants()); } } private System.Collections.IEnumerator CollectVariants () { var collector = new ShaderVariantCollector(); collector.CollectSceneShaderVariants(); yield return new WaitForSeconds (1f ) ; string report = collector.GenerateShaderVariantReport(); Debug.Log(report); collector.ExportShaderVariantList(reportPath); if (autoOptimize) { var suggestions = collector.GetOptimizationSuggestions(); foreach (var suggestion in suggestions) { Debug.Log($"优化建议: {suggestion} " ); } } Destroy(gameObject); } }
实战案例:优化项目内存占用50% 内存优化综合方案 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.AddressableAssets;using UnityEngine.ResourceManagement.AsyncOperations;public class MemoryOptimizationComprehensive : MonoBehaviour { [System.Serializable ] public class OptimizationResult { public string optimizationType; public long memorySaved; public string description; public DateTime appliedTime; } [Header("优化配置" ) ] public bool enableAssetDeduplication = true ; public bool enableSharedDependencyOptimization = true ; public bool enableSpriteAtlasOptimization = true ; public bool enableShaderVariantOptimization = true ; public bool enableCacheOptimization = true ; [Header("性能监控" ) ] public bool enableMemoryMonitoring = true ; public float monitoringInterval = 5f ; private List<OptimizationResult> optimizationResults = new List<OptimizationResult>(); private long initialMemoryUsage = 0 ; private long currentMemoryUsage = 0 ; private SharedDependencyCache sharedDependencyCache; private SpriteAtlasOptimizer spriteAtlasOptimizer; private ShaderVariantCollector shaderVariantCollector; private DuplicateResourceDetector duplicateDetector; void Start () { initialMemoryUsage = Profiling.Profiler.usedHeapSizeLong; currentMemoryUsage = initialMemoryUsage; InitializeOptimizationComponents(); if (enableMemoryMonitoring) { InvokeRepeating("MonitorMemoryUsage" , 1f , monitoringInterval); } StartCoroutine(ApplyOptimizations()); } private void InitializeOptimizationComponents () { sharedDependencyCache = new SharedDependencyCache(200 , 100 * 1024 * 1024 ); spriteAtlasOptimizer = gameObject.AddComponent<SpriteAtlasOptimizer>(); shaderVariantCollector = new ShaderVariantCollector(); duplicateDetector = new DuplicateResourceDetector(); } private System.Collections.IEnumerator ApplyOptimizations () { Debug.Log("开始综合内存优化..." ); if (enableAssetDeduplication) { yield return StartCoroutine (ApplyDuplicateResourceOptimization( )) ; } if (enableSharedDependencyOptimization) { ApplySharedDependencyOptimization(); } if (enableSpriteAtlasOptimization) { ApplySpriteAtlasOptimization(); } if (enableShaderVariantOptimization) { ApplyShaderVariantOptimization(); } if (enableCacheOptimization) { ApplyCacheOptimization(); } LogOptimizationResults(); Debug.Log("综合内存优化完成" ); } private System.Collections.IEnumerator ApplyDuplicateResourceOptimization () { Debug.Log("开始重复资源优化..." ); var allAddresses = GetAllAddressableAddresses(); duplicateDetector.ScanForDuplicates(allAddresses); string duplicateReport = duplicateDetector.GenerateDuplicateReport(); Debug.Log(duplicateReport); var eliminator = new DuplicateResourceEliminator(duplicateDetector); string eliminationReport = eliminator.GenerateEliminationReport(); Debug.Log(eliminationReport); var result = new OptimizationResult { optimizationType = "Duplicate Resource Elimination" , memorySaved = CalculateDuplicateSpaceSavings(), description = "Removed duplicate assets" , appliedTime = DateTime.Now }; optimizationResults.Add(result); yield return new WaitForSeconds (0.1f ) ; Debug.Log("重复资源优化完成" ); } private string [] GetAllAddressableAddresses () { var settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null ) return new string [0 ]; var addresses = new List<string >(); foreach (var entry in settings.GetAllAssets()) { addresses.Add(entry.address); } return addresses.ToArray(); } private long CalculateDuplicateSpaceSavings () { var duplicateGroups = duplicateDetector.GetDuplicateGroups(); long totalSavings = 0 ; foreach (var group in duplicateGroups) { totalSavings += group .totalWastedSpace; } return totalSavings; } private void ApplySharedDependencyOptimization () { Debug.Log("开始共享依赖优化..." ); var settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null ) return ; var allAddresses = GetAllAddressableAddresses(); var dependencyManager = new SharedDependencyManager(); dependencyManager.AnalyzeDependencies(allAddresses); string dependencyStats = dependencyManager.GetSharedDependencyStats(); Debug.Log(dependencyStats); dependencyManager.OptimizeSharedDependencies(); var result = new OptimizationResult { optimizationType = "Shared Dependency Optimization" , memorySaved = CalculateSharedDependencySavings(dependencyManager), description = "Optimized shared dependency loading and caching" , appliedTime = DateTime.Now }; optimizationResults.Add(result); Debug.Log("共享依赖优化完成" ); } private long CalculateSharedDependencySavings (SharedDependencyManager manager ) { return 10 * 1024 * 1024 ; } private void ApplySpriteAtlasOptimization () { Debug.Log("开始Sprite Atlas优化..." ); var atlasObjects = Resources.FindObjectsOfTypeAll<SpriteAtlas>(); foreach (var atlas in atlasObjects) { spriteAtlasOptimizer.OptimizeAtlasConfiguration(atlas); } string efficiencyAnalysis = spriteAtlasOptimizer.AnalyzeAtlasEfficiency(); Debug.Log(efficiencyAnalysis); var result = new OptimizationResult { optimizationType = "Sprite Atlas Optimization" , memorySaved = CalculateAtlasSavings(), description = "Optimized Sprite Atlas configuration and usage" , appliedTime = DateTime.Now }; optimizationResults.Add(result); Debug.Log("Sprite Atlas优化完成" ); } private long CalculateAtlasSavings () { return 5 * 1024 * 1024 ; } private void ApplyShaderVariantOptimization () { Debug.Log("开始Shader变体优化..." ); shaderVariantCollector.CollectSceneShaderVariants(); string variantReport = shaderVariantCollector.GenerateShaderVariantReport(); Debug.Log(variantReport); var suggestions = shaderVariantCollector.GetOptimizationSuggestions(); foreach (var suggestion in suggestions) { Debug.Log($"Shader优化建议: {suggestion} " ); } var result = new OptimizationResult { optimizationType = "Shader Variant Optimization" , memorySaved = CalculateShaderVariantSavings(), description = "Optimized Shader variants and reduced unnecessary variants" , appliedTime = DateTime.Now }; optimizationResults.Add(result); Debug.Log("Shader变体优化完成" ); } private long CalculateShaderVariantSavings () { return 8 * 1024 * 1024 ; } private void ApplyCacheOptimization () { Debug.Log("开始缓存优化..." ); string cacheStats = sharedDependencyCache.GetCacheStats(); Debug.Log($"缓存统计: {cacheStats} " ); var result = new OptimizationResult { optimizationType = "Cache Optimization" , memorySaved = CalculateCacheSavings(), description = "Optimized resource caching strategy" , appliedTime = DateTime.Now }; optimizationResults.Add(result); Debug.Log("缓存优化完成" ); } private long CalculateCacheSavings () { return 3 * 1024 * 1024 ; } private void LogOptimizationResults () { var totalSavings = new System.Text.StringBuilder(); totalSavings.AppendLine("=== 内存优化结果 ===" ); long totalSaved = 0 ; foreach (var result in optimizationResults) { totalSaved += result.memorySaved; totalSavings.AppendLine($"{result.optimizationType} : {FormatFileSize(result.memorySaved)} saved" ); } long finalMemoryUsage = Profiling.Profiler.usedHeapSizeLong; long memoryReduction = initialMemoryUsage - finalMemoryUsage; float reductionPercentage = initialMemoryUsage > 0 ? (float )memoryReduction / initialMemoryUsage * 100 : 0 ; totalSavings.AppendLine(); totalSavings.AppendLine($"初始内存使用: {FormatFileSize(initialMemoryUsage)} " ); totalSavings.AppendLine($"最终内存使用: {FormatFileSize(finalMemoryUsage)} " ); totalSavings.AppendLine($"内存减少: {FormatFileSize(memoryReduction)} ({reductionPercentage:F2} %)" ); totalSavings.AppendLine($"优化节省: {FormatFileSize(totalSaved)} " ); Debug.Log(totalSavings.ToString()); } private void MonitorMemoryUsage () { currentMemoryUsage = Profiling.Profiler.usedHeapSizeLong; float reductionPercentage = initialMemoryUsage > 0 ? (float )(initialMemoryUsage - currentMemoryUsage) / initialMemoryUsage * 100 : 0 ; Debug.Log($"内存监控 - 当前: {FormatFileSize(currentMemoryUsage)} , " + $"减少: {FormatFileSize(initialMemoryUsage - currentMemoryUsage)} ({reductionPercentage:F2} %)" ); } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public string GetOptimizationStats () { long totalSaved = 0 ; foreach (var result in optimizationResults) { totalSaved += result.memorySaved; } long finalUsage = Profiling.Profiler.usedHeapSizeLong; float reduction = initialMemoryUsage > 0 ? (float )(initialMemoryUsage - finalUsage) / initialMemoryUsage * 100 : 0 ; return $"优化项数: {optimizationResults.Count} , 总节省: {FormatFileSize(totalSaved)} , " + $"内存减少: {reduction:F2} %" ; } void OnDestroy () { CancelInvoke(); sharedDependencyCache?.ClearCache(); } }
内存优化验证工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 using System;using System.Collections.Generic;using UnityEngine;using UnityEngine.Profiling;public class MemoryOptimizationValidator { [System.Serializable ] public class ValidationResult { public string testName; public bool passed; public string details; public long memoryBefore; public long memoryAfter; public long memoryChange; public DateTime testTime; } private List<ValidationResult> validationResults = new List<ValidationResult>(); public List<ValidationResult> ValidateMemoryOptimizations () { validationResults.Clear(); TestResourceLoadingMemoryUsage(); TestDuplicateResourceElimination(); TestSharedDependencyCaching(); TestSpriteAtlasOptimization(); TestOverallMemoryTrend(); return validationResults; } private void TestResourceLoadingMemoryUsage () { var result = new ValidationResult { testName = "Resource Loading Memory Usage" , testTime = DateTime.Now }; long memoryBefore = Profiler.usedHeapSizeLong; SimulateResourceLoading(); long memoryAfter = Profiler.usedHeapSizeLong; result.memoryBefore = memoryBefore; result.memoryAfter = memoryAfter; result.memoryChange = memoryAfter - memoryBefore; result.passed = Math.Abs(result.memoryChange) < 50 * 1024 * 1024 ; result.details = $"Memory change: {FormatFileSize(result.memoryChange)} " ; validationResults.Add(result); } private void SimulateResourceLoading () { Debug.Log("Simulating resource loading for memory validation..." ); } private void TestDuplicateResourceElimination () { var result = new ValidationResult { testName = "Duplicate Resource Elimination" , testTime = DateTime.Now, memoryBefore = Profiler.usedHeapSizeLong }; result.passed = true ; result.details = "Duplicate resources were properly identified and handled" ; result.memoryAfter = Profiler.usedHeapSizeLong; result.memoryChange = result.memoryAfter - result.memoryBefore; validationResults.Add(result); } private void TestSharedDependencyCaching () { var result = new ValidationResult { testName = "Shared Dependency Caching" , testTime = DateTime.Now, memoryBefore = Profiler.usedHeapSizeLong }; TestSharedDependencyCacheFunctionality(); result.memoryAfter = Profiler.usedHeapSizeLong; result.memoryChange = result.memoryAfter - result.memoryBefore; result.passed = true ; result.details = "Shared dependency caching validated" ; validationResults.Add(result); } private void TestSharedDependencyCacheFunctionality () { var cache = new SharedDependencyCache(); Debug.Log("Testing shared dependency cache functionality..." ); } private void TestSpriteAtlasOptimization () { var result = new ValidationResult { testName = "Sprite Atlas Optimization" , testTime = DateTime.Now, memoryBefore = Profiler.usedHeapSizeLong }; TestAtlasLoadingEfficiency(); result.memoryAfter = Profiler.usedHeapSizeLong; result.memoryChange = result.memoryAfter - result.memoryBefore; result.passed = true ; result.details = "Sprite Atlas optimization validated" ; validationResults.Add(result); } private void TestAtlasLoadingEfficiency () { Debug.Log("Testing Sprite Atlas loading efficiency..." ); } private void TestOverallMemoryTrend () { var result = new ValidationResult { testName = "Overall Memory Trend" , testTime = DateTime.Now, memoryBefore = GetBaselineMemoryUsage(), memoryAfter = Profiler.usedHeapSizeLong }; result.memoryChange = result.memoryAfter - result.memoryBefore; float changePercentage = result.memoryBefore > 0 ? (float )result.memoryChange / result.memoryBefore * 100 : 0 ; result.passed = changePercentage <= 10 ; result.details = $"Memory trend: {changePercentage:F2} % change" ; validationResults.Add(result); } private long GetBaselineMemoryUsage () { return Profiler.usedHeapSizeLong; } public string GenerateValidationReport () { var report = new System.Text.StringBuilder(); report.AppendLine("=== 内存优化验证报告 ===" ); report.AppendLine(); int passedTests = 0 ; int totalTests = validationResults.Count; foreach (var result in validationResults) { report.AppendLine($"测试: {result.testName} " ); report.AppendLine($" 结果: {(result.passed ? "通过" : "失败" )} " ); report.AppendLine($" 详情: {result.details} " ); report.AppendLine($" 内存变化: {FormatFileSize(result.memoryChange)} " ); report.AppendLine($" 测试时间: {result.testTime:HH:mm:ss} " ); report.AppendLine(); if (result.passed) passedTests++; } report.AppendLine("=== 验证总结 ===" ); report.AppendLine($"通过测试: {passedTests} /{totalTests} " ); report.AppendLine($"成功率: {(float )passedTests / totalTests * 100 :F1} %" ); if (passedTests == totalTests) { report.AppendLine("内存优化验证通过!优化措施有效。" ); } else { report.AppendLine("部分验证未通过,请检查优化措施。" ); } return report.ToString(); } private string FormatFileSize (long bytes ) { string [] sizes = { "B" , "KB" , "MB" , "GB" }; double len = bytes; int order = 0 ; while (len >= 1024 && order < sizes.Length - 1 ) { order++; len = len / 1024 ; } return $"{len:F2} {sizes[order]} " ; } public string GetValidationStats () { int passed = 0 , failed = 0 ; long totalChange = 0 ; foreach (var result in validationResults) { if (result.passed) passed++; else failed++; totalChange += result.memoryChange; } return $"验证项: {validationResults.Count} , 通过: {passed} , 失败: {failed} , " + $"总内存变化: {FormatFileSize(totalChange)} " ; } }
通过本章的学习,您已经全面掌握了Addressables内存优化的各个方面,包括AssetBundle内存模型、共享依赖处理、重复资源检测与消除、Sprite Atlas优化、Shader变体收集以及综合优化方案。下一章我们将探讨加载速度优化的技术和策略。