使用工具:VS2017,unity3d
使用语言:c#
作者:Gemini_xujian
参考:siki老师-《丛林战争》视频教程
上一篇文章中,我已经完成了游戏客户端与服务器端的初步连接,接下来将开始进行游戏场景与开始界面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.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.CrossFadeAlpha(1,0.2f,true);
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 panelPathDict;//存储所有面板Prefab的路径
private Dictionary panelDict;//保存所有实例化面板的游戏物体身上的BasePanel组件
private Stack panelStack;
private MessagePanel msgPanel;
public UIManager(GameFacade facade) : base(facade)
{
ParseUIPanelTypeJson();
}
///
/// 把某个页面入栈, 把某个页面显示在界面上
///
public void PushPanel(UIPanelType panelType)
{
if (panelStack == null)
panelStack = new Stack();
//判断一下栈里面是否有页面
if (panelStack.Count > 0)
{
BasePanel topPanel = panelStack.Peek();
topPanel.OnPause();
}
BasePanel panel = GetPanel(panelType);
panel.OnEnter();
panelStack.Push(panel);
}
///
/// 出栈 ,把页面从界面上移除
///
public void PopPanel()
{
if (panelStack == null)
panelStack = new Stack();
if (panelStack.Count <= 0) return;
//关闭栈顶页面的显示
BasePanel topPanel = panelStack.Pop();
topPanel.OnExit();
if (panelStack.Count <= 0) return;
BasePanel topPanel2 = panelStack.Peek();
topPanel2.OnResume();
}
///
/// 根据面板类型 得到实例化的面板
///
///
private BasePanel GetPanel(UIPanelType panelType)
{
if (panelDict == null)
{
panelDict = new Dictionary();
}
//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().UIMng = this;
panelDict.Add(panelType, instPanel.GetComponent());
return instPanel.GetComponent();
}
else
{
return panel;
}
}
[Serializable]
class UIPanelTypeJson
{
public List infoList;
}
private void ParseUIPanelTypeJson()
{
panelPathDict = new Dictionary();
TextAsset ta = Resources.Load("UIPanelType");
UIPanelTypeJson jsonObject = JsonUtility.FromJson(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);
}
///
/// just for test
///
//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;
}
}
///
/// 界面被显示出来
///
public virtual void OnEnter()
{
}
///
/// 界面暂停
///
public virtual void OnPause()
{
}
///
/// 界面继续
///
public virtual void OnResume()
{
}
///
/// 界面不显示,退出这个界面,界面被关系
///
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
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();
closebtn.onClick.AddListener(OnCloseClick);
}
public override void OnEnter()
{
base.OnEnter();
gameObject.SetActive(true);
if (closebtn == null)
{
closebtn = transform.Find("closebtn").GetComponent();
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组件(例如按钮)注册事件时为了防止重复,防止重复的原因是多次重复可能会导致事件执行多次,例如点击按钮时重复注册事件会导致事件中的方法多次执行,可能会发生不可预料的错误。