-
前言
在制作播放器得时候我们需要加载网络图片,我们经常使用UnityWebRequest去加载
UnityWebRequest www = new UnityWebRequest(networkPath);
DownloadHandlerTexture download = new DownloadHandlerTexture(true);
www.downloadHandler = download;
yield return www.SendWebRequest();
while (!www.isDone || www.isNetworkError)
yield return new WaitForEndOfFrame();
if (img != null)
{
if (download.texture != null)
{
img.sprite = (Sprite.Create(download.texture, new Rect(0, 0, download.texture.width, download.texture.height), new Vector2(0.5f, 0.5f)));
}
else
{
img.sprite = null;
}
}
www.Dispose();
很多同学经常只管使用,不管回收,加载出来得图片全部加载到内存中,这样内存占用会越来越大,导致游戏卡顿严重。我们就需要引出对象池概念。
-
什么是对象池
对象池中包含若干提前准备好的若干实例,当需要时从对象池中提取,当不需要时,则重新放入对象池。
使用对象池不需要频繁的产生和销毁实例对象,对象池中的实例如果不够程序调用才会继续产生实例,这大大节省了性能。
举个栗子:
I have a plane,I have a bullet, a~, plane fired bullets
飞机发射子弹
做法一,不停得实例化子弹,达到条件则销毁子弹。
做法二,优先创建N个子弹放入容器中,使用得时候取出没有使用的子弹,使用完的子弹设置为未使用状态同时放入到容器中,如果容器中的都使用了,则实例化一个新得放入容器中使用。
做法三,依据做法二的方式,增加一个对象过期时间和对象上限,如果对象没使用的时间超过了过期时间则销毁对象,以此保证内存大小。
-
对象池的使用
这里对加载网络的Sprite图片放入到对象池中使用,并保存到本地
并设置池对象上限,池对象过期时间,池对象优先级。
(1).获取本地存储路径中的图片
private string FolderPath
{
get
{
return Path.Combine(Application.persistentDataPath, "YourFileName");
//return string.Format("{0}/{1}", Application.persistentDataPath, "YourFileName");
}
}
public FileInfo[] GetFolderChildsPath(string folderPath, string pngSuffix, string jpgSuffix)
{
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
DirectoryInfo di = new DirectoryInfo(folderPath);
FileInfo[] files = di.GetFiles();
string fileName;
List<FileInfo> list = new List<FileInfo>();
for (int i = 0; i < files.Length; i++)
{
fileName = files[i].Name.ToLower();
if (fileName.EndsWith(pngSuffix) || fileName.EndsWith(jpgSuffix))
{
list.Add(files[i]);
}
}
return list.ToArray();
}
//初始化获取
public void FileRecv()
{
//存名字用于判断本地是否有这张图片
m_PersistentAllName.Clear();
Log.Info(FolderPath);
FileInfo[] completePath = GetFolderChildsPath(FolderPath, PNGFileSuffix, JPGFileSuffix);
for (int i = 0; i < completePath.Length; i++)
{
//Debug.LogWarning("图片完整路径: " + completePath[i].Name);
m_PersistentAllName.Add(completePath[i].Name);
}
}
m_PersistentAllName ; //存名字用于判断本地是否有这张图片
(2).创建GF的ObjectPool
1.池对象需继承于ObjectBase基类
public class SpriteItemObject : ObjectBase
{
public static SpriteItemObject Create(string name, object target)
{
SpriteItemObject hpBarItemObject = new SpriteItemObject(name, target);
return hpBarItemObject;
}
public SpriteItemObject(string name, object target) : base(name, target)
{
}
protected override void Release(bool isShutdown)
{
Sprite sprite = (Sprite)Target;
if (sprite == null)
{
return;
}
Object.Destroy(sprite);
}
}
对象的基类请查看GameFramework.ObjectPool.ObjectBase
2.创建对象池
private IObjectPool<SpriteItemObject> m_SpriteObjectPool = null;
m_SpriteObjectPool = GameEntry.ObjectPool.CreateSingleSpawnObjectPool<SpriteItemObject>(
"NetworkSprites",
m_InstancePoolCapacity,
m_InstancePoolExpireTime,
m_InstancePoolPriority);
简简单单的一句话就吧对象池创建好了
详细代码请查看UnityGameFramework.Runtime.ObjectPoolComponent
创建池分2种:
1).池类对象能多次被获取 CreateMultiSpawnObjectPool
2).池类对象只能被单次获取 CreateSingleSpawnObjectPool
对象池Manager请查看GameFramework.ObjectPool.ObjectPoolManager
(3).对象池的销毁
记得在你的管理类的释放函数种销毁创建的对象池
if (m_SpriteObjectPool != null)
{
GameEntry.ObjectPool.DestroyObjectPool(m_SpriteObjectPool);
m_SpriteObjectPool = null;
}
(4).获取对象池中的Sprite对象
private Sprite CreateSprite(string fullname, string filename, Texture2D download = null)
{
//从对象池中取出一个对象
SpriteItemObject spriteItemObject = m_SpriteObjectPool.Spawn(filename);
Sprite sprite;
//对象是否为空
if (spriteItemObject != null)
{
//对象不为空直接获取
sprite = (Sprite)spriteItemObject.Target;
}
else
{
//对象为空,并且没有下载的图片数据,直接从本地加载
if (download == null)
download = TextureUtility.LoadTexture(fullname);
//创建sprite
sprite = (Sprite.Create(download, new Rect(0, 0, download.width, download.height), new Vector2(0.5f, 0.5f)));
//sprite放入到SpriteItemObject.Target中存入m_SpriteObjectPool,并设置该对象已被获取
m_SpriteObjectPool.Register(SpriteItemObject.Create(filename, sprite), true);
}
return sprite;
}
(5).回收对象池内的Sprite对象
if (m_SpriteObjectPool == null)
return;
m_SpriteObjectPool.Unspawn(target);
(6).结合LoadNetworkImage使用与回收
public IEnumerator LoadSprites(string networkPath, Image img, string appointName)
{
string fileName;
//指定一个名字
if (string.IsNullOrEmpty(appointName))
{
fileName = networkPath.Substring(networkPath.LastIndexOf('/') + 1);
}
else
{
fileName = ReplaceName(appointName, "/", "_"); ;
}
//本地存在这个名字
if (FindName(fileName))
{
Sprite sprite;
//从本地加载图片,塞入到对象池中
yield return sprite = CreateSprite(Path.Combine(FolderPath, fileName), fileName, null);
if (img)
{
//回收之前img上引用的sprite对象
UnspawnReference(img.sprite);
//重新赋值
img.sprite = sprite;
}
}
//下载保存本地
else
{
if (string.IsNullOrEmpty(networkPath))
{
if (img != null)
{
UnspawnReference(img.sprite);
img.sprite = null;
}
yield break;
}
UnityWebRequest www = new UnityWebRequest(networkPath);
DownloadHandlerTexture download = new DownloadHandlerTexture(true);
www.downloadHandler = download;
yield return www.SendWebRequest();
while (!www.isDone || www.isNetworkError)
yield return new WaitForEndOfFrame();
if (img != null)
{
if (download.texture != null)
{
//回收之前img上引用的sprite对象
UnspawnReference(img.sprite);
string full_path = Path.Combine(FolderPath, fileName);
//获取的网络图片塞到对象池中
yield return img.sprite = CreateSprite(full_path, fileName, download.texture);
//保存到本地
CreatePNG(full_path, download.data, fileName);
}
else
{
img = null;
}
}
www.Dispose();
}
}
-
效果
注意左下角的动态加载的图片
查看对象池中的所有sprite对象
1.名称 2.是否加锁 3.是否正在使用 4.优先级 5.该对象上上次使用的时间戳
第三个如果是false的话,代表该对象已经开始做释放倒计时, 如果在期间没有使用将会自动销毁
3个sprite在使用,1.用户头像 2.歌单头像 3.当前歌曲的头像
1个sprite可以释放
GF大于此版本的对象池的对象基类继承了引用池的对象基类,所以在使用上也有很大的改变
public class SpriteItemObject : ObjectBase { public static SpriteItemObject Create(object target) { SpriteItemObject spriteObject = ReferencePool.Acquire<SpriteItemObject>(); spriteObject.Initialize(target); return spriteObject; } protected override void Release(bool isShutdown) { Sprite sprite = (Sprite)Target; if (sprite == null) { return; } Object.Destroy(sprite); } }
有兴趣的小伙伴可以关注一波
o(* ̄▽ ̄*)ブ