2017-05-14 20:14:32 jxw167 阅读数 2719
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12792 人正在学习 去看看 宋晓波

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

在利用Unity3D引擎开发程序时,UI资源的加载,卸载,隐藏以及UI渐变动画等功能是UI架构设计必须考虑的。

做每一款游戏都需要将这些功能编写一遍非常耗时,在此给读者介绍一种快速的实现方式,因为我们这个是通用的

模块,所以必须要使用模板实现,而且我们的逻辑脚本是不挂接到对象上的。接下来首先设计一个管理类Manager,

代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// 抽象管理类
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
public class Manager<T,K, V> : Singleton<T>
    where V : class ,IDisposable
    where T : Singleton<T>, new()
{
    protected Dictionary<K, V> mMap = new Dictionary<K, V>();
 
    /// <summary>
    /// 获取 对应实体
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public V Get(K key)
    {
        if (key == null) return null;
        return mMap.ContainsKey(key) ? mMap[key] : null;
    }

    /// <summary>
    /// 获取类型T的 Value
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <returns></returns>
    public U Get<U>(K key) where U : class,V 
    {
        V v = Get(key);
        return v as U;
    }

    /// <summary>
    /// 获取类型T的Value
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T Get<T>() where T : class,V 
    {
        foreach(V value in mMap.Values)
        {
            if(value.GetType().Equals(typeof(T)))
            {
                return value as T;
            }
        }
        return null;
    }
    /// <summary>
    /// 添加对应实体
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public bool Put(K key, V value)
    {
        if (mMap.ContainsKey(key))
        {
            if (value == mMap[key])
            {
                return false;
            }
            V v = mMap[key];
            mMap[key] = value;
            v.Dispose();
        }
        else
        {
            mMap.Add(key, value);
        }
        return true;
    }

    /// <summary>
    /// 删除
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public bool Remove(K key)
    {
        if (mMap.ContainsKey(key))
        {
            V v = mMap[key];
            mMap.Remove(key);
            v.Dispose();
        }
        return true;
    }


    public Dictionary<K,V>.ValueCollection Values
    {
        get { return mMap.Values; }
    }
    /// <summary>
    /// 清除所有管理的对象
    /// </summary>
    public void Clear()
    {
        foreach (V value in mMap.Values)
        {
            value.Dispose();
        }
        mMap.Clear();
    }
}

public class ManagerT<K, V> : Manager<ManagerT<K,V>, K, V>
    where V : class ,IDisposable
{

}


在这个类属于抽象类,它利用Dictionary实现了对象的管理操作,接下来需要实现UI的管理类了,先把代码给读者展示如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

/// <summary>
/// UI 服务类
/// </summary>
public class UIService : Manager<UIService, string, UIService.UI>,IDisposable
{
    public void Dispose()
    {
        DestroyAll();
    }

    /// <summary>
    /// 创建UI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    private T _CreateUI<T>(string name) where T : UI
    {
        T ui = Activator.CreateInstance(typeof(T), name) as T;
        return ui;
    }

    public class UIHolder : MonoBehaviour
    {
        public UI ui { get; set; }
    }
    public class UI : IDisposable
    {
        public UI(string name)
        {
                _Init(name);  
        }

        public enum UIStyle
        {
            Normal,//默认类型
            HideByTapScene,//点击空白处隐藏类型,和黑底不冲突
        }

        UIStyle mStyle = UIStyle.Normal;

        public UIStyle Style
        {
            get { return mStyle; }
            set { mStyle = value; }
        }

        internal GameObject mPrefab = null;
        protected UIPanel mPanel = null;
        #region 面板动画处理
        protected UITweener[] mTweens = null;
        protected UITweener   mMainTween = null;
        protected UITweener.ToggleStyle mMainToggleStyle = UITweener.ToggleStyle.normal;
        private void _TweensInit()
        {
            mTweens = mPrefab.GetComponentsInChildren<UITweener>();
            if (mTweens != null)
            {
                UITweener tween;
                for (int i = 0; i < mTweens.Length; ++i)
                {
                    tween = mTweens[i];
                    if (tween.toggleStyle == UITweener.ToggleStyle.OnShow)
                    {
                        mMainTween = tween;
                        mMainToggleStyle = UITweener.ToggleStyle.OnShow;
                    }
                    if (tween.toggleStyle == UITweener.ToggleStyle.OnShowAndHide)
                    {
                        mMainTween = tween;
                        mMainToggleStyle = UITweener.ToggleStyle.OnShowAndHide;
                        break;
                    }
                }
            }
        }

        private bool _HasTween()
        {
            return mMainTween != null && mMainTween.toggleStyle !=  UITweener.ToggleStyle.normal;
        }
        private void _TweenOnShow(EventDelegate.Callback call)
        {
            mMainTween.SetOnFinished(call);
            UITweener tween;
            for (int i = 0; i < mTweens.Length; ++i)
            {
                tween = mTweens[i];
                if(tween.toggleStyle != UITweener.ToggleStyle.normal)
                {
                    tween.ResetToStart(true);
                    tween.PlayForward();
                }
            }
        }

        private void _TweenOnHide(EventDelegate.Callback call)
        {
            if (mMainToggleStyle == UITweener.ToggleStyle.OnShow)
            {
                call();
                return;
            }
            mMainTween.SetOnFinished(call);
            UITweener tween;
            for (int i = 0; i < mTweens.Length; ++i)
            {
                tween = mTweens[i];
                if (tween.toggleStyle == UITweener.ToggleStyle.OnShowAndHide)
                {
                    tween.ResetToStart(!true);
                    tween.PlayReverse();
                }
            }
        }
        private void _Dummy()
        {

        }
        private void _Hide()
        {
            mPrefab.SetActive(false);
            mHiding = false;
        }
        #endregion

        internal void _Init(string name)
        {
            try
            {
                GameObject prefab = Resource.LoadUI(name);
                mPrefab = GameObject.Instantiate(prefab) as GameObject;
                UIHolder uiholder = UtilGameObject.GetOrAddComponent<UIHolder>(mPrefab);
                uiholder.ui = this;
                mPrefab.name = name;
                mPanel = mPrefab.GetComponent<UIPanel>(); //允许Panel为空
                _TweensInit();
            }
            catch (Exception e)
            {
                Looper.LogException(e);
            }
            UIService.Instance.InitUI(mPrefab);
            UIService.Instance._OnShowUI(this, true);
        }
        #region <默认属性>
 
        
        /// <summary>
        /// 深度信息
        /// </summary>
        public int depth
        {
            get
            {
                if (mPanel==null)
                {
                    return -1;
                }
                return mPanel.depth;
            }
            set
            {
                if (mPanel == null) return;
                mPanel.depth = value;
            }
        }
        /// <summary>
        /// 是否正在显示
        /// </summary>
        /// <returns></returns>
        public bool IsShowing()
        {
            if (mPrefab == null)
                return false;
            return mPrefab.activeSelf;
        }
        float mLastShowTime = Time.time;
        /// <summary>
        /// 最后一次显示的时间
        /// </summary>
        public float LastShowTime
        {
            get { return mLastShowTime; }
            set { mLastShowTime = value; }
        }
        #endregion

