游戏资源管理实现

思考并回答以下问题:

游戏中的资源量是必须要考虑的问题,游戏品质的好坏都是通过资源表现的,这些资源的管理,作为开发者必须要处理的。对于游戏资源管理,通常的做法是简单的封装几个接口用于资源的加载,如果只是做个 Demo,这样做是没问题的,但是如果做产品,对于资源的需求量是非常大的,而且各个资源的加载也会由于使用不当,出现各种问题,而且游戏讲究的是团队协作,不同的人会有不同的需求,简单的封装几个接口很难满足需求,如果没有一个统一的资源架构管理,代码会出现各种接口版本,最后会出现大量的冗余代码,这样对游戏产品运行效率会产生影响。

另外,还要考虑游戏资源的动态加载更新,主要是为了减少游戏包体的大小,Unity3D 虽然为用户提供了 AssetBundle 资源打包,方便用户将资源打包上传到资源服务器,在游戏启动时会通过本地存放资源的 MD5 文本文件与服务器的保存资源最新的 MD5 码的文本文件作对比,根据其资源对应的 MD5 码不同,将新的资源下载到本地使用,同时将资源文件代替本地的资源文件。我们在封装资源管理类时,也是从产品的角度考虑资源管理问题。

下面开始讲解如何进行资源管理的代码封装,我们对资源管理的封装做了一个比较完善的思考,代码模块如下图所示:

enter image description here

下面来告诉读者为什么这么设计。我们在游戏开发时,对于 Unity 资源,每个资源都是一个 GameObject,只是单独的 GameObject 显然不能满足需求,因为资源既可以是 Scene,也可以是 Prefab,同时也可以是 Asset 文件。这就会涉及到不同的资源类型,如何表示这些资源类型,比如我测试的时候可以使用 prefab,而在正式发布时采用 asset,如果不做分类,在游戏发布时还要修改接口,非常麻烦。但如果设计一个通用的接口,对于资源类型可以使用枚举进行表示,有了这些想法后,开始逐步去实施我们的思想。

首先需要设计一个 ResourceUnit 模块,它是资源的基本单位,也是程序自己封装的资源基本单位,ResourceUnit 类的代码如下所示:

1
2
3
4
5
6
7
public enum ResourceType
{
ASSET,
PREFAB,
LEVELASSET,
LEVEL,
}

上面就是我们定义的资源枚举,每一个加载的资源都是一个 ResourceUnit,它可以是 assetbundle,可以是 prefab 实例化,当然也可以是 scene。下面继续完善 ResourceUnit 类,它的实现代码如下所示:

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
 public class ResourceUnit : IDisposable
{
private string mPath;
private Object mAsset;
private ResourceType mResourceType;
private List<ResourceUnit> mNextLevelAssets;
private AssetBundle mAssetBundle;
private int mReferenceCount;

internal ResourceUnit(AssetBundle assetBundle, int assetBundleSize, Object asset, string path, ResourceType resourceType)
{
mPath = path;
mAsset = asset;
mResourceType = resourceType;
mNextLevelAssets = new List<ResourceUnit>();
mAssetBundle = assetBundle;
mAssetBundleSize = assetBundleSize;
mReferenceCount = 0;
}

public List<ResourceUnit> NextLevelAssets
{
get
{
return mNextLevelAssets;
}

internal set
{
foreach (ResourceUnit asset in value)
{
mNextLevelAssets.Add(asset);
}
}
}

public int ReferenceCount
{
get
{
return mReferenceCount;
}
}
//增加引用计数
public void addReferenceCount()
{
++mReferenceCount;
foreach (ResourceUnit asset in mNextLevelAssets)
{
asset.addReferenceCount();
}
}
//减少引用计数
public void reduceReferenceCount()
{
--mReferenceCount;

foreach (ResourceUnit asset in mNextLevelAssets)
{
asset.reduceReferenceCount();
}
if (isCanDestory())
{
Dispose();
}
}

public bool isCanDestory() { return (0 == mReferenceCount); }

public void Dispose()
{
ResourceCommon.Log("Destory " + mPath);

if (null != mAssetBundle)
{
//mAssetBundle.Unload(true);
mAssetBundle = null;
}
mNextLevelAssets.Clear();
mAsset = null;
}
}

