2015-10-29 01:32:36 yuxikuo_1 阅读数 532

转载自:http://www.luzexi.com/unity3d/游戏通用模块/前端技术/2014/04/16/Unity3D之AssetBundle资源加载封装/

GitHub:https://github.com/luzexi

Unity3D之AssetBundle资源加载封装。在《临兵斗者三国志》中我使用了U3D的AssetBundle资源动态加载机制,原因是某些画面资源太多,IO阻塞过慢会造成游戏奔溃。在开发过程中,遇到点问题:

1.当资源更改变化时,如何能快速得反应到开发中。

解决方案:

我使用宏定义UNITY_EDITOR来判断是否是开发编辑状态。当处于开发编辑状态时,自动读取指定目录下U3D本身资源,而不使用AssetBundle。这样就达到了当prefb变化时能快速反应到开发编辑中。而当不是处于编辑状态时,则正常使用异步加载读取AssetBundle。这个方式唯一的毛病就是,必须让所有U3D程序员都非常清除明白,如果写错,编辑模式下会没问题,发布后会出问题,所以需要检查。

2.当不同资源之间有重复的资源时如何将AssetBundle空间占有量最小化。

解决方案:

GUI资源之间有特别多的重复的问题,挑出几个重复得特别厉害的,比如ICON图集,公用图集。在打包期间把他们设为共享资源,并在加载时首先加载共享资源,这样既节省了AssetBundle空间占有量,也节省了内存。这个方式的毛病是当你将资源更改要打包某个资源时,需要将所有与共享有关的资源重新打包一遍。

3.如何应对自动释放资源问题。

解决方案:

在游戏中有指定资源释放和自动释放所有AssetBundle资源以销毁内存(这里不是指销毁U3D内存,而是AssetBundle内存,U3D内存管理分图片内存,AssetBundle内存,编译程序)。销毁指定资源就按正常来没有争议。销毁所有资源就要有点措施了,因为有些资源是不能被销毁的,因为它们是共享资源,需要全程跟着游戏走,所以当自动销毁所有资源时,将共享资源排除在外。并且在销毁后调用Resources.UnloadUnusedAssets();和GC.Collect();

4.打包AssetBundle方式。

解决方案:

打包AssetBundle方式有几种:1.单资源打包,也就是说一个.prefb或Texture打一个包。2.多个资源打包,将某些资源都打成一个AssetBundle,节省了几个资源包之间的共享资源也减小了多个AssetBundle引起的空间扩大问题。但并不是说所有项目都是多个资源打成一个AssetBundle是好的。《临兵斗者三国志》就是一大部分使用单一打包,而共享资源使用多个资源打成一个AssetBundle的方式。

最后奉上本人对AssetBundle封装的源码。https://github.com/luzexi/Unity3DGameResource

你也可以去我的github上查看找我做的一些源码插件,如果喜欢的话可以star或者fllow。

转发请注明出自:http://www.luzexi.com


2015-12-26 16:40:16 SnoopyNa2Co3 阅读数 3438

对于u3d客户端最好的学习方法就是实践!!
今天终于有时间可以写写博客,这次我写一个基于ugui的ui模块,也可以理解为封装,把ui相关封装起来方便使用。
回到正题!
首先我会用到单例模式,不懂的自行百度。我这里添加一个创建gameobject的单例。

using UnityEngine;
using System.Collections;

public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
    protected static readonly T instance = Create();
    public static T GetSingleton()
    {
        return instance;
    }

    private static T Create()
    {
        T instance = GameObject.FindObjectOfType<T>();
        if (null == instance)
        {
            instance = new GameObject(typeof(T).Name + "_Singleton").AddComponent<T>();
        }
        GameObject.DontDestroyOnLoad(instance);
        return instance;
    }

    protected static V AddComponent<V>() where V : MonoBehaviour
    {
        return instance.gameObject.AddComponent<V>();
    }
}

1.ui我采用的是ngui那种双摄像机,首先创建一个ui摄像机的单例吧

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class UICamera : MonoSingleton<UICamera>
{
    public Camera UI_Camera { get; set; }

    void Awake()
    {
        UI_Camera = GetComponent<Camera>();
        UI_Camera.clearFlags = CameraClearFlags.Depth;
        UI_Camera.cullingMask = 1 << LayerMask.NameToLayer("UI");
        UI_Camera.orthographic = true;
        UI_Camera.orthographicSize = 3.2f;
    }
}

然后测试一下这个单例
这里写图片描述
测试结果
这里写图片描述

2.基本东西已经准备好了,现在开始书写我们的ui框架
ui基本框架有三个东西
这里写图片描述
UISystem :管理ui,包括创建ui,查找ui,删除ui等等(其他功能可以自己扩展)
UIWidget:处理ui逻辑,注册ui事件(点击,拖拽之类),设置sprite、设置label等等接口(很多功能可以自己扩展,例如:点击音效,ui动画)
IEventListener : 是对EventTrigger进行下一层封装,封装成类似ngui事件监听

看UISystem的代码之前,我要补充一下,RectTransformUtility.CalculateRelativeRectTransformBounds可以计算ui的包围盒,不过他的大小位置是根据屏幕缩放之前的rect。 Canvas获取RectTransform. rect是屏幕缩放过的rect,因为我是按照高度适应,所以根据高度来算出比例来算出屏幕缩放之前rect,根据包围盒和Canvas的rect可以用来判断ui是否超出界面,判断超出有什么用?例如:物品属性面板是跟着物品图标位置改变而改变,如果靠太边,属性面板就会超出屏幕。如果超出会做相应工作把他放在屏幕内,这个代码已经写好,下次放出。
计算缩放前的rect代码如下
这里写图片描述

UISystem代码

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[RequireComponent(typeof(Canvas))]
[RequireComponent(typeof(CanvasScaler))]
[RequireComponent(typeof(GraphicRaycaster))]
[RequireComponent(typeof(EventSystem))]
[RequireComponent(typeof(StandaloneInputModule))]
[RequireComponent(typeof(TouchInputModule))]
public sealed class UISystem : MonoSingleton<UISystem>
{
    public const int WIDTH = 960;
    public const int HEIGHT = 640;

    public Canvas Canvas { get; set; }
    public CanvasScaler CanvasScaler { get; set; }
    public Camera UI_Camera { get; set; }
    public RectTransform UIRectTranform { get; set; }
    public Rect UI_Rect { get; set; }