        /// <summary>
        /// 名字(和预制件名称一样)
        /// </summary>
        public  string Name
        {
            get { return mPrefab != null ? mPrefab.name : ""; }
        }
        /// <summary>
        /// 销毁对象
        /// </summary>
        public void Dispose()
        {
            UIService.Instance._OnHidUI(this);

            try
            {
                OnClose();
                if (mPrefab != null)
                {
                    GameObject.DestroyImmediate(mPrefab);
                    mPrefab = null;
                }

            }
            catch (Exception e)
            {
                Looper.LogException(e);
            }
            
        }

        /// <summary>
        /// 刷新界面
        /// 1 Grid 重排问题
        /// </summary>
        public void Refresh()
        {
           
                UIGrid[] grids = mPrefab.GetComponentsInChildren<UIGrid>();
                foreach (UIGrid grid in grids)
                {
                    if (grid != null && !grid.animateSmoothly)
                    {
                        grid.Reposition();
                    }
                }
           
        }


        public IEnumerator _Refresh()
        {
            {
                UIGrid[] grids = mPrefab.GetComponentsInChildren<UIGrid>();
                foreach (UIGrid grid in grids)
                {
                    if (grid != null && !grid.enabled)
                    {
                        grid.repositionNow = true;
                        grid.Reposition();
                    }
                }
            }

            yield return null;
        }


        //解决同一个UI动画隐藏还没结束,动画显示就开始了 状态不对的问题
        bool mHiding = false;
        /// <summary>
        /// 是否显示
        /// </summary>
        public void Show(bool v)
        {

            if (v)
            {
                if (mHiding)
                {
                    _Hide();
                }
                UIService.Instance._OnShowUI(this);
            }
            else
            {
                UIService.Instance._OnHidUI(this);
            }

            try
            {

                if (mPrefab != null)
                {
                    
                    if (_HasTween())
                    {
                        if (v)
                        {
                            mPrefab.SetActive(true);
                            _TweenOnShow(_Dummy);
                        }
                        else
                        {
                            mHiding = true;
                            _TweenOnHide(_Hide);
                        }
                    }
                    else
                    {
                        mPrefab.SetActive(v);
                    }
                    OnShow(v);
                    if (v)
                    {
                        LastShowTime = Time.time;
                        Refresh();
                        //();
                    }
                }
                
            }
            catch (Exception e)
            {
                Looper.LogException(e);
            }


        }


        /// <summary>
        /// 跟随场景中物体
        /// </summary>
        /// <param name="target"></param>
        public void ApplyHub(Transform target)
        {
            NGUIExt guiExt = PluginManager.Instance.Get<NGUIExt>();
            if (guiExt != null)
            {
                guiExt.ApplyHud(mPrefab, target);
            }
        }

        public virtual void OnCreate() { }
        public virtual void OnShow(bool v) { }
        public virtual void OnClose() { }

        /// <summary>
        /// 点击事件
        /// </summary>
        /// <param name="obj">被点击的控件</param>
        public virtual void OnClick(GameObject obj) { }

        /// <summary>
        /// 双击事件
        /// </summary>
        /// <param name="obj">被双击的控件</param>
        public virtual void OnDoubleClick(GameObject obj) { }
        /// <summary>
        /// 按住事件
        /// </summary>
        /// <param name="obj">被Pressed的控件</param>
        /// <param name="pressed"></param>
        public virtual void OnPress(GameObject obj, bool isPressed) { }

        /// <summary>
        /// 拖动事件
        /// </summary>
        /// <param name="obj">拖动的控件</param>
        /// <param name="delta"></param>
        public virtual void OnDrag(GameObject obj, Vector2 delta) { }

        /// <summary>
        /// 拖放事件
        /// </summary>
        /// <param name="obj">拖放的当前控件</param>
        /// <param name="objSelected">一直被拖住的控件</param>
        public virtual void OnDrap(GameObject obj, GameObject objSelected) { }

        /// <summary>
        /// 显示Tooltip事件
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="isShow">显示或隐藏Tooltip</param>
        public virtual void OnTooltip(GameObject obj, bool isShow) { }

        /// <summary>
        /// 被选中事件
        /// </summary>
        /// <param name="obj">被选中的控件</param>
        /// <param name="isSelected">选中或取消被选中</param>
        public virtual void OnSelect(GameObject obj, bool isSelected) { }

        /// <summary>
        /// 光标划过事件
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="isHover">光标进入或光标离开</param>
        public virtual void OnHover(GameObject obj, bool isHover) { }

        /// <summary>
        /// 根据类型获取对应名称控件;
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        protected T GetChild<T>(string name,GameObject obj = null, int index = 0) where T : MonoBehaviour
        {
            if(obj == null)
            {
                obj = mPrefab;
            }

            Transform child = obj.transform.Find(name);
            if (child == null)
            {
                T[] childs = obj.GetComponentsInChildren<T>();
                foreach (T t in childs)
                {
                    if (t.gameObject.name == name)
                    {
                        return t;
                    }
                }
            }
            else
            {
                if (child.childCount == 0)
                {
                    return child.GetComponent<T>();
                }

                {
                    int count = 0;
                    T[] comps = child.GetComponents<T>();
                    foreach (T t in comps)
                    {

                        if (t.gameObject.name == name && count == index)
                        {
                            return t;
                        }
                        count++;
                    }
                }
                {
                    int count = 0;
                    T[] childs = child.GetComponentsInChildren<T>();
                    foreach (T t in childs)
                    {

                        if (t.gameObject.name == name && count == index)
                        {
                            return t;
                        }
                        count++;
                    }
                }

            }

            Debug.LogError(obj.name + " hasn't Components :" + typeof(T).Name + " in children named:" + name);
            return null;
        }

        protected GameObject FindChild(string name)
        {
            var child = mPrefab.transform.FindChild(name);
            return child.gameObject;
        }

        protected T FindChild<T>(string name) where T : Component
        {
            var child = mPrefab.transform.FindChild(name);
            T cmp = child.GetComponent<T>();
        
            return cmp;
        }

        /// <summary>
        /// 获取对应名称子控件;
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        protected GameObject GetChild(string name, GameObject obj = null)
        {
            if(obj == null)
            {
                obj = mPrefab;
            }
            Transform child = obj.transform.Find(name);
            if (child == null)
            {
                Debug.LogError(obj.name + "is not find child of:" + name);
                return null;
            }
            return child.gameObject;
        }
        /// <summary>
        /// 用指定的对象替换子对象
        /// </summary>
        /// <param name="name">子对象名字</param>
        /// <param name="obj">指定的对象</param>
        /// <returns></returns>
        protected bool ReplaceChild(string name, GameObject obj)
        {
            GameObject orginal = GetChild(name);

            if (orginal == null) {
				GameObject.DestroyImmediate (obj);
				return false;
			}
            obj.transform.parent = orginal.transform.parent;
            obj.transform.localPosition = Vector3.zero;
            obj.transform.localRotation = Quaternion.identity;
            obj.transform.localScale = Vector3.one;
			obj.name = orginal.name;

			orginal.transform.parent = null;
			GameObject.DestroyImmediate (orginal);

            return true;
        }

