为什么要动态打图集
比如在英雄联盟中的选择英雄界面,有很多的图标供我们选择,而我们进入游戏之后只需要选择的那两三个图标而已,这是如果我们将所有图标都打成图集,就造成内存浪费,因为我们只需要两三个而已,那么我们有什么办法让我们只将要用到的图标进行打图集,其他的不打进图集吗?有的,那就是动态打图集。
效果如下
点击下面三个框中的一个,再点击上面十个图标中的一个,就完成了选择图标,一个个选。
如何进行动态打图集
打图集肯定要有打图集的算法,这里提供了打图集的算法供我们使用,去下载就好了。
图集分块算法地址:https://github.com/DaVikingCode/UnityRuntimeSpriteSheetsGenerator/tree/master/Assets/RuntimeSpriteSheetsGenerator/Scripts
下载AssetPacker文件和RectanglePacking文件就可以了,总共有6个C#脚本,将它拖入到Unity中。
有了上面的打图集算法,我们写个生成图集的类。新建一个名为AssetPackerMgr的空物体,将AssetPackerMgr .cs挂载上去。
using System;
using System.Collections;
using System.Collections.Generic;
using DaVikingCode.AssetPacker;
using UnityEngine;
public class AssetPackerMgr : MonoBehaviour
{
public static AssetPackerMgr Instance { get; private set; }
private Dictionary<string,AssetPacker> _packers = new Dictionary<string, AssetPacker>();
private void Awake()
{
//这样写单例是因为避免存在两个AssetPackerMgr,比如跳到下个场景再跳回来的时候。
if (Instance == null)
{
Instance = this;
GameObject.DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
/// <summary>
/// 生成新图集的方法
/// </summary>
/// <param name="altasName">图集名</param>
/// <param name="paths">路径</param>
/// <param name="complete">打完图集后的回调</param>
public void GentatorNewAltas(string altasName,Dictionary<string,string> paths,Action complete = null)
{
if(paths == null)
return;
AssetPacker packer = new GameObject(altasName).AddComponent<AssetPacker>();
packer.transform.SetParent(transform);
packer.cacheName = altasName;
foreach (KeyValuePair<string,string> path in paths)
{
packer.AddTextureToPack(path.Value,path.Key);
}
packer.Process();
//打完图集后的回调方法,即为异步加载场景
packer.OnProcessCompleted.AddListener(() =>
{
if (complete != null)
complete();
});
_packers.Add(altasName,packer);
}
/// <summary>
/// 获取AssetPacker
/// </summary>
/// <param name="altasName"></param>
/// <returns></returns>
public AssetPacker GetAltas(string altasName)
{
if (_packers.ContainsKey(altasName))
{
return _packers[altasName];
}
else
{
Debug.LogError("can not find altas name is "+altasName);
return null;
}
}
/// <summary>
/// 清除altasName所对应的AssetPacker
/// </summary>
/// <param name="altasName"></param>
public void ClearAltas(string altasName)
{
if (_packers.ContainsKey(altasName))
{
AssetPacker packer = _packers[altasName];
_packers.Remove(altasName);
Destroy(packer.gameObject);
}
else
{
Debug.LogError("can not remove altas,because it can not find,name is"+altasName);
return;
}
}
}
图片中的1~10表示的是上面那10个小图标,打图集的话,肯定要知道图片路径的,所以我定义 了一个图片路径的类。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RuntimeAltasItem : MonoBehaviour
{
public string Path;
}
挂载在这10个图标上。
我这里写个编辑器的扩展方法,来获取这十个图标的路径,因为编辑器下有获取路径的API。
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
public class SpritePathHelper : MonoBehaviour
{
[MenuItem("GameObject/SpritePathHelper",false,0)]
private static void SetPath()
{
foreach (GameObject go in Selection.gameObjects)
{
foreach (Transform trans in go.transform)
{
if(trans.GetComponent<Image>() == null)
continue;
Sprite sprite = trans.GetComponent<Image>().sprite;
string path = AssetDatabase.GetAssetPath(sprite);
//Application.dataPath后面包含Assets,path前面也包含Assets,组合后重复了,所以用Substring(6)从Assets之后取值
path = Application.dataPath + path.Substring(6);
RuntimeAltasItem item = trans.GetComponent<RuntimeAltasItem>();
if (item == null)
item = trans.gameObject.AddComponent<RuntimeAltasItem>();
item.Path = path;
}
}
}
}
我们选中SelectView,然后点击GameObject/SpritePathHelper就完成了获取图片路径并赋值了。
SelectView上挂载一个同名脚本,用来给每个图标添加监听事件,点击后就执行委托事件。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SelectView : MonoBehaviour
{
private Action<Sprite, string> _onSelected;
// Start is called before the first frame update
void Start()
{
foreach (var child in GetComponentsInChildren<Button>())
{
child.onClick.AddListener(() =>
{
Sprite sprite = child.GetComponent<Image>().sprite;
RuntimeAltasItem item = child.GetComponent<RuntimeAltasItem>();
if (_onSelected != null)
_onSelected(sprite, item.Path);
});
}
}
/// <summary>
/// 提供一个添加委托的方法
/// </summary>
/// <param name="selected"></param>
public void AddSelectedListener(Action<Sprite, string> selected)
{
_onSelected = selected;
}
}
新建一个ShowItem.cs,后面会通过代码添加该脚本到Icon1,Icon2,Icon3的。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ShowItem : MonoBehaviour
{
public ShowName ID { get; private set; }
private Image _image;
private RuntimeAltasItem _altasItem;
/// <summary>
/// 初始化数据
/// </summary>
/// <param name="id"></param>
public void Init(ShowName id)
{
ID = id;
_image = GetComponent<Image>();
_altasItem = GetComponent<RuntimeAltasItem>();
}
/// <summary>
/// 设置图片
/// </summary>
/// <param name="sprite"></param>
public void SetSprite(Sprite sprite)
{
_image.sprite = sprite;
}
/// <summary>
/// 给Button添加点击事件
/// </summary>
/// <param name="selected"></param>
public void AddListener(Action selected)
{
gameObject.GetComponent<Button>().onClick.AddListener(()=>selected());
}
/// <summary>
/// 返回ID和图片路径
/// </summary>
/// <returns></returns>
public KeyValuePair<string, string> GetData()
{
return new KeyValuePair<string, string>(ID.ToString(),_altasItem.Path);
}
}
public enum ShowName
{
ICON_1,
ICON_2,
ICON_3,
}
新建一个SelectShowView 挂载到Show上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SelectShowView : MonoBehaviour
{
private ShowItem _selectedItem;
private RuntimeAltasItem _altasItem;
// Start is called before the first frame update
void Start()
{
ShowName id = ShowName.ICON_1;
foreach (Transform trans in transform)
{
RuntimeAltasItem altasItem = trans.gameObject.AddComponent<RuntimeAltasItem>();
ShowItem item =trans.gameObject.AddComponent<ShowItem>();
item.Init(id);
id++;
item.AddListener(() =>
{
_selectedItem = item;
_altasItem = altasItem;
});
}
}
public void SetShowItem(Sprite sprite,string path)
{
_selectedItem.SetSprite(sprite);
_altasItem.Path = path;
}
/// <summary>
/// 返回存要显示的图片的ID和图片路径的字典
/// </summary>
/// <returns></returns>
public Dictionary<string,string> GetPaths()
{
//存要显示的图片的ID和图片路径的字典
Dictionary<string,string> temp = new Dictionary<string, string>();
KeyValuePair<string, string> tempPair;
foreach (ShowItem item in GetComponentsInChildren<ShowItem>())
{
tempPair = item.GetData();
temp.Add(tempPair.Key,tempPair.Value);
}
return temp;
}
}
新建一个名为Complete的Button,添加SelectedComplete .cs脚本,当选择完图标的时候,可以执行异步加载场景。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SelectedComplete : MonoBehaviour
{
public void Init(Func<Dictionary<string,string>> getPaths,LoadingView loadingView)
{
GetComponent<Button>().onClick.AddListener(() =>
{
loadingView.SetActiveState(true);
AssetPackerMgr.Instance.GentatorNewAltas("Test",getPaths(),()=>loadingView.SwitchScene(SceneName.Game.ToString()));
});
}
}
public enum SceneName
{
SelectView,
Game
}
新建一个ShowView.cs,挂载在Canvas上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ShowView : MonoBehaviour
{
private SelectShowView _selectShowView;
void Start()
{
GetComponentInChildren<SelectView>().AddSelectedListener(SetShowItem);
_selectShowView = GetComponentInChildren<SelectShowView>();
GetComponentInChildren<SelectedComplete>().Init(()=>
_selectShowView.GetPaths(),
GetComponentInChildren<LoadingView>(true));
}
/// <summary>
/// 设置将上面选择的图标显示在下面的Image上
/// </summary>
/// <param name="sprite"></param>
/// <param name="path"></param>
private void SetShowItem(Sprite sprite,string path)
{
if(_selectShowView == null)
return;
_selectShowView.SetShowItem(sprite,path);
}
}
新建一个名为Loading的Image,设置成纯色的全屏背景充当异步加载的过度背景,上面添加LoadingView .cs,在Loading下面新建一个Text子物体,用于制作异步加载的过渡动画。Loading先设置隐藏。
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class LoadingView : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GetComponentInChildren<Text>().DOText("Loading...", 3).SetLoops(-1, LoopType.Restart);
}
/// <summary>
/// 设置加载界面的隐藏显示
/// </summary>
/// <param name="isShow"></param>
public void SetActiveState(bool isShow)
{
gameObject.SetActive(isShow);
}
/// <summary>
/// 异步加载场景
/// </summary>
/// <param name="sceneName">需要加载的场景</param>
public void SwitchScene(string sceneName)
{
StartCoroutine(LoadScene(sceneName));
}
private IEnumerator LoadScene(string scenceName)
{
AsyncOperation async = SceneManager.LoadSceneAsync(scenceName);
async.allowSceneActivation = false;
while (!async.isDone)
{
if (async.progress >= 0.9f)
{
yield return new WaitForSeconds(2);
async.allowSceneActivation = true;//为true的时候才会跳转场景
}
yield return new WaitForSeconds(0.5f);
}
}
}
上面的代码中的文本动画用到了Dotween插件,可自行去下载,或者可以自己手写一个简单的过渡动画也可以。
以上就是动态打图集并演示的过程。