まず、UI フレームワークの機能は UI パネル間の相互ジャンプを制御することであることを説明します。UI フレームワークを使用した後の最大の用途は、ページ切り替え時の複雑な操作を回避することです。UI フレームワークを使用すると、UI ページ、表示、および操作をより適切に管理できます。コントロール ページの終了も 1 つの関数のみで制御され、コードが大幅に最適化されます。
まずはUIフレーム図を見てみましょう
上の図と組み合わせて、UI フレームワークを段階的に作成し始めます
1. まず UI の各パネルを個別に作成し、それをプレハブとして Resources ファイルに配置します (これの目的は、ロード時にすべてのパネルを直接取得することです (具体的な実装は、ディクショナリ内の json 解析を通じて行われます)。具体的な実装は以下の通りです)
2. 列挙型 UIPanelType を作成して、対応するパネルを保存します
UIPanelType の json ファイルを作成して、パネルとその対応するパスを保存します。後で解析を成功させるために、ここでの panelType の値は列挙型の値と一致している必要があることに注意してください (このファイルは、 Resources ファイルなので、Resources ファイル内に配置します (別の Resources ファイルを作成できます))
3. 3 つの関数を持つ UIManager クラスを作成します。
最初の機能: パネル情報の解析と保存 (json ファイルを解析し、それを辞書に保存することで、キーは列挙型の UIType で、値はパスです) (json を解析するときは、データであることに注意してください)これを受け取るには UIPanelInfo クラスが必要なので、json 解析データを受け取る UIPanelInfo クラスを作成します) ここでの UIPanelInfo クラスは対応する [ {} , {}] ですが、この構造が配列またはリストとして解析されることがわかります。ただし、Unity 解析ではクラスを解析することしかできません (バージョン 0.9.0 以降、LitJSON は後でクラスをさらに作成する必要なく、List オブジェクトの JSON 文字列へのシリアル化をサポートしています)。そのため、この構造を { [ { に変更する必要があります。 }、{}、{} ] }、および配列を {} に配置します。つまり、最も外側の {} を表すために別のクラスUIPanelTypeJsonが必要です。json を解析するときは、列挙型に文字列を使用する必要があることに注意してください (列挙型に直接解析することはできません) 詳細については、「文字列と列挙型の変換」を参照してください (現在、関数の反復では uimanager のみが使用されます)、最初の関数を読み取った後は、手順 4 を参照してください。
Jsonファイルの内容もこうなります
2 番目の関数: パネルのインスタンスを作成して保存します (ディクショナリを介して、キーはパネルの列挙であり、値はパネルの BasePanel コンポーネントです (親クラスはサブクラス オブジェクト、および BasePanel を通じて取得できます)ページを表すために使用されます))、作成メソッド( GetPanel () ) パネル列挙によりパネルの親クラスを取得します(取得時に辞書を拡張するメソッドがあります。辞書の拡張を参照してください)
3 番目の機能: 表示されているすべてのパネルの管理と保存 (表示されているページはスタックによって保存され、表示されているすべてのページはスタックに表示されます。スタックの先頭が操作可能なページ、つまりページです)スタックの一番下(ページは表示されるだけで操作はできません)なので、スタックの作成、ポップ、プッシュの操作があります(すべてのページの表示と閉じるため、この2つが最も重要です)このポップとプッシュに依存する)操作 (および次のステップ 5 のステータスが追加されます)
4. パネルの共通基本クラス BasePanel を作成します。これにより、すべてのパネルが BasePanel から継承されます。これは、各パネルのスクリプトが BasePanel から継承されるため (ここでのパネル クラスは独自に作成されます)、各パネルをインスタンス化した後、直接実行できます。パネルを通じてオブジェクトをインスタンス化します。GetComponent<BasePanel>() により、UIManager クラスの 2 番目の関数を追加できます。
これらは独自のプロジェクトに従って作成されたパネルであり、下部にパネル クラス コードを添付します。
5. 各ページには 4 つの状態があります。OnEnter (ページが表示されている)、OnPause (他のページが表示されたためページが一時停止されている)、OnResume (ページが続行され、このページでカバーされている他のページが削除され、ページが停止されます)。インタラクション)、OnExit (ページの削除、ページの表示の削除) 詳細については、フローチャートを参照してください。各ページにはこれら 4 つの状態があるため、これらの 4 つの状態は仮想メソッドを通じて BasePanel で定義され、バックパック パネルを参照できる各サブクラスの書き換えには、これら 4 つの状態が含まれます (表示アニメーションと終了アニメーションを追加したい場合は、OnEnter と OnExit に追加します)。
6. 各パネルの各ボタンのクリック イベントを追加します (BasePanel から継承したスクリプト内) たとえば、メイン メニューがあり、スクリプトは MainPanel で、メイン メニューにはタスク、バックパック、およびその他のボタンがあります。このとき、UI フレームワークが機能します。ボタン クリック イベントで UIManager の Push メソッドを通じてページを直接表示し、pop メソッドでページを閉じることができます。パネルの列挙型も渡す必要があることを忘れないでください。 (クリックイベントはここで列挙型を渡すことはできません。列挙型のパラメーターなので、文字列を列挙型に変換するステップが依然として必要です)、次のようになります。
7. これは GameRoot 部分であり、UI フレームワークを開始するために使用されます。
コード部分を上で紹介した順序で以下に添付します。
UIPanelType クラス:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum UIPanelType
{
ItemMessage,
Knapsack,
MainMenu,
Shop,
Skill,
System,
Task
}
UIPanelType の json ファイル:
{
"infoList": [
{
"panelTypeString": "ItemMessage",
"path": "UIPanel/ItemMessagePanel"
},
{
"panelTypeString": "Knapsack",
"path": "UIPanel/KnapsackPanel"
},
{
"panelTypeString": "MainMenu",
"path": "UIPanel/MainMenuPanel"
},
{
"panelTypeString": "Shop",
"path": "UIPanel/ShopPanel"
},
{
"panelTypeString": "Skill",
"path": "UIPanel/SkillPanel"
},
{
"panelTypeString": "System",
"path": "UIPanel/SystemPanel"
},
{
"panelTypeString": "Task",
"path": "UIPanel/TaskPanel"
}
]
}
UIManager クラス (フレームワークのコアコンテンツ)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIManager
{
#region 第一部分功能
private Dictionary<UIPanelType, string> panelPathDict;//面板信息字典,用来存放面板所对应的Prefab路径
private static UIManager _instance;
public static UIManager Instance//单例模式不是这章重点,不做讲解
{
get
{
if(_instance == null)
{
_instance = new UIManager();
}
return _instance;
}
}
private UIManager()
{
ParseUIPanelTypeJson();
}
[Serializable]
class UIPanelTypeJson
{
public List<UIPanelInfo> infoList;
}
private void ParseUIPanelTypeJson()//解析json文件
{
panelPathDict=new Dictionary<UIPanelType, string>();
TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//json文件在unity中是属于TextAsset类,因此用这个类来接收
//这里应该也可以用txt文件保存json数据,然后用读取流的形式读取
UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(ta.text);//注意,如果这样的话就会报错,原因在解析json的时候,并不能解析成枚举类型,因为在UIPanelInfo类的panelType成员为枚举类型,因此就会报解析错误
//将解析的json信息,填入panelPathDict字典中,键对应面板枚举值,值对应面板路径
foreach (UIPanelInfo info in jsonObject.infoList)
{
panelPathDict.Add(info.panelType, info.path);
}
}
#endregion
#region 第二部分功能
private Dictionary<UIPanelType, BasePanel> panelDict;//保存所有实例化面板的游戏物体身上的脚本组件(继承自BasePanel的)
private Transform canvasTransform;//获取面板
public Transform CanvasTransform
{
get
{
if(canvasTransform == null)
{
canvasTransform = GameObject.Find("Canvas").transform;
}
return canvasTransform;
}
}
private BasePanel GetPanel(UIPanelType panelType)//根据面板的枚举类型,得到实例化的面板
{
if (panelDict == null)//判断字典是否为空
{
panelDict = new Dictionary<UIPanelType, BasePanel>();//如果为空就创建字典
}
// BasePanel panel;
//panelDict.TryGetValue(panelType, out panel);
BasePanel panel = panelDict.TryGet(panelType);//字典扩展方法
if (panel == null)//判断是否得到该面板
{
//如果为空就根据panelPathDict字典中路径实例化面板,并保存在panelDict中和放在Canvas画布下
//string path;
// panelPathDict.TryGetValue(panelType, out path);
string path = panelPathDict.TryGet(panelType);
//GameObject instPanelPrefab = Resources.Load<GameObject>(path);//这是通过动态加载的形式获取到了面板预制件的引用
//GameObject instPanel=GameObject.Instantiate(instPanelPrefab);//因为没有继承自MonoBehaviour,所有需要加GameObject
GameObject instPanel = GameObject.Instantiate(Resources.Load<GameObject>(path));//这是简洁写法
panelDict.Add(panelType, instPanel.GetComponent<BasePanel>());
instPanel.transform.SetParent(CanvasTransform, false);//放在画布下
return instPanel.GetComponent<BasePanel>();
}
else
{
return panel;
}
}
#endregion
#region 第三部分功能
private Stack<BasePanel> panelStack;
//都是根据页面的枚举类型来获取到页面,才能方便出栈入栈操作
public void PushPanel(UIPanelType panelType )//把页面入栈,把某个页面显示在界面上
{
if(panelStack == null)
{
panelStack = new Stack<BasePanel>();
}
//判断栈里面是否有页面
if(panelStack.Count > 0 )
{
BasePanel topPanel=panelStack.Peek();//这是获取到栈顶元素Pop是出栈
topPanel.OnPause();//将栈顶元素暂停
}
BasePanel panel=GetPanel(panelType);//这里就用到了第二部分功能中的获取面板的方法
panelStack.Push(panel);//入栈
panel.OnEnter();//这是新的栈顶元素显示
//注意这里都只是统一调用BasePanel中的方法,因为BasePanel中的方法是虚方法没有具体实现,需要在子类中具体实现后,就能有具体的功能
}
public void PopPanel()//把页面出栈, 把页面从界面上移除
{
if (panelStack == null)
{
panelStack = new Stack<BasePanel>();
}
if (panelStack.Count <= 0)
{
return;
}
BasePanel topPanel=panelStack.Pop();//栈顶出栈
topPanel.OnExit();
if (panelStack.Count <= 0)//这是如果关闭页面后下方还有页面就需要将栈顶页面恢复
{
return;
}
BasePanel topPanel2 = panelStack.Peek();
topPanel2.OnResume();//这里同理,具体实现都是在子类脚本中重新,这里只负责调用
}
#endregion
}
UIPanelInfoクラス:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class UIPanelInfo:ISerializationCallbackReceiver
{
[NonSerialized]//因为json解析成不了枚举类型,因此就不解析成它
public UIPanelType panelType;//注意,因为这个类时用来接收解析的json的,因此变量成员名一定要和json中的名字要相同
public string panelTypeString;//json解析成字符串
public string path;
public void OnAfterDeserialize()//反序列化成功之后调用
{
// throw new NotImplementedException();
//这里就是将字符串转化为枚举类型的过程,放在反序列化成功之后执行
UIPanelType Type = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
panelType = Type;
}
public void OnBeforeSerialize()//反序列化成功之前调用
{
// throw new NotImplementedException();
}
}
DictionaryExtension クラス: 辞書拡張クラス
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class DictionaryExtension //字典扩展类,对字典使用方法进行扩展
{
public static Tvalue TryGet<Tkey,Tvalue>(this Dictionary<Tkey,Tvalue> dict,Tkey key)
{
Tvalue value;
dict.TryGetValue(key, out value);
return value;
}
}
BasePanel クラス:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BasePanel : MonoBehaviour
{
/// <summary>
/// 界面被显示出来
/// </summary>
public virtual void OnEnter()
{
}
/// <summary>
/// 界面暂停
/// </summary>
public virtual void OnPause()
{
}
/// <summary>
/// 界面继续
/// </summary>
public virtual void OnResume()
{
}
/// <summary>
/// 界面不显示,退出这个界面,界面被关系
/// </summary>
public virtual void OnExit()
{
}
}
ゲームルートクラス:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameRoot : MonoBehaviour
{
private void Start()
{
UIManager.Instance.PushPanel(UIPanelType.MainMenu);
}
}
フレームワークの一部ではなく、プロジェクト パネルの一部 (BasePanel から継承) を表示するクラス
メイン メニュー クラス (メイン メニュー ページに実装)、クリック イベント メソッド (メイン メニューのタスク ボタンをクリックしてタスク ページを表示するなど) は、独自の関数を記述してボタンにバインドする必要があります。ここでは、 OnClickPushPanel() 関数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainMenuPanel : BasePanel
{
private CanvasGroup canvasGroup;
private void Start()
{
canvasGroup = GetComponent<CanvasGroup>();
}
//这里是重新的MainMenuPanel页面枚举类型对应的BasePanel中的OnPause方法,因为在UIManager中的panelDict保存了枚举类型对应的BasePanel
//因此这里的调用流程就是先调用下方的PushPanel,在PushPanel函数中根据panelType获取到BasePanel,然后执行 panel.OnPause();等方法,又因为
//获取到的BasePanel就是 MainMenuPanel的父类这个 BasePanel,因此调用的panel.OnPause();方法就是执行的下方的内容
public override void OnPause()//这里具体的暂停方法就是取消掉主页面中的所有可交互的东西,因此可以用CanvasGroup控制,详细使用方法见手册
{
canvasGroup.blocksRaycasts = false;//暂停该页面,让鼠标不再和该页面交互
}
public override void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
public void OnClickPushPanel(string panelString )//定义点击事件,将要显示的页面添加进栈中,这里是因为要复用才通过字符串形式转换
{
UIPanelType panelType=(UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelString);
UIManager.Instance.PushPanel( panelType );
}
}
もう 1 つの例はバックパック クラスです。
using DG.Tweening;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class KnapsackPanel : BasePanel
{
private CanvasGroup canvasGroup;
private void Start()
{
if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();
}
public override void OnEnter()
{
if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();//这里是防止面板在实例化出来后,立马调用了OnEnter()方法,导致 canvasGroup还没赋值而报空
canvasGroup.alpha = 1;
canvasGroup.blocksRaycasts = true;
// gameObject.SetActive(true); //这里也可以直接可以设置页面的active来表示进入和退出
Vector3 temp = transform.localPosition;
temp.x = 1300;
transform.localPosition = temp;
transform.DOLocalMoveX(0, 0.5f);
}
public override void OnExit()//关闭该页面的具体实现内容
{
//canvasGroup.alpha = 0;
canvasGroup.blocksRaycasts = false;
transform.DOLocalMoveX(1800, 0.5f).OnComplete(() => { canvasGroup.alpha = 0; });
// gameObject.SetActive(false);
}
public override void OnPause()
{
canvasGroup.blocksRaycasts = false;
}
public override void OnResume()
{
canvasGroup.blocksRaycasts = true;
}
public void OnClosePanel()
{
UIManager.Instance.PopPanel();
}
public void OnItemButtonClick()//点击显示物品按钮
{
UIManager.Instance.PushPanel(UIPanelType.ItemMessage);
}
}
要約すると、UI フレームワークに属するクラスは 7 つだけです。
KnapsackPanel などの他のクラスもプロジェクトの変更に応じて変更されます