当前位置:网站首页>《学Unity的猫》——第十二章:使用Unity制作背包,皮皮的梦想背包

《学Unity的猫》——第十二章:使用Unity制作背包,皮皮的梦想背包

2020-12-07 21:05:07 林新发

简介:我是一名Unity游戏开发工程师,皮皮是我养的猫,会讲人话,它接到了喵星的特殊任务:学习编程,学习Unity游戏开发。
于是,发生了一系列有趣的故事。
在这里插入图片描述

12.1 皮皮的梦想背包

皮皮:“太阳天空照,花儿对我笑,小鸟说早早早,你为什么背着小书包?”
我:“我去上ang班,天天不迟到,爱学习爱工作,要不哪里有钱买猫粮。”
皮皮:“包里都装什么东西呀?有吃的吗?”
我打开我的双肩包:“没有吃的,有雨伞、水杯、工牌、纸巾、移动电源、充电器、笔、本子、各种卡,还有水电煤的单子。”
皮皮:“我也想要一个背包,不过我想要里面装满了我的玩具和爱吃的食物。”
我:“现在就给你做一个吧。”
在这里插入图片描述
模块设计如下
在这里插入图片描述
本工程使用的Unity版本为2020.1.14f1c1 (64-bit),工程已上传到GitHub,感兴趣的同学可以下载下来学习。
GitHub地址:https://github.com/linxinfa/Unity-BackpackDemo
在这里插入图片描述

12.2 准备道具图片

在这里插入图片描述
皮皮:“你这道具图片也太没诚意了。”
我:“不要在意这些细节。”
我们需要把图片放到Unity工程中,如下,放在Assets/Atlas/Bag文件夹中。
在这里插入图片描述
皮皮:“为什么不放在Resources文件夹中呢,这样可以直接通过Resources.Load加载图片。”
我:“因为后面我要将这些图片打成一个图集,放在Resources中的图片是不会被打进图集中的。”

12.3 UGUI打图集

UGUI中,我们可以使用ImageRawImage组件来显示图片。
皮皮:“RawImageImage有什么区别呢?”
RawImageImage都继承MaskableGraphic,但ImageRawImage的封装重一些。
RawImage只为我们提供了修改UV的方法,主要用来显示未加工的图片,比如背景图;而Image提供了四种ImageTypeSimple(普通)、Sliced(切割)、Tiled(平铺)、Filled(填充),显示图片的方式丰富;另外,Image支持精灵图的图集合批,可以减少DrawCall
道具种类繁多,道具图片可以打在一个图集中,这样显示多个道具图片可以合成一个批次显示,提升性能。
皮皮:“问题来了,怎么打图集呢?”

12.3.1 设置图集模式:Always Enabled(Legacy Sprite Packer)

点击菜单Edit - Project Settings...,打开Project Settings窗口。
在这里插入图片描述
点击Editor标签页,将Sprite PackerMode选为Always Enabled(Legacy Sprite Packer)
在这里插入图片描述

12.3.2 设置图片为Sprite(2D and UI)类型

将图片的Texture Type设置为为Sprite(2D and UI)
在这里插入图片描述
设置成功后,会有个小箭头,点开可以看到对应的Sprite
在这里插入图片描述

12.3.3 设置Packing Tag

想要把多张图片打在一个图集中,需要将这些图片的Packing Tag设置为相同的值。
比如都设置成Bag
在这里插入图片描述
不过这样每次都手动设置有点麻烦,写个Editor工具监听图片导入,自动修改图片格式,并设置Packing Tag为所在文件夹名字。
代码如下,将其保存为PostTexture.cs放在Editor文件夹中。
在这里插入图片描述

using UnityEngine;
using UnityEditor;
using System.IO;

public class PostTexture : AssetPostprocessor
{
    
    /// <summary>
    /// 监听图片导入,自动修改图片格式,并设置Packing Tag为所在文件夹名字
    /// </summary>
    /// <param name="texture"></param>
    void OnPostprocessTexture(Texture2D texture)
    {
    
        var dirName = Path.GetDirectoryName(assetPath);
        // 文件夹名字作为图集名字
        string atlasName = new DirectoryInfo(dirName).Name;
        TextureImporter textureImporter = assetImporter as TextureImporter;
        // 设置为Sprite(2D and UI)
        textureImporter.textureType = TextureImporterType.Sprite;
        // 设置设置Packing Tag为图集名字
        textureImporter.spritePackingTag = atlasName;
        // 不使用mipmap,否则图片可能会变模糊
        textureImporter.mipmapEnabled = false;
    }
}
12.3.4 使用Sprite Packer打图集