        /// <summary>
        /// 用指定的对象替换子对象
        /// </summary>
        /// <param name="orginal">原件</param>
        /// <param name="obj">指定的对象</param>
        /// <returns></returns>
        protected bool ReplaceChild(GameObject orginal, GameObject obj)
        {
            if (orginal == null) {
				GameObject.DestroyImmediate (obj);
				return false;
			}
            obj.transform.parent = orginal.transform.parent;
            obj.transform.localPosition = Vector3.zero;
            obj.transform.localRotation = Quaternion.identity;
            obj.transform.localScale = Vector3.one;
			obj.name = orginal.name;

			orginal.transform.parent = null;
			GameObject.DestroyImmediate (orginal);
            return true;
        }

    }


    NGUIExt mPlugin;
    GameObject mBG;
    UIPanel mBGPanel;
    int mBGCount = 0;

    private void _OnShowUI(UI t,bool init = false)
    {
        if (t.IsShowing())
        {
            if(!init)
                return;
        }
        if (t.IsShowBlackBG())
        {
            mBGCount++;
            if (mBGCount > 0 && mBG.activeSelf == false)
            {
                mBG.SetActive(true);
            }
            if (mBG.activeSelf)
            {
                mBGPanel.depth = t.depth-1;
            }
        }

    }
    private void _OnHidUI(UI t)
    {
        if (!t.IsShowing()) return;

        if (t.IsShowing() && t.IsShowBlackBG())
        {
            mBGCount--;
            if(mBGCount <= 0)
            {
                mBGCount = 0;
                if (mBG.activeSelf == true)
                {
                    mBG.SetActive(false);
                }
            }


            if (mBG.activeSelf)
            {
                UI ui = _GetLastShowUI(true, t);
                if (ui != null) mBGPanel.depth = ui.depth-1;
            }
        }

    }

    public bool IsFingerHoverGUI()
    {
        if(mPlugin == null)
        {
            return false;
        }
        return mPlugin.IsFingerHoverGUI;
    }

    public bool IsFingerHoverGUI3D()
    {
        if (mPlugin == null)
        {
            return false;
        }
        return mPlugin.IsFingerHoverGUI3D();
    }

    public bool IsFingerHoverGUIWithout3D()
    {
        if (mPlugin == null)
        {
            return false;
        }
        return mPlugin.IsFingerHoverGUIWithout3D();
    }

    public bool IsPrefab(GameObject ui_prefab)
    {
        return mPlugin.IsPrefab(ui_prefab);
    }
    
    /// <summary>
    /// 初始化 继承与 Singleton 对象构建的时候被调用
    /// </summary>
    protected override void OnCreate()
    {
        
        mPlugin = PluginManager.Instance.Get<NGUIExt>();
        if (mPlugin != null)
        {
            mPlugin.SetEventHandler(this._HandlerUIEvent);
        }
        GameObject prefab = Resource.LoadUICommon("black_background");
        Looper.Assert(prefab != null , "默认黑底 black_background Prefab不存在 !!");
        if(prefab != null)
        {
            mBG = mPlugin.AddChild(prefab);
            mBGPanel = mBG.GetComponent<UIPanel>();
            mBG.transform.localScale = new Vector3(1, 1, 1);
            mBG.transform.localPosition = new Vector3(mBG.transform.localPosition.x, mBG.transform.localPosition.y, Mathf.Clamp(mBG.transform.localPosition.z, -2f, 2f));
            if(mBG != null)
            {
                UISprite sprite = mBG.GetComponent<UISprite>();
                BoxCollider collider = mBG.GetComponent<BoxCollider>();
                if(sprite!= null)
                {
                    Vector2 size = mPlugin.GetSize();
                    sprite.SetDimensions((int)size.x, (int)size.y);
                    collider.size = new Vector3(size.x, size.y, 0);
                }
            }
            
            mBG.SetActive(false);
        }
        //EasyTouch.On_SimpleTap += On_SimpleTap;
        
    }

    public void InitUI(GameObject ui)
    {
        try
        {
            LayerUtils.SetLayer(ui.transform, (int)LayerUtils.ELayerIndex.ui);
            ui.transform.parent = mPlugin.GetRoot().transform;
            
            switch(ui.name)
            {
                default:
                    ui.transform.localScale = new Vector3(1, 1, 1);
                    ui.transform.localPosition = Vector3.zero;
                    break;
            }
        }
        catch (Exception e)
        {
            Looper.LogException(e);
        }
      
    }
    /// <summary>
    /// 创建UI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    public T CreateUI<T>(string name) where T: UI
    {
        UI  t = this.Get(name) as T;
        if(t == null)
        {
            t = _CreateUI<T>(name) as UI;
           try
           {
               t.OnCreate();
           }
           catch (Exception e)
           {
               Looper.LogException(e);
           }
           
           Put(name, t);
        }
        return t as T;
    }
    List<UI> mUIScene = new List<UI>();
    /// <summary>
    /// 创建一个不加入管理队列的UI,主要用于世界地图上关卡界面的创建。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    public T CreateUIWithoutManager<T>(string name) where T : UI
    {
        T ui = _CreateUI<T>(name);
        try
        {
            ui.OnCreate();
        }
        catch (Exception e)
        {
            Looper.LogException(e);
        }
        mUIScene.Add(ui);
        return ui;
    }
	
    /// <summary>
    /// 销毁不加入队列的UI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="ui"></param>
    public void CloseUIWithoutManager<T>(T ui) where T : UI
    {
        try
        {
            ui.OnClose();
            ui.Dispose();
        }
        catch (Exception e)
        {
            Looper.LogException(e);
        }
        mUIScene.Remove(ui);
    }
    /// <summary>
    /// 显示UI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    public T ShowUI<T>(string name) where T : UI
    {
        T t = CreateUI<T>(name);
        if(t != null)
        {
            t.Show(true);
        }
        return t;
    }
    /// <summary>
    /// 显示UI
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    public T HideUI<T>(string name) where T : UI
    {
        T t = Get<T>(name);
        if (t != null)
        {
            t.Show(!true);

        }
        return t;
    }

    Stack<UI> mStack = new Stack<UI>();

    /// <summary>
    /// 清除栈
    /// </summary>
    public void StackClean()
    {
        mStack.Clear();
    }
    /// <summary>
    /// UI隐藏压栈
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public UI Push(UI t)
    {
        if (t != null)
        {
            t.Show(false);
            mStack.Push(t);
        }
        return t;
    }
    /// <summary>
    /// UI隐藏压栈
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public T Push<T>(string name) where T: UI
    {
        T t = Get<T>(name);
        if (t != null)
        {
            t.Show(false);
            mStack.Push(t);
        }
        return t;
    }
    /// <summary>
    /// UI显示出栈
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public UI Pop()
    {
        UI t = mStack.Pop();
        if (t != null)
        {
            t.Show(true);
        }
        return t;
    }