    /// <summary>
    /// 存放由uisystem创建的ui
    /// </summary>
    private Dictionary<string, UIWidget> m_UI_Dic = new Dictionary<string, UIWidget>();

    void Awake()
    {
        this.gameObject.layer = LayerMask.NameToLayer("UI");
        this.Canvas = GetComponent<Canvas>();
        this.CanvasScaler = GetComponent<CanvasScaler>();
        this.UI_Camera = UICamera.GetSingleton().UI_Camera;

        this.Canvas.renderMode = RenderMode.ScreenSpaceCamera;
        this.Canvas.worldCamera = UI_Camera;

        this.CanvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        this.CanvasScaler.matchWidthOrHeight = 1;
        this.CanvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
        this.CanvasScaler.referenceResolution = new Vector2(WIDTH, HEIGHT);

        this.UIRectTranform = GetComponent<RectTransform>();

        Rect screenRect = UIRectTranform.rect;
        float scale = CanvasScaler.matchWidthOrHeight == 1 ? (float)HEIGHT / (float)Screen.height : (float)WIDTH / (float)Screen.width;
        UI_Rect = new Rect(screenRect.x * scale, screenRect.y * scale, screenRect.width * scale, screenRect.height * scale);
    }

    /// <summary>
    /// 创建ui控件
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="resourcePath"></param>
    /// <param name="visable"></param>
    /// <returns></returns>
    public T CreateWight<T>(string resourcePath,bool visable = true) where T : UIWidget
    {
        T panel = null;

        if (m_UI_Dic.ContainsKey(resourcePath))
        {
            panel = GetWidget<T>(resourcePath);
            panel.SetVisable(visable);
            panel.rectTransform.SetAsLastSibling();
            return panel;
        }

        panel = CreateResource(resourcePath, transform).AddComponent<T>();
        panel.SetVisable(visable);
        panel.rectTransform.SetAsLastSibling();

        panel.rectTransform.sizeDelta = Vector2.zero;
        panel.rectTransform.anchoredPosition = Vector3.zero;
        panel.transform.localScale = Vector3.one;

        m_UI_Dic[resourcePath] = panel;

        return panel;
    }

    /// <summary>
    /// 创建一个ui控件
    /// </summary>
    /// <param name="resoucePath"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    public GameObject CreateWidget(string resoucePath,Transform parent)
    {
        GameObject go = CreateResource(resoucePath,parent);
        RectTransform rt = go.GetComponent<RectTransform>();
        rt.anchoredPosition = Vector2.zero;
        rt.localScale = Vector3.one;
        return go;
    }

    /// <summary>
    /// 获取ui控件
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="resourcePath"></param>
    /// <returns></returns>
    public T GetWidget<T>(string resourcePath) where T : UIWidget
    {
        if (!m_UI_Dic.ContainsKey(resourcePath)) { return null; }
        if (null == m_UI_Dic[resourcePath]) { return null; }
        return m_UI_Dic[resourcePath] as T;
    }

    /// <summary>
    /// 创建ui的游戏对象
    /// </summary>
    /// <param name="resourcePath"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    private GameObject CreateResource(string resourcePath,Transform parent)
    {
        GameObject resource = Resources.Load(resourcePath) as GameObject;

        if (null == resource)
        {
            throw new System.NullReferenceException(resourcePath);
        }

        GameObject widget = (GameObject)Instantiate(resource);

        widget.name = resourcePath;
        widget.transform.parent = parent;
        return widget;
    }

    /// <summary>
    /// 删除创建的ui
    /// </summary>
    /// <param name="resourcePath"></param>
    public void DestoryWidget(string resourcePath)
    {
        if (!m_UI_Dic.ContainsKey(resourcePath)) { return; }
        if (null == m_UI_Dic[resourcePath]) { m_UI_Dic.Remove(resourcePath); }
        Destroy(m_UI_Dic[resourcePath].gameObject);
        m_UI_Dic.Remove(resourcePath);
    }
}

