《学Unity的猫》——第十一章:Unity猫咪救济管理系统,山岗的星光

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

11.1 山岗的星光

稀疏的星光,清辉洒地,流浪猫小花凝望着窗外,它没有想到一个月前它还是一只无家可归的流浪小猫,此时已经有了一个温暖的家。
事情要回到一个月前的那个夜晚,秋风瑟瑟,小花在路边蜷缩着身子,有个人类经过它的身旁,那个人站住了,嘴里发出蹩脚的喵喵声,慢慢想要靠近,小花见状立刻躲进了斜坡中的草丛里,那人停留片刻后便离开了。
两天后,小花又看到了那个人,而且还带着一只猫一台电脑,那只猫朝着小花挥手:“喂,过来这边,不要怕,我们不是坏人。”
小花不敢靠前,那只猫又喊道:“我叫皮皮,这个是我的铲屎官,这里有吃的,快过来。”
后来,小花不仅饱饱的吃了一餐,还得知了他们正在做一个猫咪救济管理系统,皮皮是项目的策划猫,它希望可以为那些流浪猫提供基础的保障,实名登记流浪猫,统一管理,调度资源,在设定的救济点投放食物和过冬物资,并与人类铲屎官合作,希望可以给流浪猫寻找合适的家。
每一只来到地球的喵星人都有一个唯一的编号,只要登记了这个编号,就可以随时随地通过猫电波进行通信,小花成为了救济系统的第一只猫。
很快便有人类来领养了小花,以前,小花总是坐在小斜坡上望着星星,它不知道自己从哪里来的,那里还有很多和它一样的猫,有的从来没见过,有的离开了又回来了,有的离开了就再也没有回来,只有星星一直陪着它。现在它有了家,它也想和皮皮它们一起,帮助流离失所的猫寻找温暖的家。
山岗的星光,愿你不再流浪,被世界温柔以待。

11.2 Unity猫咪救济管理系统

猫咪救济管理系统,模块设计如下:
在这里插入图片描述
运行效果如下:
在这里插入图片描述

本工程使用的Unity版本为2020.1.14f1c1 (64-bit),工程已上传到GitHub,感兴趣的同学可以下载下来学习。
GitHub地址:https://github.com/linxinfa/Unity-CatInfoSystem在这里插入图片描述

11.3 设置UI摄像机

创建一个摄像机,重命名为UICamera
在这里插入图片描述
设置Culling Mask只渲染UI层。
在这里插入图片描述
设置视口Projection为正交视口Orthographic
在这里插入图片描述
设置Main Camera不渲染UI层。
在这里插入图片描述

11.4 设置Canvas

创建Canvas,并设置Render ModeScreen Space -Camera,设置Render CameraUICamera,再把Canvas Scaler组件的UI Scale Mode设置为Scale With Screen Size
在这里插入图片描述

11.5 制作登录界面预设

LoginPanel.prefab
在这里插入图片描述
在这里插入图片描述
两个Input Field组件,用来输入账号和密码(accountpassword),一个Button组件作为登录按钮(LoginButton)。

11.6 制作大厅界面预设

PlazaPanel.prefab
在这里插入图片描述
在这里插入图片描述
一个Button组件作为登记按钮(AddButton),一个Scroll View组件作为拖拉列表,一个Item作为列表的项,一个Button组件作为退出登录按钮(LogoutButton)。其中,Item中使用多个Text组件作为信息显示。

11.7 制作信息界面预设

InfoPnel.prefab
在这里插入图片描述
在这里插入图片描述
使用三个Button组件作为确定、删除、关闭按钮,使用多个Input Field组件和一个Drop Down组件作为信息输入。

11.8 UI界面管理器:UIManager

UI预设资源放在Resources目录中。
在这里插入图片描述
封装一个UIManager类,用来加载界面预设资源和显示界面。

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

public class UIManager
{
    public void Init(Canvas canvas)
    {
        m_canvasTrans = canvas.transform;
    }

    /// <summary>
    /// 显示界面
    /// </summary>
    /// <param name="panelName">界面名称</param>
    /// <returns></returns>
    public GameObject ShowPanel(string panelName)
    {
        GameObject prefabObj = null;
        if (m_panelRes.ContainsKey(panelName))
        {
            prefabObj = m_panelRes[panelName];
        }
        else
        {
            prefabObj = Resources.Load<GameObject>(panelName);
            m_panelRes[panelName] = prefabObj;
        }
        GameObject panelObj = Object.Instantiate(prefabObj);
        panelObj.transform.SetParent(m_canvasTrans, false);
        return panelObj;
    }