点击菜单Window - 2D - Sprite Packer
在这里插入图片描述
点击Pack按钮,即可将多张图片打成一个图集了。
在这里插入图片描述
打图集成,即可看到对应的图集了。
在这里插入图片描述
如果有多张图集,可以在View Atlas下拉菜单中切换显示。
在这里插入图片描述

12.4 制作精灵预设,通过预设加载精灵

使用过NGUI的同学应该知道,在NGUI中图集会有一个对应的图集预设,要加载某个精灵图,需要先加载对应的图集。UGUI则不同,直接加载对应的精灵即可。但是上面我们的精灵并不是放在Resources文件夹中,是不会被打进包体中的。如果将精灵图拷贝到Resources文件夹中,则会产生双份资源,造成资源冗余。
解决办法就是将精灵图包装成预设,把预设放在Resources文件夹中,这样就可以通过Resources.Load接口加载精灵图了。
预设中使用Sprite Renderer组件,设置它的Sprite属性为具体的精灵图。
在这里插入图片描述
这样挨个制作精灵预设很麻烦,所以写个Editor工具批量执行。
将下面的代码保存为MakeSpritePrefabs.cs放在Editor文件夹中。

using UnityEngine;
using UnityEditor;
using System.IO;

public class MakeSpritePrefabs
{
    
    [MenuItem("Tools/MakeSpritePrefabs")]
    private static void Make()
    {
    
        // 创建 Assets/Resources/Sprite 文件夹,用来存放生成的精灵预设
        var prefabRootDir = Path.Combine(Application.dataPath, "Resources/Sprite");
        if (!Directory.Exists(prefabRootDir))
        {
    
            Directory.CreateDirectory(prefabRootDir);
        }

        // 遍历 Assets/Atlas 文件夹中的 png 图片
        var pngRootDir = Path.Combine(Application.dataPath, "Atlas");
        var pngFils = Directory.GetFiles(pngRootDir, "*.png", SearchOption.AllDirectories);
        foreach (string filePath in pngFils)
        {
    
            string assetPath = filePath.Substring(filePath.IndexOf("Assets"));
            Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
            GameObject go = new GameObject(sprite.name);
            go.AddComponent<SpriteRenderer>().sprite = sprite;

            var prefabPath = Path.Combine(prefabRootDir, sprite.name + ".prefab");
            prefabPath = prefabPath.Substring(prefabPath.IndexOf("Assets"));
            // 将 gameObject 保存为预设
            PrefabUtility.SaveAsPrefabAsset(go, prefabPath);
            GameObject.DestroyImmediate(go);
        }

        // 刷新目录
        AssetDatabase.Refresh();
    }
}

点击菜单Tools - MakeSpritePrefabs即可自动将精灵图制作成预设了。
接着,我们就可以通过Resources.Load接口加载精灵图了。

var obj = Resources.Load<GameObject>("Sprite/bj");
var sprite = .GetComponent<SpriteRenderer>().sprite;

12.5 json配置表

我们将道具以json格式弄成配置表,如下

[
    {
    
        "id":1,
        "name":"逗猫棒",
        "sprite":"dmb",
        "desc":"逗猫棒是一种深受猫咪喜爱的玩具"
    },
    {
    
        "id":2,
        "name":"猫布丁",
        "sprite":"mbd",
        "desc":"猫布丁是以香滑布丁粉为主要材料制作而成的一道甜品"
    },
    {
    
        "id":3,
        "name":"猫薄荷",
        "sprite":"mbh",
        "desc":"猫薄荷是指能让家猫等猫科动物产生幻觉的一类多年生草本植物"
    },
	{
    
        "id":4,
        "name":"鸡胸肉",
        "sprite":"jxr",
        "desc":"鸡胸肉,是鸡身上最大的两块肉,富含丰富的蛋白质"
    },
	{
    
        "id":5,
        "name":"猫麦草",
        "sprite":"mmc",
        "desc":"猫麦草可以刺激猫的肠胃蠕动,帮助猫吐出在胃中结成团的毛球"
    },
	{
    
        "id":6,
        "name":"猫罐头",
        "sprite":"mgt",
        "desc":"猫罐头是根据猫咪的特殊身体因素研制而成的食物,营养丰富"
    },
	{
    
        "id":7,
        "name":"猫条",
        "sprite":"mt",
        "desc":"猫条是一种猫的零食,口味多种多样"
    }
]