UIWidget代码,注册事件我添加了一个根据ui名字注册和gameobject注册,其实都是gameobject注册 ,里面只添加了点击事件,其他事件可以自己添加。部分接口隐藏ui,显示ui,设置精灵,设置文字等等接口

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UIWidget : MonoBehaviour
{
    protected Dictionary<string, GameObject> m_Widgets = new Dictionary<string, GameObject>();

    public const string CONTAIN_WORLD = "UI";

    /// <summary>
    /// 初始化控件
    /// </summary>
    /// <param name="aryName"></param>
    protected void InstallWidgets(string[] aryName)
    {
        List<RectTransform> trans = new List<RectTransform>(gameObject.GetComponentsInChildren<RectTransform>());
        for (int i = 0; i < aryName.Length; i++)
        {
            for (int j = 0; j < trans.Count; j++)
            {
                if (trans[j].name == aryName[i])
                {
                    m_Widgets[aryName[i]] = trans[j].gameObject;
                    trans.RemoveAt(j);
                    break;
                }
            }
        }
    }

    /// <summary>
    /// 当前面板的rect
    /// </summary>
    public RectTransform rectTransform
    {
        get
        {
            if(null == m_RectTransform)
            {
                m_RectTransform = GetComponent<RectTransform>();
            }
            return m_RectTransform;
        }
    }
    private RectTransform m_RectTransform;

    /// <summary>
    /// 查找ui控件接口
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public GameObject Find(string name)
    {
        if (!m_Widgets.ContainsKey(name)) { return null; }
        return m_Widgets[name];
    }

    /// <summary>
    /// 通过gameobject注册点击事件
    /// </summary>
    /// <param name="go"></param>
    /// <param name="execute"></param>
    public void RegisterClickEvent(GameObject go, UIEventListener.VoidDelegate execute)
    {
        if (null == go) { return; }
        UIEventListener.Get(go).onPointerClick = execute;
    }

    /// <summary>
    /// 通过控件名称注册点击事件
    /// </summary>
    /// <param name="widget"></param>
    /// <param name="execute"></param>
    public void RegisterClickEvent(string widget, UIEventListener.VoidDelegate execute)
    {
        RegisterClickEvent(Find(widget), execute);
    }

    /// <summary>
    /// 设置精灵
    /// </summary>
    /// <param name="go"></param>
    /// <param name="sprite"></param>
    public void SetSprite(GameObject go,Sprite sprite)
    {
        if (null == go) { return; }
        Image image = go.GetComponent<Image>();
        if (null == image) { return; }
        image.sprite = sprite;
    }

    public void SetSprite(string widget, Sprite sprite)
    {
        SetSprite(Find(widget), sprite);
    }

    /// <summary>
    /// 设置进度条
    /// </summary>
    /// <param name="go"></param>
    /// <param name="value"></param>
    public void SetSlider(GameObject go,float value)
    {
        if (null == go) { return; }
        Slider slider = go.GetComponent<Slider>();
        if (null == slider) { return; }
        slider.value = value;
    }

    public void SetSlider(string widget, float value)
    {
        SetSlider(Find(widget), value);
    }

    /// <summary>
    /// 设置toggle
    /// </summary>
    /// <param name="go"></param>
    /// <param name="enable"></param>
    public void SetToggle(GameObject go,bool enable)
    {
        if (null == go) { return; }
        Toggle toggle = go.GetComponent<Toggle>();
        if (null == toggle) { return; }
        toggle.isOn = enable;
    }

    public void SetToggle(string widget,bool enable)
    {
        SetToggle(Find(widget), enable);
    }

    /// <summary>
    /// 设置文字
    /// </summary>
    /// <param name="go"></param>
    /// <param name="content"></param>
    public void SetText(GameObject go,string content)
    {
        if (null == go) { return; }
        Text text = go.GetComponent<Text>();
        if (null == text) { return; }
        text.text = content;
    }

    public void SetText(string widget,string content)
    {
        SetText(Find(widget), content);
    }

    /// <summary>
    /// 设置输入文字
    /// </summary>
    /// <param name="go"></param>
    /// <param name="content"></param>
    public void SetInputText(GameObject go,string content)
    {
        if (null == go) { return; }
        InputField input = go.GetComponent<InputField>();
        if (null == input) { return; }
        input.text = content;
    }

    public void SetInputText(string widget,string content)
    {
        SetInputText(Find(widget), content);
    }

    /// <summary>
    /// 设置控件颜色
    /// </summary>
    /// <param name="go"></param>
    /// <param name="color"></param>
    public void SetColor(GameObject go,Color color)
    {
        if (null == go) { return; }
        Graphic graphic = go.GetComponent<Graphic>();
        if (null == graphic) { return; }
        graphic.color = color;
    }

    public void SetColor(string widget,Color color)
    {
        SetColor(Find(widget), color);
    }

    /// <summary>
    /// 获取进度条的value
    /// </summary>
    /// <param name="go"></param>
    /// <returns></returns>
    public float GetSliderValue(GameObject go)
    {
        if (null == go) { return 0; }
        Slider slider = go.GetComponent<Slider>();
        if (null == slider) { return 0; }
        return slider.value;
    }

    public float GetSliderValue(string widget)
    {
        return GetSliderValue(Find(widget));
    }

    /// <summary>
    /// 获取toggle状态
    /// </summary>
    /// <param name="go"></param>
    /// <returns></returns>
    public bool GetToggle(GameObject go)
    {
        if (null == go) { return false; }
        Toggle toggle = go.GetComponent<Toggle>();
        if (null == toggle) { return false; }
        return toggle.isOn;
    }

    public void GetToggle(string widget)
    {
        GetToggle(Find(widget));
    }

    /// <summary>
    /// 获取输入框的文字
    /// </summary>
    /// <param name="go"></param>
    /// <returns></returns>
    public string GetInputText(GameObject go)
    {
        if (null == go) { return string.Empty; }
        InputField input = go.GetComponent<InputField>();
        if (null == input) { return string.Empty; }
        return input.text;
    }

    public string GetInputText(string widget)
    {
        return GetInputText(Find(widget));
    }

    /// <summary>
    /// 显示控件
    /// </summary>
    /// <param name="name"></param>
    protected void Show(string name)
    {
        GameObject go = Find(name);
        if (null == go) { return; }
        go.SetActive(true);
    }

    /// <summary>
    /// 隐藏控件
    /// </summary>
    /// <param name="name"></param>
    protected void Hide(string name)
    {
        GameObject go = Find(name);
        if (null == go) { return; }
        go.SetActive(false);
    }

    public void SetVisable(bool visable)
    {
        gameObject.SetActive(visable);
    }
}

UIEventListener 代码
这个不多说看过用过ngui的都知道。我只是继承EventTrigger,然后重写方法而已

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;

public class UIEventListener : EventTrigger
{
    public delegate void VoidDelegate(GameObject go, BaseEventData eventData);
    public VoidDelegate onBeginDrag;
    public VoidDelegate onCancel;
    public VoidDelegate onDeSelect;
    public VoidDelegate onDrag;
    public VoidDelegate onDrop;
    public VoidDelegate onEndDrag;
    public VoidDelegate onInitializePotentialDrag;
    public VoidDelegate onMove;
    public VoidDelegate onPointerClick;
    public VoidDelegate onPointerDown;
    public VoidDelegate onPointerEnter;
    public VoidDelegate onPointerExit;
    public VoidDelegate onPointerUp;
    public VoidDelegate onScroll;
    public VoidDelegate onSelect;
    public VoidDelegate onSubmit;
    public VoidDelegate onUpdateSelect;

    public override void OnBeginDrag(PointerEventData eventData)
    {
        if(null != onBeginDrag) { onBeginDrag(gameObject, eventData); }
    }

    public override void OnCancel(BaseEventData eventData)
    {
        if (null != onCancel) { onCancel(gameObject, eventData); }
    }

    public override void OnDeselect(BaseEventData eventData)
    {
        if (null != onDeSelect) { onDeSelect(gameObject, eventData); }
    }

    public override void OnDrag(PointerEventData eventData)
    {
        if (null != onDrag) { onDrag(gameObject, eventData); }
    }

    public override void OnDrop(PointerEventData eventData)
    {
        if (null != onDrop) { onDrop(gameObject, eventData); }
    }

    public override void OnEndDrag(PointerEventData eventData)
    {
        if (null != onEndDrag) { onEndDrag(gameObject, eventData); }
    }

    public override void OnInitializePotentialDrag(PointerEventData eventData)
    {
        if (null != onInitializePotentialDrag) { onInitializePotentialDrag(gameObject, eventData); }
    }

    public override void OnMove(AxisEventData eventData)
    {
        if (null != onMove) { onMove(gameObject, eventData); }
    }

    public override void OnPointerClick(PointerEventData eventData)
    {
        if (null != onPointerClick) { onPointerClick(gameObject, eventData); }
    }

    public override void OnPointerDown(PointerEventData eventData)
    {
        if (null != onPointerDown) { onPointerDown(gameObject, eventData); }
    }

