接触了Unity制作不管是程序还是游戏都避免不了UI的制作,但是在网上搜的UI制作的学习过程,额…一言难尽,就像是拼图一样在那一块块搜索然后再将它拼装起来,痛苦万分.因此撰此文以记录我的UI的学习过程.首先要理解的就是UI设计的含义,UI包括登陆界面,暂停界面等等但不只有这些东西.UI称之为(User Interface),是指对软件的人机交互,操作逻辑,界面美观的整体设计.UI设计的目的是操作变得更舒适简单自由.一般来说在游戏中接触最多的UI就是登陆界面,暂停界面,设置界面,教程界面之类的,或者说游戏右上角假如有一个暂停按键(开挂按键)那么它也可以称之为UI.
参考网址:https://docs.unity.cn/cn/current/Manual/UIToolkits.html
一般来说在开始搜索“UI的制作过程”都是在要制作登陆界面或者暂停界面遇到了瓶颈,所以本文将着重于如何快速的利用unity的内置工具制作出一个能用能看的简易UI,用到的是unity教程里的unity UI而不是unity工具包,若在自行查阅文档过程中可以着重看这一部分.
如果目标是为了制作一个登陆界面,那么我们现在最需要的就是一个界面,一个按钮,然后按下这个按钮从界面进入到游戏中(假设游戏为场景SceneGame并且已经制作完成),那么登陆界面的操作就是创建一个新的场景SceneLogin然后里面有个按钮Button,按下Button后场景切换(利用UnityEngine.SceneManagement)切换到SceneGame,这就是一个简单的登陆界面.暂停界面则是设备检测到一个按键被按下(比如Esc),然后让整个游戏暂停,再调出一个界面,上面有两个按钮“恢复游戏”与“退出游戏”,前者让一切恢复正常,后者则是直接退出游戏.这篇文章的目的就是建立这两个界面.
只要在百度这个离谱的搜索引擎上搜索过unity的UI制作就不会对这个词感到陌生
参考:https://blog.csdn.net/qq_45548042/article/details/121011915.
一切UI的控制部件都是在canvas进行布置的.令人疑惑不解的是为什么所有的UI组件都需要在canvas上进行布置呢?官方的说法如下:
画布 (Canvas) 是应该容纳所有 UI 元素的区域。画布是一种带有画布组件的游戏对象,所有 UI 元素都必须是此类画布的子项。
创建新的 UI 元素(如使用菜单 GameObject > UI > Image 创建图像)时,如果场景中还没有画布,则会自动创建画布。UI 元素将创建为此画布的子项。
画布区域在 Scene 视图中显示为矩形。这样可以轻松定位 UI 元素,而无需始终显示 Game 视图。
画布 使用 EventSystem 对象来协助消息系统。
以我浅薄的理解,canvs设计的必要性有以下几条:
Canvas总共有三种模式,第一种模式(Screen Space-Overlay)是camera镜头里只有canvas,就相当于我们所做的界面操作,unity只会渲染canvas而不会渲染其他的物品,大部分登陆界面用的是第一种模式.第二种模式(Screen Space-Camera)canvas会一直放在camera的前面,显示在镜头前面的一定距离的地方,它不会影响正常场景的渲染,视觉感觉来说就是你面前一直存在一个具有一定大小的界面.第三种模式(World Space),Canvas就被当成了正常的物品对象,跟一个正方体没有什么区别,也不会随着摄像头的移动而移动,个人理解为在制作3D人物上的血条,或者是很炫酷的那种控制界面用这种Canvas比较多.
哦,要不我们开始试试创建一个登陆界面?
首先在“项目”界面右键创建一个新场景(命名为SceneLogin),然后再在左侧的“层级”界面右键-UI-按钮,因为所有的UI组件都会在Canvas里面,所以Unity会自动帮你创建一个Canvas.应该能注意到按钮挂在Canvas下面,而按钮下面还挂着一个Text,这是控制你按钮上的字体显示的.有可能你的按钮上没有显示字体,这是因为你还没有把字体文件库导入进去,字体文件在Text里的Font Asset里进行设置,点开来会发现里面是空.这时候点击菜单栏(就最上面那一排)的窗口——TextMeshPro——导入TMP基本资源,就可以将字体文件导入进去了,此时字体就配置好了.这就是我们的登陆界面,虽然它什么功能都没有,但至少是像一个登陆界面了.里面的细节可以根据手册自己摸索,这不是我们的主线任务,我们就不再进行赘述了
创建按钮参考:https://www.csdn.net/tags/MtjaUgysNDU3MzMtYmxvZwO0O0OO0O0O.html
现在我们给按钮加上一个脚本附件,我想这个操作对于依葫芦画瓢做过一次项目的人(如果没有建议去做一下)已经是轻车熟路了
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine;
public class LoadScene: MonoBehaviour
{
Button LoginButton;
public void OnClickLogin()
{
SceneManager.LoadScene(1);
}
private void Awake()
{
LoginButton = GetComponent
这里面用到了UnityEngine.UI
和UnityEngine.SceneManagement
,分别用作给按钮加上监听函数,以及对场景进行切换,注意要对(Building Setting 生成设置)进行操作
切换场景参考:https://blog.csdn.net/weixin_44831924/article/details/100055216
这篇文章里面的一些操作已经过时了,但是里面对生成设置的操作有必要阅读
现在我们一个最最最最基础的登陆界面就制作完成了,但是我们不能只知其然不知其所以然,所以接下来我们会对代码进行深一步的挖掘
SceneManager是对场景进行管理的类,具体的成员和方法详细见附录,用的最多的就是LoadScene
,利用场景编号或者场景名称对场景进行切换,这里要注意下其与LoadSceneAsync
的区分,可参考这篇文章:https://blog.csdn.net/qq_42462109/article/details/83096135?spm=1001.2014.3001.5502.在loadscene的过程中还可以指定加载哪种类型的场景,可以加载单模式和附加模式,单模式就是普通的切换场景,附加模式使得屏幕中中包含两个场景,比如说做背包血条栏可以利用这种模式.注意与MergeScene
的区别,合并场景会破坏原场景,这是一种破坏性行为.
另外,在用附加模式的加载场景的过程中,要注意活动场景的设置,对应方法是SetActiveScene
,因为一些操作都是需要对应活动场景的,比如新建一个Object,烘焙等等操作,都是对应活动的场景,参考https://www.jianshu.com/p/f897ac4376f9
using UnityEngine;
using UnityEngine.SceneManagement;
public class ExampleClass : MonoBehaviour
{
void Start()
{
// Only specifying the sceneName or sceneBuildIndex will load the Scene with the Single mode
SceneManager.LoadScene("OtherSceneName", LoadSceneMode.Additive);
}
}
这里的Button成员指UnityEngine.UI.Button,参考文档:https://docs.unity.cn/cn/2019.2/ScriptReference/UI.Button.html
Button的继承树如下:UI.Button
->UI.Selectable
->EventSystem.UIBehaviour
->MonoBehaviour
MonoBehaviour大家都很熟了,老朋友了,所有的Unity脚本都派生于此类.UIBehaviour手册上写的是具有Unity生命周期函数的受保护实现的基准行为,将里面类似Start
之类的方法添加protected
关键字,相当于Unity已经实现这些方法并且不希望有人去动它.UI.Selectable
为简单的可选择对象(Selectable Object),通过它创造可选择的对象,比如Button.(详细见附录)
对于Button来说,最重要的就是里面的成员onClick
,这是一个当按钮被按下后所触发的Unity的事件,它的类型是UI.Button.ButtonClickedEvent
,继承于UnityEvent
,详情可以看附录里记录的UnityEvent的用法,最常用就是增加监听函数的方法AddListener
.
哦如果想用一点复杂的按钮的用法的话,可以尝试了解下OnPointerClick
回调函数,它是鼠标按下后的回调函数Unity会传过来一个EventSystems.PointerEventData
类型的数据称作eventData,通过重载这个回调函数可以实现更加复杂的代码,参考代码如下:
//Attatch this script to a Button GameObject
using UnityEngine;
using UnityEngine.EventSystems;
public class Example : MonoBehaviour, IPointerClickHandler
{
//Detect if a click occurs
public void OnPointerClick(PointerEventData pointerEventData)
{
//Use this to tell when the user right-clicks on the Button
if (pointerEventData.button == PointerEventData.InputButton.Right)
{
//Output to console the clicked GameObject's name and the following message. You can replace this with your own actions for when clicking the GameObject.
Debug.Log(name + " Game Object Right Clicked!");
}
//Use this to tell when the user left-clicks on the Button
if (pointerEventData.button == PointerEventData.InputButton.Left)
{
Debug.Log(name + " Game Object Left Clicked!");
}
}
}
关于PointerEventData
的文档:https://docs.unity.cn/cn/2019.2/ScriptReference/EventSystems.PointerEventData.html
接下来便是制作暂停界面,暂停界面的要求是按下Esc后,游戏停止,并跳出界面,上面应该至少含有两个按钮,一个按钮为Resume用于恢复游戏,另一个按钮为Quit用于退出游戏.参考文章https://blog.csdn.net/dangoxiba/article/details/122922065
首先我们创建一个Canvas,并且在Canvas下创建两个按钮,分别命名和调整里面的字体内容为Resume和Quit.接着编写以下脚本,并且将其添加为Canvas的组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PauseMenu : MonoBehaviour
{
public GameObject QuitButton;
public GameObject ResumeButton;
bool isPause=false;
// Start is called before the first frame update
void Start()
{
QuitButton.SetActive(false);
ResumeButton.SetActive(false);
}
public void QuitEvent()
{
Application.Quit(0);
}
public void ResumeEvent()
{
isPause = false;
QuitButton.SetActive(false);
ResumeButton.SetActive(false);
Time.timeScale = 1.0f;
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.Escape)&& !isPause)
{
isPause = true;
QuitButton.SetActive(true);
ResumeButton.SetActive(true);
Time.timeScale = 0.0f;
}
}
}
添加到Canvas组件后可以看到组件PauseMenu上有两个属性,将QuitButton和ResumeButton分别拖到里面去.如图所示
这次我们不用脚本实现事件添加而是手动去添加,选中Button对象,在鼠标单击的栏目下添加Canvas对象,再在右边的事件下选择Canvas对象中的对应的方法,像Resume按钮就选择ResumeEvent方法,Quit按钮选择QuitEvent方法,如图所示
将Canvas的图层设置为1,一直将其放置在顶层,至此一个最最最基础的暂停界面就完成了,按下Esc即可暂停.
此功能的代码并没有太多的新知识点,Input
和GameObject
都是Unity基础知识里了解过的东西,GameObject.SetActive
设置对象是否活动,设置为True即活动并且可见,设置为False即不活动且不可见,Input.GetKeyDown
检测键盘上是否有按键是否按下,Application.Quit
退出播放器程序,对于UnityEngine.Application
更多的用法详见用户手册https://docs.unity.cn/cn/2021.3/ScriptReference/Application.html
time里的timescale设置对像物理运动的快慢,可以通过增加或减小timescale来实现感觉上的加速和减速.设置Time.timescale=0.0f
即可达到停止效果,反之设置Time.timescale=1.0f
恢复正常.关于Time的更多用法详见用户手册https://docs.unity.cn/cn/2021.3/ScriptReference/Time-timeScale.html
UnityEvent 的抽象基类。
此类为 UnityEvent 提供了基本功能。
备注:不要看中文解释,去看他英文名字以理解其含义,中文解释跟坨?一样
公共函数:
名称 | 解释 |
---|---|
GetPersistentEventCount | 获取已注册的持久性监听器的数量。 |
GetPersistentMethodName | 获取索引处的监听器的目标方法名称。 |
GetPersistentTarget | 获取索引处的监听器的目标组件。 |
RemoveAllListeners | 从事件中删除所有非持久性(即通过脚本创建的)监听器。 |
SetPersistentListenerState | 修改持久性监听器的执行状态。 |
静态函数:
名称 | 解释 |
---|---|
GetValidMethodInfo | 提供了对象、函数名称和参数类型列表;找到匹配的方法。 |
可以与场景一起保存的0参数持久回调
构造函数
UnityEvent:构造函数。
公共函数:
名称 | 解释 |
---|---|
AddListener | 向UnityEvent添加非持久性监听器。 |
Invoke | 调用所有已注册的回调(运行时和持久性)。 |
RemoveListener | 从UnityEvent中删除非持久性监听器。 |
同:
这相当于UnityEvent的多参数版本,要注意的是如果需要用这种多参数的版本需要对参数进行覆盖,以下将利用两参数版本进行范例:
using UnityEngine;
using UnityEngine.Events;
[System.Serializable]
public class MyIntEvent : UnityEvent
{
}
public class ExampleClass : MonoBehaviour
{
public MyIntEvent m_MyEvent;
void Start()
{
if (m_MyEvent == null)
m_MyEvent = new MyIntEvent();
m_MyEvent.AddListener(Ping);
}
void Update()
{
if (Input.anyKeyDown && m_MyEvent != null)
{
m_MyEvent.Invoke(5, 6);
}
}
void Ping(int i, int j)
{
Debug.Log("Ping" + i + ", " + j);
}
}
简单的可选择对象 - 可从中派生,以创建可选择控件。
静态变量:
名称 | 解释 |
---|---|
allSelectables | List of all the selectable objects currently active in the Scene. |
变量:
名称 | 解释 |
---|---|
animationTriggers | 用于此可选择对象的 AnimationTriggers。 |
animator | 便捷函数,用于获取 GameObject 中的 Animator 组件。 |
colors | 用于此可选择对象的 ColorBlock。 |
image | 便捷函数,可将引用的 Graphic 转换为 Image(如果可能)。 |
interactable | 用于启用或禁用对可选择 UI 元素(例如,按钮)进行选择的功能。 |
navigation | 此可选择对象的 Navigation 设置。 |
spriteState | 用于此可选择对象的 SpriteState。 |
targetGraphic | 将过渡的 Graphic。 |
transition | 过渡的类型,当状态更改时将应用于 targetGraphic。 |
公共函数:
名称 | 解释 |
---|---|
FindSelectable | 查找此对象旁边的可选择对象。 |
FindSelectableOnDown | 查找此对象下方的可选择对象。 |
FindSelectableOnLeft | 查找此对象左侧的可选择对象。 |
FindSelectableOnRight | 查找此对象右侧的可选择对象。 |
FindSelectableOnUp | 查找此对象上方的可选择对象。 |
IsInteractable | UI.Selectable.IsInteractable。 |
OnDeselect | 撤消选择并过渡到适当状态。 |
OnMove | 确定应在 4 个移动方向中的哪个方向找到下一个可选择对象。 |
OnPointerDown | 评估当前状态并过渡至按下状态。 |
OnPointerEnter | 评估当前状态并过渡至适当状态。 |
OnPointerExit | 评估当前状态并过渡至正常状态。 |
OnPointerUp | 评估 eventData 并过渡至适当状态。 |
OnSelect | 设置选择并过渡到适当状态。 |
Select | 选择此 Selectable 对象。 |
运行时的场景管理。
静态变量:
名称 | 解释 |
---|---|
sceneCount | 当前加载的场景总数。 |
sceneCountInBuildSettings | Build Settings 中的场景数量。 |
静态函数:
名称 | 解释 |
---|---|
CreateScene | 在运行时使用给定名称创建一个新的空场景。 |
GetActiveScene | 获取当前活动的场景。 |
GetSceneAt | 获取 SceneManager 的已加载场景列表中索引处的场景。 |
GetSceneByBuildIndex | 从构建索引中获取场景结构。 |
GetSceneByName | 搜索已加载的场景,查找包含给定名称的场景。 |
GetSceneByPath | 搜索所有已加载的场景,查找具有给定资源路径的场景。 |
LoadScene | 按照 Build Settings 中的名称或索引加载场景。 |
LoadSceneAsync | 在后台异步加载场景。 |
MergeScenes | 这会将源场景合并到 destinationScene 中。 |
MoveGameObjectToScene | 将游戏对象从当前场景移至新场景。 |
SetActiveScene | 将场景设置为活动状态。 |
UnloadSceneAsync | 销毁所有与给定场景关联的游戏对象,并将场景从 SceneManager 中移除。 |
事件:
名称 | 解释 |
---|---|
activeSceneChanged | 订阅此事件可在活动场景发生变化时收到通知。 |
sceneLoaded | 向此事件添加委托,以在加载场景时收到通知。 |
sceneUnloaded | 向此事件添加委托以在卸载场景时收到通知。 |