12.6 配置表读取

将上面的配置表保存为PropCfg.json放在Resources目录中。

接着我们就可以通过Resources.load加载配置文件,再通过LitJson解析。

关于LitJson,第十一章已讲过,可以查阅第十一章:《学Unity的猫》——第十一章:Unity猫咪救济管理系统,山岗的星光

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class PropCfg
{
    
    /// <summary>
    /// 读取配置表
    /// </summary>
    public void LoadCfg()
    {
    
        var cfgJson = Resources.Load<TextAsset>("PropCfg");
        var cfg = JsonMapper.ToObject<List<PropCfgItem>>(cfgJson.text);
        foreach(var cfgItem in cfg)
        {
    
            m_cfgDic[cfgItem.id] = cfgItem;
        }
    }

    /// <summary>
    /// 通过道具id获取道具配置
    /// </summary>
    /// <param name="id">道具id</param>
    /// <returns></returns>
    public PropCfgItem GetCfg(int id)
    {
    
        if (m_cfgDic.ContainsKey(id))
            return m_cfgDic[id];
        return null;
    }

    /// <summary>
    /// 配置表
    /// </summary>
    private Dictionary<int, PropCfgItem> m_cfgDic = new Dictionary<int, PropCfgItem>();

    /// <summary>
    /// 单例模式
    /// </summary>
    private static PropCfg s_instance;
    public static PropCfg Instance
    {
    
        get
        {
    
            if (null == s_instance)
                s_instance = new PropCfg();
            return s_instance;
        }
    }
}

/// <summary>
/// 道具配置数据结构
/// </summary>
public class PropCfgItem
{
    
    public int id;
    public string name;
    public string sprite;
    public string desc;
}

12.7 制作界面预设

大厅界面:PlazaPanel.prefab
在这里插入图片描述
背包界面:BackpackPanel.prefab
在这里插入图片描述
提示语预设:TipsUi.prefab
在这里插入图片描述

12.8 资源管理器

编写一个资源管理器ResourceMgr,用于加载资源,缓存资源。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 资源管理器
/// </summary>
public class ResourceMgr
{
    
    /// <summary>
    /// 加载精灵图
    /// </summary>
    /// <param name="name">精灵名</param>
    /// <returns></returns>
    public Sprite LoadSprite(string name)
    {
    
        if (m_spritesDic.ContainsKey(name))
            return m_spritesDic[name];
        var obj = Resources.Load<GameObject>("Sprite/" + name);

        var sprite = obj.GetComponent<SpriteRenderer>().sprite;
        m_spritesDic[name] = sprite;
        return sprite;
    }

    /// <summary>
    /// 加载界面预设
    /// </summary>
    /// <param name="name">界面名</param>
    /// <returns></returns>
    public GameObject LoadPanel(string name)
    {
    
        GameObject prefab = null;
        if (m_panelsDic.ContainsKey(name))
            prefab = m_panelsDic[name];
        else
        {
    
            prefab = Resources.Load<GameObject>("Panel/" + name);
            m_panelsDic[name] = prefab;
        }
        return prefab;
    }

    /// <summary>
    /// 精灵资源
    /// </summary>
    private Dictionary<string, Sprite> m_spritesDic = new Dictionary<string, Sprite>();
    /// <summary>
    /// 界面资源
    /// </summary>
    private Dictionary<string, GameObject> m_panelsDic = new Dictionary<string, GameObject>();

    /// <summary>
    /// 单例模式
    /// </summary>
    private static ResourceMgr s_instance;
    public static ResourceMgr Instance
    {
    
        get
        {
    
            if (null == s_instance)
                s_instance = new ResourceMgr();
            return s_instance;
        }
    }
}