    /// <summary>
    /// 销毁UI
    /// </summary>
    /// <param name="name"></param>
    public void Distroy(string name)
    {
        UI ui = Get(name);
        if(ui != null)
        {
            ui.Dispose();
            Remove(name);
        }
    }

    public void Distroy<T>(string name) where T : UI
    {
        UI ui = Get<T>(name);
        if (ui != null)
        {
            ui.Dispose();
            Remove(name);
        }
    }
    public void DestroyAll(bool includeScene= false)
    {
        StackClean();
        Clear();
        if(includeScene)
        {
            foreach (UI ui in mUIScene)
            {
                ui.Dispose();
            }
            mUIScene.Clear();
        }

    }
    /// <summary>
    /// 隐藏其他UI
    /// </summary>
    /// <param name="name"></param>
    public void HideOtherUI(string name)
    {
        foreach (UI ui in Values)
        {
            if (ui.Name.Equals(name))
                continue;
            ui.Show(false);
        }
    }

    UI _GetLastShowUI(bool showBG = false,UI except=null)
    {
        UI lastShowUI = null;
        float lastShowTime = 0;
        foreach (UI ui in Values)
        {
            if (ui == except) continue;
            if (ui.IsShowing() && ui.LastShowTime > lastShowTime)
            {
                if (showBG)
                {
                    if (ui.IsShowBlackBG())
                    {
                        lastShowUI = ui;
                        lastShowTime = ui.LastShowTime;
                    }
                }else
                {
                    lastShowUI = ui;
                    lastShowTime = ui.LastShowTime;
                }
            }
        }
        return lastShowUI;
    }

    /// <summary>
    /// 隐藏最新显示的UI
    /// </summary>
    public void HideLastShow()
    {
        UI lastShowUI = _GetLastShowUI();
        if (lastShowUI != null)
        {
            lastShowUI.Show(false);
        }
    }
    /// <summary>
    /// 隐藏所有UI
    /// </summary>
    public void HideAllUI()
    {
        foreach(UI ui in Values)
        {
            if(ui != null)ui.Show(false);
        }
    }
    /// <summary>
    /// 获取UI跟面板
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
     GameObject _GetRootPanelExt(GameObject obj)
    {
        Transform parent = obj.transform.parent;
        UIHolder uiHolder = null;// = root.GetComponent<UIService.UIHolder>();

        while (parent != null)
        {
            if (parent.gameObject.GetComponent<UICamera>() != null)
                break;

            UIHolder tempPanel = parent.GetComponent<UIHolder>();
            if (tempPanel != null)
            {
                uiHolder = tempPanel;
            }
            parent = parent.parent;
        }
        if (uiHolder == null)
        {
            return null;
        }
        return uiHolder.gameObject;
    }
    /// <summary>
    /// NGUI事件处理器
    /// </summary>
    /// <param name="eventNane">事件名称</param>
    /// <param name="sender">发送事件的控件</param>
    /// <param name="arg">事件参数</param>
    private void _HandlerUIEvent(string eventName, GameObject sender, object arg)
    {
        try
        {
            if(sender == mBG)
            {
                //MainLooper.LogError("mBG, eventName:" + eventName + "!!");
                if (eventName.Equals("OnClick"))
                {
                   
                }
                return;
            }
            GameObject root = _GetRootPanelExt(sender);//mPlugin.GetRootPanel(sender);
            if (root == null) return;
            UI ui = Get(root.name);
            if(ui == null)
            {
                UIHolder uiHolder = root.GetComponent<UIHolder>();
                if( uiHolder!= null) ui = uiHolder.ui;
            }
            if (ui == null)
            {
                Debug.LogWarning("UI:" + root.name + " not match prefab's name " + sender.name);
                return;
            }

            if (eventName.Equals("OnClick"))
            {
                ui.OnClick(sender);
            }
            else if (eventName.Equals("OnPress"))
            {
                ui.OnPress(sender, (bool)arg);
            }
            else if (eventName.Equals("OnDrag"))
            {
                ui.OnDrag(sender, (Vector2)arg);
            }
            else if (eventName.Equals("OnDrop"))
            {
                ui.OnDrap(sender, (GameObject)arg);
            }
            else if (eventName.Equals("OnSelect"))
            {
                ui.OnSelect(sender, (bool)arg);
            }
            else if (eventName.Equals("OnHover"))
            {
                ui.OnHover(sender, (bool)arg);
            }
            else if (eventName.Equals("OnTooltip"))
            {
                ui.OnTooltip(sender, (bool)arg);
            }
            else if (eventName.Equals("OnDoubleClick"))
            {
                ui.OnDoubleClick(sender);
            }
        }
        catch(Exception e)
        {
            Looper.LogException(e);
        }

    }
}

该类实现了UI的创建,也就是我们说的实例化操作,以及UI的显示,隐藏,动画等效果。在代码的最后使用了点击的回调函数避免

将脚本挂接到对象上。以上类的实现基本上把UI的大部分功能都实现出来了,可以直接拿过来使用。


2016-10-16 16:45:47 chy_xfn 阅读数 6573
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12792 人正在学习 去看看 宋晓波

学习知识,分享知识。

  1. 列记录下自己经常使用的这个UI框架。首先说下这个UI框架整体吧,该框架主要实现了UI的的显示、隐藏、按钮点击、UI数值更新,这也是大多数游戏UI的功能。

  2. 该框架主要分三个部分来理解,分别是窗口(window)、视图(view)、控制(control),看起来有点像mvc框架是吧,但这里并没有实现model数据这块,现在只是实现UI的一些显示功能,并不需要做Model数据处理内容,所以暂时不管。如果加上model模块,那么窗口和视图可以合成View的。

  3. 细说三部分

    1、窗口(window):为了将UI分类,将其分成相应的窗口理解,如:商店窗口、设置窗口等;
    2、视图(view):每个窗口内的具体UI视图,一个窗口可有多个UI视图,如:商店窗口有显示所有商品的视图、然后选择单个商品时又可以弹出一个商品详情视图,点击购买又可以弹出一个购买视图。当然,把这些都放在同一视图也行,但是就是显得脚本程序臃肿了,毕竟分类管理能让程序更直观清晰。
    3、控制(control):用来实现UI的创建、记录、显示和隐藏等功能的具体逻辑。

代码实现

  • 根据上面三部分可以创建三个类UIBaseWindow(窗口)、UIBaseView(视图)、UIWindowCtrl(控制);
    详细如下图:

    这里写图片描述
    

