Unity 动态打图集并完成小Demo的实现

为什么要动态打图集

比如在英雄联盟中的选择英雄界面,有很多的图标供我们选择,而我们进入游戏之后只需要选择的那两三个图标而已,这是如果我们将所有图标都打成图集,就造成内存浪费,因为我们只需要两三个而已,那么我们有什么办法让我们只将要用到的图标进行打图集,其他的不打进图集吗?有的,那就是动态打图集。

效果如下
点击下面三个框中的一个,再点击上面十个图标中的一个,就完成了选择图标,一个个选。
在这里插入图片描述

如何进行动态打图集

打图集肯定要有打图集的算法,这里提供了打图集的算法供我们使用,去下载就好了。

图集分块算法地址: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插件,可自行去下载,或者可以自己手写一个简单的过渡动画也可以。

在这里插入图片描述
以上就是动态打图集并演示的过程。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity寻路算法的实现和封装可以通过Unity自带的NavMesh系统完成。NavMesh是一种用于自动寻路的数据结构,能够在场景中的不规则地形上为单位提供路径。 要实现和封装寻路算法,首先需要在Unity中建立NavMesh。可以通过在场景中创建一个NavMesh Surface组件,然后在其设置中选择需要生成NavMesh的地形和区域。 接下来,在需要的游戏对象上添加NavMeshAgent组件,这将用于完成单位的寻路任务。在NavMeshAgent组件中,可以设置单位的移动速度、转向速度和停止距离等参数,以及将目标点设置为单位希望到达的位置。 在代码中,可以通过调用NavMeshAgent的SetDestination方法来设置单位的目标点,然后NavMeshAgent将自动计算路径并开始移动。而NavMeshAgent的路径计算算法使用的就是A*寻路算法,该算法可以高效地找到最优路径。 为了封装寻路算法,可以创建一个自定义脚本,将NavMeshAgent的操作封装在其中。该脚本可以包含设置目标点、启动寻路、停止寻路等方法,并可将NavMeshAgent的功能暴露给其他对象进行调用。 最后,为了演示寻路算法的动态效果,可以创建一个简单的场景。在场景中放置一些障碍物和目标点,然后让单位在场景中随机移动或根据玩家的输入来移动。当单位移动时,可以实时显示单位的路径和当前位置,通过不断更新路径和位置,可以展示出寻路算法的实时效果。 这样,通过Unity的NavMesh系统以及自定义脚本的封装,就可以实现和展示Unity寻路算法的实现和封装,并带有动态演示demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值