    private Transform m_canvasTrans;
    /// <summary>
    /// 界面资源缓存
    /// </summary>
    private Dictionary<string, GameObject> m_panelRes = new Dictionary<string, GameObject>();

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

/// <summary>
/// 界面名称,对应预设文件名称
/// </summary>
public class PanelName
{
    public const string LOGIN_PANEL = "LoginPanel";
    public const string PLAZA_PANEL = "PlazaPanel";
    public const string INFO_PANEL = "InfoPanel";
}
11.9 封装猫信息的类:CatInfo

猫的信息中有个uuid是系统生成的,id是猫星人的编号,由喵星人手动输入。

/// <summary>
/// 猫咪信息
/// </summary>
public class CatInfo 
{
    public CatInfo()
    {
        uuid = System.Guid.NewGuid().ToString("N");
    }


    /// <summary>
    /// 系统唯一标识符
    /// </summary>
    public string uuid;
    /// <summary>
    /// 喵星身份编号
    /// </summary>
    public string id;
    /// <summary>
    /// 昵称
    /// </summary>
    public string nickname;
    /// <summary>
    /// 品种
    /// </summary>
    public string kind;
    /// <summary>
    /// 毛色
    /// </summary>
    public string color;
    /// <summary>
    /// 性别,0:母,1:公
    /// </summary>
    public int gender;
    /// <summary>
    /// 出生
    /// </summary>
    public string birth;
    /// <summary>
    /// 流浪原因
    /// </summary>
    public string reason;
}
11.10 猫信息管理器:CatManager

封装一个猫信息的管理类,CatManager,负责数据加载和增删改查。

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

public class CatManager
{
    public void Init()
    {
        // 从数据库加载数据
        m_datas = CatDataBase.QueryDatas();
    }

    /// <summary>
    /// 新增或修改猫信息
    /// </summary>
    /// <param name="info"></param>
    public void AddOrModify(CatInfo info)
    {
        CatDataBase.AddOrModify(info);
        // 抛出事件,更新界面
        EventDispatcher.instance.DispatchEvent(EventNameDef.EVENT_ADD_OR_MODIFY_CAT, info);
    }

    /// <summary>
    /// 删除猫信息
    /// </summary>
    /// <param name="uuid"></param>
    public void Del(string uuid)
    {
        CatDataBase.Del(uuid);
        // 抛出事件,更新界面
        EventDispatcher.instance.DispatchEvent(EventNameDef.EVENT_DEL_CAT, uuid);
    }

    public Dictionary<string, CatInfo> data
    {
        get { return m_datas; }
    }

    /// <summary>
    /// 猫信息数据缓存
    /// </summary>
    private Dictionary<string, CatInfo> m_datas = null;

    /// <summary>
    /// 单例模式
    /// </summary>
    private static CatManager s_instance;
    public static CatManager Instance
    {
        get
        {
            if (null == s_instance)
                s_instance = new CatManager();
            return s_instance;
        }
    }
}
11.11 数据存储:CatDataBase

封装一个CatDataBase类,负责数据的本地写入和读取。

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

/// <summary>
/// 猫数据库
/// </summary>
public class CatDataBase
{

    /// <summary>
    /// 新增或修改数据
    /// </summary>
    /// <param name="info"></param>
    public static void AddOrModify(CatInfo info)
    {
        if (s_datas.ContainsKey(info.uuid))
        {
            s_datas[info.uuid] = info;
        }
        else
        {
            s_datas.Add(info.uuid, info);
        }
        
        SaveData();
    }

    /// <summary>
    /// 删数据
    /// </summary>
    /// <param name="uuid"></param>
    public static void Del(string uuid)
    {
        if (!s_datas.ContainsKey(uuid)) return;
        s_datas.Remove(uuid);
        SaveData();
    }

    /// <summary>
    /// 查询所有猫信息
    /// </summary>
    /// <returns></returns>
    public static Dictionary<string, CatInfo> QueryDatas()
    {
        string jsonStr = PlayerPrefs.GetString("cats", null);
        if (string.IsNullOrEmpty(jsonStr))
        {
            s_datas = new Dictionary<string, CatInfo>();
        }
        else
        {
            s_datas = JsonMapper.ToObject<Dictionary<string, CatInfo>>(jsonStr);
        }
        return s_datas;
    }

    /// <summary>
    /// 使用PlayerPrefs将数据写入本地
    /// </summary>
    public static void SaveData()
    {
        string jsonStr = JsonMapper.ToJson(s_datas);
        PlayerPrefs.SetString("cats", jsonStr);
    }