1、首先我们看看UIBaseWindow类下包含四个主要方法,由命名可以看出其功能分别是窗口的创建、显示、隐藏、重现;下面几个子类则可以直接继承这些功能并使用,这几个子类分别是主界面、商城、设置等窗口。子类的作用主要是用来初始化对应显示的视图。

2、下面看看UIBaseView类,该类的作用则是包含了具体的UI视图了,该基类也自带了几个非常重要的方法,包括Veiw的初始化、获取当前View所属窗口、设置View所属窗口、View的显示和隐藏,而继承该基类的子类除了显示具体的UI外,还扩展了按钮的点击事件;从上图看到多了一个叫CommonView的通用视图类,因为我们的View UI通常都是共用一个父窗口的,就是外部轮廓一致的,只有标题不同而已,而且窗口都会带有一个关闭按钮的,那么我们就可以把这些提取出来做成一个通用的CommonView视图了。

3、最后一个UIWindowCtrl类,顾名思义就是UI总控制类,包括窗口的具体创建逻辑、已有窗口的存储、已有窗口重现的判断逻辑等功能都是写在这个类。该类是个管理类,不允许继承的。

好了,这次就先写到这吧,具体代码下篇在详细介绍。

2017-11-13 09:19:45 qq_33747722 阅读数 3690
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12792 人正在学习 去看看 宋晓波

博客地址:blog.liujunliang.com.cn

开发工具:VS2017、Unity2017

本文介绍使用Socket/TCP来开发客户端与服务器端通信框架

博主使用过PhotonServer,由于其简单使用,所以本文模仿PhotonServer服务器框架来编写的

其中可以参考博主之前写的文章Unity3d与PhotonServer通信Unity3d Socket网络编程

接下来介绍自己编写的一个基于Socket的游戏服务器通信框架的设计与实现

服务器端

客户端的连接请求与每个客户端的数据接收是通过线程来处理

using LJLNet.Application;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using LJLNet.Thread;

namespace LJLNet
{
    class Program
    {
        private static LJLNet.Application.Application application;

        static void Main(string[] args)
        {            
            //主类 主入口类(可配置)
            application = new GameContext();
            application.Setup();
           
            //绑定监听消息IP和端口号(可配置ip地址和端口号)
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            EndPoint endPoint = new IPEndPoint(ip, 6000);

            //创建一个socket对象
            //寻址方式 套接字类型 协议方式  
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(endPoint);//向操作系统申请一个ip和端口号  
            Console.WriteLine("服务器端启动完成");


            //开始监听客户端的连接请求
            serverSocket.Listen(100);//最多可以接收100个客户端请求  

            //开启线程 来接收客户端请求
            BaseThread thread = new AcceptThread(application, serverSocket);
            thread.Start();

            //断开连接
        }
    }
}

主入口类(框架的启动类),框架中的唯一个入口类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using LJLNet.SocketTCPPeer;

namespace LJLNet.Application
{
    /// <summary>
    /// 该类是主类  框架主入口(如果需要修改主入口函数 需要在Program类中修改)
    /// </summary>
    public class GameContext : Application
    {
        /// <summary>
        /// 客户端接入函数
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public BasePeer CreatePeer(Socket socket)
        {
            BasePeer peer = new ClientPeer(socket);
            ServerMgr.GetInstance.peerList.Add(peer); 

            return peer;
        }



        /// <summary>
        /// 框架启动函数
        /// </summary>
        public void Setup()
        {
            Console.WriteLine("启动框架");        
        }



        /// <summary>
        /// 框架取消函数
        /// </summary>
        public void TearDown()
        {
            Console.WriteLine("停止框架");
        }
    }
}


当我们的一个客户端连接进服务器时候,便创建一个ClientPeer对象(客户端)

客户端支持数据的接收与响应、事件

其基类如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using LJLNet.Thread;
using LJLCommon.Helper;
using LJLCommon.Package;

namespace LJLNet.SocketTCPPeer
{
    public abstract class BasePeer
    {
        public Socket mSocket { get; set; }

        public BasePeer(Socket socket)
        {
            mSocket = socket;
            //启动socket
            InitSocket();
        }

        public abstract void OnDisconnect();
        public abstract void OnOperationRequest(Dictionary<byte, object> parameters);        

        protected void InitSocket()
        {
            //开启线程
            BaseThread thread = new ReceiveThread(this);
            thread.Start();
        }

        public void OnOperationResponse(Dictionary<byte, object> parameters)
        {
            //向客户端发送消息
            //序列化
            string message = DictToPackageHelper.Serializa(parameters);

            Package package = new Package() { type = PackageType.Response, parameters = message };
            string packageMessage = XMLHelper.Serialze<Package>(package);

            //字节转化
            var date = ASCIIEncoding.UTF8.GetBytes(packageMessage);
            mSocket.Send(date);
        }

        public void OnOperationEvent(Dictionary<byte, object> dict)
        {
            //向客户端发送消息
            //序列化
            string message = DictToPackageHelper.Serializa(dict);

            Package package = new Package() { type = PackageType.Event, parameters = message };
            message = XMLHelper.Serialze<Package>(package);

            //字节转化
            var date = ASCIIEncoding.UTF8.GetBytes(message);
            mSocket.Send(date);
        }
    }
}

ClientPeer.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace LJLNet.SocketTCPPeer
{
    public class ClientPeer : BasePeer
    {
        public ClientPeer(Socket socket) : base(socket)
        {
        }
         
        public override void OnDisconnect()
        {
            
        }

        public override void OnOperationRequest(Dictionary<byte, object> parameters)
        {
            Console.WriteLine(parameters.FirstOrDefault(q=>q.Key==1).Value.ToString());
            OnOperationResponse(new Dictionary<byte, object>() { { 0, "你好" } });
        }
    }
}

在客户端中开辟一个线程,用于接收数据请求

线程基类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace LJLNet.Thread
{
    public abstract class BaseThread
    {
        protected System.Threading.Thread mThread;

        public BaseThread()
        {
            mThread = new System.Threading.Thread(Run);
        }

        public virtual void Start()
        {
            mThread.Start();
        }
        protected abstract void Run();
        public void Stop()
        {
            mThread.Abort();
        }
    }
}

接收数据线程

using LJLNet.SocketTCPPeer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LJLCommon.Helper;
using System.Threading.Tasks;

namespace LJLNet.Thread
{
    public class ReceiveThread : BaseThread
    {
        private BasePeer mPeer;

        public ReceiveThread(BasePeer peer)
        {
            mPeer = peer;
        }

        //运行的内容
        protected override void Run()
        {
            if (mPeer.mSocket != null)
            {
                while (true)
                {
                    try
                    {
                        //从客户端接收消息
                        byte[] buffer = new byte[1024];//设置一个消息接收缓冲区  
                        mPeer.mSocket.Receive(buffer);//该状态处于一个暂停状态,知道接收到消息,并返回字节数  

                        Dictionary<byte, object> parameters = DictToPackageHelper.DeSerializa(ASCIIEncoding.UTF8.GetString(buffer));
                        mPeer.OnOperationRequest(parameters);                       
                    }
                    catch
                    {
                        Console.WriteLine("一个客户端断开连接");
                        //断开线程
                        this.Stop();
                        //将该客户端移除
                        mPeer.OnDisconnect();
                        ServerMgr.GetInstance.peerList.Remove(mPeer);
                        mPeer = null;
                    }
                }
            }
        }
    }
}