例:加载精灵图

// 加载猫麦草精灵图
var sprite = ResourceMgr.Instance.LoadSprite("mmc");

12.9 界面管理器

编写一个界面管理器PanelMgr,用于显示界面。

using UnityEngine;

/// <summary>
/// 界面管理器
/// </summary>
public class PanelMgr 
{
    
    /// <summary>
    /// 初始化
    /// </summary>
    /// <param name="canvas"></param>
    public void Init(Canvas canvas)
    {
    
        m_canvasTrans = canvas.transform;
    }

    /// <summary>
    /// 显示界面
    /// </summary>
    /// <param name="panelName"></param>
    /// <returns></returns>
    public GameObject ShowPanel(string panelName)
    {
    
        var prefab = ResourceMgr.Instance.LoadPanel(panelName);
        var obj = Object.Instantiate(prefab);
        obj.transform.SetParent(m_canvasTrans, false);
        return obj;
    }

    /// <summary>
    /// 缓存Canvas
    /// </summary>
    private Transform m_canvasTrans;

    private static PanelMgr s_instance;
    public static PanelMgr Instance
    {
    
        get
        {
    
            if (null == s_instance)
                s_instance = new PanelMgr();
            return s_instance;
        }
    }
}

/// <summary>
/// 界面名称
/// </summary>
public class PanelName
{
    
    public const string PlazaPanel = "PlazaPanel";
    public const string BackpackPanel = "BackpackPanel";
    public const string TipsUi = "TipsUi";
}

例:显示背包界面

// 显示背包界面
var obj = PanelMgr.Instance.ShowPanel(PanelName.BackpackPanel);

12.10 背包数据读写

封装一个本地数据读写的类,模拟数据库,用到了PlayerPrefsLitJson这两个类。
通过PlayerPrefs可以方便地进行数据读写,通过LitJson可以方便地进行json数据格式转换。

using System.Collections.Generic;
using UnityEngine;
using LitJson;

/// <summary>
/// 道具数据库
/// </summary>
public class PropsDatabase 
{
    
    /// <summary>
    /// 从本地读取数据
    /// </summary>
    /// <returns></returns>
    public static Dictionary<int, int> LoadData()
    {
    
        PlayerPrefs.SetString("props", "{}");
        var jsonStr = PlayerPrefs.GetString("props", "{}");
        Debug.Log("LoadData: \n" + jsonStr);
        var result = JsonMapper.ToObject<Dictionary<string, int>>(jsonStr);
        Dictionary<int, int> data = new Dictionary<int, int>();
        foreach (var item in result)
        {
    
            data[int.Parse(item.Key)] = item.Value;
        }
        return data;
    }

    /// <summary>
    /// 写入数据到本地
    /// </summary>
    /// <param name="data"></param>
    public static void SaveData(Dictionary<int, int> data)
    {
    
        var jsonStr = JsonMapper.ToJson(data);
        PlayerPrefs.SetString("props", jsonStr);
    }
}

12.11 背包管理器

再封装一个背包管理器BackPackMgr,提供读取道具数据和增减道具数量的方法。

using System.Collections.Generic;

/// <summary>
/// 背包管理器
/// </summary>
public class BackPackMgr
{
    
    /// <summary>
    /// 初始化
    /// </summary>
    public void Init()
    {
    
        // 从本地读取背包数据
        m_propsDic = PropsDatabase.LoadData();
    }

    /// <summary>
    /// 修改道具数量
    /// </summary>
    /// <param name="id">道具id</param>
    /// <param name="deltaCnt">数量变化值</param>
    public void ChangePropCnt(int id, int deltaCnt)
    {
    
        if (m_propsDic.ContainsKey(id))
        {
    
            m_propsDic[id] += deltaCnt;
        }
        else
        {
    
            m_propsDic[id] = deltaCnt;
        }
        if (m_propsDic[id] < 0)
        {
    
            m_propsDic[id] = 0;
        }
        // 写入数据到本地
        PropsDatabase.SaveData(m_propsDic);
        // 抛出事件
        EventDispatcher.instance.DispatchEvent(EventNameDef.EVENT_UPDATE_PROP_CNT, id, m_propsDic[id]);
    }

