前言:
前面已经成功通过ProcedureChangeScene流程转到了菜单流程,并且能够成功加载出菜单场景。
但是目前菜单场景里面空无一物,这篇的主要内容就是加载出菜单UI。所以这一次又会用到框架里的一个新的模块——UI。
前置准备:
背景图
一张场景的背景图:
然后在Menu场景里面装上背景图(创建材质,Shader选择为Unlit/Texture然后选上背景图):
然后挂到场景的一个Quad板子上:
UI预制体
然后就直接在这个场景里面拼UI,创建一个Canvas改名为MenuForm:
然后拼一个极丑UI:
但是嘞,看StarForce的菜单UI就会发现一些事情:
它的UI中的文字内容有点奇怪,比如开始是:Menu.StartButton。我猜测最终显示之前,框架会对UI的这些文字内容进行替换(为了方便配置本地化语言之类的吧)。
不过这次先不搞本地化了。。。
然后就是按钮逻辑。
StarForce作者是真的强,按钮组件是自己写的:
阅读一下它的源码,分析一波菜单UI的组织结构:
首先它的每个按钮都挂上了CommonButton类,这样就可以如上面的图里面Inspector里面那样选择脚本的函数挂到按钮的点击等事件上。
然后按钮的点击事件(逻辑)的实现,都在当前UI页面的一个类里面。比如菜单页面就是在MenuForm.cs里面:
然后我们再分析这个类的内容,可以看到每个Ui面板的类都继承自UGuiForm类:
而UGuiForm是StarForce中实现的,继承自框架中UIFormLogic类的一个抽象类:
UIFormLogic类主要管理了界面的逻辑(外部自定义的UI界面主要通过继承这个类来受到框架管理),比如初始化要干啥,关闭的时候要干啥,每一帧要轮询干啥,界面激活的时候要干啥等等。可以通过重写对应的一些函数自定义一个界面的逻辑,如下为部分函数截图:
然后UGuiForm类是StarForce作者大佬自己封装的一个继承自UIFormLogic的抽象类。。。里面加了一些功能比如对本界面进行Text的本地化,字体设置,开关界面的渐变效果等。。。
行呗,开始复刻(抄)!
首先,按钮我们就不写新的脚本了,就用UnityEngine内置的Button组件。
然后再照着StarForce完成UGuiForm抽象类(好叭就是复制过来删改一些东西)(其实直接写界面类继承UIFormLogic也可以运行,但是后期扩展不方便)。
不过里面有些函数需要仿照StarForce里面的UIExtension.cs类对UIComponent进行扩展,也出现了其他封装的类。这里就不复刻了否则的话感觉花在学大佬封装的精力都远超学习框架了。
最终修改完的UGuiForm的代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace ShadowU
{
public abstract class UGuiForm : UIFormLogic
{
public const int DepthFactor = 100;
private const float FadeTime = 0.3f;
//private static Font s_MainFont = null;
private Canvas m_CachedCanvas = null;
private CanvasGroup m_CanvasGroup = null;
private List<Canvas> m_CachedCanvasContainer = new List<Canvas>();
public int OriginalDepth
{
get;
private set;
}
public int Depth
{
get
{
return m_CachedCanvas.sortingOrder;
}
}
public void Close()
{
Close(false);
}
public void Close(bool ignoreFade)
{
StopAllCoroutines();
if (ignoreFade)
{
GameEntry.UI.CloseUIForm(this.UIForm);
}
else
{
StartCoroutine(CloseCo(FadeTime));
}
}
/*
public void PlayUISound(int uiSoundId)
{
GameEntry.Sound.PlayUISound(uiSoundId);
}
*/
/*
public static void SetMainFont(Font mainFont)
{
if (mainFont == null)
{
Log.Error("Main font is invalid.");
return;
}
s_MainFont = mainFont;
}*/
#if UNITY_2017_3_OR_NEWER
protected override void OnInit(object userData)
#else
protected internal override void OnInit(object userData)
#endif
{
base.OnInit(userData);
m_CachedCanvas = gameObject.GetOrAddComponent<Canvas>();
m_CachedCanvas.overrideSorting = true;
OriginalDepth = m_CachedCanvas.sortingOrder;
m_CanvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
RectTransform transform = GetComponent<RectTransform>();
transform.anchorMin = Vector2.zero;
transform.anchorMax = Vector2.one;
transform.anchoredPosition = Vector2.zero;
transform.sizeDelta = Vector2.zero;
gameObject.GetOrAddComponent<GraphicRaycaster>();
/*
Text[] texts = GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
texts[i].font = s_MainFont;
if (!string.IsNullOrEmpty(texts[i].text))
{
texts[i].text = GameEntry.Localization.GetString(texts[i].text);
}
}*/
}
#if UNITY_2017_3_OR_NEWER
protected override void OnRecycle()
#else
protected internal override void OnRecycle()
#endif
{
base.OnRecycle();
}
#if UNITY_2017_3_OR_NEWER
protected override void OnOpen(object userData)
#else
protected internal override void OnOpen(object userData)
#endif
{
base.OnOpen(userData);
m_CanvasGroup.alpha = 0f;
StopAllCoroutines();
StartCoroutine(FadeToAlpha(m_CanvasGroup,1f, FadeTime));
}
#if UNITY_2017_3_OR_NEWER
protected override void OnClose(bool isShutdown, object userData)
#else
protected internal override void OnClose(bool isShutdown, object userData)
#endif
{
base.OnClose(isShutdown, userData);
}
#if UNITY_2017_3_OR_NEWER
protected override void OnPause()
#else
protected internal override void OnPause()
#endif
{
base.OnPause();
}
#if UNITY_2017_3_OR_NEWER
protected override void OnResume()
#else
protected internal override void OnResume()
#endif
{
base.OnResume();
m_CanvasGroup.alpha = 0f;
StopAllCoroutines();
StartCoroutine(FadeToAlpha(m_CanvasGroup,1f, FadeTime));
}
#if UNITY_2017_3_OR_NEWER
protected override void OnCover()
#else
protected internal override void OnCover()
#endif
{
base.OnCover();
}
#if UNITY_2017_3_OR_NEWER
protected override void OnReveal()
#else
protected internal override void OnReveal()
#endif
{
base.OnReveal();
}
#if UNITY_2017_3_OR_NEWER
protected override void OnRefocus(object userData)
#else
protected internal override void OnRefocus(object userData)
#endif
{
base.OnRefocus(userData);
}
#if UNITY_2017_3_OR_NEWER
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
#else
protected internal override void OnUpdate(float elapseSeconds, float realElapseSeconds)
#endif
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
}
#if UNITY_2017_3_OR_NEWER
protected override void OnDepthChanged(int uiGroupDepth, int depthInUIGroup)
#else
protected internal override void OnDepthChanged(int uiGroupDepth, int depthInUIGroup)
#endif
{
int oldDepth = Depth;
base.OnDepthChanged(uiGroupDepth, depthInUIGroup);
// int deltaDepth = UGuiGroupHelper.DepthFactor * uiGroupDepth + DepthFactor * depthInUIGroup - oldDepth + OriginalDepth;
int deltaDepth = 10000 * uiGroupDepth + DepthFactor * depthInUIGroup - oldDepth + OriginalDepth; //直接把StarForce里面的值替换过来了
GetComponentsInChildren(true, m_CachedCanvasContainer);
for (int i = 0; i < m_CachedCanvasContainer.Count; i++)
{
m_CachedCanvasContainer[i].sortingOrder += deltaDepth;
}
m_CachedCanvasContainer.Clear();
}
private IEnumerator CloseCo(float duration)
{
yield return FadeToAlpha(m_CanvasGroup,0f, duration);
GameEntry.UI.CloseUIForm(this.UIForm);
}
/// <summary>
/// 从StarForce的UIExtension.cs里面搬过来改改的
/// </summary>
/// <param name="canvasGroup"></param>
/// <param name="alpha"></param>
/// <param name="duration"></param>
/// <returns></returns>
private IEnumerator FadeToAlpha(CanvasGroup canvasGroup, float alpha, float duration)
{
float time = 0f;
float originalAlpha = canvasGroup.alpha;
while (time < duration)
{
time += Time.deltaTime;
canvasGroup.alpha = Mathf.Lerp(originalAlpha, alpha, time / duration);
yield return new WaitForEndOfFrame();
}
canvasGroup.alpha = alpha;
}
}
}
然后写MenuForm.cs(因为我这里没有学StarForce里面那样写CommonButton组件,所以这里对于按钮事件的添加就采用public拖动变量的方法):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace ShadowU
{
public class MenuForm : UGuiForm
{
public Button button_start;
public Button button_settings;
public Button button_about;
public Button button_quit;
private ProcedureMenu m_procedureMenu = null;
private void OnStartButtonClick()
{
m_procedureMenu.StartGame();
}
private void OnSettingsButtonClick()
{
}
private void OnAboutButtonClick()
{
}
private void OnQuitButtonClick()
{
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
m_procedureMenu = (ProcedureMenu)userData;
if(m_procedureMenu == null)
{
Log.Warning("ProcedureMenu is invalid when opening Menu");
return;
}
}
protected override void OnInit(object userData)
{
base.OnInit(userData);
//添加按钮事件
button_start.onClick.AddListener(OnStartButtonClick);
button_settings.onClick.AddListener(OnSettingsButtonClick);
button_about.onClick.AddListener(OnAboutButtonClick);
button_quit.onClick.AddListener(OnQuitButtonClick);
}
}
}
(顺便在菜单流程里面加了个开始游戏方法(待完成)):
using GameFramework.Fsm;
using UnityGameFramework.Runtime;
namespace ShadowU
{
public class ProcedureMenu :ProcedureBase
{
public override bool UseNativeDialog
{
get
{
return true;
}
}
public void StartGame()
{
Log.Debug("开始游戏!");
}
protected override void OnEnter(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner)
{
base.OnEnter(procedureOwner);
Log.Debug("成功转到菜单流程");
}
}
}
然后我们把写好的MenuForm挂到拼好的菜单UI界面上,并且把按钮拖进去:
然后把做好的页面拖成预制体放到UIForms目录下:
ProcedureMenu菜单流程
终于我们可以回到ProcedureMenu里面继续了。在OnEnter里面注册加载UI成功的事件,设置游戏开始的标志为false,然后打开UI界面:
using GameFramework.Fsm;
using UnityGameFramework.Runtime;
using GameFramework.Event;
namespace ShadowU
{
public class ProcedureMenu :ProcedureBase
{
private bool m_StartGame = false;
//private MenuForm m_MenuForm;
public override bool UseNativeDialog
{
get
{
return true;
}
}
public void StartGame()
{
Log.Debug("开始游戏!");
}
protected override void OnEnter(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner)
{
base.OnEnter(procedureOwner);
Log.Debug("成功转到菜单流程");
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
m_StartGame = false;
//打开菜单界面
GameEntry.UI.OpenUIForm(AssetUtility.GetUIFormAsset("MenuForm"),"Default");
}
protected override void OnLeave(IFsm<GameFramework.Procedure.IProcedureManager> procedureOwner, bool isShutdown)
{
base.OnLeave(procedureOwner, isShutdown);
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
/*
if (m_MenuForm != null)
{
m_MenuForm.Close(isShutdown);
m_MenuForm = null;
}
*/
}
private void OnOpenUIFormSuccess(object sender, GameEventArgs e)
{
OpenUIFormSuccessEventArgs ne = (OpenUIFormSuccessEventArgs)e;
if (ne.UserData != this)
{
return;
}
// m_MenuForm = (MenuForm)ne.UIForm.Logic;
}
}
}
细心点可以注意到框架中OpenUIForm函数第二个参数需要UIGroup,我写的是"Default"。如果现在运行的话,会报错,界面并不会加载出来。因为这里需要在场景里框架预制体的子物体UI中设置一下,添加个UI组Default:
最后运行:
按钮可以点击,但是开始按钮点击后报错了。。。。(如果按钮没反应检查Menu场景是否加了EventSystem) 很显然是m_procedureMenu没有获取成功。原因是ProcedureMenu里面打开Form的函数用得不对:
用这个方法的话传入的userData就是为空null:
所以最终传给MenuForm里面OnOpen函数的userData也是空的了:
所以我们改一下打开界面函数为这样:
(优先级1随便设置的设的不好别怪我)
然后再运行看看结果:
终于 搞定了
版权归原作者 Fighlone 所有, 如有侵权,请联系我们删除。