    public override void OnPointerEnter(PointerEventData eventData)
    {
        if (null != onPointerEnter) { onPointerEnter(gameObject, eventData); }
    }

    public override void OnPointerExit(PointerEventData eventData)
    {
        if (null != onPointerExit) { onPointerExit(gameObject, eventData); }
    }

    public override void OnPointerUp(PointerEventData eventData)
    {
        if (null != onPointerUp) { onPointerUp(gameObject, eventData); }
    }

    public override void OnScroll(PointerEventData eventData)
    {
        if (null != onScroll) { onScroll(gameObject, eventData); }
    }

    public override void OnSelect(BaseEventData eventData)
    {
        if (null != onSelect) { onSelect(gameObject, eventData); }
    }

    public override void OnSubmit(BaseEventData eventData)
    {
        if (null != onSubmit) { onSubmit(gameObject, eventData); }
    }

    public override void OnUpdateSelected(BaseEventData eventData)
    {
        if (null != onUpdateSelect) { onUpdateSelect(gameObject, eventData); }
    }

    static public UIEventListener Get(GameObject go)
    {
        UIEventListener listener = go.GetComponent<UIEventListener>();
        if (listener == null) listener = go.AddComponent<UIEventListener>();
        return listener;
    }
}

现在测试一下这个ui模块
首先添加一个ui预设先
这里写图片描述
然后预设路径放在
这里写图片描述
ui对应的脚本UITest
这里写图片描述
添加测试代码
这里写图片描述
运行效果,点击按钮就有打印
这里写图片描述
下期会放出ui超出屏幕的处理以及把ui位置设定在某个ui的8个方向的位置的计算

2015-03-06 12:32:39 ycl295644 阅读数 2241

转载出处:http://www.luzexi.com

Unity3D-HTTP网络层封装。短连接的C#封装在这里做些分享。我把网络层封装成DLL在项目中使用,所以在设计时要将接口封装的很好。 我又对HTTP部分进行了改造不再使用DDL封装了(所以去除文章部分暂时用横线做标记),所有源码都以git的submodule形式作为项目的模块。源码会再文章的最后给出来。这次改装主要是针对程序员是否能快速理解,快速上手的方面来进行的。随着对HTTP网络层不断的理解加深,我将给出更加全面的HTTP源码模块,我还对本HTTP模块写了测试案例,你们可以看测试案例,进行使用。谢谢各位的关注,再次感谢不断得支持。

一.首先说下网络层需要的一些接口,在游戏里需要用到的网络层都具有大部分的共同点,无论是TCP长连接还是HTTP短连接有部分区别,HTTP的网络事件要相对少一点。

HTTP接口基本为:数据发送Send接口,网络错误事件,数据包回调执行句柄。HTTP网络层的数据包预发送接口被我去除,因为我回想,这个功能完全可以用模块外部的程序代替,而且可以针对每个界面来写,这样更加清晰,而且主要是这个功能障碍了程序员对程序的理解。

二.说明下网络层中几个类的用途和作用。 HTTPSession类,是整个网络层的主类,承载了发送,接受,事件相应的事务。

HTTPPacketRequest类,网络请求数据包基础类。每个请求数据包的最基础的数据,包含一个m_strAction地址变量,这个是因为每个HTTP请求的地址不同而设的变量,每个请求地址前缀都相同比如:http://luzexi.com/ ,而后缀有可能变化,比如author/regist/。所以这个变量要在你创建的时候在构建函数里进行设置。

HTTPPacketAck类,网络回调数据包基础类。每个回调数据包的基础数据,每个数据包都含有几个基础的变量,我现在写的是code错误码 和 desc错误描述,你也可以改成其他你想要的,如果你理解整这段程序。总之,继承这个回调数据类的类中可以放置本次回调的数据,有数据回调时,系统将自动解析成这个类。

HTTPSession类,会话类是网络层的主类,有发送接口和网络主地址,还有一个报错接口。是整个HTTP网络层的接入口。

HttpDummySession类,我还做了虚拟会话,主要是给HTTP和单机切换用,有些游戏想做网络和单机一起兼顾,可以用HTTP虚拟类来实现,不需要改原来写好的HTTP请求,只要你对加个请求句柄处理就可以了。不过一般人不会用到。

三.网络层最重要的是能够快速,方便使用,能够适应变化多端的需求改变。

每个请求类(HTTPPacketRequest子类)和回调类(HTTPPacketAck子类)都是自动生成参数和自动映射回调类实例的,但千万记住,回调过来的数据和HTTPPacketAck子类里的变量名必须一致。

四.关于HTTP的Head数据部分

HEAD部分我已经写好了,但是一个统一的HEAD变量,在session类中你可以找到。因为head部分都是统一的参数变量,动的比较少,所以才这么做。在游戏项目里,可以用HEAD部分的数据也可以不用,因为完全可以用数据包的形式代替。

 

最后奉上源码:Unity3D-HTTP    测试案例:Unity3D-HTTP-Test


2017-04-16 17:37:09 tankerhunter 阅读数 1384

重要说明:

首先,目前的unity3d只支持.net3.5的功能,最新的测试版貌似可以使用.net的高级功能,但也还不能发布使用。为此这里有一个可以实现类似功能的方案--利用unity3d,进程启动另一个微软的中间软件,在unity3d中向其传入参数,并利用反射将开发的dll加载到其中,最后将得到的结果返回到unity3d。这样的过程有点复杂,但我想如果封装的好,应该还是比较容易使用的。在说一个开发这个例子的背景,前些日子,利用windows自带的api可以实现打开窗口选择文件,但由于使用参数复杂,封装了两个有简单参数的类,但目前发现不稳定,会出现崩溃的意外,而且还不能正常显示一些基本信息。为此伤透了脑筋。如果是微软自己的软件来调用这些接口应该不会出现这些问题,更何况winform和wpf已经把这样常用的功能封装好了,只需要调用就好了。无奈我的大unity3d还需要购插件才能使用类似的选择文件的功能。其次,由于实际开发过程中需要一些联合unity和wpf或winform的功能,如果将winform做成3.5到是可以放置到unity中使用,但这样做的情况是丑到爆的界面。如果将使用wpf封装一些界面模块的话,应该就是不能使用了,这时如果用wpf开发一个启动器专门用来加载其相关的dll,然后将最后的结果传回到unity就好了。当然本文只使用了winform开发了一个启动器,还有大量的工作需要去完善和实现,最跟本的还是要得项目能用上才有时间继续开发了。