客户端

使用Unity3d作为游戏客户端

当连接服务器时创建一个SocketTCPPeer对象(客户端),客户端中主要处理数据的发送和响应与事件的接收

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using LJLCommon.Helper;


namespace LJLNet
{
    public class SocketTCPPeer
    {
        private BaseThread mBaseThread;

        public ISocketListener mMono;

        public Socket mTcpSocket;

        public SocketTCPPeer(ISocketListener mono)
        {
            //创建socket  
            mTcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            mMono = mono;
        }

        //连接服务器  
        public void Connect(string ip, int point)
        {
            if (mTcpSocket != null)
            {
                mTcpSocket.Connect(IPAddress.Parse(ip), point);
                if (mTcpSocket.Connected)
                {
                    mBaseThread = new ReceiveThread(this);
                    mBaseThread.Start();
                }
            }
        }

        //发送数据
        public void OnOperationRequest(byte operationType, Dictionary<byte, object> dataDict)
        {
            if (mTcpSocket != null && mTcpSocket.Connected)
            {
                string parameters = null;
                try
                {
                    parameters = DictToPackageHelper.Serializa(dataDict);
                }
                catch
                {
                    Debug.LogError("字典容器序列化失败");
                }

                if (!string.IsNullOrEmpty(parameters))
                {
                    mTcpSocket.Send(ASCIIEncoding.UTF8.GetBytes(parameters));
                }
            }
            else
            {
                Debug.Log("与服务器断开连接");
            }
        }

        //断开连接
        public void Disconnect()
        {
            if (mBaseThread != null)
            {
                mBaseThread.Stop();
            }
            if (mTcpSocket != null)
            {
                mTcpSocket.Close();
            }            
        }
    }
}


其中在客户端中开辟一个线程用于对数据的接收(与服务器端类似)

线程基类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.Net.Sockets;

public abstract class BaseThread
{
    protected Thread mThread;

    public BaseThread()
    {
        mThread = new Thread(Run);
    }

    public virtual void Start()
    {
        mThread.Start();
    }
    public abstract void Run();
    public void Stop()
    {
        mThread.Abort();
    }
}

接收响应线程类

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using LJLNet;
using LJLCommon.Package;
using LJLCommon.Helper;
using System.Text;

public class ReceiveThread : BaseThread
{
    private SocketTCPPeer mSocketTCPPeer;

    public ReceiveThread(SocketTCPPeer socketTCPPeer)
    {
        mSocketTCPPeer = socketTCPPeer;
    }

    public override void Run()
    {
        if ( mThread != null && mSocketTCPPeer.mTcpSocket != null&& mSocketTCPPeer.mTcpSocket.Connected )
        {
            try
            {
                //socket接收消息  
                byte[] bt = new byte[1024];
                mSocketTCPPeer.mTcpSocket.Receive(bt);
                Debug.Log("接收响应");
                //对数据进行处理
                Package package = XMLHelper.Deserialze<Package>(ASCIIEncoding.UTF8.GetString(bt));
                Dictionary<byte, object> parameters = DictToPackageHelper.DeSerializa(package.parameters);
                //回调给主类处理           
                switch (package.type)
                {
                    case PackageType.Response:
                        mSocketTCPPeer.mMono.OnOperationResponse(parameters);
                        break;
                    case PackageType.Event:
                        mSocketTCPPeer.mMono.OnOperationEvent(parameters);
                        break;
                    default:
                        break;
                }
            }
            catch
            {
                //断开线程
                Stop();
                //断开连接
                Debug.Log("与服务器断开连接");
                mSocketTCPPeer.Disconnect();
            }
        }
    }
}

启动客户端-------创建一个Monobehavior脚本挂载到Camera上

在客户端启动时候创建SocketTCPPeer与服务器端进行连接并发送数据

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LJLNet;
using System.Text;
using System;

public class GameContext : MonoBehaviour, ISocketListener
{
    public SocketTCPPeer peer { get; set; }

    private void Start()
    {
        peer = new SocketTCPPeer(this);
        peer.Connect("127.0.0.1", 6000);

        peer.OnOperationRequest(1, new Dictionary<byte, object>() { { 1, "你好" } });
    }

    private void OnDestroy()
    {
        if (peer != null)
        {
            peer.Disconnect();
        }
    }

    public void OnOperationResponse(Dictionary<byte, object> paratemers)
    {
        Debug.Log(paratemers[0].ToString());
    }

    public void OnOperationEvent(Dictionary<byte, object> paratemers)
    {
        
    }
}

其中主类需要实现ISocketListener接口

将该接口传入到客户端SocketTCPPeer类中,当处理数据的响应与事件时候调用

方便框架的管理与简单使用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace LJLNet
{
    public interface ISocketListener
    {
        SocketTCPPeer peer { get; set; }

        void OnOperationResponse(Dictionary<byte,object> paratemers);
        void OnOperationEvent(Dictionary<byte, object> paratemers);
    }
}


检测

运行服务器端与客户端

日志显示如下


该框架支持多人在线游戏的开发

可以参考博主编写的PhotonServer服务器MMO多人在线游戏开发一文

博客地址:blog.liujunliang.com.cn



2016-08-22 15:24:17 linshuhe1 阅读数 13116
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12792 人正在学习 去看看 宋晓波

前言:

        之前使用NPOI插件编写的导表工具,其实就是直接将数据进行序列化,解析时还需要进行反序列化,步骤比较繁复,最近看到Google的一个开源的项目protobuf,不仅可以用于进行excel表格数据的导出,还能直接用于网络通信协议的定制。


一、protobuf简介:

        protobuf是由google公司发布的一个开源的项目,是一款方便而又通用的数据传输协议。所以我们在Unity中也可以借助protobuf来进行数据存储网络协议两方面的开发,这里先说说数据存储部分的操作,也就是:

        将.xls表格数据通过protobuf进行序列化,并在Unity中使用

1.下载资源:

       

2.流程图:

   

        从上图可看出基本的操作步骤:

  • .xls表格文件,先通过xls_deploy_tool.py生成对应的.data文件和.proto文件,其中.data文件就是表格数据序列化后的结果,而.proto文件则是用于生成反序列化时使用的解析类的中间状态;
  • 解析类.proto经过protoc.exe转换成.desc文件,用于后面通过protobuf-net等工具转化为特定的语言,这里我们需要得到的是C#解析类,即.cs类;
  • 在Unity中导入protobuf-net.dll库,在C#代码中调用上述生成的.cs解析类来解析.data中的数据。


二、导表环境配置:

1.Python相关配置:

        由于从.xls文件生成.data和.proto,Python需要依赖Proto库xlrd库,安装配置步骤:

        

  • setuptools:这是Python的组件安装管理器,需要在安装protobuff组件前进行安装,到setuptools官网下载插件的安装包,解压到指定目录,然后使用命令行进入安装包目录,执行指令:python setup.py install

  • Protobuff:首先,我们将之前下载好的源码包protobuf-2.5.0.zip编译包protoc-2.5.0-win32.zip压缩包解压到指定目录,路径最好不要包含中文;
  • 这里我解压protobuf-2.5.0.zip到的位置是“E:\Unity_Workplace\protobuf_250”;
  • 然后复制protoc-2.5.0-win32.zip解压得到的protoc.exe到protobuf_250\src目录下;
  • 在protobuf-2.5.0\python\google\protobuf下创建一个文件夹命名为compiler(安装完成后会在此目录下生成两个文件__init__.py和plugin_pb2.py);
  • 使用命令行进入到解压后的目录下面的Python目录,执行:python setup.py install
        
  • xlrd(xls reader):这其实是读取xls表格数据的一个工具插件,到xlrd官网下载xrld的安装包,解压安装包然后使用命令行进入安装包目录,执行指令:python setup.py install

2.导表外部工具:

  • xls_deploy_tool.py:这个工具其实是github上的一个开源的符合protobuff标准的根据excel自动生成匹配的PB的定义(.proto文件)并将数据序列化后生成二进制数据或者文本数据(.data文件)的一个工具,github下载地址:xls_deploy_tool.py

  • protoc.exe和protogen.exe:通过上面的工具,我们得到了两个文件:存储数据的.data文件和用于解析数据的.proto文件,但是我们在真正使用解析类来进行数据文件的解析时,必须是高级语言,当然protobuf-net提供很多种高级语言的支持。就像我们在Unity中我们使用的是C#语言,这需要两个工具来实现,一个是protobuf-2.5.0中的protoc.exe将.proto文件转换为“FileDescriptorSet”中间格式;另一个是使用protobuf-net中的protogen.exe,将中间格式的文件转换为最终状态,即高级语言的解析类.cs文件。
  • 可以到github上下载protobuf-net的源码:protobuf-net,下载后解压到本地,然后进入到解压后protobuf-net-master\protobuf-net目录下,通过Visual Studio打开protobuf-net.csproj:
                
  • 编译完成后在当前目录下面的bin\Release目录下,生成了编译后的文件,其中我们需要的是protobuf-net.dll:
        
  • 将protobuf-net.dll复制到protobuf-net-master\ProtoGen目录下,用Visual Studio打开ProtoGen.csproj,参照上面步骤编译ProtoGen项目,得到protobuf-net-master\ProtoGen\bin\Release目录下面的protogen.exe及一些额外的文件,但在真正使用时此目录下面的所有文件都是必须的:
        


三、样例:

1.建立表格.xls:

        当然使用此工具进行导表的表格需要符合指定的格式,根据xls_deploy_tool.py的备注内容:

# 说明:
#   excel 的前四行用于结构定义, 其余则为数据,按第一行区分, 分别解释:
#       required 必有属性
#       optional 可选属性
#           第二行: 属性类型
#           第三行:属性名
#           第四行:注释
#           数据行:属性值
#       repeated 表明下一个属性是repeated,即数组
#           第二行: repeat的最大次数, excel中会重复列出该属性
#           2011-11-29 做了修改 第二行如果是类型定义的话,则表明该列是repeated
#           但是目前只支持整形
#           第三行:无用
#           第四行:注释
#           数据行:实际的重复次数
#       required_struct 必选结构属性
#       optional_struct 可选结构属性
#           第二行:结构元素个数
#           第三行:结构名
#           第四行:在上层结构中的属性名
#           数据行:不用填

#    1  | required/optional | repeated  | required_struct/optional_struct   |
#       | ------------------| ---------:| ---------------------------------:|
#    2  | 属性类型          |           | 结构元素个数                      |
#    3  | 属性名            |           | 结构类型名                        |
#    4  | 注释说明          |           | 在上层结构中的属性名              |
#    5  | 属性值            |           |                                   |

        当然可以参考github上下载到的样例表格,下载tnt项目,然后复制其中python目录下面的内容,其中xls文件中就有一个goods_info.xls的样例表格:

        


2.xls_deploy_tool.py转换得到.data和.proto:

        进行导表的操作只需用在命令行中的一句指令即可完成:

python xls_deploy_tool.py sheet_name xls_path

       其中包含两个参数:sheet_name是.xls中要进行导表的表格页名,xls_path是要进行导表的.xls文件的路径。创建一个测试工程Test_protobuf,将1中的两个文件和protoc.exe放入其中:

        

        在命令行定位到该目录下,然后运行指令:

call python xls_deploy_tool.py GOODS_INFO xls/goods_info.xls
        运行结束后,该目录下多出了几个文件,但我们真正需要的只有两个文件,即.data数据文件和.proto解析类:

        
        


3.得到最终解析类:

        protoc.exe得到中间格式文件,假设后缀为.protodesc,使用指令:

protoc 输入文件路径(.proto文件) --descriptor_set_out=输出文件路径(.protodesc)

        在步骤2中的测试工程基础上继续执行指令:

protoc tnt_deploy_goods_info.proto --descriptor_set_out=goods_info.protodesc
        运行此步之后,在项目中又多出了一个与.proto对应的.protodesc文件:

        

        protogen.exe得到.cs解析类,使用指令:

protogen -i:输入文件路径(.protodesc) -o:输出文件路径(.cs)

        将之前生成protogen.exe时protobuf-net-master\ProtoGen\bin\Release目录下面的所有文件复制到当前工程中,用一个文件夹ProtoGen来存放,假如不想执行这么繁琐的过程,也可以直接使用我编译好的ProtoGen文件目录压缩包:ProtoGen.zip,在当前项目的根目录下执行以下指令:

call ProtoGen\protogen -i:goods_info.protodesc -o:goods_info.cs
        执行结果,在当前目录下生成了解析类的最终状态goods_info.cs:

        

        当然,以上三步可以直接用批处理来完成,直接在当前项目根目录下新建一个文件,命名为generator.bat,内容为:

call python xls_deploy_tool.py GOODS_INFO xls/goods_info.xls
call protoc tnt_deploy_goods_info.proto --descriptor_set_out=goods_info.protodesc
call ProtoGen\protogen -i:goods_info.protodesc -o:goods_info.cs
pause
        直接双击此文件即可完成以上所有操作生成最终的.data和.cs文件

        


4.Unity导入库文件:

        将几个文件添加到Unity工程中,将.data文件放在Assets\StreamingAssets\DataConfig目录下,将protobuf-net.dll和goods_info.cs放在Assets目录下:

           

         创建一个Test.cs测试脚本,在脚本中using Protobuf用于导入protobuf-net.dll中的库,然后使用using tnt_deploy导入导表生成的.cs表格数据解析类,脚本具体代码内容为:

