Unity3D应用嵌入WPF应用并实现通信之进阶篇

2017-07-11 07:38:40来源:CSDN作者:lordwish人点击

在之前的一篇博文中描述了Unity3D应用嵌入WPF应用的具体实现方式,但仅仅是解决了最基本的技术问题,想要将其具体的应用到项目中还需要具体的细化。经过近期在项目中的实践进行了细化,现将本人最近的一些过程整理成文,供大家讨论。上篇博文地址如下:

Unity3D应用嵌入WPF应用并实现通信


问题&需求

为什么要将Unity3D应用嵌入WPF应用?

Unity3D是近些年比较流行的游戏引擎之一,在三维空间展现方面有着不错的效果,尤其是开源后有很多可用的资源,最重要的是其支持C#语言脚本开发,能够与传统.NET应用程序相融合。WPF和WinForm多用于桌面应用程序开发,处理业务逻辑有很大的优势,但用来做三维场景展示却是捉襟见肘(虽然WPF支持3D绘图,但从头开发工作量太大);另一方面Unity3D擅长三维场景展示、动画特效、场景交互,但处理业务逻辑有一定的局限性。
从实际项目上考虑,我们系统的业务逻辑处理已经比较完善,如果所用功能使用Unity3D从头开发一遍,学习、开发、测试都需要大量的时间,所以基于这种考虑,保留原有WPF系统的业务处理,将Unity3D的场景展示融合进来,发挥两者各自的优势。

Unity3D应用需要做什么?

作为三维图形处理引擎,我们希望Unity3D能做它最擅长的:动画、模型交互、场景渲染烘培。作为整个业务平台的一部分,我们希望Unity3D将业务系统中抽象的数据以直观的形式展示出来:空间定位、模型交互、空间移动。

方案&实现

解决方案

在上一篇博文中我们已经解决了Unity3D与WPF通信的功能,由于通信是双向的,这就意味者既可以让WPF告诉Unity3D去做什么,也可以让Unity3D告诉WPF去做什么。关键在于要提前定义好两者交互的标准,也就是WPF和Unity3D互相发送消息的“口令”。下面是在项目中整理的WPF和Unity3D的交互规则,便于Unity3D开发人员和WPF开发人员的协同开发:

Unity3D应具备的功能(与WPF无关)

  • 视角平移、缩放、旋转
  • 场景漫游(前进、后退、左转、右转)
  • 指南针
  • 空间测距

WPF应用向Unity3D应用发送消息

  • 查找指定对象
  • 指定对象高亮显示或执行动画
  • 指定对象显示、隐藏或半透明
  • 移动相机至指定位置
  • 获取相机当前位置信息

Unity3D应用向WPF应用发送消息

  • 当前被选中对象或对象组标识
  • 当前相机位置信息

交互接口

针对WPF和Unity3D的交互需要,我们创建了一个公共类库WPF.UnityConnector来定义两者的通信规范。