    /// <summary>
    /// 背包道具数据缓存,key: id, value: cnt
    /// </summary>
    private Dictionary<int, int> m_propsDic = new Dictionary<int, int>();
    public Dictionary<int, int> propsDic
    {
    
        get {
     return m_propsDic; }
    }

    /// <summary>
    /// 单例模式
    /// </summary>
    private static BackPackMgr s_instance;
    public static BackPackMgr Instance
    {
    
        get
        {
    
            if (null == s_instance)
                s_instance = new BackPackMgr();
            return s_instance;
        }
    }
}

例:增加一个id1的道具(逗猫棒)

// 增加一个逗猫棒道具
BackPackMgr.Instance.ChangePropCnt(1, 1);

12.12 事件管理器

上面背包管理器中用到了事件管理器,当道具数量发生改变的时候,要通知界面ui更新显示。使用观察者模式,通过事件订阅的方式实现道具数量更新时同步更新ui的显示。
事件管理器如下

using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// 事件委托
/// </summary>
public delegate void MyEventHandler(params object[] objs);

/// <summary>
/// 事件管理器
/// </summary>
public class EventDispatcher
{
    
    /// <summary>
    /// 注册事件的响应函数
    /// </summary>
    /// <param name="type"></param>
    /// <param name="handler"></param>
    public void Regist(string type, MyEventHandler handler)
    {
    
        if (handler == null)
            return;

        if (!listeners.ContainsKey(type))
        {
    
            listeners.Add(type, new Dictionary<int, MyEventHandler>());
        }
        var handlerDic = listeners[type];
        var handlerHash = handler.GetHashCode();
        if (handlerDic.ContainsKey(handlerHash))
        {
    
            handlerDic.Remove(handlerHash);
        }
        listeners[type].Add(handler.GetHashCode(), handler);
    }

    /// <summary>
    /// 注销事件的响应函数
    /// </summary>
    /// <param name="type"></param>
    /// <param name="handler"></param>
    public void UnRegist(string type, MyEventHandler handler)
    {
    
        if (handler == null)
            return;

        if (listeners.ContainsKey(type))
        {
    
            listeners[type].Remove(handler.GetHashCode());
            if (null == listeners[type] || 0 == listeners[type].Count)
            {
    
                listeners.Remove(type);
            }
        }
    }

    /// <summary>
    /// 抛出事件,触发之前注册过的响应函数
    /// </summary>
    /// <param name="evt"></param>
    /// <param name="objs"></param>
    public void DispatchEvent(string evt, params object[] objs)
    {
    
        if (listeners.ContainsKey(evt))
        {
    
            var handlerDic = listeners[evt];
            if (handlerDic != null && 0 < handlerDic.Count)
            {
    
                var dic = new Dictionary<int, MyEventHandler>(handlerDic);
                foreach (var f in dic.Values)
                {
    
                    try
                    {
    
                        f(objs);
                    }
                    catch (System.Exception ex)
                    {
    
                        Debug.LogErrorFormat(szErrorMessage, evt, ex.Message, ex.StackTrace);
                    }
                }
            }
        }
    }

    /// <summary>
    /// 清理事件
    /// </summary>
    /// <param name="key"></param>
    public void ClearEvents(string key)
    {
    
        if (listeners.ContainsKey(key))
        {
    
            listeners.Remove(key);
        }
    }

    /// <summary>
    /// 事件监听缓存
    /// </summary>
    private Dictionary<string, Dictionary<int, MyEventHandler>> listeners = new Dictionary<string, Dictionary<int, MyEventHandler>>();
    private readonly string szErrorMessage = "DispatchEvent Error, Event:{0}, Error:{1}, {2}";

    /// <summary>
    /// 单例模式
    /// </summary>
    private static EventDispatcher s_instance;
    public static EventDispatcher Instance
    {
    
        get
        {
    
            if (null == s_instance)
                s_instance = new EventDispatcher();
            return s_instance;
        }
    }
}

例:事件订阅

// 事件订阅
EventDispatcher.Instance.Regist("EVENT_UPDATE_PROP_CNT", OnEventUpdatePropCnt);
// 注销事件订阅
EventDispatcher.Instance.UnRegist("EVENT_UPDATE_PROP_CNT", OnEventUpdatePropCnt);