using UnityEngine;
using System.Collections;
using ProtoBuf;
using System.IO;
using tnt_deploy;

public class Test : MonoBehaviour {
	void Start () {
        GOODS_INFO_ARRAY goods_infos = ReadOneDataConfig<GOODS_INFO_ARRAY>("goods_info");
        Debug.Log("goods_id==================" + goods_infos.items[0].goods_id);
	}

    private T ReadOneDataConfig<T>(string FileName)
    {
        FileStream fileStream;
        fileStream = GetDataFileStream(FileName);
        if (null != fileStream)
        {
            T t = Serializer.Deserialize<T>(fileStream);
            fileStream.Close();
            return t;
        }

        return default(T);
    }
    private FileStream GetDataFileStream(string fileName)
    {
        string filePath = GetDataConfigPath(fileName);
        if (File.Exists(filePath))
        {
            FileStream fileStream = new FileStream(filePath, FileMode.Open);
            return fileStream;
        }

        return null;
    }
    private string GetDataConfigPath(string fileName)
    {
        return Application.streamingAssetsPath + "/DataConfig/" + fileName + ".data";
    }
}
        在Unity中新建一个场景,将Test.cs挂载在Main Camera主相机上,运行场景,看到打印结果,说明解析表格数据成功:

        


5.平台兼容问题:

        由于直接把protobuf-net.dll放到项目中时,在iOS中会出现JIT错误(ExecutionEngineException: Attempting to JIT compile method)。原因是因为iOS不允许JIT(Just In Time),只允许AOT(Ahead Of Time)。

解决方法:

        直接把protprotobuf-net-master\protobuf-net目录下面的全部源码复制到Unity项目的目录下面,但是由于protobuf-net的编译过程是unsafe编译,所以Unity会出现编译报错:

        

        需要在Assets目录下添加一个smsc.rsp文件,其内容很简单,只有一行“-unsafe”,添加完成后关闭Unity然后重新打开Unity,一切就正常了。


四、总结:

        虽然导表环境的配置过程比较繁琐,但是配置完成之后的工作效率却很高,而且proto具有突出的通用性,可以应用于各种语言环境。

2015-07-08 13:38:14 yangwei19680827 阅读数 3055
  • unity3D游戏/AR/VR在线就业班

    本套课程是一套完整的 Unity3D-游戏/AR/VR 学习课程,具体的课程内容包括《C#语言》、《Unity引擎》、《编程思想》,《商业级项目实践》等开发课程,引导您一步步循序渐进、由易到难,终获得Unity 3D/游戏/AR/VR工程师的岗位技能。

    12792 人正在学习 去看看 宋晓波

2D ToolKit

2D Toolkit是一组与Unity环境无缝集成的工具,提供高效的2D精灵和文本系统。

官方地址:https://www.assetstore.unity3d.com/en/#!/content/908

NGUI:Next-Gen UI Kit

NGUI是严格遵循KISS原则并用C#编写的(适用于专业版和免费版)插件,提供强大的UI系统和事件通知框架

官方地址:https://www.assetstore.unity3d.com/en/#!/content/2413

 

Itween

iTween是一个动画库,用它可以轻松实现各种动画,晃动,旋转,移动,褪色,上色,控制音频等等

官方地址:https://www.assetstore.unity3d.com/en/#!/content/84

Bitmap2Material 3.0

Bitmap2Material是一个强大的工具帮助您从位图中生成无缝材质(法线、高度、高光等),也可以帮助你直接在Unity中从任意位图产生完整的、高质量的、无缝的瓦片状材质(法线、高度、高光、环境遮挡等贴图)

官方地址:https://www.assetstore.unity3d.com/en/#!/content/24267

 

Scaleform

Scaleform专门致力于视频游戏的用户界面设计,Scaleform设计的用户界面包括PS3XB360Wii以及PC等多个平台,目前已经有超过600个游戏在使用Scaleform来进行游戏界面的开发,包括:《星际争霸2》、《孤岛危机》等等

官方地址:https://www.assetstore.unity3d.com/en/#!/content/13468

Playmaker

可视化脚本语言playMaker,具有高度有好的界面、整合性高、功能强大、修改容易等特点。开发者只需将集成的功能模块用连线的方式,通过逻辑关系将其连接,即可快速创建所需功能,非常适合非编程人员与项目制作使用。

官方地址:https://www.assetstore.unity3d.com/en/#!/content/368

BIG Environment Pack Vol.2

灌木丛拥有8个不同种类,一共超过80种变种。草拥有5个不同种类,一共超过21种变种。植物拥有8个不同种类,一共22种变种。树木拥有8个不同种类,一共240种变种。草丛拥有超过40种的预制

官方地址:https://www.assetstore.unity3d.com/en/#!/content/5939

ProBuilder

ProBuilder 2.0允许用户进行快速、高度优化,编辑器水平的建设。从环境细节,到结构体,到整体水平生成自定义的几何形状并将其贴上文理。利用即时反馈和零中断创作过程的特性来进行创作和测试

官方地址:https://www.assetstore.unity3d.com/en/#!/content/3558

Cemetery Starter Pack

Cemetery Starter Pack超过20组的资源让你能够快速的建立起一个墓地场景。该软件包包含了移动和桌面版本的模型外加加1k4k地图纹理

官方地址:https://www.assetstore.unity3d.com/en/#!/content/6835

Substance Designer

Substance Designer 3.5为那些想创造更多的美术作品,但是拥有较少的时间和为数不多的软件选择的3D艺术家而设计的一个全面工具。能够实时的看到最后的材质的真正的样子,并且直接应用到您的模型,和您选择的Shader

官方地址:https://www.assetstore.unity3d.com/en/#!/content/18464

Top-Down Assets Mobile

Top-Down Assets Mobile设置包括大约90种环境的模型:灌木,树木,树干、模块化的废墟,模块化建筑,很多手工地图纹理的,地面,还有一些粒子效果等。屏幕上的drawcall的平均数字是30-9020-40ktris

官方地址:https://www.assetstore.unity3d.com/en/#!/content/5860

FX Maker

FX Maker适用于Unity 3.5.3或更高版本。包含的资源:300个特效,300个纹理,100个网格,100个曲线动画。FX Maker包含300个特效Prefabs。支持MeshLegacyShuriken粒子系统

官方地址:https://www.assetstore.unity3d.com/en/#!/content/4580

Vectrosity

Unity3D中用Vectrosity插件画直线、画点、画曲线、画方框Vectrosity插件是Unity3D目前发现的一个画线最好的工具插件。

官方地址:https://www.assetstore.unity3d.com/en/#!/content/82

Mixamo

Mixamo是一个3D角色动画在线生成工具,能够创建高质量的动画。有很多免费动作供你下载。可以满足不同层次人的需求,无论你是专业人士还是非专业的。

官方地址:https://www.assetstore.unity3d.com/en/#!/content/18466

没有更多推荐了,返回首页