ResourceUnit 类同时实现了资源的引用计数,该设计思想跟内存的使用比较类似,这样便于程序知道对于加载的资源什么时候销毁,什么时候可以继续使用,它还声明了一些变量,比如资源的名字等。

另外,程序要加载资源,首先要知道资源加载路径,其次要知道资源类型是 asset bundle 还是 prefab。我们通常会使用一个类专用于资源路径的设置,包括获取资源文件夹、资源路径、获取资源文件以及获取 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
     public class ResourceCommon
{
public static string textFilePath = Application.streamingAssetsPath;

public static string assetbundleFilePath = Application.dataPath + "/assetbundles/";

public static string assetbundleFileSuffix = ".bytes";

public static string DEBUGTYPENAME = "Resource";

//根据资源路径获取资源名称
public static string getResourceName(string resPathName)
{
int index = resPathName.LastIndexOf("/");
if (index == -1)
return resPathName;
else
{
return resPathName.Substring(index + 1, resPathName.Length - index - 1);
}
}
//获取文件名字
public static string getFileName(string fileName)
{
int index = fileName.IndexOf(".");
if (-1 == index)
throw new Exception("can not find .!!!");
return fileName.Substring(0, index);
}
//获取文件名字
public static string getFileName(string filePath, bool suffix)
{
if (!suffix)
{
string path = filePath.Replace("\\", "/");
int index = path.LastIndexOf("/");
if (-1 == index)
throw new Exception("can not find .!!!");
int index2 = path.LastIndexOf(".");
if (-1 == index2)
throw new Exception("can not find /!!!");
return path.Substring(index + 1, index2 - index - 1);
}
else
{
string path = filePath.Replace("\\", "/");
int index = path.LastIndexOf("/");
if (-1 == index)
throw new Exception("can not find /!!!");
return path.Substring(index + 1, path.Length - index - 1);
}
}
//获取文件夹
public static string getFolder(string path)
{
path = path.Replace("\\", "/");
int index = path.LastIndexOf("/");
if (-1 == index)
throw new Exception("can not find /!!!");
return path.Substring(index + 1, path.Length - index - 1);
}
//获取文件后缀
public static string getFileSuffix(string filePath)
{
int index = filePath.LastIndexOf(".");
if (-1 == index)
throw new Exception("can not find Suffix!!! the filePath is : " + filePath);
return filePath.Substring(index + 1, filePath.Length - index - 1);
}
//获取文件
public static void getFiles(string path, bool recursion, Dictionary<string, List<string>> allFiles, bool useSuffix, string suffix)
{
if (recursion)
{
string[] dirs = Directory.GetDirectories(path);
foreach (string dir in dirs)
{
if (getFolder(dir) == ".svn")
continue;
getFiles(dir, recursion, allFiles, useSuffix, suffix);
}
}

string[] files = Directory.GetFiles(path);
foreach (string file in files)
{
string fileSuffix = getFileSuffix(file);
if (fileSuffix == "meta" || fileSuffix == "dll")
continue;
if (useSuffix && fileSuffix != suffix)
continue;
string relativePath = file.Replace("\\", "/");
relativePath = relativePath.Replace(Application.dataPath, "");
string fileName = getFileName(file, true);
if (allFiles.ContainsKey(fileName))
{
allFiles[fileName].Add(relativePath);
}
else
{
List<string> list = new List<string>();
list.Add(relativePath);
allFiles.Add(fileName, list);
}
}
}
//检查文件夹
public static void CheckFolder(string path)
{
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
//获取文件路径
public static string getPath(string filePath)
{
string path = filePath.Replace("\\", "/");
int index = path.LastIndexOf("/");
if (-1 == index)
throw new Exception("can not find /!!!");
return path.Substring(0, index);
}
//获取本地路径
public static string getLocalPath(string path)
{
string localPath = string.Format("{0}/{1}", Application.persistentDataPath, path);
if (!File.Exists(localPath))
{
if (Application.platform == RuntimePlatform.Android)
{
localPath = string.Format("{0}/{1}", Application.streamingAssetsPath, path);
}
else if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer)
{
localPath = string.Format("file://{0}/{1}", Application.streamingAssetsPath, path);
}
return localPath;
}
return "file:///" + localPath;
}
//获取AssetBundles文件字节
public static byte[] getAssetBundleFileBytes(string path, ref int size)
{
string localPath;

//Andrio跟IOS环境使用沙箱目录
if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
{
localPath = string.Format("{0}/{1}", Application.persistentDataPath, path + ResourceCommon.assetbundleFileSuffix);
}
//Window下使用assetbunlde资源目录
else
{
localPath = ResourceCommon.assetbundleFilePath + path + ResourceCommon.assetbundleFileSuffix;
}

Debug.Log(localPath);

//首先检测沙箱目录中是否有更新资源
if (File.Exists(localPath))
{
try
{
FileStream bundleFile = File.Open(localPath, FileMode.Open, FileAccess.Read);
byte[] bytes = new byte[bundleFile.Length];
bundleFile.Read(bytes, 0, (int)bundleFile.Length);
size = (int)bundleFile.Length;
bundleFile.Close();
return bytes;
}
catch (Exception e)
{
Debug.LogError(e.Message);
return null;
}
}
//原始包中
else
{
TextAsset bundleFile = Resources.Load(path) as TextAsset;

if (null == bundleFile)
Debug.LogError("load : " + path + " bundleFile error!!!");
size = bundleFile.bytes.Length;
return bundleFile.bytes;
}
}


以上封装了资源模块通用的一些接口,便于我们在开发中使用。在游戏处理资源过程中,还需要考虑一个问题,程序在请求资源时,要知道资源是在加载过程中,还是已经卸载完成。在程序中会使用一个枚举值进行设置,用于通知程序资源的使用状态,同时会使用委托函数进行具体回调操作,比如资源加载完成,我要知道什么时候加载完成了。根据这些设想,我们用一个类把它实现出来,这个也就是 Request 类,代码实现如下:

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
//资源请求类型
public enum RequestType
{
LOAD,
UNLOAD,
LOADLEVEL,
UNLOADLEVEL,
}

class Request
{
internal string mFileName; //请求资源相对Assets/完整路径名称
internal ResourceType mResourceType;
//委托回调函数
internal ResourcesManager.HandleFinishLoad mHandle;
internal ResourcesManager.HandleFinishLoadLevel mHandleLevel;
internal ResourcesManager.HandleFinishUnLoadLevel mHandleUnloadLevel;
internal RequestType mRequestType;
internal ResourceAsyncOperation mResourceAsyncOperation;

//构造函数
internal Request(string fileName, ResourceType resourceType, ResourcesManager.HandleFinishLoad handle, RequestType requestType, ResourceAsyncOperation operation)
{
mFileName = fileName;
mResourceType = resourceType;
mHandle = handle;
mRequestType = requestType;
mResourceAsyncOperation = operation;
}
//构造函数
internal Request(string fileName, ResourceType resourceType, ResourcesManager.HandleFinishLoadLevel handle, RequestType requestType, ResourceAsyncOperation operation)
{
mFileName = fileName;
mResourceType = resourceType;
mHandleLevel = handle;
mRequestType = requestType;
mResourceAsyncOperation = operation;
}

}

场景与场景之间进行切换过渡时,尤其对于比较大的资源加载,我们通常使用一个进度条进行过渡,为此在框架中封装了一个通用的资源过渡类,代码实现如下:

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
public class ResourceAsyncOperation
{
internal RequestType mRequestType;
internal int mAllDependencesAssetSize;
internal int mLoadDependencesAssetSize;
internal bool mComplete;
public AsyncOperation asyncOperation;

internal ResourceUnit mResource;

internal ResourceAsyncOperation(RequestType requestType)
{
mRequestType = requestType;
mAllDependencesAssetSize = 0;
mLoadDependencesAssetSize = 0;
mComplete = false;
asyncOperation = null;
mResource = null;
}

public bool Complete
{
get
{
return mComplete;
}
}
//资源加载进度
public int Prograss
{
get
{
if (mComplete)
return 100;
else if (0 == mLoadDependencesAssetSize)
return 0;
else
{
//使用assetbundle
if (ResourcesManager.Instance.UsedAssetBundle)
{
if (RequestType.LOADLEVEL == mRequestType)
{
int depsPrograss = (int)(((float)mLoadDependencesAssetSize / mAllDependencesAssetSize) * 100);
int levelPrograss = asyncOperation != null ? (int)((float)asyncOperation.progress * 100.0f) : 0;
return (int)(depsPrograss * 0.8) + (int)(levelPrograss * 0.2);
}
else
{
return (int)(((float)mLoadDependencesAssetSize / mAllDependencesAssetSize) * 100);
}
}
//不使用
else
{
if (RequestType.LOADLEVEL == mRequestType)
{
int levelPrograss = asyncOperation != null ? (int)((float)asyncOperation.progress * 100.0f) : 0;
return levelPrograss;
}
else
{
return 0;
}
}
}
}
}
}

关于资源的架构思想,我们基本已经完成了,接下来就要考虑如何使用了,但不能直接使用它们,因为它们既不是单例,也不是静态类,它没有提供对外接口,那怎么办呢?这就要想到管理类,对,我们可以使用管理类提供对外的接口,也就是 ResourceManager 类,管理类是对外提供接口的,对于管理类,它通常是单例模式,我们把游戏中的单例分为两种:一种是继承 mono 的单例,一种是不继承 mono 的。我们设计的资源管理类是可以挂接到对象上的,这主要是为了资源更新时使用的。管理类它可以加载资源、销毁资源等等。它的内容实现代码如下:

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
     public class ResourcesManager : UnitySingleton<ResourcesManager>
{
//是否通过assetbundle加载资源
public bool UsedAssetBundle = false;

private bool mInit = false;
private int mFrameCount = 0;
private Request mCurrentRequest = null;
private Queue<Request> mAllRequests = new Queue<Request>();

//保存读取的Resource信息
//private AssetInfoManager mAssetInfoManager = null;
private Dictionary<string, string> mResources = new Dictionary<string, string>();
//加载的资源信息
private Dictionary<string, ResourceUnit> mLoadedResourceUnit = new Dictionary<string, ResourceUnit>();

public delegate void HandleFinishLoad(ResourceUnit resource);
public delegate void HandleFinishLoadLevel();
public delegate void HandleFinishUnLoadLevel();
private void Start()
{

}

public void Init()
{

mInit = true;
}

public void Update()
{
if (!mInit)
return;

if (null == mCurrentRequest && mAllRequests.Count > 0)
handleRequest();

++mFrameCount;
if (mFrameCount == 300)
{
mFrameCount = 0;
}
}

private void handleRequest()
{
//使用assetbundle打包功能
if (UsedAssetBundle)
{
mCurrentRequest = mAllRequests.Dequeue();

//相对Asset的完整资源路径
string fileName = mCurrentRequest.mFileName;

switch (mCurrentRequest.mRequestType)
{
case RequestType.LOAD:
{
switch (mCurrentRequest.mResourceType)
{
case ResourceType.ASSET:
case ResourceType.PREFAB:
{
if (mLoadedResourceUnit.ContainsKey(fileName))
{

mCurrentRequest.mResourceAsyncOperation.mComplete = true;
mCurrentRequest.mResourceAsyncOperation.mResource = mLoadedResourceUnit[fileName] as ResourceUnit;

if (null != mCurrentRequest.mHandle)
mCurrentRequest.mHandle(mLoadedResourceUnit[fileName] as ResourceUnit);
handleResponse();
}
else
{

}
}
break;
case ResourceType.LEVELASSET:
{

}
break;
case ResourceType.LEVEL:
{
//
}
break;
}
}
break;
case RequestType.UNLOAD:
{
if (!mLoadedResourceUnit.ContainsKey(fileName))
Debug.LogError("can not find " + fileName);
else
{

}
handleResponse();
}
break;
case RequestType.LOADLEVEL:
{
StartCoroutine(_loadLevel(fileName, mCurrentRequest.mHandleLevel, ResourceType.LEVEL, mCurrentRequest.mResourceAsyncOperation));
}
break;
case RequestType.UNLOADLEVEL:
{
if (!mLoadedResourceUnit.ContainsKey(fileName))
Debug.LogError("can not find level " + fileName);
else
{
if (null != mCurrentRequest.mHandleUnloadLevel)
mCurrentRequest.mHandleUnloadLevel();
}
handleResponse();
}
break;
}
}
//不使用打包
else
{
mCurrentRequest = mAllRequests.Dequeue();

switch (mCurrentRequest.mRequestType)
{
case RequestType.LOAD:
{
switch (mCurrentRequest.mResourceType)
{
case ResourceType.ASSET:
case ResourceType.PREFAB:
{
//暂时不处理,直接使用资源相对路径

}
break;
case ResourceType.LEVELASSET:
{

}
break;
case ResourceType.LEVEL:
{

}
break;
}
}
break;
case RequestType.UNLOAD:
{
handleResponse();
}
break;
case RequestType.LOADLEVEL:
{
StartCoroutine(_loadLevel(mCurrentRequest.mFileName, mCurrentRequest.mHandleLevel, ResourceType.LEVEL, mCurrentRequest.mResourceAsyncOperation));
}
break;
case RequestType.UNLOADLEVEL:
{
if (null != mCurrentRequest.mHandleUnloadLevel)
mCurrentRequest.mHandleUnloadLevel();
handleResponse();
}
break;
}
}
}

private void handleResponse()
{
mCurrentRequest = null;
}

//传入Resources下相对路径名称 例如Resources/Game/Effect1 传入Game/Effect1
public ResourceUnit loadImmediate(string filePathName, ResourceType resourceType, string archiveName = "Resources")
{
//使用assetbundle打包
if (UsedAssetBundle)
{
//添加Resource
string completePath = "Resources/" + filePathName;
//加载本身预制件
ResourceUnit unit = _LoadImmediate(completePath, resourceType);

return unit;
}
//不使用
else
{
Object asset = Resources.Load(filePathName);
ResourceUnit resource = new ResourceUnit(null, 0, asset, null, resourceType);
return resource;
}

}
//加载场景
public ResourceAsyncOperation loadLevel(string fileName, HandleFinishLoadLevel handle, string archiveName = "Level")
{

{
ResourceAsyncOperation operation = new ResourceAsyncOperation(RequestType.LOADLEVEL);
mAllRequests.Enqueue(new Request(fileName, ResourceType.LEVEL, handle, RequestType.LOADLEVEL, operation));
return operation;
}
}

private IEnumerator _loadLevel(string path, HandleFinishLoadLevel handle, ResourceType resourceType, ResourceAsyncOperation operation)
{
//使用assetbundle打包
if (UsedAssetBundle)
{
//加载场景assetbundle
int scenAssetBundleSize = 0;
byte[] binary = ResourceCommon.getAssetBundleFileBytes(path, ref scenAssetBundleSize);
AssetBundle assetBundle = AssetBundle.LoadFromMemory(binary);
if (!assetBundle)
Debug.LogError("create scene assetbundle " + path + "in _LoadImmediate failed");

//添加场景大小
operation.mLoadDependencesAssetSize += scenAssetBundleSize;

AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(ResourceCommon.getFileName(path, false));
operation.asyncOperation = asyncOperation;
yield return asyncOperation;

handleResponse();

operation.asyncOperation = null;
operation.mComplete = true;
operation.mResource = null;

if (null != handle)
handle();

}
//不使用
else
{
ResourceUnit level = new ResourceUnit(null, 0, null, path, resourceType);

//获取加载场景名称
string sceneName = ResourceCommon.getFileName(path, true);
AsyncOperation asyncOperation = Application.LoadLevelAsync(sceneName);
operation.asyncOperation = asyncOperation;
yield return asyncOperation;

handleResponse();

operation.asyncOperation = null;
operation.mComplete = true;

if (null != handle)
handle();
}
}

//单个资源加载
ResourceUnit _LoadImmediate(string fileName, ResourceType resourceType)
{
//没有该资源,加载
if (!mLoadedResourceUnit.ContainsKey(fileName))
{
//资源大小
int assetBundleSize = 0;
byte[] binary = ResourceCommon.getAssetBundleFileBytes(fileName, ref assetBundleSize);
AssetBundle assetBundle = AssetBundle.LoadFromMemory(binary);

if (!assetBundle)
Debug.LogError("create assetbundle " + fileName + "in _LoadImmediate failed");

Object asset = assetBundle.LoadAsset(fileName);
if (!asset)
Debug.LogError("load assetbundle " + fileName + "in _LoadImmediate failed");

ResourceUnit ru = new ResourceUnit(assetBundle, assetBundleSize, asset, fileName, resourceType);

//添加到资源中
mLoadedResourceUnit.Add(fileName, ru);

return ru;
}
else
{
return mLoadedResourceUnit[fileName];
}
}

资源管理类进行到这里其实还没有完成,有的读者可能会说,UI 资源的处理,比如要把一个 UI 资源结点动态的挂接到父类的下面,该如何处理?这问题提的非常好,我们在资源管理框架中会专用于 UI 资源的类处理。代码实现如下:

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
    public class LoadUiResource
{
public static GameObject LoadRes(Transform parent,string path)
{
if(CheckResInDic(path))
{
if(GetResInDic(path) != null){
return GetResInDic(path);
}
else{
LoadResDic.Remove(path);
}
}

GameObject objLoad = null;

ResourceUnit objUnit = ResourcesManager.Instance.loadImmediate(path, ResourceType.PREFAB);

if (objUnit == null || objUnit.Asset == null)
{
Debug.LogError("load unit failed" + path);
return null;
}
objLoad = GameObject.Instantiate(objUnit.Asset) as GameObject;

objLoad.transform.parent = parent;
objLoad.transform.localScale = Vector3.one;
objLoad.transform.localPosition = Vector3.zero;
LoadResDic.Add(path,objLoad);
return objLoad;
}

//创建窗口子对象,不加入资源管理
public static GameObject AddChildObject(Transform parent, string path)
{
GameObject objLoad = null;

ResourceUnit objUnit = ResourcesManager.Instance.loadImmediate(path, ResourceType.PREFAB);
if (objUnit == null || objUnit.Asset == null)
{
Debug.LogError("load unit failed" + path);
return null;
}
objLoad = GameObject.Instantiate(objUnit.Asset) as GameObject;
objLoad.transform.parent = parent;
objLoad.transform.localScale = Vector3.one;
objLoad.transform.localPosition = Vector3.zero;

return objLoad;
}
//删除所有的孩子
public static void ClearAllChild(Transform transform)
{
while (transform.childCount > 0)
{
GameObject.DestroyImmediate(transform.GetChild(0).gameObject);
}
transform.DetachChildren();
}

public static void ClearOneChild(Transform transform,string name)
{
for (int i = 0; i < transform.childCount; i++)
{
if (transform.GetChild(i).gameObject.name == name)
{
GameObject.DestroyImmediate(transform.GetChild(i).gameObject);
}
}
}
//删除加载
public static void DestroyLoad(string path)
{
if(LoadResDic == null || LoadResDic.Count == 0)
return;
GameObject obj = null;
if (LoadResDic.TryGetValue(path, out obj) && obj != null)
{
GameObject.DestroyImmediate(obj);
LoadResDic.Remove(path);
//System.GC.Collect();
}
}

public static void DestroyLoad(GameObject obj)
{
if(LoadResDic == null || LoadResDic.Count == 0)
return;
if(obj == null)
return;
foreach(string key in LoadResDic.Keys)
{
GameObject objLoad;
if(LoadResDic.TryGetValue(key,out objLoad) && objLoad == obj)
{
GameObject.DestroyImmediate(obj);
LoadResDic.Remove(key);
break;
}
}
}

//获取在目录中的资源
public static GameObject GetResInDic(string path)
{
if(LoadResDic == null || LoadResDic.Count == 0)
return null;
GameObject obj = null ;
if(LoadResDic.TryGetValue(path,out obj))
{
return obj;
}
return null;
}
//检查资源是否存在
public static bool CheckResInDic(string path)
{
if(LoadResDic == null || LoadResDic.Count == 0)
return false;
return LoadResDic.ContainsKey(path);
}

public static void Clean()
{
if(LoadResDic == null || LoadResDic.Count == 0)
return;
for(int i = LoadResDic.Count - 1;i >=0;i--)
{
GameObject obj = LoadResDic.ElementAt(i).Value ;
if( obj != null)
{
GameObject.DestroyImmediate(obj);
}
}
LoadResDic.Clear();
}

public static Dictionary<string,GameObject> LoadResDic = new Dictionary<string, GameObject>();

}

该类主要作用是提供了加载 UI 资源的接口,同时会将资源放到字典中便于统一处理。

这样整个资源管理的设计就完成了,在使用时需要把 ResourceManager 类挂接到对象上,目的是为了同资源更新模块结合起来。

0%