抛出事件

EventDispatcher.Instance.DispatchEvent("EVENT_UPDATE_PROP_CNT", id, cnt);

响应函数

private void OnEventUpdatePropCnt(params object[] args)
{
    
	int id = (int)args[0];
    int cnt = (int)args[1];
}

12.13 界面代码

12.13.1 大厅界面

大厅界面主要是两个按钮,一个按钮点击了随机增加一个道具,另一个按钮点击了打开背包界面。
在这里插入图片描述

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 大厅界面
/// </summary>
public class PlazaPanel : MonoBehaviour
{
    
    public Button packpackBtn;
    public Button addPropBtn;

    void Start()
    {
    
        // 背包按钮
        packpackBtn.onClick.AddListener(() => 
        {
    
            PanelMgr.Instance.ShowPanel(PanelName.BackpackPanel);
        });
        // 加道具按钮
        addPropBtn.onClick.AddListener(() => {
    
            // 随机加一个道具
            var propId = Random.Range(1, 8);
            BackPackMgr.Instance.ChangePropCnt(propId, 1);
            var cfg = PropCfg.Instance.GetCfg(propId);
            TipsUi.Show("恭喜获得" + cfg.name + "x1");
        });
    }
}
12.13.2 背包界面

背包界面稍复杂一点点,界面左边用一个Scroll View用来显示道具列表,选中某个道具,界面右边显示道具信息,点击使用按钮,扣除一个道具。
在这里插入图片描述
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BackpackPanel : MonoBehaviour
{
    

    private void Awake()
    {
    
        // 订阅事件
        EventDispatcher.Instance.Regist(EventNameDef.EVENT_UPDATE_PROP_CNT, OnEventUpdatePropCnt);
    }

    private void OnDestroy()
    {
    
        // 注销事件
        EventDispatcher.Instance.UnRegist(EventNameDef.EVENT_UPDATE_PROP_CNT, OnEventUpdatePropCnt);
    }

    private void Start()
    {
    
        propItemUi.SetActive(false);
        // 关闭按钮
        closeBtn.onClick.AddListener(() =>
        {
    
            Destroy(gameObject);
        });
        // 使用道具按钮
        usePropBtn.onClick.AddListener(() =>
        {
    
            if (-1 == m_selectPropId) return;
            var cfg = PropCfg.Instance.GetCfg(m_selectPropId);
            TipsUi.Show("使用了" + cfg.name);
            BackPackMgr.Instance.ChangePropCnt(m_selectPropId, -1);
        });
        // 创建道具列表
        CreatePropList();
    }

    /// <summary>
    /// 道具数量发生变化
    /// </summary>
    /// <param name="args"></param>
    private void OnEventUpdatePropCnt(params object[] args)
    {
    
        int id = (int)args[0];
        int cnt = (int)args[1];

        if (cnt <= 0)
        {
    
            if (m_propUiDic.ContainsKey(id))
            {
    
                Destroy(m_propUiDic[id].obj);
                m_propUiDic.Remove(id);
                if (id == m_selectPropId)
                    AutoSelectOneProp();
            }
        }
        else
        {
    
            if (m_propUiDic.ContainsKey(id))
            {
    
                m_propUiDic[id].cnt.text = "x" + cnt;
            }
            else
            {
    
                CreatePropItem(id, cnt);
            }
        }
    }

    /// <summary>
    /// 创建道具列表
    /// </summary>
    private void CreatePropList()
    {
    
        foreach (var item in BackPackMgr.Instance.propsDic)
        {
    
            CreatePropItem(item.Key, item.Value);
        }
        // 自动选择一个道具
        AutoSelectOneProp();
    }

    /// <summary>
    /// 自动选择一个道具
    /// </summary>
    private void AutoSelectOneProp()
    {
    
        var itor = m_propUiDic.GetEnumerator();
        if (itor.MoveNext())
        {
    
            var item = itor.Current.Value;
            item.tgl.isOn = true;
            itor.Dispose();
            OnPropItemSelected(item.propId);
        }
        else
        {
    
            OnPropItemSelected(-1);
        }
    }

    /// <summary>
    /// 创建一个道具item
    /// </summary>
    /// <param name="propId">道具id</param>
    /// <param name="cnt">道具数量</param>
    private void CreatePropItem(int propId, int cnt)
    {
    
        if (m_propUiDic.ContainsKey(propId)) return;
        if (cnt <= 0) return;
        PropItemUI ui = new PropItemUI();
        var obj = Instantiate(propItemUi);
        obj.SetActive(true);
        obj.transform.SetParent(propItemUi.transform.parent, false);
        ui.obj = obj;
        ui.propId = propId;
        ui.icon = obj.transform.Find("Button/Icon").GetComponent<Image>();
        ui.cnt = obj.transform.Find("Button/Cnt").GetComponent<Text>();
        ui.tgl = obj.transform.Find("Button").GetComponent<Toggle>();
        ui.tgl.onValueChanged.AddListener((v) =>
        {
    
            if (ui.tgl.isOn) OnPropItemSelected(propId);
        });

        var cfg = PropCfg.Instance.GetCfg(propId);
        if (null != cfg)
        {
    
            var sprite = ResourceMgr.Instance.LoadSprite(cfg.sprite);
            ui.icon.sprite = sprite;
            //ui.icon.SetNativeSize();
        }
        ui.cnt.text = "x" + cnt;
        m_propUiDic[propId] = ui;

    }

    /// <summary>
    /// 道具被选中
    /// </summary>
    /// <param name="propId"></param>
    private void OnPropItemSelected(int propId)
    {
    
        m_selectPropId = propId;
        if (-1 == m_selectPropId)
        {
    
            // 没有道具被选中
            rightInfoRoot.SetActive(false);
        }
        else
        {
    
            rightInfoRoot.SetActive(true);
            var cfg = PropCfg.Instance.GetCfg(propId);
            nameText.text = cfg.name;
            descText.text = cfg.desc;
            icon.sprite = ResourceMgr.Instance.LoadSprite(cfg.sprite);
        }
    }

    public Button closeBtn;
    public GameObject propItemUi;
    public GameObject rightInfoRoot;

    public Text nameText;
    public Text descText;
    public Image icon;
    public Button usePropBtn;

    private int m_selectPropId;


    /// <summary>
    /// 道具列表ui缓存
    /// </summary>
    private Dictionary<int, PropItemUI> m_propUiDic = new Dictionary<int, PropItemUI>();

    private class PropItemUI
    {
    
        public int propId;
        public GameObject obj;
        public Image icon;
        public Toggle tgl;
        public Text cnt;

    }
}
12.13.3 提示语UI