    private static Dictionary<string, CatInfo> s_datas = new Dictionary<string, CatInfo>();
}

其中写入本地数据用到了Unity提供的PlayerPrefs类。
读取数据接口

public static string GetString(string key, string defaultValue);

写入数据接口

public static void SetString(string key, string value);
11.12 Json库:LitJson

需要将数据序列号到本地文件中,采用Json的格式,所以需要导入一个Json库,从GitHub下载LitJson源码:
https://github.com/LitJSON/litjson/releases/tag/0.15.0
在这里插入图片描述
下载后将源码导入到工程中。
在这里插入图片描述
使用时引入命名空间。

using LitJson;

主要用到JsonMapper的两个接口。
object json字符串接口

public static string ToJson (object obj)

json字符串转object 接口

public static T ToObject<T> (string json)
11.13 观察者模式

数据更新的时候要更新ui的显示,这里采用观察者模式,ui界面的代码监听事件,数据变化时抛出事件,从而实现ui的显示刷新。
封装一个事件订阅和发送的类:EventDispatcher

using UnityEngine;
using System.Collections.Generic;

public delegate void MyEventHandler(params object[] objs);

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;
        }
    }
}

定义事件

public class EventNameDef 
{
    /// <summary>
    /// 新增或修改一只猫的信息
    /// </summary>
    public const string EVENT_ADD_OR_MODIFY_CAT  = "EVENT_ADD_OR_MODIFY_CAT";

    /// <summary>
    /// 删除一只猫的信息
    /// </summary>
    public const string EVENT_DEL_CAT = "EVENT_DEL_CAT";
}
11.14 登录界面代码:LoginPanel

登录界面代码LoginPanel,挂在LoginPanel.prefab上。
简单判断下账号和密码,如果账号为admin且密码为123456则进入大厅界面。
(实际开发需要有账号注册,与服务器通信才能完成登录,这里只是演示效果)。

using UnityEngine;
using UnityEngine.UI;

public class LoginPanel : MonoBehaviour
{
    public InputField nameInput;
    public InputField pwdInput;

    public Button loginBtn;

    void Start()
    {
        loginBtn.onClick.AddListener(() =>
        {
            if ("admin" == nameInput.text && "123456" == pwdInput.text)
            {
                Destroy(gameObject);
                UIManager.Instance.ShowPanel(PanelName.PLAZA_PANEL);
            }
        });
    }
}

绑定ui对象
在这里插入图片描述

11.15 大厅界面代码:PlazaPanel

大厅界面代码PlazaPanel,挂在PlazaPanel.prefab上。

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

public class PlazaPanel : MonoBehaviour
{
    public Button logoutBtn;
    public Button signBtn;
    public GameObject catItemObj;

    private Transform listRoot;

    void Awake()
    {
        // 注册事件
        EventDispatcher.instance.Regist(EventNameDef.EVENT_ADD_OR_MODIFY_CAT, OnEventAddOrModifyCat);
        EventDispatcher.instance.Regist(EventNameDef.EVENT_DEL_CAT, OnEventDelCat);

        CatManager.Instance.Init();
        catItemObj.SetActive(false);
        listRoot = catItemObj.transform.parent;
    }

    void Start()
    {
        // 退出登录
        logoutBtn.onClick.AddListener(() =>
        {
            Destroy(gameObject);
            UIManager.Instance.ShowPanel(PanelName.LOGIN_PANEL);
        });

        // 登记新信息
        signBtn.onClick.AddListener(() =>
        {
            UIManager.Instance.ShowPanel(PanelName.INFO_PANEL);
        });

        // 遍历数据,创建列表
        foreach (CatInfo catInfo in CatManager.Instance.data.Values)
        {
            CreateItem(catInfo);
        }

    }

    /// <summary>
    /// 创建信息列表的一行ui
    /// </summary>
    /// <param name="info"></param>
    void CreateItem(CatInfo info)
    {
        var obj = Instantiate(catItemObj);
        obj.SetActive(true);
        obj.transform.SetParent(listRoot, false);
        var itemUi = obj.GetComponent<CatListItem>();
        // 使用数据更新ui
        itemUi.UpdateUi(info);
        // 缓存,方便后面更新ui
        m_catUiList[info.uuid] = itemUi;
    }