下面将介绍这个模块的使用方法,以打开文件和通信为例子,但并不是说这个模块只有这点功能,如果需要可以自行扩展。最后会后讲一下程序中的重点和难点,然后会附上github源码地址,如果有兴趣自己去研究一下,如果能扩展和提出宝贵的意见更好。

一、最终功能简单实现:

1.可以使用所有的window自带的dialog(文件选择,颜色选择)


(图一.颜色和路径选择后)


(图二、打开文件选择框)

2.可实时通信交互数据


(图三、利用外部面板来操作unity3d对象)

二、以打开文件夹选择为例的使用方法说明:

1.在unity3d的中定义中间程序的相对路径位置

程序中有一些需要使用协程进行等待的功能,同时在运行和结束时自动注册和注销一些信息,所以将这部分功能写到了一个继承于Monobehaiver的脚本中,但使用时,用了一个静态的脚本,便于调用,初始化路径相关的功能都放置到这个静态脚本中,而有关于通信模块的调用的事件的分发调用都放置到了这个组件脚本中。

下面是程序路径配制方法,在FormLoaderUser脚本中

public static void InitEnviroment()
    {
        GameObject go = new GameObject(typeof(FormLoaderBehavier).ToString());
        behaiver = go.AddComponent<FormLoaderBehavier>();
        string path = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Demo")) + "FormLoader/FormLoader/bin/Debug/FormLoader.exe";
        Behaiver.TryOpenHelpExe(path);
    }

2.编写第三方dll

由于中间程序只负责启动的功能,主要的逻辑将在具体的dll中实现,下面是我写的对话框dll中的一个选择文件对话框的方法。要注意的一点是,直接传字符串过来始终会出现乱码,暂时还没有解决这个问题,直接传byte[]会出现新的问题,所以这部分暂时用这个方法来进行中文字符串的处理。

        private string ConverByte(object titlebytes)
        {
            JSONArray array = JSONArray.Parse(titlebytes.ToString()).AsArray;
            byte[] byts = new byte[array.Count];
            for (int i = 0; i < byts.Length; i++)
            {
                byts[i] = (byte)array[i].AsInt;
            }
            string title = System.Text.Encoding.UTF8.GetString(byts);
            return title;
        }
        public string OpenFileDialog(object titlebytes, string filter, string initialDirectory)
        {
            string title = ConverByte(titlebytes);

            string resultFile = "";
            try
            {
                OpenFileDialog openFileDialog1 = new OpenFileDialog();
                openFileDialog1.InitialDirectory = initialDirectory;
                openFileDialog1.Filter = filter;// "txt files (*.txt)|*.txt|All files (*.*)|*.*" ;
                openFileDialog1.FilterIndex = 2;
                openFileDialog1.Title = title;
                openFileDialog1.RestoreDirectory = true;
                if (openFileDialog1.ShowDialog() == DialogResult.OK)
                {

                    // Insert code to read the stream here.
                    resultFile = openFileDialog1.FileName;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
            }

            return resultFile;

        }
3.在unity通过中间程序调用以上的dll

   由于中间程序可能会出现问题,首先可以进行不通过中间程序的方法,确保dll和unity中的方法都能正常运行,Test方法调用后在编辑器模式下可以直接打开.net 2.0的winform
界面 ,如图四所示:
public static void OpenFileDialog(string title, FileType fileType, string initialDirectory, Action<string> onReceive)
    {
        List<byte> byts = new List<byte>(Encoding.UTF8.GetBytes(title));
        string path = Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Demo")) + "Demo/FileDialogHelp/FileDialogHelp/bin/Debug/FileDialogHelp.dll";
        string clsname = "FileDialogHelp.FileDialog";
        string methodname = "OpenFileDialog";
        string filter = "";
        switch (fileType)
        {
            case FileType.Txt:
                filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
                break;
            default:
                break;
        }
        object[] aregument = new object[] { byts, filter, initialDirectory };
        DllFuction pro = new DllFuction(path, clsname, methodname, aregument);
        string text = JsonConvert.SerializeObject(pro);
        Behaiver.AddSendQueue(ProtocalType.dllfunction, text);
        Behaiver.RegisteReceive(ProtocalType.dllfunction, onReceive);
        //Test(text);
    }

 static void Test(string x)
    {
        DllFuction protocal = JsonConvert.DeserializeObject<DllFuction>(x);
        System.Reflection.Assembly asb = System.Reflection.Assembly.LoadFrom(protocal.dllpath);
        var cls = asb.GetType(protocal.classname);
        var method = cls.GetMethod(protocal.methodname);
        var instence = Activator.CreateInstance(cls);
        string back = (string)method.Invoke(instence, protocal.argument);
        Debug.Log(back);
    }


(图四,在编辑器模式下不通过中间程序反射得到的打开文件窗口界面)

4.异步调用

在view 层,可以直接关联一个按扭来触发打开界面事件,如下所示,当选择完成后会自动将title的信息设置好,但直得到注意的是,这了防止unity因为通信卡死,主程序已经将

这部分功能写在了线程中,如果在选择文件之前,title已经被销毁了,就会出现问题,所以在程序中为了防止这问题,最好是要判断回调时候是否还有需要。当然一个成熟的功能模块应该也要在内部判断异常的机制。

  private void openFileBtnClicked()
    {
        FormLoaderUser.OpenFileDialog("打开文件选择窗口", FormLoaderUser.FileType.Txt, "C://", (x) =>
        {
            title.text = x;
        });
    }
三、双向通信模块为例说明可扩展性
以上的对话框打开的实现有多种方法,本文介绍的是相对复杂的实现方式,但也是目前我所知最稳定的解决方式,但用其作为本模块功能介绍的目的并不是要实现一个窗口打开的功能,而是要介绍一种unity中调用window窗体的可行的方案。下面将介绍在unity中发信息到winfom,和在winform中控制unity对象的方式,而注意的是这里的winform不是一个独立的程序,而是一个基于中间程序的dll模块!

1.封装一个简单的form(图五)

(图五,一个简单的form界面)

这个form启动器中,向form注册了状态改变的事件,当进行点击按扭的操作时,需要在这个dll中去定义一个和unity3d通信的协议,注意到如果用其他dll时,这并不会对中间程序产生任何需要修改的功能需求,下面是这个form的所有代码,就是告诉unity3d中的一个简单对象应该怎么变化

public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }
        public Action<string> changedaction;
        private void turnBig(object sender, EventArgs e)
        {
            var obj = new { methodName = "turnBig" };
            string txt = JsonConvert.SerializeObject(obj);
            if(changedaction != null)
            {
                changedaction.Invoke(txt);
            }
            else
            {
                MessageBox.Show("changedaction == null");
            }
        }

        private void turnSmall(object sender, EventArgs e)
        {
            var obj = new { methodName = "turnSmall" };
            string txt = JsonConvert.SerializeObject(obj);
            if (changedaction != null)
            {
                changedaction.Invoke(txt);
            }
            else
            {
                MessageBox.Show("changedaction == null");
            }

        }

        private void toLeft(object sender, EventArgs e)
        {
            var obj = new { methodName = "toLeft" };
            string txt = JsonConvert.SerializeObject(obj);

            if (changedaction != null)
            {
                changedaction.Invoke(txt);
            }
            else
            {
                MessageBox.Show("changedaction == null");
            }

        }

        private void toRight(object sender, EventArgs e)
        {
            var obj = new { methodName = "toRight" };
            string txt = JsonConvert.SerializeObject(obj);

            if (changedaction != null)
            {
                changedaction.Invoke(txt);
            }
            else
            {
                MessageBox.Show("changedaction == null");
            }
        }

        private void toDown(object sender, EventArgs e)
        {
            var obj = new { methodName = "toDown" };
            string txt = JsonConvert.SerializeObject(obj);

            if (changedaction != null)
            {
                changedaction.Invoke(txt);
            }
            else
            {
                MessageBox.Show("changedaction == null");
            }
        }

        private void toUp(object sender, EventArgs e)
        {
            var obj = new { methodName = "toUp" };
            string txt = JsonConvert.SerializeObject(obj);

            if (changedaction != null)
            {
                changedaction.Invoke(txt);
            }
            else
            {
                MessageBox.Show("changedaction == null");
            }
        }

        public void SetLable(string txt)
        {
            label1.Text = txt;
        }

        internal void RegisterHolder(Action<string> onStateChange)
        {
            this.changedaction = onStateChange;
        }
    }

2.将unity3d中的一个Cube脚本关联到模块中

在程序的运行初其就将需要操作的对向注册到了这个通信模块中,只要form中触发了对应的事件,那么unity中可以触发相应的效果。

public static void ReigsterCube(Cube cube)
    {
        try
        {
            behaiver.RegisteReceive(ProtocalType.communicate, (txt) =>
            {
                Debug.Log(txt);
                JSONNode node = JSONNode.Parse(txt);
                string methodName = node.AsObject["methodName"].Value;
                var method = cube.GetType().GetMethod(methodName);
                method.Invoke(cube, null);
            });
        }
        catch (Exception e)
        {
            Debug.Log(e);
        }

    }

cube脚本挂到场景的一个对象上面就好

public class Cube : MonoBehaviour {
    void Start()
    {
        FormLoaderUser.ReigsterCube(this);
    }
    public void turnBig()
    {
        transform.localScale *= 2;
    }
    public void turnSmall()
    {
        transform.localScale *= 0.5f;
    }
    public void toLeft()
    {
        transform.localPosition += Vector3.left;
    }
    public void toRight()
    {
        transform.localPosition += Vector3.right;
    }
    public void toDown()
    {
        transform.localPosition += Vector3.down;
    }
    public void toUp()
    {
        transform.localPosition += Vector3.up;
    }
}



3.在unity3d中触发winform的事件

同理,只要在这个窗体打开的时候注册好了相应的事件,在unity可以简单的这样发送信息,效果如图六所示,当前,有一点需要注意,目前还没有解决通过dll加载时会出现中文乱码的问题,如果有方案解决,一定尽快更新代码。

    /// <summary>
        /// dll中的事件回调
        /// </summary>
        /// <param name="state"></param>
        private void OnHolderStateChanged(string state)
        {
            dataSender.SendMessage(ProtocalType.communicate.ToString(), state);
            //由于在unityedior模式下不支持信息传回所以用读写的方式
            if (writeData)
            {
                string path = System.IO.Directory.GetCurrentDirectory() + "/" + ProtocalType.communicate.ToString() + ".txt";
                System.IO.File.WriteAllText(path, state);
            }
        }

(图六,在unity3d中向已经打开的模块发信息)

四、要点总结

1.中间程序加载说明

中间程序启动后,只是等待信息接收和分发,并不会消耗多少内存和cpu,下面是这个中间程序的启动是属于窗口程序切换模块的一中,我已经将这部分功能放置到了一个dll中(windowswitch),于是调用一个新的程序只需要关心传入什么参数等不需要关心进程启动,然后怎么获取参数,关闭进程等问题

  public void TryOpenHelpExe(string exePath)
    {
        windowswitch = new WindowSwitchUnity();
        string path = exePath;// Application.dataPath.Substring(0, Application.dataPath.LastIndexOf("Demo")) + "FormLoader/FormLoader/bin/Debug/FormLoader.exe";
        if (System.IO.File.Exists(path))
        {
#if UNITY_EDITOR
            if (windowswitch.OpenChildWindow(path, false, "1"))
#else
            if (windowswitch.OpenChildWindow(path, false))
#endif
            {
                //打开子应用程序
                StartCoroutine(DelyRegisterSender());
            }
        }
        else
        {
            Debug.LogError("exe not fond");
        }
        sendThread.Start();
    }
需要说明的事,sendmessage还有个问题目前没有解决,暂时无法向unity的编辑器发送信息,只有打包出来之后才能相互沟通,所以暂时,在编辑器模式下只写了一个模拟的实时通信的功能(信息保存到txt中再读取),"1"这个参数就是告诉中间程序应该是以模拟方式运行还是直接发回信息

2.中间程序批量处理dll

unity中序列化的字符串,在中间程序中的解析,下面是一个简单的调用dll中的一个方法,并获取返回值的功能。这个方法写在一个专门处理具体信息解析的脚本中,然后在一个管理程序中进行注册。

Win32API.SetWindowPos(currWin,-1,0,0,0,0, Win32API.SetWindowPosFlags.DoNotRedraw);
这句代码的作用就是将窗口置于最前端显示,不然打开了窗口,怕是找不到、
public void InvokeSimpleMethodByProtocal(string x)
        {
            Win32API.SetWindowPos(currWin,-1,0,0,0,0, Win32API.SetWindowPosFlags.DoNotRedraw);

            try
            {
                DllFuction protocal = JsonConvert.DeserializeObject<DllFuction>(x);
                Assembly asb = Assembly.LoadFrom(protocal.dllpath);
                var cls = asb.GetType(protocal.classname);
                var method = cls.GetMethod(protocal.methodname);
                var instence = Activator.CreateInstance(cls);
                string back = (string)method.Invoke(instence, protocal.argument);
                dataSender.SendMessage(ProtocalType.dllfunction.ToString(), back);
                //由于在unityedior模式下不支持信息传回所以用读写的方式
                if (writeData)
                {
                    string path = System.IO.Directory.GetCurrentDirectory() + "/" + ProtocalType.dllfunction.ToString() + ".txt";
                    System.IO.File.WriteAllText(path, back);
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }

        }
以上这种方法的注册是这样写的,分别用于处理不同的dll类型
  /// <summary>
        /// 注册执行事件
        /// </summary>
        private void RegistEntry()
        {
            exeHelp = new ExecuteHelp(swi.Current,dataSender,writeData);
            Win32API.SendMessage(swi.Current, Win32API.WM_ACTIVATE, Win32API.WA_ACTIVE, IntPtr.Zero);
            dataReceiver.RegisterEvent(ProtocalType.dllfunction.ToString(), exeHelp.InvokeSimpleMethodByProtocal);
            dataReceiver.RegisterEvent(ProtocalType.lunchholder.ToString(), exeHelp.LunchCommnuicateHolder);
            dataReceiver.RegisterEvent(ProtocalType.communicate.ToString(), exeHelp.RegisterCommuateModle);
        }

3.  不显示主窗口的中间程序

 由于sendmessage的实现是基于窗体程序的,如果没有主窗体这功能貌似有点问题,handle找不到,所以研究了下不显示窗体的启动方式,自己定义个HideOnStartupApplicationContext继承于ApplicationContext,在unity中找到了handle后将窗体隐藏了就好,这样:

   public HideOnStartupApplicationContext(string[] datas)
        {
            mainForm = new Form();
            mainForm.WindowState = FormWindowState.Minimized;
            mainForm.Show();

            swi = new WindowSwitchWinform();
            if (swi.OnOpenedByParent(ref datas))
            {
                try
                {
                    if (datas.Length > 0) writeData = int.Parse(datas[0]) == 1;
                }
                catch (Exception e)
                {
                    MessageBox.Show(e.Message);
                }

                RegistMessageTrans(parent);
            }
            mainForm.Hide();
        }
五、参考资源:

1.通信核心模块来源

这个大神写的sendmessage功能,实现了unity3d中和其他进程进行通信的方案

unity3d进程通信利用WM_COPYDATE和HOOK

2.常用winapi功能说明

这个网址有所以window开发的api,及c#调用的方法,比较于详细

PInvoke.net

3.本程序相关的dll源码地址

这是个人开发的模块功能,github上有不少开源功能,欢迎收藏

通信功能的封装及demo --sendmessage_unity

窗口切换功能的封装及demo -- windowswitch

4.c#使用json必需品

非常方便的进行json的转换

Newtonsoft.Json

5.隐藏窗口程序的实现方法

WinForm 之 程序启动不显示主窗体


2017-04-02 21:32:30 jxw167 阅读数 1735

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

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

继续MVC代码的封装工作,上一节封装到了Control模块,我们现在很多的窗体,如何管理?这需要我们设计一个管理类去管理所有的窗体,下面给读者展示如何编写WindowManager类,代码如下:

namespace Game.View
{
   //场景类型
    public enum EScenesType
    {
        EST_None,
        EST_Login,
        EST_Play,
    }

   //窗体类型
    public enum EWindowType
    {
        EWT_LoginWindow, //登录
        EWT_UserWindow, //用户
        EWT_LobbyWindow,
        EWT_BattleWindow,
        EWT_RoomWindow,
        EWT_HeroWindow,
    }

    public class WindowManager : Singleton<WindowManager>
    {
        public WindowManager()
        {
            mWidowDic = new Dictionary<EWindowType, BaseWindow>();

            mWidowDic[EWindowType.EWT_LoginWindow] = new LoginWindow();
            mWidowDic[EWindowType.EWT_UserWindow] = new UserInfoWindow();
            mWidowDic[EWindowType.EWT_LobbyWindow] = new LobbyWindow();
            mWidowDic[EWindowType.EWT_BattleWindow] = new BattleWindow();
            mWidowDic[EWindowType.EWT_RoomWindow] = new RoomWindow();
            mWidowDic[EWindowType.EWT_HeroWindow] = new HeroWindow();
        }

        public BaseWindow GetWindow(EWindowType type)
        {
            if (mWidowDic.ContainsKey(type))
                return mWidowDic[type];

            return null;
        }

        public void Update(float deltaTime)
        {
            foreach (BaseWindow pWindow in mWidowDic.Values)
            {
                if (pWindow.IsVisible())
                {
                    pWindow.Update(deltaTime);
                }
            }
        }

        public void ChangeScenseToPlay(EScenesType front)
        {
            foreach (BaseWindow pWindow in mWidowDic.Values)
            {
                if (pWindow.GetScenseType() == EScenesType.EST_Play)
                {
                    pWindow.Init();

                    if(pWindow.IsResident())
                    {
                        pWindow.PreLoad();
                    }
                }
                else if ((pWindow.GetScenseType() == EScenesType.EST_Login) && (front == EScenesType.EST_Login))
                {
                    pWindow.Hide();
                    pWindow.Realse();

                    if (pWindow.IsResident())
                    {
                        pWindow.DelayDestory();
                    }
                }
            }
        }

        public void ChangeScenseToLogin(EScenesType front)
        {
            foreach (BaseWindow pWindow in mWidowDic.Values)
            {
                if (front == EScenesType.EST_None && pWindow.GetScenseType() == EScenesType.EST_None)
                {
                    pWindow.Init();

                    if (pWindow.IsResident())
                    {
                        pWindow.PreLoad();
                    }
                }

                if (pWindow.GetScenseType() == EScenesType.EST_Login)
                {
                    pWindow.Init();

                    if (pWindow.IsResident())
                    {
                        pWindow.PreLoad();
                    }
                }
                else if ((pWindow.GetScenseType() == EScenesType.EST_Play) && (front == EScenesType.EST_Play))
                {
                    pWindow.Hide();
                    pWindow.Realse();

                    if (pWindow.IsResident())
                    {
                        pWindow.DelayDestory();
                    }
                }
            }
        }

        /// <summary>
        /// 隐藏该类型的所有Window
        /// </summary>
        /// <param name="front"></param>
        public void HideAllWindow(EScenesType front)
        {
            foreach (var item in mWidowDic)
            {
                if (front == item.Value.GetScenseType())
                {
                    Debug.Log(item.Key);
                    item.Value.Hide();
                    //item.Value.Realse();
                }
            }
        }

        public void ShowWindowOfType(EWindowType type)
        { 
            BaseWindow window;
            if(!mWidowDic.TryGetValue(type , out window))
            {
                return;
            }
            window.Show();
        }

        private Dictionary<EWindowType, BaseWindow> mWidowDic;
    }
}

对窗体进行注册,显示,隐藏等操作,该函数实现了对游戏中所有窗体的管理工作。窗体之间如何切换?我们使用了状态机去编写逻辑,每个窗体都有自己的状态类,状态类也有共性,我们将其抽离出来,实现代码如下:

namespace Game.GameState
{
    public interface IGameState
    {
        GameStateType GetStateType();
        void SetStateTo(GameStateType gsType);
        void Enter();
        GameStateType Update(float fDeltaTime);
        void FixedUpdate(float fixedDeltaTime);
        void Exit();
    }
}
父类实现了后,LoginState的实现如下:

namespace Game.GameState
{
    class LoginState : IGameState
    {
        GameStateType stateTo;

        GameObject mScenesRoot;
	public LoginState()
	{
	}

        public GameStateType GetStateType()
        {
            return GameStateType.GS_Login;
        }

        public void SetStateTo(GameStateType gs)
        {
            stateTo = gs;
        }

        public void Enter()
        {
            SetStateTo(GameStateType.GS_Continue);
            ResourceUnit sceneRootUnit = ResourcesManager.Instance.loadImmediate(GameConstDefine.GameLogin, ResourceType.PREFAB);
            mScenesRoot = GameObject.Instantiate(sceneRootUnit.Asset) as GameObject;

            LoginCtrl.Instance.Enter();
        
            ResourceUnit audioClipUnit = ResourcesManager.Instance.loadImmediate(AudioDefine.PATH_UIBGSOUND, ResourceType.ASSET);
            AudioClip clip = audioClipUnit.Asset as AudioClip;       

            AudioManager.Instance.PlayBgAudio(clip);

            EventCenter.AddListener<CEvent>(EGameEvent.eGameEvent_InputUserData, OnEvent);
            EventCenter.AddListener<CEvent>(EGameEvent.eGameEvent_IntoLobby, OnEvent);
            EventCenter.AddListener(EGameEvent.eGameEvent_SdkLogOff, SdkLogOff);            
        }

        public void Exit()
        {
            EventCenter.RemoveListener<CEvent>(EGameEvent.eGameEvent_InputUserData, OnEvent);
            EventCenter.RemoveListener<CEvent>(EGameEvent.eGameEvent_IntoLobby, OnEvent);
            EventCenter.RemoveListener(EGameEvent.eGameEvent_SdkLogOff, SdkLogOff);       

            //LoadUiResource.DestroyLoad(mUIRoot);
            LoginCtrl.Instance.Exit();
            GameObject.DestroyImmediate(mScenesRoot);            
        }

        public void FixedUpdate(float fixedDeltaTime)
        {
            
        }

        public GameStateType Update(float fDeltaTime)
        {
            return stateTo;
        }

        public void OnEvent(CEvent evt)
        {
            UIPlayMovie.PlayMovie("cg.mp4", Color.black, 2/* FullScreenMovieControlMode.Hidden*/, 3/*FullScreenMovieScalingMode.Fill*/);
            switch (evt.GetEventId())
            {
                case EGameEvent.eGameEvent_InputUserData:
                    SetStateTo(GameStateType.GS_User);
                    break;
                case EGameEvent.eGameEvent_IntoLobby:
                    GameStateManager.Instance.ChangeGameStateTo(GameStateType.GS_Lobby);
                    break;
            }
        }
    }
}
以上是关于State状态机的类实现,代码如下:

namespace Game.GameState
{
    class LoginState : IGameState
    {
        GameStateType stateTo;

        GameObject mScenesRoot;

       // GameObject mUIRoot;
	
		public LoginState()
		{
		}

        public GameStateType GetStateType()
        {
            return GameStateType.GS_Login;
        }

        public void SetStateTo(GameStateType gs)
        {
            stateTo = gs;
        }

        public void Enter()
        {
            SetStateTo(GameStateType.GS_Continue);
            ResourceUnit sceneRootUnit = ResourcesManager.Instance.loadImmediate(GameConstDefine.GameLogin, ResourceType.PREFAB);
            mScenesRoot = GameObject.Instantiate(sceneRootUnit.Asset) as GameObject;

            LoginCtrl.Instance.Enter();
        
            ResourceUnit audioClipUnit = ResourcesManager.Instance.loadImmediate(AudioDefine.PATH_UIBGSOUND, ResourceType.ASSET);
            AudioClip clip = audioClipUnit.Asset as AudioClip;              
        }
        public void Exit()
        {
            EventCenter.RemoveListener<CEvent>(EGameEvent.eGameEvent_InputUserData, OnEvent);
            EventCenter.RemoveListener<CEvent>(EGameEvent.eGameEvent_IntoLobby, OnEvent);
            
            //LoadUiResource.DestroyLoad(mUIRoot);
            LoginCtrl.Instance.Exit();
            GameObject.DestroyImmediate(mScenesRoot);            
        }

        public void FixedUpdate(float fixedDeltaTime)
        {
            
        }

        public GameStateType Update(float fDeltaTime)
        {
            return stateTo;
        }

        public void OnEvent(CEvent evt)
        {
            UIPlayMovie.PlayMovie("cg.mp4", Color.black, 2/* FullScreenMovieControlMode.Hidden*/, 3/*FullScreenMovieScalingMode.Fill*/);
            switch (evt.GetEventId())
            {
                case EGameEvent.eGameEvent_InputUserData:
                    SetStateTo(GameStateType.GS_User);
                    break;
                case EGameEvent.eGameEvent_IntoLobby:
                    GameStateManager.Instance.ChangeGameStateTo(GameStateType.GS_Lobby);
                    break;
            }
        }
    }
}
在Enter函数中,使用了LoginCtrl类接口的调用。另外,我们编写的逻辑是不挂在脚本上的,资源都是动态加载的,后面会告诉读者如何使用,后面会继续给读者讲解。。。。。。




Unity3D游戏框架设计

阅读数 7712

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