使用工具:VS2017,unity3d
使用语言:c#
作者:Gemini_xujian
上一篇文章中,我已经完成了游戏客户端与服务器端的初步连接,接下来将开始进行游戏场景与开始界面UI的搭建。
01-控制场景的视野漫游动画作为菜单界面背景
首先,需要将资源包中的一个scene场景作为游戏的主场景,这里,提供一下资源的下载地址(https://download.csdn.net/download/gemini_xujian/10465872),如果有需要的同学可以下载一下去使用。
接下来开始制作漫游动画。选中主摄像机,点击Windows菜单下面的animation选项,点击中间的create按钮,将animation文件保存在新建的animations目录下,命名为CameraWander,如图所示:
然后在animation编辑器中添加需要变化的属性,这里我们只需要修改position和rotation即可,如图:
添加完成之后是这样子的:
然后按照自己的想法修改位置和旋转,我在这里让摄像机围绕场景旋转一周回到起点,并调节帧率为4。如图:
这样就实现了开场动画的制作,附画面截图:
02-开发登录按钮
首先在Hierarchy视图下右键UI->Image与Text,修改image名字为startpanel并将锚点设置为全屏四角,再创建好text,将text做为startpanel的子物体,调整字体大小以及位置,并将字体选择为自己喜欢的一种字体,这里我是用资源提供的方正胖娃字体,调整好后,为字体添加shadow组件,使字体看起来更有立体感,然后为字体添加一个button组件,并将组件下的Transition选择为Animation,点击下面的Auto Generate Animation按钮,会弹出animation文件保存位置,我放在了animations文件夹下,并改名为button,然后打开animation编辑器,添加变化属性rect transform里面的scale,在中间位置将scale的x和y改为1.2,保存并查看效果,这样就完成了登录按钮的制作。如图:
03-设计登录面板UI
右键canvas,选择UI->image,作为登录面板的背景,然后将输入框以及按钮等组件做为其子物体,完成效果如图所示:
目前的UI层级为:
04-开发注册面板及提示信息面板
直接复制loginpanel修改成registerpanel,然后再搭建一个提示面板,效果如图:
UI面板层级结构如图:
05-创建面板脚本
将我们之前搭建好的UI框架中的base文件夹拖拽到scripts目录下,改名为uipanel,然后我们给每一个面板都创建一个脚本,命名方式与每个面板名称相同,创建好后,将这些脚本都继承自basepanel这个类,实现效果如图:
每个脚本继承自basepanel(以startpanel为例):
06-创建面板的prefab并修改json和paneltype
将所有的UI面板做成prefab并放入resources目录的uipanel文件夹下,在UIFramework/UIPanel文件夹下找到UIPanelType,然后删除原来的枚举值,添加上我们刚才创建的几个面板的枚举值,分别是Start,Message,Login,Register,然后修改json文件,json文件位于UIFramework/Resources目录下,按照原来的命名方式进行修改,修改后的代码如下:
UIPanelType.json:
{ "infoList": [ {"panelTypeString":"Message", "path":"UIPanel/MessagePanel"}, {"panelTypeString":"Start", "path":"UIPanel/StartPanel"}, {"panelTypeString":"Login", "path":"UIPanel/LoginPanel"}, {"panelTypeString":"Register", "path":"UIPanel/RegisterPanel"} ] }
UIPanelType.cs:
using UnityEngine; using System.Collections; using System; public enum UIPanelType { Message, Start, Login, Register, }
07-开发提示信息模块
先上代码:
MessagePanel.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class MessagePanel : BasePanel { private Text text; private float showTime=1f; public override void OnEnter() { base.OnEnter(); text = GetComponent<Text>(); text.enabled = false; uiMng.InjectMsgPanel(this); } public override void OnExit() { base.OnExit(); } public override void OnPause() { base.OnPause(); } public override void OnResume() { base.OnResume(); } public void ShowMessage(string msg) { text.color = Color.white; text.text = msg; text.enabled = true; Invoke("Hide", showTime); } private void Hide() { text.CrossFadeAlpha(0, showTime, true); } }
UIManager.cs:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System; public class UIManager:BaseManager { /// /// 单例模式的核心 /// 1,定义一个静态的对象 在外界访问 在内部构造 /// 2,构造方法私有化 //private static UIManager _instance; //public static UIManager Instance //{ // get // { // if (_instance == null) // { // _instance = new UIManager(); // } // return _instance; // } //} private Transform canvasTransform; private Transform CanvasTransform { get { if (canvasTransform == null) { canvasTransform = GameObject.Find("Canvas").transform; } return canvasTransform; } } private Dictionary<UIPanelType, string> panelPathDict;//存储所有面板Prefab的路径 private Dictionary<UIPanelType, BasePanel> panelDict;//保存所有实例化面板的游戏物体身上的BasePanel组件 private Stack<BasePanel> panelStack; private MessagePanel msgPanel; public UIManager(GameFacade facade) : base(facade) { ParseUIPanelTypeJson(); } /// <summary> /// 把某个页面入栈, 把某个页面显示在界面上 /// </summary> public void PushPanel(UIPanelType panelType) { if (panelStack == null) panelStack = new Stack<BasePanel>(); //判断一下栈里面是否有页面 if (panelStack.Count > 0) { BasePanel topPanel = panelStack.Peek(); topPanel.OnPause(); } BasePanel panel = GetPanel(panelType); panel.OnEnter(); panelStack.Push(panel); } /// <summary> /// 出栈 ,把页面从界面上移除 /// </summary> 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(); } /// <summary> /// 根据面板类型 得到实例化的面板 /// </summary> /// <returns></returns> private BasePanel GetPanel(UIPanelType panelType) { if (panelDict == null) { panelDict = new Dictionary<UIPanelType, BasePanel>(); } //BasePanel panel; //panelDict.TryGetValue(panelType, out panel);//TODO BasePanel panel = panelDict.TryGet(panelType); if (panel == null) { //如果找不到,那么就找这个面板的prefab的路径,然后去根据prefab去实例化面板 //string path; //panelPathDict.TryGetValue(panelType, out path); string path = panelPathDict.TryGet(panelType); GameObject instPanel = GameObject.Instantiate(Resources.Load(path)) as GameObject; instPanel.transform.SetParent(CanvasTransform,false); instPanel.GetComponent<BasePanel>().UIMng = this; panelDict.Add(panelType, instPanel.GetComponent<BasePanel>()); return instPanel.GetComponent<BasePanel>(); } else { return panel; } } [Serializable] class UIPanelTypeJson { public List<UIPanelInfo> infoList; } private void ParseUIPanelTypeJson() { panelPathDict = new Dictionary<UIPanelType, string>(); TextAsset ta = Resources.Load<TextAsset>("UIPanelType"); UIPanelTypeJson jsonObject = JsonUtility.FromJson<UIPanelTypeJson>(ta.text); foreach (UIPanelInfo info in jsonObject.infoList) { //Debug.Log(info.panelType); panelPathDict.Add(info.panelType, info.path); } } public void InjectMsgPanel(MessagePanel msgPanel) { this.msgPanel = msgPanel; } public void ShowMessage(string msg) { if (msg == null) { Debug.Log("无法显示提示信息,msgpanel为空"); return; } msgPanel.ShowMessage(msg); } /// <summary> /// just for test /// </summary> //public void Test() //{ // string path ; // panelPathDict.TryGetValue(UIPanelType.Knapsack,out path); // Debug.Log(path); //} }
BasePanel.cs:
using UnityEngine;
using System.Collections;
public class BasePanel : MonoBehaviour {
protected UIManager uiMng;
public UIManager UIMng
{
set
{
uiMng = value;
}
}
/// <summary>
/// 界面被显示出来
/// </summary>
public virtual void OnEnter()
{
}
/// <summary>
/// 界面暂停
/// </summary>
public virtual void OnPause()
{
}
/// <summary>
/// 界面继续
/// </summary>
public virtual void OnResume()
{
}
/// <summary>
/// 界面不显示,退出这个界面,界面被关系
/// </summary>
public virtual void OnExit()
{
}
}
GameFacade.cs:
using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameFacade : MonoBehaviour {
private static GameFacade _instance;
public static GameFacade Instance
{
get
{
return _instance;
}
}
private UIManager uiMng;
private AudioManager audioMng;
private PlayerManager playerMng;
private RequestManager requestMng;
private CameraManager cameraMng;
private ClientManager clientMng;
private void Awake()
{
if (_instance != null)
{
Destroy(this.gameObject);
return;
}
_instance = this;
}
// Use this for initialization
void Start() {
InitManager();
}
// Update is called once per frame
void Update() {
}
private void InitManager()
{
uiMng = new UIManager(this);
audioMng = new AudioManager(this);
playerMng = new PlayerManager(this);
requestMng = new RequestManager(this);
cameraMng = new CameraManager(this);
clientMng = new ClientManager(this);
uiMng.OnInit();
audioMng.OnInit();
playerMng.OnInit();
requestMng.OnInit();
cameraMng.OnInit();
clientMng.OnInit();
}
private void DestroyManager()
{
uiMng.OnDestory();
audioMng.OnDestory();
playerMng.OnDestory();
requestMng.OnDestory();
cameraMng.OnDestory();
clientMng.OnDestory();
}
private void OnDestroy()
{
DestroyManager();
}
public void AddRequest(ActionCode actionCode, BaseRequest request)
{
requestMng.AddRequest(actionCode, request);
}
public void RemoveRequest(ActionCode actionCode)
{
requestMng.RemoveRequest(actionCode);
}
public void HandleResponse(ActionCode actionCode, string data)
{
requestMng.HandleResponse(actionCode, data);
}
public void ShowMessage(string msg)
{
uiMng.ShowMessage(msg);
}
}
说明:首先,我们对messagepanel进行实现,通过showmessage方法来显示提示信息,在showmessage中调用invoke方法,来调用hide方法,invoke方法的两个参数分别表示将要调用的方法名以及多长时间后调用,crossfadealpha的三个参数分别表示目标的透明度值,过渡完成的时间长短以及是否忽略时间的大小快慢。之后,我们想要让其他面板可以调用showmsaage方法,那么,我们可以在basepanel中得到uimanager,通过uimanager这个类来调度所有的UI面板对外的方法,我们在uimanager调用messagepanel中的方法之前,需要先得到messagepanel的实例,这里通过messagepanel面板调用显示信息方法时将自身传递给uimanager,这样我们就可以方便的得到messagepanel实例并使用他的方法,除了在UI面板中调用外,我们还想要在其他的模块中也可以调用显示提示信息的方法,那么,我们就可以将gamefacade类作为中介,用它来完成对messagepanel中showmessage方法的调用。
08-开发开始界面和面板进入的动画
在使用dotween动画之前,需要先进行初始化,初始化的方法可以通过使用dotween提供的可视化界面进行初始化,在工具栏有会有一个tools菜单栏选项,这个选项是在导入dotween后出现的,我们点击Tools->demigiant->dotween utility panel选项,会弹出来一个面板,点击面板中的setup dotween按钮。这样就完成了dotween插件初始化工作。
先上修改的代码:
StartPanel.cs:
using DG.Tweening; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class StartPanel : BasePanel { private Button loginbtn; private Animator btnAnimator; private void Start() { loginbtn = transform.Find("loginbtn").GetComponent<Button>(); loginbtn.onClick.AddListener(OnLoginClick); btnAnimator = transform.Find("loginbtn").GetComponent<Animator>(); } public override void OnEnter() { base.OnEnter(); if (loginbtn == null) { loginbtn = transform.Find("loginbtn").GetComponent<Button>(); loginbtn.onClick.AddListener(OnLoginClick); } if(btnAnimator==null) btnAnimator = transform.Find("loginbtn").GetComponent<Animator>(); } public override void OnExit() { base.OnExit(); } public override void OnPause() { base.OnPause(); btnAnimator.enabled = false; loginbtn.transform.DOScale(0, 0.4f).OnComplete(() => loginbtn.gameObject.SetActive(false)); } public override void OnResume() { base.OnResume(); loginbtn.gameObject.SetActive(true); loginbtn.transform.DOScale(1, 0.4f).OnComplete(()=> btnAnimator.enabled = true); } public void OnLoginClick() { uiMng.PushPanel(UIPanelType.Login); } }
LoginPanel.cs:
using DG.Tweening; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class LoginPanel : BasePanel { private Button closebtn; private void Start() { closebtn = transform.Find("closebtn").GetComponent<Button>(); closebtn.onClick.AddListener(OnCloseClick); } public override void OnEnter() { base.OnEnter(); gameObject.SetActive(true); if (closebtn == null) { closebtn = transform.Find("closebtn").GetComponent<Button>(); closebtn.onClick.AddListener(OnCloseClick); } transform.localScale = Vector3.zero; transform.localPosition = new Vector3(800,0,0); transform.DOScale(1, 0.4f); transform.DOLocalMove(Vector3.zero, 0.4f); } public override void OnExit() { base.OnExit(); gameObject.SetActive(false); } public override void OnPause() { base.OnPause(); } public override void OnResume() { base.OnResume(); } public void OnCloseClick() { transform.DOScale(0, 0.4f); transform.DOLocalMove(new Vector3(800,0,0), 0.4f).OnComplete(()=> { uiMng.PopPanel(); }); } }说明:当我们点击开始面板的登录按钮的时候,需要显示出来下一个面板并将当前的面板隐藏,因为我们继承了basepanel,所以我们可以通过定义的四种面板状态进行一些动画的接入,动画效果的实现是由dotween实现的,我们在得到一些物体或者组件时,需要同时在start方法以及enter方法中得到,要注意的是,在enter中得到物体或者组件时需要判断是否已经得到了,尤其是对UI组件(例如按钮)注册事件时为了防止重复,防止重复的原因是多次重复可能会导致事件执行多次,例如点击按钮时重复注册事件会导致事件中的方法多次执行,可能会发生不可预料的错误。