Unity3D交互数据类

    public class MessageData    {        private OperateCommand _operateType;        private string _operateTarget;        private object _operateData;        public MessageData()        {        }        public MessageData(OperateCommand type)        {            _operateType = type;        }        public MessageData(OperateCommand type,string target)        {            _operateType = type;            _operateTarget = target;        }        public MessageData(OperateCommand type,string target,object data)        {            _operateType = type;            _operateTarget = target;            _operateData = data;        }        /// <summary>        /// 操作类型        /// </summary>        public OperateCommand OperateType        {            get { return _operateType; }            set { _operateType = value; }        }        /// <summary>        /// 操作对象        /// </summary>        public string OperateTarget        {            get { return _operateTarget; }            set { _operateTarget = value; }        }        /// <summary>        /// 数据主体        /// </summary>        public object OperateData        {            get { return _operateData; }            set { _operateData = value; }        }        public override string ToString()        {            return base.ToString();        }    }

操作类型枚举类

  [JsonConverter(typeof(StringEnumConverter))]    public enum OperateCommand    {        /// <summary>        ///        /// </summary>        None,        /// <summary>        /// 定位        /// </summary>        SetPosition,        /// <summary>        /// 获取位置        /// </summary>        GetPosition,        /// <summary>        /// 选择单个对象        /// </summary>        SelectSignle,        /// <summary>        /// 选择对象组        /// </summary>        SelectGroup,        /// <summary>        /// 设置显示        /// </summary>        SetVisible,        /// <summary>        /// 设置隐藏        /// </summary>        SetHidden,        /// <summary>        /// 设置透明        /// </summary>        SetTransparent,        /// <summary>        /// 设置高亮        /// </summary>       SetHighLight,       /// <summary>       /// 设置动画       /// </summary>        SetAnimation,        /// <summary>        /// 无坐标数据通过算法定位        /// </summary>        SetPositionNoData,        /// <summary>        /// 设置帮助说明是否可见        /// </summary>        SetHelpTextVisible,        /// <summary>        /// 返回默认全局视点        /// </summary>        ReturnBack,        /// <summary>        /// 漫游        /// </summary>        SetFreeWalk,        /// <summary>        /// 自动旋转        /// </summary>        SetAutoRotate    }

代码示例

举个例子,假设我们有一个设施管理系统,在WPF业务界面是一个房间内所有家具的列表,在Unity3D展示界面是这个房间的三维模型,想要实现的效果是点击WPF列表中的某一个家具,Unity3D模型中的对应家具模型高亮显示并将相机推近,或者是点击Unity3D模型中的某个家具,WPF界面弹出其详细信息。

WPF列表点击事件:

        private void Btn_Click(object sender, RoutedEventArgs e)        {            Button Btn = sender as Button;            DataRow dr = Btn.Tag as DataRow;            Helpers.BIMOperator.SetPosition(dr["RowGuid"].ToString());            //自己的业务代码//        }

由于最终效果实际上包含两部分:相机推近和物体高亮,所以需要发送两个命令。

命令拼接:

        public static void SetPosition(string guid)        {            List<MessageData> datas = new List<MessageData>();            string ViewPoint = "10_10_10_10_120_12";//由数据库查询所得            //向Unity发送消息,定位设备            MessageData serverMsg = new MessageData();            serverMsg.OperateType = OperateCommand.SetPosition;//定位                    serverMsg.OperateTarget = guid;            serverMsg.OperateData = ViewPoint;//数据格式"0_1_-5_20_0_0"            datas.Add(serverMsg);                  //高亮显示            MessageData msg = new MessageData(OperateCommand.SetVisible, "", guid);            datas.Add(msg);            SendMsgToUnity(datas);     }

WPF发送消息:

        public SocketHelper _connector;        private static void SendMsgToUnity(List<MessageData> msgdata)        {            string sendmsg = JsonConvert.SerializeObject(msgdata);            _connector.SendMessage(sendmsg);        }

Unity3D接收消息

    private void ReceiveMessage(IAsyncResult ar)    {        try        {            _error = "";            int bytesRead;            bytesRead = _client.GetStream().EndRead(ar);            if (bytesRead < 1)            {                return;            }            else            {                string message = System.Text.Encoding.ASCII.GetString(_data, 0, bytesRead);                ReceiveMsgDo(message);                _error = string.Format("{0}:{1}", DateTime.Now.ToString(), message);                //this._client.GetStream().BeginRead(_data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);            }        }        catch (Exception ex)        {            _error = ex.Message;            LogicMgr.Instance.GetLogic<LogicTips>().ShowTips(ex.Message);        }        finally        {            this._client.GetStream().BeginRead(_data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);        }    }    /// <summary>    /// 接收Unity消息进行操作    /// </summary>    /// <param></param>    public void ReceiveMsgDo(string msg)    {        List<MessageData> datas = JsonConvert.DeserializeObject<List<MessageData>>(msg);        foreach (MessageData data in datas)        {            GameMassageReciver.Instance.SetCurrentOperateCommand(data.OperateType, data.OperateData.ToString(), data.OperateTarget);        }    }

Unity3D命令转换

public class GameMassageReciver : SingletonMonoBase<GameMassageReciver>{    private OperateCommand currentOperateCommand;    private string currentMessage;    private string currentTarget;    // Use this for initialization    void Start () {    }    // Update is called once per frame    void Update () {        ReciveMessageDo();    }    private void ReciveMessageDo()    {        switch (currentOperateCommand)        {            case OperateCommand.None://空                break;            case OperateCommand.SetPosition://定位摄像机                               MessageSetPosition(currentMessage);                break;            case OperateCommand.SetVisible://设置显示                MessageSetVisible(currentMessage);                break;            case OperateCommand.SetHidden://设置隐藏                MessageSetHidden(currentMessage);                break;            case OperateCommand.SetTransparent://设置透明                MessageSetTransparent(currentMessage);                break;            case OperateCommand.SetHighLight:                MessageSetHighlight(currentTarget);                break;            case OperateCommand.SetFreeWalk://漫游                MessageSetFreeWalk();                break;            default:                break;        }        SetCurrentOperateCommand(OperateCommand.None,null,null);    }    public void SetCurrentOperateCommand(OperateCommand current,string msg,string target)    {        this.currentMessage = msg;        this.currentOperateCommand = current;        this.currentTarget = target;    }    /// <summary>    /// WPF请求Unity设置摄像机位置    /// </summary>    private void MessageSetPosition(string data)    {        if (data != null)        {            string[] _arr = data.Split('_');            if (_arr.Length == 6)            {                CameraPointData _data = new CameraPointData();                _data.mPosition = new Vector3(GetFloat(_arr[0]), GetFloat(_arr[1]), GetFloat(_arr[2]));                _data.mRotation = new Vector3(GetFloat(_arr[3]), GetFloat(_arr[4]), GetFloat(_arr[5]));                CameraController.Instance.SetPosition(_data);            }            else            {                Debug.LogWarning("数据转化失败!");            }        }        else        {            Debug.LogWarning("数据为空!");        }    }    private float GetFloat(string str)    {        return float.Parse(str);    }    /// <summary>    /// WPF请求Unity显示某物体    /// </summary>    /// <param></param>    public void MessageSetVisible(string id)    {        if (id != null)        {            GameObject _go = ModelMgr.Instance.GetModelById(id);            if (_go != null)            {                ModelMgr.Instance.SetObjVisible(_go);                HighLightManager.Instance.HideCurrentEffect();                HighLightManager.Instance.SwitchFlashHighLight(_go);            }            else            {                Log.Warning("场景中不存在该物体或ID错误!" + id);                          }        }        else        {            Debug.LogWarning("数据为空!");        }    }    /// <summary>    /// WPF请求Unity高亮显示物体    /// </summary>    /// <param></param>    public void MessageSetHighlight(string id)    {        if (id != null)        {            LogicMgr.Instance.GetLogic<LogicTips>().ShowTips("id:" + id);            GameObject _go = ModelMgr.Instance.GetModelById(id);            if (_go != null)            {                ModelMgr.Instance.SetObjVisible(_go);                HighLightManager.Instance.SwitchFlashHighLight(_go);            }            else            {                Log.Warning("场景中不存在该物体或ID错误!" + id);            }        }        else        {            Debug.LogWarning("数据为空!");        }    }    /// <summary>    /// WPF请求Unity隐藏某物体    /// </summary>    /// <param></param>    public void MessageSetHidden(string id)    {        if (id != null)        {            LogicMgr.Instance.GetLogic<LogicTips>().ShowTips("id:" + id);            GameObject _go = ModelMgr.Instance.GetModelById(id);            if (_go != null)            {                ModelMgr.Instance.SetObjHidden(_go);             }            else            {                Log.Warning("场景中不存在该物体或ID错误!" + id);                Log.Warning("场景中不存在该物体或ID错误!18cc8045-3d61-4082-ba0b-d7ddd7fb6f70");            }        }        else        {            Debug.LogWarning("数据为空!");        }    } }

Unity3D相机位移:

    [HideInInspector]    public Vector3 m_target;//目标    private float m_targetDistance = 2f;    private float m_currentDistance;    private Quaternion m_currentRotation;    public void SetPosition(CameraPointData data)    {        isSettingPosition = false;        StopCoroutine("OnSetCameraPosition01");        m_target = data.mPosition;        m_targetDistance = 0;        CaculatePosition(data.mRotation.y, data.mRotation.x, m_targetDistance);        StartCoroutine("OnSetCameraPosition01");    }     void CaculatePosition(float eulerY, float eulerX, float dis = 0)    {        Log.Debug("eulerX:" + eulerX + "eulerY:" + eulerY);        m_Anglex = eulerY;        m_Angley = eulerX;        m_distance = dis;        m_desiredRotation = Quaternion.Euler(m_Angley, m_Anglex, 0);        m_desiredPosition = m_target + m_desiredRotation * new Vector3(0, 0, -m_distance);    }

Unity3D对象高亮:

    //当前所有的高亮物体    private List<HighlightableObject> m_highLightObjs = null;    private HighlightableObject m_currentObj = null;public void SwitchFlashHighLight(GameObject obj)    {        FlashHighLightGameObject(obj, true);    }    private void FlashHighLightGameObject(GameObject obj, bool isSwitch=false,bool isShow=true)    {        if (obj==null)        {            return;        }        //获取高亮物体控制脚本        HighlightableObject _highLightObj;        _highLightObj = obj.GetComponent<HighlightableObject>();        if (_highLightObj==null)        {            _highLightObj = obj.AddComponent<HighlightableObject>();        }        m_currentObj = _highLightObj;        //加入列表,便于以后去除所有高亮效果        if (!m_highLightObjs.Contains(_highLightObj))        {            m_highLightObjs.Add(_highLightObj);        }        //设置高亮颜色        _highLightObj.FlashingParams(Color.white, Color.red, 1f);        if (isSwitch)        {            _highLightObj.FlashingSwitch();            return;        }        if (isShow)        {            _highLightObj.FlashingOn();        }        else        {            _highLightObj.FlashingOff();        }    }

Unity3D发送消息由WPF接收并弹出界面的过程参考上述过程类比实现。

其他

为了确保Unity3D和WPF能够顺利交互,Unity3D在编译时建议进行以下配置:
这里写图片描述

勾选Run In Background和Visible in Background,否则会出现在WPF界面操作时Unity3D会最小化的情况。


以上就是本人关于Unity3D嵌入WPF应用的一些思考和实践,欢迎大家批评指正,不吝赐教!

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台