    /// <summary>
    /// 数据发生变化,更新ui
    /// </summary>
    /// <param name="args"></param>
    void OnEventAddOrModifyCat(params object[] args)
    {
        var info = args[0] as CatInfo;
        if (m_catUiList.ContainsKey(info.uuid))
        {
            m_catUiList[info.uuid].UpdateUi(info);
        }
        else
        {
            // 创建多一行
            CreateItem(info);
        }
    }

    /// <summary>
    /// 信息被删除,删除对应的ui
    /// </summary>
    /// <param name="args"></param>
    void OnEventDelCat(params object[] args)
    {
        var uuid = (string)args[0];
        if (m_catUiList.ContainsKey(uuid))
        {
            var ui = m_catUiList[uuid];
            if(null != ui)
            {
                Destroy(ui.gameObject);
            }
            m_catUiList.Remove(uuid);
        }

    }

    /// <summary>
    /// 界面被销毁
    /// </summary>
    private void OnDestroy()
    {
        // 注销事件
        EventDispatcher.instance.UnRegist(EventNameDef.EVENT_ADD_OR_MODIFY_CAT, OnEventAddOrModifyCat);
        EventDispatcher.instance.UnRegist(EventNameDef.EVENT_DEL_CAT, OnEventDelCat);
    }

    private Dictionary<string, CatListItem> m_catUiList = new Dictionary<string, CatListItem>();
}

绑定PlazaPanel组件的ui对象。
在这里插入图片描述
其中,列表的行ui单独封装成一个组件CatListItem ,挂在Item节点上。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 列表的一行ui
/// </summary>
public class CatListItem : MonoBehaviour
{
    public Text idText;
    public Text nameText;
    public Text kindText;
    public Text colorText;
    public Text genderText;
    public Text birthText;
    public Text resonText;

    public Button itemBtn;

    public void Start()
    {
        // 列表的行被点击,打开信息界面
        itemBtn.onClick.AddListener(() => 
        {
            var panelObj =  UIManager.Instance.ShowPanel(PanelName.INFO_PANEL);
            var infoPanel = panelObj.GetComponent<InfoPanel>();
            infoPanel.Init(m_info);
        });
    }

    /// <summary>
    /// 更新ui
    /// </summary>
    /// <param name="info"></param>
    public void UpdateUi(CatInfo info)
    {
        m_info = info;
        idText.text = info.id;
        nameText.text = info.nickname;
        kindText.text = info.kind;
        colorText.text = info.color;
        genderText.text = 0 == info.gender ? "母" : "公";
        birthText.text = info.birth;
        resonText.text = info.reason;
    }

    private CatInfo m_info;
}

绑定CatListItem组件的ui对象。
在这里插入图片描述

11.16 信息界面代码:InfoPanel

信息界面代码InfoPanel,挂在InfoPanel.prefab上。

using UnityEngine;
using UnityEngine.UI;

public class InfoPanel : MonoBehaviour
{
    public InputField idInput;
    public InputField kindInput;
    public InputField nameInput;
    public InputField colorInput;
    public Dropdown genderDropdown;
    public InputField birthInput;
    public InputField reasonInput;

    public Button okBtn;
    public Button delBtn;
    public Button closeBtn;

    public CatInfo m_catInfo;

    public void Init(CatInfo info)
    {
        m_catInfo = info;
        idInput.text = info.id;
        kindInput.text = info.kind;
        nameInput.text = info.nickname;
        colorInput.text = info.color;
        genderDropdown.value = info.gender;
        birthInput.text = info.birth;
        reasonInput.text = info.reason;
    }

    private CatInfo MakeCatInfo()
    {
        if (null == m_catInfo)
            m_catInfo = new CatInfo();
        m_catInfo.id = idInput.text;
        m_catInfo.kind = kindInput.text;
        m_catInfo.nickname = nameInput.text;
        m_catInfo.color = colorInput.text;
        m_catInfo.gender = genderDropdown.value;
        m_catInfo.birth = birthInput.text;
        m_catInfo.reason = reasonInput.text;
        return m_catInfo;
    }

    void Start()
    {
        okBtn.onClick.AddListener(() =>
        {
            // 新增或修改
            CatManager.Instance.AddOrModify(MakeCatInfo());
            Destroy(gameObject);

        });
        delBtn.onClick.AddListener(() =>
        {
            // 删除
            if(null != m_catInfo)
            {
                CatManager.Instance.Del(m_catInfo.uuid);
            }
            
            Destroy(gameObject);
        });

        closeBtn.onClick.AddListener(() => 
        {
            Destroy(gameObject);
        });
    }
}

绑定ui对象。
在这里插入图片描述
完成。
如果有任何疑问欢迎留言或私信。


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

  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值