UI框架:
管理场景中全部UI面板
控制面板之间的跳转php
若是没有UI框架,会经过面板之间的交叉访问来实现这些功能,管理混乱html
建立工程UIFrameWork:
建立工程目录
json
导入素材,将素材放入Images文件夹下
将全部素材的Texture Type修改成Sprite(2D and UI)canvas
建立 UI->Panel,命名MainMenuPanel,
Image->SourceImage: BG数组
将Canvas的Canvas Scaler.UI Scale Mode = Scale with Screen Size
将Match设置为Height
-- 即自适应:
若是UI Scale Mode为默认的 Constant Pixel Size时,文字等的大小不会基于屏幕大小而改变
设置为Scale With Screen Size, 控件的大小会基于Height或Width(只有一个)的变化而变化安全
新建空物体,命名IconPanel,位于屏幕右下角,并设置Anchorapp
新建Image: SourceImage 任务 TastButton
添加Button组件
新建Text:颜色黄白色,大小,字体,对齐等
给Text添加Shadow组件
取消Raycast Target的勾选框架
在Project的Images文件夹下新建UI Prefab文件夹,将上述按钮制做成prefabide
经过上述prefab制做其余按钮,依次为背包、战斗、技能、商店、系统函数
在MainMenuPanel中建立Image,命名PersonalInfoPanel,SourceImage: pic_我的信息
取左上角为Anchor,位置也移到左上角
新建Image,命名portrait,SourceImage: 头像底板女性
将整个MainMenuPanel制做成Prefab,放入Resources->UIPanel文件夹
新建Image,命名TaskPanel,SourceImage: bg-任务
位置居中,anchor居中
新建Text,内容"任务",位于标题栏处,修改字体、颜色等
新建Button,命名CloseButton,SourceImage: btn_关闭1
删除自带的Text
Button.Transition选择Sprite Swap (默认ColorTint)
分别选择:
Hightlighted Sprite: btn_关闭2
Pressed Sprite: btn_关闭3
Disabled Sprite: btn_关闭4
制做成Prefab,放入Resources.UIPanel文件夹
背包面板:
新建Image,命名KnapsackPanel,SourceImage: bg_背包,居中
新建关闭按钮,使用上一节中的便可
新建Image,命名ItemSlot,SourceImage: bg_道具
新建Image,命名Item,SourceImage: 大致力丹
由于KnapsackPanel和ItemSlot不须要点击,所以将Image的Raycast Target取消勾选
在Item中添加Button组件
物品弹框信息面板:
新建Image,命名ItemInfoPanel,SourceImage: bg_弹框,取消RaycastTarget的勾选
新建关闭按钮
点击战斗按钮,会直接跳转到战斗场景,所以不须要面板
技能、商城、系统的面板,都和TaskPanel差很少,赋值TaskPanel,修改便可
技能面板:SkillPanel
商城面板:ShopPanel
系统面板:SettingPanel
修改完名字,制做成Prefab,进行更改
将Text的内容分别修改成技能、商城、系统设置便可
系统面板的宽度修改小一些
在进行面板切换以前,须要知道当前项目有哪些面板、各个面板的加载路径
将这些信息传递给UI框架后,再由UI框架进行UI面板的加载操做
-- 使用Json信息来保存全部的面板和路径
每个面板都定义一个枚举类型
新建脚本UIPanelType.cs
枚举类型不须要继承自MonoBehaviour
一个Panel对应一个UIType类型
public enum UIPanelType { TaskPanel, KnapsackPanel, ItemInfoPanel, SkillPanel, ShopPanel, SettingPanel }
新建Json文件(在电脑中UIFramework文件夹下建立文本文档)
UIPanelType.json -- 用来保存全部面板对应的存储路径
-- bug!!! 任务11中发现不能这么写,不能被JsonUtility解析
[ { "panelType": "MainMenuPanel", "path": "UIPanel/MainMenuPanel" }, { "panelType": "TaskPanel", "path": "UIPanel/TaskPanel" }, { "panelType": "KnapsackPanel", "path": "UIPanel/KnapsackPanel" }, { "panelType": "ItemInfoPanel", "path": "UIPanel/ItemInfoPanel" }, { "panelType": "SkillPanel", "path": "UIPanel/SkillPanel" }, { "panelType": "ShopPanel", "path": "UIPanel/ShopPanel" }, { "panelType": "SettingPanel", "path": "UIPanel/SettingPanel" } ]
经过百度,进行Json文件的校验
使用Unity自带工具 JsonUtility进行Json数据的解析
将Json中的信息读取并存储到Dictionary中,key为type,value为path
在UIFramework文件夹下建立UIManager.cs类
UIManager是一个管理器,不是组件,所以不须要继承自MonoBehaviour
UIManager是单例模式
private Dictionary<UIPanelType, string> panelPathDict; // 来存储全部面板prefab的路径
解析Json信息的方法:
在UIFramework文件夹下建立Resources文件夹,将.json文件放入(由于须要使用Resources.Load()加载数据
TextAsset ta = Resources.Load<TextAsset>("path");
// 这里的path是UIPanelType,见Unity的project中,是没有json后缀的,因此不用写
JsonUtility API:
FromJson() -- Create an Object from its Json representation
ToJson() -- Generate a Json representation of the public fields of object
首先须要定义一个类,用于存储Json文件中的数据,类中成员变量须要和Json数据彻底对应
注意点:
JsonUtility支持的数据格式是对象的格式,必须符合:{ "key": ["", "", "", ... ] }
若是是 json 数组会提示错误 JSON must represent an object type
被转换的对象必须是可被序列化的,须要标记 [System.Serializable] 属性
新建脚本UIPanelData.cs
相同的,UIPanelData用于存储信息,不须要继承自MonoBehaviour
[Serializable] -- 可序列化的 -- using System;
序列化:将内存的数据存储到硬盘上
反序列化:从文本信息到对象的过程
这里须要把文本文件的内容读取到内存中,而这个类就是用于传递数据的。
因此这个类须要用 [Serializable] 标识
变量名彻底相同:
public UIPanelType panelType;
public string path;
[System.Serializable] public class UIPanelInfo { public UIPanelType panelType; public string path; }
-- 注意,这里的两个Serializable都是必须的,UIPanelInfo和UIPanelInfoList
经过JsonUtility将数据读取并存储到UIPanelInfo的对象中
-- (这里是有bug的,下面详述)
JsonUtility.FromJson< List<UIPanelInfo> >(textAsset.text);
// 返回值即List<UIPanelInfo>,新建变量存储
List<UIPanelInfo> panelInfoList = ...;
将List中的信息传递到Dictionary中
foreach( var element in panelInfoList) {
panelPathDict.Add(element.panelType, element.path);
}
将UIManager写成单例模式
-- 构造方法私有化,防止在外界实例化/ 调用构造方法
内部进行实例化
在构造方法中调用上述获取数据的方法
private UIManager() {
ParsePanelTypeJson();
}
public class UIManager { private Dictionary<UIPanelType, string> panelPathDict; // 单例模式 -- 构造函数为private private UIManager () { ParsePanelTypeJson(); } private void ParsePanelTypeJson() { // 用Resources.Load()从Json文件中读取text数据 TextAsset textAsset = Resources.Load<TextAsset>("UIPanelType"); // 将Json格式的text数据转换成List<>数据 List<UIPanelInfo> panelInfoList = JsonUtility.FromJson<List<UIPanelInfo>>(textAsset.text); // 把List的存储到Dictionary中 foreach(var element in panelInfoList) { panelPathDict.Add(element.panelType, element.path); }}}
定义一个静态私有变量
private static UIManager _instance;
提供这个静态私有变量的构造方法
public static UIManager Instance { // get方法公有,由于须要在外界访问该实例 get{ if(_instance == null) { // 第一次访问Get时,就会建立一个UIManager,且只有在这里可以建立 _instance = new UIManager(); // UIManager构造函数为私有,所以只能在内部调用 // 构造的时候,对Json数据进行解析读取 } // 以后访问时,就会直接获得以前建立的UIManager,保证了单例 return _instance; } }
新建GameRoot.cs脚本,用于控制游戏启动,至关于开始脚本
把GameRoot挂载在Canvas上,由于他是UI的启动器
UIManager.Instance.Test();
报错:ArgumentException: JSON must represent an object type.
https://answers.unity.com/questions/1148632/jsonutility-and-arrays-error-json-must-represent-a.html
https://blog.csdn.net/kenight/article/details/78787259
缘由:JSON须要转为一个Object,而不能是一个List的Object
在UIManager中建立类UIPanelInfoList,用来存储List<UIPanelInfo>
[System.Serializable]
class UIPanelInfoList {
public List<UIPanelInfo> panelInfoList;
}
将UIPanelType.json中的数据改成 -- 一个对象,对象里只有一个属性
{
"panelTypeList" : [
{...}, {...}, {...}, ...
]
}
此时,从JsonUtility.FromJson<UIPanelInfoList>(textAsset.text)返回的为UIPanelInfoList类型的对象
进行foreach (UIPanelInfo panelInfo in panelInfoList.panelInfoList) { }
如果Debug.Log出每个panelInfo.panelType,会发现输出了7个MainMenuPanel
-- 为何呢???
缘由:JsonUtility解析时发生了错误:返回的都是枚举类型UIPanelType的默认值MainMenuPanel
详细说明:Json解析对于UIPanelInfo中的成员变量UIPanelType没法解析
解决方案:
在public UIPanelType panelType;上加上 [NonSerialized],表示不须要解析
添加成员变量 public sting panelTypeString;
相对应的,将json文件中的key从"panelType"改成"panelTypeString"
如今,Json数据就能被正常解析了
可是:UIPanelInfo.panelType并未赋值
让UIPanelInfo继承自ISerializationCallbackReceiver接口
须要实现两个方法
public void OnAfterDeserialize() {
// 在反序列化以后自动调用 -- 即从Json文本数据解析至对象以后,进行调用
panelType = (UIPanelType) System.Enum.Parse(typeof(UIPanelType, panelTypeString);
}
public void OnBeforeSerialize() {
// 在序列化以前自动调用 -- 这里没有牵涉到序列化
// 将对象的数据写成Json文本文件
}
// System.Enum.Parse(System.Type enumType, string value);
// 返回类型为object,将object强制转换为UIPanelType,赋值给panelType
// 即完成了赋值操做 -- 将string的值赋给了相应的枚举类型
[System.Serializable] public class UIPanelInfo : ISerializationCallbackReceiver { [System.NonSerialized] public UIPanelType panelType; public string panelTypeString; // Json解析赋值给string,不能给Enum public string path; public void OnAfterDeserialize() { // 反序列化以后调用,即从Json文本数据解析至对象以后,会进行调用 // Debug.Log(panelTypeString); panelType = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString); } public void OnBeforeSerialize() { } }
成功输出每一个UIPanelType,和最后一个从Dictionary中取得的path值
获得了panel的路径之后,就能够经过路径来加载panel的prefab
加载出来的panel就是一个游戏物体,每一个panel都须要一个对应的脚原本实现它的功能
BasePanel.cs -- 实现全部面板共有的功能
其余全部面板的脚本都须要继承自BasePanel,如 ItemInfoPanel : BasePanel
由于其余面板是可变的,不属于UI框架的,所以将这些脚本放入Scripts->Panel,而BasePanel.cs放入UIFramework->Base
UIManager.cs中管理全部UI面板
建立字典 panelDict -- 保存全部的实例化后的面板上所挂载的BasePanel脚本组件
private Dictionary<UIPanelType, BasePanel> panelDict;
方法GetPanel -- 根据panelType来获得实例化面板上所挂载的BasePanel脚本组件
private BasePanel GetBasePanel(UIPanelType panelType) {
// 判断字典是否实例化
if(panelDict == null) {
// 建立字典
panelDict = new Dictionary<UIPanelType, BasePanel>();
}
BasePanel panel;
panelDict.TryGetValue(panelType, out panel);
// 若是panel为空,则该类型panel未实例化 -- 须要实例化该panel
if(panel == null) {
string path;
panelPathDict.TryGetValue(panelType, out path);
GameObject instantPanel = GameObject.Instantiate(Resource.Load(path)) as GameObject;
// 须要将panel放在Canvas下做为子物体
-- private Transform canvasTrans;
-- private Transform CanvasTrans {
get {
if(canvasTrans == null) {
canvasTrans = GameObject.Find("Canvas").transform;
}
return canvasTrans;
}}
instantPanel.transform.SetParent(CanvasTrans);
// 以后运行的时候会发现,若是就这么写,由于canvas的scale不是1,因此panel的位置信息会错乱,
// 须要修改为instantPanel.transform.SetParent(CanvasTrans, false);
// -- 重载方法SetParent(Transform, bool worldPositionStays): 是否保持物体在世界坐标中的位置
若是为true,则物体的局部坐标会为了使物体在世界中的位置不变而改变
若是为false,则物体的局部坐标就是本来的世界坐标
// 将刚实例化的panel存入panelDict
panelDict.Add(panelType, instantPanel.GetComponent<BasePanel>());
return instantPanel.GetComponent<BasePanel>();
} else { // panel已经存在
return panel;
}
-- 由于Dictionray.TryGetValue(key, out value),所以每次都须要定义一个BasePanel panel,显得很麻烦
定义一个Dictionary的扩展方法,简化操做(放在UIFramework->Extension文件夹)
-- 如何给系统内之类扩展方法
扩展方法的用处:当一个类的源码没法修改,可是又想后期添加方法的时候,就可使用扩展方法
思路:
1. 类应该为static
2. 扩展的方法为static
3. 返回值类型为泛型,由Dictionary的value类型而定
4. 须要传入Dictionary和key -- 在使用的时候不须要传递Dictionary,详见下
这里的Dictionary须要加修饰符this,表示这个方法是Dictionary类的扩展方法,经过这个类的对象进行调用
由于this就表示了当前对象
DictionaryExtension.cs
public static class DictionaryExtension { public static TValue TryGetValueExtension<TKey,TValue>( this Dictionary<TKey,TValue> dict, TKey key) { TValue value; dict.TryGetValue(key, out value); return value; } }
使用:
在UIManager中,有两个地方替换
BasePanel basePanel处:
BasePanel basePanel = panelDict.TryGetValueExtension(panelType);
string path处:
string path = panelPathDict.TryGetValueExtension(panelType);
Panel的三种状态:
prefab -- 完成 -- panelPathDict
在场景中被实例化 -- 完成 -- panelDict
在场景中显示 -- 未完成 -- 经过栈来管理
为何适合用栈来实现呢?
面板是一个一个出现,一个一个关闭的
先打开的最后关闭 -- 先入后出
代码实现:UIManager.cs中
private Stack<BasePanel> panelDisplayedStack;
何时入栈?
显示页面的时候
public void PushPanel(UIPanelType panelType) {
// 获得该类型的panel
BasePanel panel = GetBasePanel(panelType);
// 入栈
if(panelDisplayedStack == null) {
panelDisplayedStack = new Stack<BasePanel>();
}
panelDisplayedStack.Push(panel);
}
MainMenuPanel是须要存在的
在GameRoot.cs中
Start() {
UIManager.Instance.PushPanel(UIPanelType.MainMenuPanel);
}
Panel之间的切换 -- 经过点击MainMenuPanel上的按钮进行切换
切换Panel的代码在每个继承自BasePanel的子类 (这里是MainMenuPanel.cs)中实现
public void OnPushPanel(UIPanelType panelType) {
}
在Inspector中,将这个方法注册到全部按钮上,这时,发现找不到这个方法
缘由多是识别不了UIPanelType类型的参数,将其换成string类型,就能够成功注册了
在OnPushPanel中:
// 将string转换成枚举类型UIPanelType
UIPanelType panelType = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
// 入栈
UIManager.Instance.PushPanel(panelType);
如今,当咱们点击按钮时,就会进行入栈
(出栈在任务22中讲解)
运行,发现点击按钮,相应的panel就会显示出来
1. battle按钮没有对应的panel,了解一下
2. 若重复点击一个按钮,会报错:
ArgumentException: An element with the same key already exists in the dictionary.
1. 删除battle按钮的OnPushPanel()注册
2. 发现是由于在GetBasePanel()实例化一个panel后,出现判断错误
如:
前三句:在GameRoot中执行PushPanel(MainMenuPanel);
中间三局:点击TaskButton
最后一句:再次点击TaskButton,报错
缘由很明显:在进行panelDict.Add()的时候没有判断是否panel已在dict中存在
可是这个缘由并不能成为缘由,由于panelDict.Add()的调用前提就是没有找到panel,才进行实例化的
所以报错的缘由在与basePanel == null的判断上
发现第3句和第6句的区别:
第一次push时,将MainMenuPanel push进去了,可是第二次push时,push的为空物体
解决方案:
在PushPanel中,将panel输出,结果为空
而在上面实例化的instantPanel是没问题的,可是instantPanel.GetComponent<BasePanel>()为空
找了半天,妈蛋,是由于没有将TaskPanel等的TaskPanel类声明为BasePanel的子类
就像大司马说的同样:哇,好烦
3. 为何会直接显示呢,并且
由于入栈会GetBasePanel()
-- 第一次调用,在panelDict中找不到对应panel,会进行Instantiate(prefab)
所以,第一次调用的时候,会按照点击按钮的顺序直接在Game视图中显示出panel
可是,以后再点击,就没有反应了。
并且,再次点击虽然不会Instantiate新的Prefab,可是会对该prefab进行push stack操做
解决方法:是要进行在Stack中是否存在的判断吗?
不,直接将后台禁用。
正常的游戏设计是,只有在Stack栈顶的panel才能进行操做
好比,打开TaskPanel后,MainMenuPanel的功能就不能实现了
代码实现:
给每一个panel添加自身的生命周期,经过生命周期来控制panel当前的状态
生命周期状态:
进入显示 -- 点击相关按钮打开该panel,push入栈
暂停 -- 在该panel中点击按钮,打开其余panel,不在栈顶
继续 -- 关闭了其余panel,回到栈顶
移出显示 -- 关闭了自身,从栈中pop出去
由于每一个页面都有这个生命周期,所以将这些定义在BasePanel中
在UIManager中进行调用
在BasePanel中
public virtual void OnEnter() {}
public virtual void OnPause() {}
public virtual void OnResume() {}
public virtual void OnExit() {}
上一节中在基类BasePanel中定义了虚函数
这些函数的触发在UIManager中实现
UIManager.cs中:
入栈 --
在Push入栈以前,调用新入栈panel的OnEnter()
同时判断,若以前栈不为空,须要调用以前的栈顶panel的OnPause()
在PushPanel()中
panel.OnEnter();
if(stack.Count > 0) {
stack.Peek().OnPause();
}
MainMenuPanel中的override:
public override void OnPause() {
// 让MainMenuPanel再也不跟鼠标进行交互
// -- 在MainMenuPanel物体中添加组件Canvas Group
// -- 若将CanvasGroup.BlocksRaycasts设为false时,就不会进行鼠标射线检测了
// -- private CanvasGroup canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.blocksRaycasts = false;
}
出栈 --
当点击关闭按钮时,须要注册给关闭按钮一个方法:
以TaskPanel为例,由于MainMenuPanel没有关闭按钮
TaskPanel.OnClosePanel();
{
UIManager.Instance.PopPanel(panelType);
}
在UIManager.PopPanel()中
{
if(panelDisplayedStack == null) { ... = new ...; }
if(panelDisplayedStack.Count > 0) {
// 我的认为通常状况下不会出现为空,由于点击关闭按钮就表示当前有panel显示
// 获得栈顶panel,即即将被关闭的panel
BasePanel panel = panelDisplayedStack.Pop();
// 关闭
panel.OnExit();
// 激活下一层的panel -- 若是有的话(没有的话就不操做)
if(panelDisplayedStack.Count > 0) {
panelDisplayedStack.Peek().OnResume();
} } }
在TaskPanel中,须要override OnExit:
public override void OnExit() {
// CanvasGroup.alpha = 0 表示不显示
// 注意,这里只是不显示,所以还需暂停鼠标交互
canvasGroup.alpha = 1;
canvasGroup.blocksRaycasts = false;
}
在MainMenuPanel中,须要override OnResume:
public override void OnResume() {
canvasGroup.blocksRaycasts = true;
}
运行:
显示主菜单,点击任务,显示任务菜单,关闭,任务菜单消失,再点击任务菜单,没有反应
缘由:由于任务面板的canvasGroup.alpha = 0;不显示(固然,也一样不能交互)
解决方法:
由于再次点击任务,会触发MainMenuPanel.OnPushPanel()
-> UIManager.PushPanel() -> TaskPanel.OnEnter()
在OnEnter()中加上
canvasGroup.alpha = 1;
canvasGroup.blocksRaycasts = true;
解决了吗?
运行:报错 --
NullReferenceException: Object reference not set to an instance of an object
TaskPanel.OnEnter () (at Assets/Scripts/Panel/TaskPanel.cs:18)
在canvasGroup.alpha = 1;处报错
缘由:
当第一次实例化TaskPanel时
即点击任务按钮->MainMenuPanel.OnPushPanel()->UIManager.PushPanel()->GetBasePanel()时
实例化完TaskPanel,返回taskPanel给PushPanel(),就当即调用了taskPanel.OnEnter()
此时,taskPanel.Start()还未被调用???为何???
解决方法:
在OnEnter()中进行判断
if(canvasGroup == null) {
canvasGroup = GetComponent<CanvasGroup>();
}
...
在Start()中也进行相同判断
// UIManager.cs中 public void PushPanel(UIPanelType panelType) { BasePanel panel = GetBasePanel(panelType); // Debug.Log(panel); if(panelDisplayedStack == null) { panelDisplayedStack = new Stack<BasePanel>(); } // 进行生命周期的调用 // 由于新Push入栈,因此须要调用OnEnter() panel.OnEnter(); // 若是以前的栈不为空,则以前的栈顶panel须要调用OnPause() if(panelDisplayedStack.Count > 0) { // Debug.Log("peek on pause: " + panelDisplayedStack.Peek()); panelDisplayedStack.Peek().OnPause(); } panelDisplayedStack.Push(panel); // Debug.Log("Push " + panel + " 进入Stack"); } public void PopPanel() { if(panelDisplayedStack == null) { // 不会吧? panelDisplayedStack = new Stack<BasePanel>(); } if(panelDisplayedStack.Count > 0) { // 通常状况不会为空,但仍是作一下安全校验 BasePanel panel = panelDisplayedStack.Pop(); panel.OnExit(); // Resume新的栈顶panel if (panelDisplayedStack.Count > 0) { panelDisplayedStack.Peek().OnResume(); }}}
public class MainMenuPanel : BasePanel { private CanvasGroup canvasGroup; private void Start() { canvasGroup = GetComponent<CanvasGroup>(); } public void OnPushPanel(string panelTypeString) { // 将string转换成UIPanelType UIPanelType panelType = (UIPanelType) System.Enum.Parse(typeof(UIPanelType), panelTypeString); UIManager.Instance.PushPanel(panelType); } public override void OnPause() { // 该面板再也不和鼠标进行交互 canvasGroup.blocksRaycasts = false; } public override void OnResume() { canvasGroup.blocksRaycasts = true; canvasGroup.alpha = 1; }}
public class TaskPanel : BasePanel { private CanvasGroup canvasGroup; private void Start() { if (canvasGroup == null) { canvasGroup = GetComponent<CanvasGroup>(); }} public void OnClosePanel() { UIManager.Instance.PopPanel(); } public override void OnEnter() { if (canvasGroup == null) { canvasGroup = GetComponent<CanvasGroup>(); } canvasGroup.alpha = 1; canvasGroup.blocksRaycasts = true; } public override void OnExit() { canvasGroup.alpha = 0; canvasGroup.blocksRaycasts = false; }}
总结:UI框架的优势:
后期,当UI框架开发完,只须要对新面板进行override上述生命周期的四个事件方法便可。
Panel之间的跳转:
MainMenu -> Knapsack; Knapsack close
Knapsack -> ItemInfo; ItemInfo close
MainMenu -> Skill; Skill close
MainMenu -> Shop; Shop close
MainMenu -> Setting; Setting close
代码实现:
在Knapsack/ ItemInfo/ Skill/ Shop/ Setting 中实现
Start() // 判断并赋值canvasGroup
OnClosePanel() // 调用UIManager的PopPanel()便可
OnEnter() // 判断canvasGroup是否为空,为空就赋值,以后设置alpha和blocksRaycasts
OnExit() // 设置canvasGroup的alpha和blocksRaycasts
注册关闭按钮
Knapsack <-> ItemInfo
实现Knapsack子物体ItemSlot->Item的按钮点击注册
OnItemButtonClick() {
UIManager.Instance.PushPanel(UIPanelType.ItemInfoPanel);
}
如今,能够实现物品信息面板的开启和关闭了
可是,开启物品信息面板时,与Knapsack仍是能够交互的,点击knapsack的关闭按钮,会关闭物品信息面板
由于Stack的存储机制
实现Knapsack的OnPause() -- 暂停交互 // canvasGroup.blocksRaycasts = false;
实现Knapsack的OnResume() -- 从新开始交互 // canvasGroup.blocksRaycasts = true;
在OnEnter()和OnExit()时作一些动画处理
导入插件DoTween
素材中有Pro版,AssetStore中能够下载到普通版
使用DoTween制做动画
http://dotween.demigiant.com/getstarted.php
以KnapsackPanel为例 -- 平移动画
引入命名空间 using DG.Tweening;
在OnEnter()的最后,实现DoTween动画
// Tweener Transform.DOLocalMove(float endValue, float duration, [bool snapping = false])
Vector3 temp = transform.localPosition;
temp.x = -Screen.width *3/4; // 屏幕外面 (自己宽度大概在屏幕的一半)
transform.localPosition = temp;
// 目标x位置为0,用时0.5f
transform.DOLocalMoveX(0, 0.5f);
相同的,在OnExit()最开始
transform.DOLocalMoveX(Screen.width *3/4, 0.5f);
这时,就不须要改变alpha了
但,若是想要改变alpha呢?
直接放在下面,会致使刚开始进行DOLocalMoveX的时候就已经设置好了Alpha
-- OnComplete()
transform.DOLocalMoveX(..., ...).OnComplete(()=>canvasGroup.alpha = 0);
这样,在进行完动画时,就会回调设置alpha值
以TaskPanel为例 -- 透明度动画
canvasGroup.DoFade(float endvalue, float duration);
在OnEnter()最后
先把alpha设置为0,而后执行动画
canvasGroup.DoFade(1, 0.5f);
在OnExit()最后
canvasGroup.DoFade(0, 0.5f);
以ItemInfoPanel为例 -- 缩放动画
transform.DOScale(float endValue, float duration);
在OnEnter()最后
先把scale设置为0
transform.localScale = Vector3.zero;
实现缩放
transform.DOScale(1, 0.5f);
在OnExit()最后
transform.DOScale(0, 0.5f).OnComplete(() => canvasGroup.alpha = 0);
总结: