- A+
在资源加载中,说了加载场景和Prefab,并不完全,还有材质、图集、贴图、远程加载东西等等。后面用到了再做补充。其实加载的流程都是一样的,这里补充一下图集和远程图片加载,完善一下UIImage组件。
一、图集的打包
这里需要定制一个规则,编写扩展工具,进行图集的打包。这里的规则是搜索打包目录下的所有名字叫做Atlas的文件夹,然后将这些目录及其子目录分别打成一个图集。将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 |
using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.U2D; using UnityEngine; using UnityEngine.U2D; public class PackageAtlas { private static string PackPath = Config.PackPath; private static string AssetDataPath = Config.AssetDataPath; public static void PackAtlas() { string path = Path.Combine(Application.dataPath, AssetDataPath); List<DirectoryInfo> atlasPaths = SearchAtlasPath(path); foreach (var str in atlasPaths) { GenAtlas(str, str.Parent.FullName); } } public static void RemoveAtlas() { string path = Path.Combine(Application.dataPath, AssetDataPath); List<DirectoryInfo> atlasPaths = SearchAtlasPath(path); foreach (var str in atlasPaths) { DeleteAtlas(str, str.Parent.FullName); } } private static void GenAtlas(DirectoryInfo info,string AtlasPath) { FileInfo[] files = info.GetFiles(); if (files.Length <= 0) return; string atlasName = info.Name; string filePath = Path.Combine(AtlasPath, atlasName); string atlasPath = UtilityTool.ChangePath(filePath) + ".spriteatlas"; CreateAtlas(atlasPath); SpriteAtlas sa = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasPath); SetSpriteAtlas(sa); List<Sprite> sprites = GetAllSprite(files); SpriteAtlasExtensions.Add(sa, sprites.ToArray()); SpriteAtlasUtility.PackAtlases(new[] { sa }, EditorUserBuildSettings.activeBuildTarget); foreach (var dirs in info.GetDirectories()) { GenAtlas(dirs, AtlasPath); } } private static void DeleteAtlas(DirectoryInfo info, string AtlasPath) { string atlasName = info.Name; string filePath = Path.Combine(AtlasPath, atlasName); string atlasPath = UtilityTool.ChangePath(filePath) + ".spriteatlas"; string atlasFullPath = filePath + ".spriteatlas"; if (File.Exists(atlasFullPath)) { AssetDatabase.DeleteAsset(atlasPath); AssetDatabase.SaveAssets(); } foreach (var dirs in info.GetDirectories()) { DeleteAtlas(dirs, AtlasPath); } } private static void CreateAtlas(string path) { SpriteAtlas sa = new SpriteAtlas(); AssetDatabase.CreateAsset(sa, path); AssetDatabase.SaveAssets(); } public static List<DirectoryInfo> SearchAtlasPath(string path) { List<DirectoryInfo> list = new List<DirectoryInfo>(); UtilityTool.GetAllDir(list, path, PackPath); return list; } private static List<Sprite> GetAllSprite(FileInfo[] infos) { List<Sprite> list = new List<Sprite>(); foreach (var info in infos) { if (info.Extension == ".meta") continue; string path = UtilityTool.ChangePath( Path.Combine(info.Directory.FullName, info.Name) ); Sprite sp = AssetDatabase.LoadAssetAtPath<Sprite>(path); list.Add(sp); } return list; } private static void SetSpriteAtlas(SpriteAtlas atlas) { TextureImporterCompression type = TextureImporterCompression.CompressedLQ; TextureImporterFormat _format = TextureImporterFormat.ASTC_6x6; // 设置参数 可根据项目具体情况进行设置 SpriteAtlasPackingSettings packSetting = new SpriteAtlasPackingSettings() { blockOffset = 1, enableRotation = false, enableTightPacking = false, padding = 2, }; atlas.SetPackingSettings(packSetting); SpriteAtlasTextureSettings textureSetting = new SpriteAtlasTextureSettings() { readable = false, generateMipMaps = false, sRGB = true, filterMode = FilterMode.Bilinear, }; atlas.SetTextureSettings(textureSetting); TextureImporterPlatformSettings platformSetting = new TextureImporterPlatformSettings() { name = "Android", maxTextureSize = 1024, format = _format, overridden = true, }; atlas.SetPlatformSettings(platformSetting); platformSetting = new TextureImporterPlatformSettings() { name = "iPhone", maxTextureSize = 1024, format = _format, overridden = true, }; atlas.SetPlatformSettings(platformSetting); } } |
二、加载图集和精灵
为了减少lua和c#之间的交互,可以在lua侧缓存加载过的图集和精灵,在有缓存的情况下可以直接在lua侧返回结果。同时为了控制内存的使用,需要做一个缓存大小的控制,如果加载的资源全部都缓存,在有些图片资源非常丰富的时候就会造成内存占用越来越大。有一个LRUCaches算法就可以很好的处理这一个情况,给定一个最大的缓存大小,再不够的时候会吧最近最少用到的资源缓存替换掉。
SetCheckCanPopCallback设置是否可以清理缓存,SetPopCallback设置要删除的需要处理的回调
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 |
--[[ fd: lru cache 算法 (to be insert)<--newest----------------->oldest local LruCache = require("libs.Struct.LruCache") ]] --indices of tuples local VALUE = 1 local PREV = 2 local NEXT = 3 local KEY = 4 local BYTES = 5 local M = Class("LruCache") function M:ctor(max_size, max_bytes, pop_cb, check_can_pop_func) assert(max_size > 1, "max_size must be >= 1") assert(not max_bytes or max_bytes >= 1, "max_bytes must be >= 1 or nil") self.max_size = max_size self.max_bytes = max_bytes --current size self.size = 0 --the bytes used self.bytes_used = 0 --callback self.pop_cb = pop_cb self.check_can_pop_func = check_can_pop_func --map is a hash map from keys to tuples --tuple: value, prev, next, key, bytes --prev and next are pointer to tuples self.map = {} self.newest = nil -- first self.oldest = nil -- last end --当需要删除时回调函数 function M:SetPopCallback(func) self.pop_cb = func end --接口,用于给调用者确定是否可以删除 function M:SetCheckCanPopCallback(func) self.check_can_pop_func = func end --添加元素 function M:Add(key, value, bytes) self:Set(key, value, bytes) end --设置元素 function M:Set(key, value, bytes) assert(key ~= nil, "key cannot be nil") local tuple = self.map[key] if tuple then self:__Del(key, tuple) end if value ~= nil then --the value is not removed bytes = bytes or 0 self:__MakeFreeSpace(bytes) local tuple1 = tuple or {} self.map[key] = tuple1 tuple1[VALUE] = value tuple1[KEY] = key tuple1[BYTES] = bytes self.size = self.size + 1 self.bytes_used = self.bytes_used + bytes self:__SetNewest(tuple1) else --证明是要设置key对应的值为空,将tuple中的值置为Nil if tuple then self:__ClearTuple(tuple) end end end --获取值 会触发lru算法 function M:Get(key) local tuple = self.map[key] if not tuple then return nil end self:__Cut(tuple) self:__SetNewest(tuple) return tuple[VALUE] end --获取值,只获取但不会触发lru算法 function M:OnlyGet(key) local tuple = self.map[key] if not tuple then return nil end return tuple[VALUE] end --获取最旧的一个值, 只获取但不会触发lru算法 function M:OnlyGetOldest() if self.oldest then return self.oldest[VALUE] end end --是否被缓存 function M:IsCached(key) return self.map[key] and true or false end --删除 function M:Remove(key) self:Set(key, nil) end --遍历 newest -> oldest function M:Traverse(func) local tuple = self.newest while true do if tuple == nil then break end func(tuple[KEY], tuple[VALUE]) tuple = tuple[NEXT] end end function M:GetSize() return self.size end function M:GetBytesSize() return self.bytes_used end function M:Clear() local tuple = self.newest while true do if tuple == nil then break end tuple_next = tuple[NEXT] self:__ClearTuple(tuple) tuple = tuple_next end self.map = {} self.size = 0 self.bytes_used = 0 self.newest = nil self.oldest = nil end function M:__ClearTuple(tuple) tuple[PREV] = nil tuple[NEXT] = nil tuple[VALUE] = nil tuple[BYTES] = nil tuple[KEY] = nil end -- remove a tuple from linked list function M:__Cut(tuple) local tuple_prev = tuple[PREV] local tuple_next = tuple[NEXT] tuple[PREV] = nil tuple[NEXT] = nil if tuple_prev and tuple_next then tuple_prev[NEXT] = tuple_next tuple_next[PREV] = tuple_prev elseif tuple_prev then --tuple is the oldest element tuple_prev[NEXT] = nil self.oldest = tuple_prev elseif tuple_next then --tuple is the newest element tuple_next[PREV] = nil self.newest = tuple_next else --tuple is the only element self.newest = nil self.oldest = nil end end --insert a tuple to newest end function M:__SetNewest(tuple) if not self.newest then self.newest = tuple self.oldest = tuple else tuple[NEXT] = self.newest self.newest[PREV] = tuple self.newest = tuple end end function M:__Del(key, tuple) self.map[key] = nil self:__Cut(tuple) self.size = self.size - 1 self.bytes_used = self.bytes_used - (tuple[BYTES] or 0) end --remotes elements to provide enough memory --returns last removed element or nil function M:__MakeFreeSpace(bytes) local tuple = self.oldest local max_check_free_times = 10 -- max check free times for avoid no tuple can free cause iterator much times local cur_check_free_time = 0 while self.size + 1 > self.max_size or (self.max_bytes and self.bytes_used + bytes > self.max_bytes) do if not tuple then break end local tuple_prev = tuple[PREV] if self.check_can_pop_func == nil or self.check_can_pop_func(tuple[KEY], tuple[VALUE]) then --can pop self:__Del(tuple[KEY], tuple) if self.pop_cb then self.pop_cb(tuple[KEY], tuple[VALUE]) end else --the host say cannot pop cur_check_free_time = cur_check_free_time + 1 if cur_check_free_time > max_check_free_times then if Config.Debug then -- zerror("lru cache detect check_free time is too much, please check code") end break end end tuple = tuple_prev end end return M |
图集和单个的Sprite的加载和卸载方式有所不同,所以这里要创建两个缓存池,一个管理图集和通过图集加载的Sprite,一个管理单个Sprite。可以看到通过SetCheckCanPopCallback设置是否可以清楚缓存的条件是引用计数为0的时候,对于使用图集加载的Sprite在卸载的时候直接销毁就行了。在上面说了图集打包识别的是Atlas目录,所以在加载的时候通过__GetSpriteInfo分析路径是通过图集加载还是单个的Sprite加载,并且可以分析出哪个图集的address和子Sprite的名字。
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 |
local ManagerBase = require("Frame.Base.Manager.ManagerBase") local M = Class("Manager.ImageLoaderManager",ManagerBase) local LruCache = require("Frame.Libs.Struct.LruCache") local CSGameObject = CS.UnityEngine.GameObject local ATLAS_KEY = "/Atlas/" function M:Init() self.single_sprite_cache = LruCache.New(100) self.sprite_atlas_cache = LruCache.New(20) self:InitSingleSpriteCache() self:InitSpriteAtlasCache() end function M:InitSingleSpriteCache( ) self.single_sprite_cache:SetCheckCanPopCallback(function(key,value) return value.ref_count == 0 end) self.single_sprite_cache:SetPopCallback(function(key,value) AddressableManager:GetInstance():ReleaseObject(value.asset) value.asset = nil value.ref_count = 0 end) end function M:InitSpriteAtlasCache() self.sprite_atlas_cache:SetCheckCanPopCallback(function(key,value) return value.ref_count == 0 end) self.single_sprite_cache:SetPopCallback(function(key,value) if value.subassets then for k,v in pairs(value.subassets) do CSGameObject.Destroy(v.asset) v.asset = nil v.ref_count = nil end end AddressableManager:GetInstance():ReleaseObject(value.asset) value.asset = nil value.ref_count = 0 end) end --加载图片 function M:LoadSprite(sprite_path, callback) coroutine.start(function() local sprite = self:CoLoadSprite(sprite_path) callback(sprite, sprite_path) end) end function M:CoLoadSprite(sprite_path) local is_atlas, asset_path, sub_name = self:__GetSpriteInfo(sprite_path) if is_atlas then local atlas = nil local cache_a = self.sprite_atlas_cache:Get(asset_path) if cache_a then if IsNull(cache_a.asset) then self.sprite_atlas_cache:Remove(asset_path) else atlas = cache_a.asset end end if not atlas then local operation_data = AddressableManager:GetInstance():LoadObjectAsync(asset_path) coroutine.waitforasyncop(operation_data) atlas = operation_data:GetAsset() operation_data:BackPool() cache_a = {asset = atlas, ref_count = 0} end if not atlas then zerror(string.format("%s not fond atlas", sprite_path)) return nil end local sprite = nil cache_a.subassets = cache_a.subassets or {} if cache_a.subassets[sub_name] then sprite = cache_a.subassets[sub_name].asset else sprite = atlas:GetSprite(sub_name) if not sprite then zerror("sprite not found:".. sprite_path .. "__" ..sub_name ) return end cache_a.subassets[sub_name] = {asset = sprite, ref_count = 0} end cache_a.subassets[sub_name].ref_count = cache_a.subassets[sub_name].ref_count + 1 cache_a.ref_count = cache_a.ref_count + 1 self.sprite_atlas_cache:Set(asset_path, cache_a) return sprite else local sprite = nil local cache_s = self.single_sprite_cache:Get(asset_path) if cache_s then if IsNull(cache_s.asset) then self.single_sprite_cache:Remove(asset_path) else sprite = cache_s.asset end end if not sprite then local operation_data = AddressableManager:GetInstance():LoadObjectAsync(asset_path) coroutine.waitforasyncop(operation_data) sprite = operation_data:GetAsset() operation_data:BackPool() cache_s = {asset = sprite, ref_count = 0} end cache_s.ref_count = cache_s.ref_count + 1 self.single_sprite_cache:Set(asset_path, cache_s) return sprite, sprite_path end end --释放图片 function M:ReleaseImage(image_path) if string.isEmpty(image_path) then return end local is_atlas, asset_path, sub_name = self:__GetSpriteInfo(sprite_path) local cacheCls = is_atlas and self.sprite_atlas_cache or self.single_sprite_cache local value = cacheCls:OnlyGet(asset_path) if value then assert(value.ref_count > 0) value.ref_count = value.ref_count - 1 if sub_name then local subassets = value.subassets or {} if subassets[sub_name] then subassets[sub_name].ref_count = subassets[sub_name].ref_count - 1 if subassets[sub_name].ref_count <= 0 then CSGameObject.Destroy(subassets[sub_name].asset) subassets[sub_name] = nil end end end cacheCls:Set(asset_path, value) end end function M:__GetSpriteInfo(sprite_path) local index = string.find(sprite_path, ATLAS_KEY) local is_atlas = index ~= nil local asset_path = nil local sub_name = nil if is_atlas then local substr = string.sub(sprite_path, index + #ATLAS_KEY) local subs = string.split(substr, "/") local prefix = string.sub(sprite_path, 0, index) if subs and #subs > 1 then --子目录会单独打成图集 最后面的子目录为图集名字 asset_path = string.format("%s%s%s", prefix, subs[#subs - 1], ".spriteatlas") sub_name = subs[#subs] else asset_path = string.format("%s%s", prefix,"Atlas.spriteatlas") sub_name = substr end local dotIndex = string.find(sub_name, '%.') sub_name = string.sub(sub_name, 1, dotIndex-1) else asset_path = sprite_path end return is_atlas, asset_path, sub_name end function M:Clear() self.sprite_atlas_cache:Traverse(function(key, value) local subasset_list = value.subassets or {} for k,v in pairs(subasset_list) do CSGameObject.Destroy(v.asset) end AddressableManager:GetInstance():ReleaseAsset(value.asset) value.asset = nil value.ref_count = nil value.subasset = nil end) self.single_sprite_cache:Traverse(function(key, value) AddressableManager:GetInstance():ReleaseAsset(value.asset) value.asset = nil value.ref_count = nil end) self.sprite_atlas_cache:Clear() self.single_sprite_cache:Clear() end return M |
三、Http封装
封装Http是为了更好的调用,对http的get、put、post、加载文件的方法和请求头及参数的传递进行封装成一些方便调用的方法。在加载远程图片的时候,对于不是重新加载的图片会优先在本地查找缓存,新的远程图片加载会缓存到黑匣子目录中,在lua层进行管理。
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 |
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using Addressable; using UnityEngine; using UnityEngine.Networking; public class HttpManager { public static HttpOperationData HttpGet(string url, Dictionary<string, string> headers, Dictionary<string, string> param, int timeout) { string strParam = ConvertParamToStr(param); string dest = url; if (strParam.Length > 0) { dest = url + "?" + strParam; } var request = UnityWebRequest.Get(dest); if (timeout > 0) { request.timeout = timeout; } foreach (var item in headers) { request.SetRequestHeader(item.Key, item.Value); } var operation = HttpOperationData.Get(request); request.SendWebRequest(); return operation; } public static HttpOperationData HttpPut(string url, Dictionary<string, string> headers, Dictionary<string, string> param, int timeout) { string strParam = ConvertParamToStr(param); var request = new UnityWebRequest(url + "?" + strParam, "PUT"); request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); foreach (var item in headers) { request.SetRequestHeader(item.Key, item.Value); } if (timeout > 0) { request.timeout = timeout; } var operation = HttpOperationData.Get(request); request.SendWebRequest(); return operation; } public static HttpOperationData HttpPost(string url, Dictionary<string, string> headers, Dictionary<string, string> param, int timeout) { string strParam = ConvertParamToStr(param); var request = new UnityWebRequest(url + "?" + strParam, "POST"); request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); foreach (var item in headers) { request.SetRequestHeader(item.Key, item.Value); } if (timeout > 0) { request.timeout = timeout; } var operation = HttpOperationData.Get(request); request.SendWebRequest(); return operation; } public static HttpOperationData HttpGetImageOnline(string url,bool reload, Dictionary<string, string> headers, int timeout) { //本地是否存在图片 if (!reload) { string local_path = LocalImage(url); if(File.Exists(local_path)) url = string.Format("file://{0}",local_path); } var request = UnityWebRequestTexture.GetTexture(url); if (timeout > 0) { request.timeout = timeout; } foreach (var item in headers) { request.SetRequestHeader(item.Key, item.Value); } var operation = HttpOperationData.Get(request); request.SendWebRequest(); return operation; } public static string LocalImage(string url) { string path = Application.persistentDataPath + "/DownloadImage/"; if (!Directory.Exists(path)) Directory.CreateDirectory(path); string savePath = path + GetCacheImageName(url); return savePath; } public static void SaveImageToLocal(string url, Texture2D texture) { string path = Application.persistentDataPath + "/DownloadImage/"; if (!Directory.Exists(path)) Directory.CreateDirectory(path); string save_path = path + GetCacheImageName(url); File.WriteAllBytes(save_path, texture.EncodeToPNG()); } public static bool RemoveLocalImage(string url) { string path = LocalImage(url); if (!File.Exists(path)) { return false; } File.Delete(path); return true; } public static string GetCacheImageName(string url) { byte[] input = Encoding.Default.GetBytes(url.Trim()); System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] output = md5.ComputeHash(input); string md5_url_string = System.BitConverter.ToString(output).Replace("-", "") + ".png"; return md5_url_string; } private static string ConvertParamToStr(Dictionary<string, string> param) { StringBuilder builder = new StringBuilder(); int flag = 0; foreach (var item in param) { if (flag == 0) { builder.Append(item.Key + "=" + item.Value); flag = 1; } else { builder.Append("&" + item.Key + "=" + item.Value); } } return builder.ToString(); } } |
其中的HttpOperationData是提供给lua层做异步处理用的,完成后可以通过它获取到相应需要加载的资源。
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 |
using Manager; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.Eventing.Reader; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.Networking; using UnityEngine.ResourceManagement.AsyncOperations; namespace Addressable { public class HttpOperationData:IEnumerator { private static Queue<HttpOperationData> pool = new Queue<HttpOperationData>(); private UnityWebRequest request; public string url { get { return request.url; } } public Texture2D texture { get { return DownloadHandlerTexture.GetContent(request); } } public string text { get { return request.downloadHandler.text; } } public string errorMsg { get { return request.error; } } public bool isSuccess { get { return request.result == UnityWebRequest.Result.Success; } } public bool isDone { get { return request.isDone; } } //获取进度 public float progress { get { if (isDone) { return 1.0f; } return 0.0f; } } public void Reset() { } public object Current { get { return null; } } public static HttpOperationData Get(UnityWebRequest _request) { HttpOperationData operation = null; if (pool.Count > 0) { operation = pool.Dequeue(); } if (operation == null) { operation = new HttpOperationData(); } operation.Init(_request); return operation; } //回收 public void BackPool() { Dispose(); pool.Enqueue(this); } private void Init(UnityWebRequest _request) { request = _request; } public bool MoveNext() { return !isDone; } public void Dispose() { if (request != null) { request.Dispose(); request = null; } } } } |
Lua侧对C#提供的接口进行调度。
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 |
local base = require("Frame.Base.Manager.ManagerBase") local M = Class("Manager.HttpManager", base) local CSHttpManager = CS.HttpManager local DefaultTimeout = 3 local DefaultTrytime = 3 function M:HttpGetDefault(url, callback, ...) self:HttpGet(url, callback, nil, nil, nil, nil, ...) end function M:HttpGet(url, callback, params, headers, trytime, timeout, ...) self:__HttpRequest("Get", url, callback, params, headers, trytime, timeout, ...) end function M:HttpPut(url, callback, params, headers, trytime, timeout, ...) self:__HttpRequest("Put", url, callback, params, headers, trytime, timeout, ...) end function M:HttpPost(url, callback, params, headers, trytime, timeout, ...) self:__HttpRequest("Post", url, callback, params, headers, trytime, timeout, ...) end function M:__HttpRequest(method, url, callback, params, headers, trytime, timeout, ...) local datas = SafePack(nil, ...) coroutine.start(function() local ret = self:__CoHttpRequest(method, url, params, headers, trytime, timeout) datas[1] = ret callback(SafeUnpack(datas)) end) end function M:__CoHttpRequest(method, url, params, headers, trytime, timeout) zprint("url = ", url, " trytime = ", trytime or DefaultTrytime) params = params or {} headers = headers or {} self:__MareSureStringValue(params) self:__MareSureStringValue(headers) trytime = trytime or DefaultTrytime timeout = timeout or DefaultTimeout local opdata = nil if method == "Get" then opdata = CSHttpManager.HttpGet(url, headers, params, timeout) elseif method == "Put" then opdata = CSHttpManager.HttpPut(url, headers, params, timeout) elseif method == "Post" then opdata = CSHttpManager.HttpPost(url, headers, params, timeout) end if not opdata then zerror('__CoHttpRequest method is fail') return end coroutine.waitforasyncop(opdata) if opdata.isSuccess then local ret = opdata.text opdata:BackPool() return ret else if trytime > 1 then opdata:BackPool() return self:__CoHttpRequest(method, url, params, headers, trytime - 1, timeout) else opdata:BackPool() return nil end end end function M:HttpGetTexture2DOnline(url, callback, reload, headers, trytime, timeout) coroutine.start(function() local sprite, tex2d = self:CoHttpGetImageOnline(url, reload, headers, trytime, timeout) callback(tex2d, url) end) end function M:CoHttpGetImageOnline(url, reload, headers, trytime, timeout) zprint("url = ", url, " trytime = ", trytime or DefaultTrytime) if not reload then reload = false end headers = headers or {} timeout = timeout or DefaultTimeout + 5 trytime = trytime or DefaultTrytime self:__MareSureStringValue(headers) local opdata = CSHttpManager.HttpGetImageOnline(url, reload, headers, timeout) coroutine.waitforasyncop(opdata) if opdata.isSuccess then local tex2d = nil tex2d = opdata.texture opdata:BackPool() return tex2d else if trytime > 1 then opdata:BackPool() return self:__CoHttpGetImageOnline(url, reload, headers, trytime - 1, timeout) else opdata:BackPool() return nil end end end function M:SaveImageToLocal(url, tex2d) CSHttpManager.SaveImageToLocal(url, tex2d) end function M:__MareSureStringValue( tb ) for k,v in pairs(tb) do tb[k] = tostring(v) end end return M |
四、远程图片的加载
和图集和Sprite的加载类似,我们也使用缓存来优化加载速度和lua与c#的交互。同时在加载完成后管理保存到本地中,加快下次加载的速度和流量的时候。同时,有了保存到本地的这个机制后,就需要提供一个重新网络加载的机制,因为有可能的是这个本地图片已经过时了,那么就需要重新到网络上下载。那么缓存就不能用原来的缓存了,但是这个又不能卸载,因为有可能有些地方已经用了旧的了。可以考虑在重新加载完成后发送一个事件,接收到事件的为使用旧资源的Image,然后更新为最新的,这样的引用计数就对了,然后旧资源也可以安全卸载了。考虑到大有可能的是使用旧资源的Image是不在激活状态的,所以,我这里在Reload的时候,做了一个资源缓存的栈,保存多个资源,保留原来的引用计数,获取的时候永远返回最新哪个资源。当引用计数为0的时候,就把栈中的资源全部卸载,这样就安全了。
远程下载的资源为Texture2D的格式,在需要使用Sprite的格式时候,需要通过Texture2D来创建一个Sprite。所以这里也是保存两个缓存池,Sprite的引用计数影响Texture2D。
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 |
local ManagerBase = require("Frame.Base.Manager.ManagerBase") local M = Class("Manager.ImageOnlineManager",ManagerBase) local LruCache = require("Frame.Libs.Struct.LruCache") local CSGameObject = CS.UnityEngine.GameObject function M:Init() self.sprite_cache = LruCache.New(100) self:InitSpriteCache() self.texture_cache = LruCache.New(100) self:InitTextureCache() end function M:InitSpriteCache( ) self.sprite_cache:SetCheckCanPopCallback(function(key,value) return value.ref_count == 0 end) self.sprite_cache:SetPopCallback(function(key,value) for i,v in ipairs(value.assets) do CSGameObject.Destroy(v) end value.assets = nil value.ref_count = nil end) end function M:InitTextureCache() self.texture_cache:SetCheckCanPopCallback(function(key, value) return value.ref_count == 0 end) self.texture_cache:SetPopCallback(function(key, value) for i,v in ipairs(value.assets) do CSGameObject.Destroy(v) end value.assets = nil value.ref_count = nil end) end function M:GetSpriteOnline(url, callback, reload) coroutine.start(function() local ret = self:CoGetSpriteOnline(url, reload) callback(ret, url) end) end function M:GetTexture2DOnline(url, callback, reload) coroutine.start(function() local ret = self:CoGetTexture2DOnline(url, reload) callback(ret, url) end) end function M:CoGetSpriteOnline(url, reload) local value_s = self.sprite_cache:Get(url) if value_s and not reload then if IsNull(value_s.assets) then self.sprite_cache:Remove(url) value_s = nil else value_s.ref_count = value_s.ref_count + 1 self.sprite_cache:Set(url, value_s) return value_s.assets[1] end end local ret = nil local tex2d = self:CoGetTexture2DOnline(url, reload) if tex2d then ret = CS.UnityExtends.TurnTex2DToSprite(tex2d) if value_s then --重新加载的情况 value_s.ref_count = value_s.ref_count + 1 table.insert(value_s.assets, 1, ret) else value_s = {assets = {ret}, ref_count = 1} end self.sprite_cache:Set(url, value_s) end return ret end function M:CoGetTexture2DOnline(url, reload) local value_t = self.texture_cache:Get(url) if value_t and not reload then if IsNull(value_t.assets) then self.texture_cache:Remove(url) value_t = nil else value_t.ref_count = value_t.ref_count + 1 self.texture_cache:Set(url, value_t) return value_t.assets[1] end end local ret = HttpManager:GetInstance():CoHttpGetImageOnline(url, reload, nil,nil,nil,type) if not ret then return nil end if value_t then --重新加载的情况 value_t.ref_count = value_t.ref_count + 1 table.insert(value_t.assets, 1, ret) else value_t = {assets = {ret}, ref_count = 1} end self.texture_cache:Set(url, value_t) --保存到本地 HttpManager:GetInstance():SaveImageToLocal(url, ret) return ret end --释放精灵 function M:ReleaseSprite(url) local value = self.sprite_cache:OnlyGet(url) if value and value.ref_count > 0 then value.ref_count = value.ref_count - 1 self.sprite_cache:Set(url, value) end self:ReleaseTexture2D(url) end --释放贴图 function M:ReleaseTexture2D(url) local value = self.texture_cache:OnlyGet(url) if value and value.ref_count > 0 then value.ref_count = value.ref_count - 1 self.texture_cache:Set(url, value) end end function M:Clear() self.sprite_cache:Traverse(function(key, value) for i,v in ipairs(value.assets) do CSGameObject.Destroy(v) end value.assets = nil value.ref_count = nil end) self.sprite_cache:Clear() self.texture_cache:Traverse(function(key, value) for i,v in ipairs(value.assets) do CSGameObject.Destroy(v) end value.assets = nil value.ref_count = nil end) self.texture_cache:Clear() end return M |
五、UIImage组件加载图片和远程图片
需要注意的是加载都是异步的,在设置的时候需要考虑连续设置的问题,例如第一个设置后异步加载比第二次加载慢,那么显示的就不是你最新加载的了。还需要考虑加载远程图片和加载本地图集之间的切换情况和连续设置的情况,要留好状态来表示。不需要用到的资源要通过管理器去释放保持引用计数的正确性。
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 |
local M = Class("Base.Component.UIImage",UIBaseComponent) local base = UIBaseComponent function M:Awake() base.Awake(self) self.unity_image = self.transform:GetComponent(typeof(CS.UnityEngine.UI.Image)) end -- 设置Sprite名称 function M:SetSpritePath(sprite_path) if string.isEmpty(sprite_path) then return end if self.sprite_path == sprite_path then return end if not string.isEmpty(self.online_image_true_url) then ImageOnlineManager:GetInstance():ReleaseImage(self.online_image_true_url) self.online_image_true_url = nil end self.online_image_url = nil self.sprite_path = sprite_path if IsNull(self.unity_image) or string.isEmpty(sprite_path) then return end ImageLoaderManager:GetInstance():LoadSprite(sprite_path, function(sprite, sprite_path) --预设已经被销毁 或 被加载的Sprite不是当前想要的Sprite:可能预设被复用,之前的加载操作就要作废 if IsNull(self.unity_image) or string.isEmpty(self.sprite_path) or sprite_path ~= self.sprite_path then ImageLoaderManager:GetInstance():ReleaseImage(sprite_path) return end if not string.isEmpty(self.sprite_true_path) then ImageLoaderManager:GetInstance():ReleaseImage(self.sprite_true_path) end self.sprite_true_path = sprite_path self.unity_image.sprite = sprite end) end --设置在线精灵 function M:SetSpriteOnline(url, reload) if string.isEmpty(url) then return end if IsNull(self.unity_image) then return end if self.online_image_url == url and not reload then return end if not string.isEmpty(self.sprite_true_path) then ImageLoaderManager:GetInstance():ReleaseImage(self.sprite_true_path) self.sprite_true_path = nil end self.sprite_path = nil self.online_image_url = url ImageOnlineManager:GetInstance():GetSpriteOnline(url, function(sprite, cur_url) if IsNull(self.unity_image) or string.isEmpty(self.online_image_url) or self.online_image_url ~= cur_url then ImageOnlineManager:GetInstance():ReleaseSprite(cur_url) return end if not string.isEmpty(self.online_image_true_url) then ImageOnlineManager:GetInstance():ReleaseSprite(self.online_image_true_url) end self.unity_image.sprite = sprite self.online_image_true_url = self.online_image_url end, reload) end function M:dtor() if not string.isEmpty(self.sprite_true_path) then ImageLoaderManager:GetInstance():ReleaseImage(self.sprite_true_path) self.sprite_true_path = nil end if not string.isEmpty(self.online_image_true_url) then ImageOnlineManager:GetInstance():ReleaseSprite(self.online_image_true_url) self.online_image_true_url = nil end self.unity_image = nil end return M |
通过Texture2D创建Sprite:
1 2 3 4 |
public static Sprite TurnTex2DToSprite(Texture2D texture) { return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); } |