unity网络实战开发(丛林战争)-正式开发阶段(015-游戏场景及开始界面UI搭建)

使用工具: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组件(例如按钮)注册事件时为了防止重复,防止重复的原因是多次重复可能会导致事件执行多次,例如点击按钮时重复注册事件会导致事件中的方法多次执行,可能会发生不可预料的错误。

猜你喜欢

转载自blog.csdn.net/GEMINI_xujian/article/details/80615654
今日推荐