制作一个提示语UI的动画,动画播放结束后通过帧事件调用OnAnimationEnd方法销毁自己。
在这里插入图片描述
在这里插入图片描述
TipsUi组件挂在TipsUi.prefab上。

using UnityEngine;
using UnityEngine.UI;

public class TipsUi : MonoBehaviour
{
    
    public Text tipsText;

    /// <summary>
    /// 显示提示语
    /// </summary>
    /// <param name="text"></param>
    public static void Show(string text)
    {
    
        var obj = PanelMgr.Instance.ShowPanel(PanelName.TipsUi);
        var bhv = obj.GetComponent<TipsUi>();
        bhv.tipsText.text = text;
    }


    /// <summary>
    /// 动画结束,销毁自己
    /// </summary>
    public void OnAnimationEnd()
    {
    
        Destroy(gameObject);
    }
}

12.14 程序入口脚本

写一个程序入口脚本,挂到Canvas上,并赋值Canvas对象。
在这里插入图片描述

using UnityEngine;

public class UIMain : MonoBehaviour
{
    
    public Canvas canvas;

    // 执行初始化
    private void Awake()
    {
    
        PropCfg.Instance.LoadCfg();
        BackPackMgr.Instance.Init();
        PanelMgr.Instance.Init(canvas);
    }


    void Start()
    {
    
        // 显示大厅界面
        PanelMgr.Instance.ShowPanel(PanelName.PlazaPanel);
    }
}

完成。
如果有什么疑问,欢迎留言或私信。

版权声明
本文为[林新发]所创,转载请带上原文链接,感谢
https://linxinfa.blog.csdn.net/article/details/110622067