2017-07-10 23:48:11 lordwish 阅读数 5838
  • 基于.NET WPF+ASP.NET MVC4技术构建夜猫商务会所运营...

    本套课程的C/S框架部分主要是基本VS2012平台,采用WPF技术呈现界面,尤其是大量使用了第三方控件Telerik和AvalonDock让界面更加的炫彩夺目,再结合win7或win10操作系统,肯定是传统的WinForm所不能比拟的;录制课程过程中凡是第一次出现的代码99%采用现场手敲的方式

    3306 人正在学习 去看看 张勇

在之前的一篇博文中描述了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 name="msg"></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 name="id"></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 name="id"></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 name="id"></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应用的一些思考和实践,欢迎大家批评指正,不吝赐教!

2016-05-20 23:03:47 mengmakies 阅读数 3497
  • 基于.NET WPF+ASP.NET MVC4技术构建夜猫商务会所运营...

    本套课程的C/S框架部分主要是基本VS2012平台,采用WPF技术呈现界面,尤其是大量使用了第三方控件Telerik和AvalonDock让界面更加的炫彩夺目,再结合win7或win10操作系统,肯定是传统的WinForm所不能比拟的;录制课程过程中凡是第一次出现的代码99%采用现场手敲的方式

    3306 人正在学习 去看看 张勇

问题描述

WinForm中可以顺利载入U3D场景,但是WPF中一直报错:【未能加载文件或程序集“Interop.UnityWebPlayerAXLib….】

原因

初步判断是因为缺少相关dll引用导致的,如WindowsFormIntegration.dll;

解决方案

在WPF中加载U3D场景的步骤:
1. 创建一个 WPF 工程;
2. 创建一个 WindowForm 自定义控件库 (UserControl)
1). 引入 UntiyWebPlayer COM 组件(AxInterop.UnityWebPlayerAXLib.dll和Interop.UnityWebPlayerAXLib);
2). 将AxInterop.UnityWebPlayerAXLib.UnityWebPlayer这个组件拖到 UserControl 里, 并将 Dock属性设置为 Fill 让它充满整个控件;
3). 在自定义的程序文件中,增加一个 UnityWebPlayer 的Public引用. 这样做的目的是,之后可以对其进行操作;
4). 生成 , 在 bin 中会有三个 DLL 文件 , 只有两个有用 . 一个是 AxInterop.UnityWebPlayerAXLib 另一个是 你定义的那个组件的 DLL;
将那两个有用的 DLL 引入到我们的 WPF 工程中. 并且 再引入 System.Windows.Forms.dll 及 WindowsFormIntegration.dll;
(有两个地方要注意:1.必须引用【System.Windows.Forms.dll】 及 【WindowsFormIntegration.dll】;2.将【Interop.UnityWebPlayerAXLib.dll】手动拷贝到debug/release目录下;)
在 WPF 的XAML的 Window 标签中 引入我们的 自定义控件的名称空间. 如: xmlns:unity=”…” 在 中, 加入一个 标签,用来承载我们的 WIndowsForm 的自定义组件. 并在其中 加入 如: . 这样, 就将UnityWebPlayer 嵌入了 WPF中.

注意
部署项目到客户机器时,建议安装Full版本的【UnityWebPlayerFull.exe】,否则会出现断网的情况下,Unity3D场景无法加载显示;
下载地址:http://download.csdn.net/detail/hunter_hz/9444921

参考
http://www.cnblogs.com/easyfrog/p/3300165.html

2017-06-08 08:35:33 lordwish 阅读数 5948
  • 基于.NET WPF+ASP.NET MVC4技术构建夜猫商务会所运营...

    本套课程的C/S框架部分主要是基本VS2012平台,采用WPF技术呈现界面,尤其是大量使用了第三方控件Telerik和AvalonDock让界面更加的炫彩夺目,再结合win7或win10操作系统,肯定是传统的WinForm所不能比拟的;录制课程过程中凡是第一次出现的代码99%采用现场手敲的方式

    3306 人正在学习 去看看 张勇

最近由于项目需要,对Unity3D应用嵌入WPF应用进行了研究,并通过Socket实现了两者的通信。由于Unity3D在5.4.x版本后不再支持WebPlayer,所以并未使用UnityWebPlayer,另外考虑到我们原有的业务系统都是基于WPF的,全部改到Unity3D里面工作量会很大,所以采用了将Unity3D生成的exe可执行程序直接嵌入到WPF中的做法。
我们的设想是WPF程序作为主程序负责业务逻辑处理和页面展示,Unity3D程序作为子程序负责模型展示和交互,二者通过Socket建立连接、相互传递和接收消息以实现操作联动。例如在主程序点击页面按钮触发模型内特定对象高亮显示或点击模型内任意对象联动主程序显示关联数据。

环境 版本
操作系统 Windows 10 prefessional
编译器 Visual Studio 2015 update3

创建WPF应用并嵌入Unity3D应用

从本质上来讲,这是一个Win32应用程序嵌入到WPF应用程序的问题,由于两者窗口绘制原理的差异,就必需依靠Win32API,也就是大家常常会提到的user32.dll。C#中使用Win32Api与C++略有不同,需要使用DllInput,对引用user32.dll进行了简单封装,代码如下:

    public class Win32Helper
    {
        [DllImport("user32.dll")]
        static extern IntPtr SetActiveWindow(IntPtr hWnd);

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true,
           CharSet = CharSet.Unicode, ExactSpelling = true,
           CallingConvention = CallingConvention.StdCall)]
        public static extern long GetWindowThreadProcessId(long hWnd, long lpdwProcessId);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

        [DllImport("user32.dll")]
        public static extern uint GetWindowLong(IntPtr hwnd, int nIndex);

        [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
        public static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern long SetWindowPos(IntPtr hwnd, long hWndInsertAfter, long x, long y, long cx, long cy, long wFlags);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);

        [DllImport("user32.dll", EntryPoint = "ostMessageA", SetLastError = true)]
        public static extern bool PostMessage(IntPtr hwnd, uint Msg, uint wParam, uint lParam);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr GetParent(IntPtr hwnd);

        [DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("User32.dll", EntryPoint = "SendMessage")]
        public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);

        public const int SWP_NOOWNERZORDER = 0x200;
        public const int SWP_NOREDRAW = 0x8;
        public const int SWP_NOZORDER = 0x4;
        public const int SWP_SHOWWINDOW = 0x0040;
        public const int WS_EX_MDICHILD = 0x40;
        public const int SWP_FRAMECHANGED = 0x20;
        public const int SWP_NOACTIVATE = 0x10;
        public const int SWP_ASYNCWINDOWPOS = 0x4000;
        public const int SWP_NOMOVE = 0x2;
        public const int SWP_NOSIZE = 0x1;
        public const int GWL_STYLE = (-16);
        public const int WS_VISIBLE = 0x10000000;
        public const int WS_MAXIMIZE = 0x01000000;
        public const int WS_BORDER = 0x00800000;
        public const int WM_CLOSE = 0x10;
        public const int WS_CHILD = 0x40000000;
        public const int WS_POPUP = -2147483648;
        public const int WS_CLIPSIBLINGS = 0x04000000;
        public const int SW_HIDE = 0; //{隐藏, 并且任务栏也没有最小化图标}
        public const int SW_SHOWNORMAL = 1; //{用最近的大小和位置显示, 激活}
        public const int SW_NORMAL = 1; //{同 SW_SHOWNORMAL}
        public const int SW_SHOWMINIMIZED = 2; //{最小化, 激活}
        public const int SW_SHOWMAXIMIZED = 3; //{最大化, 激活}
        public const int SW_MAXIMIZE = 3; //{同 SW_SHOWMAXIMIZED}
        public const int SW_SHOWNOACTIVATE = 4; //{用最近的大小和位置显示, 不激活}
        public const int SW_SHOW = 5; //{同 SW_SHOWNORMAL}
        public const int SW_MINIMIZE = 6; //{最小化, 不激活}
        public const int SW_SHOWMINNOACTIVE = 7; //{同 SW_MINIMIZE}
        public const int SW_SHOWNA = 8; //{同 SW_SHOWNOACTIVATE}
        public const int SW_RESTORE = 9; //{同 SW_SHOWNORMAL}
        public const int SW_SHOWDEFAULT = 10; //{同 SW_SHOWNORMAL}
        public const int SW_MAX = 10; //{同 SW_SHOWNORMAL}
        public const int WM_SETTEXT = 0x000C;
        public const int WM_ACTIVATE = 0x0006;
        public static readonly IntPtr WA_ACTIVE = new IntPtr(1);
        public static readonly IntPtr WA_INACTIVE = new IntPtr(0);

    }

有了Win32Api下步就可以着手将Unity3D应用嵌入WPF应用。大致思路就是创建一个进程用来启动Unity3D应用,同时在WPF应用中使用WindowsFormsHost划定一块区域用来放置Unity3D,并将其作为子窗口。XAML页面布局如下:

    <Grid>
        <WindowsFormsHost>
            <form:Panel x:Name="unityHost"></form:Panel>
        </WindowsFormsHost>
    </Grid>

后台代码如下:

        private Process process;
        public IntPtr childHandle;

        private void UnityInit()
        {
            string path = Environment.CurrentDirectory + @"\Unity\HelloUnity.exe";
            IntPtr hostHandle = unityHost.Handle;
            process = new Process();
            process.StartInfo.FileName = path;
            //process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
            process.StartInfo.UseShellExecute = true;
            process.StartInfo.CreateNoWindow = true;
            process.Start();
            //process.WaitForInputIdle();

            childHandle = process.MainWindowHandle;
            while (childHandle == IntPtr.Zero)
            {
                childHandle = process.MainWindowHandle;
            }
            uint oldStyle = Win32Helper.GetWindowLong(childHandle, Win32Helper.GWL_STYLE);
            //Win32Helper.SetWindowLong(childHandle, Win32Helper.GWL_STYLE, (oldStyle | WS_CHILD) & ~WS_BORDER);
            Win32Helper.SetWindowLong(childHandle, Win32Helper.GWL_STYLE, oldStyle & ~WS_BORDER);//去除边框
            Win32Helper.SetParent(childHandle, hostHandle);//设为子窗体
            Win32Helper.MoveWindow(childHandle, -2, -2, unityHost.Width+4, unityHost.Height+4, true);//移动窗口位置
        }

Unity3D应用与WPF应用通信

上一阶段将Unity3D应用已经嵌入到了WPF应用,但二者实际上仍没有任何联系,相互独立,并不知道对方在做什么,也就无法实现联动。为了实现联动,他们相互之间就需要传输数据,这就回归到了Windows窗口传递消息的问题上。窗口间传递消息有多种方法,比如使用Win32Api传递消息、Socket等,由于对Win32Api掌握的还不是很熟练,于是参考了网上使用Socket传递消息的示例。
Socket通信包含两部分,其中WPF应用作为服务端,Unity3D作为客户端,两者先后启动建立连接就可以相互发送和接收消息了,根据消息的内容采取相应的操作。
服务端代码如下:

    public class ConnectHelper
    {
        //私有成员
        private static byte[] result = new byte[1024];
        private int myProt = 500;   //端口  
        static Socket serverSocket;
        static Socket clientSocket;

        Thread myThread;
        static Thread receiveThread;

        //属性

        public int port { get; set; }
        //方法

        internal void StartServer()
        {
            //服务器IP地址  
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(ip, myProt));  //绑定IP地址:端口  
            serverSocket.Listen(10);    //设定最多10个排队连接请求  

            //Debug.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());
            string message = string.Format("");

            //通过Clientsoket发送数据  
            myThread = new Thread(ListenClientConnect);
            myThread.Start();

        }

        internal void QuitServer()
        {
            serverSocket.Close();
            clientSocket.Close();
            myThread.Abort();
            receiveThread.Abort();
        }

        internal void SendMessage(string msg)
        {
            clientSocket.Send(Encoding.ASCII.GetBytes(msg));
        }


        /// <summary>  
        /// 监听客户端连接  
        /// </summary>  
        private static void ListenClientConnect()
        {
            while (true)
            {
                try
                {
                    clientSocket = serverSocket.Accept();
                    clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));
                    receiveThread = new Thread(ReceiveMessage);
                    receiveThread.Start(clientSocket);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }

            }
        }

        /// <summary>  
        /// 接收消息  
        /// </summary>  
        /// <param name="clientSocket"></param>  
        private static void ReceiveMessage(object clientSocket)
        {
            Socket myClientSocket = (Socket)clientSocket;
            while (true)
            {
                try
                {
                    //通过clientSocket接收数据  
                    int receiveNumber = myClientSocket.Receive(result);
                    string message = Encoding.ASCII.GetString(result, 0, receiveNumber);
                    //Debug.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber));
                }
                catch (Exception ex)
                {
                    try
                    {
                        Debug.WriteLine(ex.Message);
                        myClientSocket.Shutdown(SocketShutdown.Both);
                        myClientSocket.Close();
                        break;
                    }
                    catch (Exception)
                    {

                    }

                }
            }
        }
    }

WPF应用初始化完成后开启Socket服务端:

        public Helpers.ConnectHelper _connector;

        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += MainWindow_Loaded;
            this.Closed += MainWindow_Closed;

            _connector = new Helpers.ConnectHelper();
            _connector.StartServer();
        }

之后Unity3D应用初始化完成后启动Socket客户端,并与服务端建立连接

public class NewBehaviourScript : MonoBehaviour
{
    const int _port = 500;
    private TcpClient _client;
    byte[] _data;
    string _error;

    // Use this for initialization
    void Start () {
        Init();
    }

    // Update is called once per frame
    void Update () {
        //按键盘上的上下左右键可以翻看模型的各个面[模型旋转]
        // 上
        if (Input.GetKey(KeyCode.UpArrow))
        {
            transform.Rotate(Vector3.right * Time.deltaTime * 10);
            SendMessage("up!");

        }
        // 下
        if (Input.GetKey(KeyCode.DownArrow))
        {
            transform.Rotate(Vector3.left * Time.deltaTime * 10);
            SendMessage("down!");

        }
        // 左
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            transform.Rotate(Vector3.up * Time.deltaTime * 10);
            SendMessage("left!");

        }
        // 右
        if (Input.GetKey(KeyCode.RightArrow))
        {
            transform.Rotate(Vector3.down * Time.deltaTime * 10);
            SendMessage("right!");

        }

    }

    void OnGUI()
    {
        GUI.Label(new Rect(50, 50, 150, 50), _error);
    }

    void OnDestory()
    {
        _client.Close();
    }

    #region 通信初始化
    private void Init()
    {
        try
        {
            _client = new TcpClient();
            _client.Connect("127.0.0.1", _port);
            _data = new byte[_client.ReceiveBufferSize];
            SendMessage("Ready!");
            _client.GetStream().BeginRead(_data, 0, _client.ReceiveBufferSize, ReceiveMessage, null);
        }
        catch (Exception ex)
        {

            _error = ex.Message;
        }

    }
    #endregion

    #region 发送消息
    public new void SendMessage(string message)
    {
        try
        {
            NetworkStream ns = _client.GetStream();
            byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            ns.Flush();
        }
        catch (Exception ex)
        {
            _error = ex.Message;
        }
    }

    #endregion

    #region 接收消息
    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);
                switch (message)
                {
                    case "up":
                        transform.Rotate(Vector3.right * 10);
                        break;
                    case "down":
                        transform.Rotate(Vector3.left * 10);
                        break;
                    case "left":
                        transform.Rotate(Vector3.up * 10);
                        break;
                    case "right":
                        transform.Rotate(Vector3.down * 10);
                        break;
                }
                _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;
        }

    }
    #endregion

}

客户端的启动是在Unity3D的一个脚本里完成的,这个脚本同时包含了通过键盘控制对象旋转的代码,将脚本挂载到游戏对象里,实现操控。
同时为了实现在WPF主程序中对Unity3D对象进行控制,在WPF主程序添加了几个按钮,点击按钮发送消息给Unity3D执行操作。

        //向上旋转
        private void btnUp_Click(object sender, RoutedEventArgs e)
        {
            _connector.SendMessage("up");
        }
        //向下旋转
        private void btnDown_Click(object sender, RoutedEventArgs e)
        {
            _connector.SendMessage("down");
        }
        //向左旋转
        private void btnLeft_Click(object sender, RoutedEventArgs e)
        {
            _connector.SendMessage("left");
        }
        //向右旋转
        private void btnRight_Click(object sender, RoutedEventArgs e)
        {
            _connector.SendMessage("right");
        }

代码下载

在这个过程中也遇到了一些问题,比如Unity3D嵌入后无法单独操控(不能响应键盘/鼠标输入),试了多次,最终才有了一个相对折衷的方案。

2018-01-07 16:29:55 lordwish 阅读数 4040
  • 基于.NET WPF+ASP.NET MVC4技术构建夜猫商务会所运营...

    本套课程的C/S框架部分主要是基本VS2012平台,采用WPF技术呈现界面,尤其是大量使用了第三方控件Telerik和AvalonDock让界面更加的炫彩夺目,再结合win7或win10操作系统,肯定是传统的WinForm所不能比拟的;录制课程过程中凡是第一次出现的代码99%采用现场手敲的方式

    3306 人正在学习 去看看 张勇

在前面的文章中写过如何搭建SignalR服务端和客户端,也写过如何将Unity3D应用嵌入到WPF中,问题是SignalR服务端和WPF客户端实时通信很简单,SignalR服务端怎么与Unity3D应用实时传输数据呢?下面就开始讨论了。


1.问题背景

1.1为什么要在Unity3D中实时传输数据?

在多人游戏中要实时显示对手的数据,如位置、血量等。在3D业务系统中可能要显示外部提供的一些数据,而这些数据是随时都有可能发生变化的,需要实时在Unity3D应用中显示出来,比如外部采集的温度、湿度等。

1.2为什么要使用SignalR进行实时通信?

选用SignalR进行实时通信并不意味着在Unity3D中进行实时通信只有SignalR这一种方案,只是在之前的文章中使用融合SignalR的WebAPI做服务端已经很顺手了,业务系统也相对成熟,所以打算继续沿用下去。目前常见的方案一般是用Socket做通信,但是需要自己做底层的封装,对于我这种技术菜鸟而言搞不定,所以最终还是希望能够使用SignalR做实时数据传输。

WebAPI集成SignalR

1.3如何将SignalR应用到Unity3D中?

最开始我的想法是,反正都是C#的类库,直接添加进去引用就好了。但是Unity3D编辑器是基于Mono解决方案的,而Mono是基于.Net Framework3.5的,SignalR客户端对运行环境的要求是至少是.Net Framework4.5的,这也就意味着是无法直接在Unity3D中使用SignalR的。问题似乎陷入了僵局,但是天无绝人之类,我们发现在Unity应用商店有一个神奇的插件:BestHttp,对比较常用的网络通信方式进行了封装,包括Http、WebSocket、Socket和我们很熟悉的SignalR。BestHttp比Unity3D自带的www功能丰富很多,但是请注意,BestHttp是收费的,BestHttp是收费的,BestHttp是收费的,重要事情说三遍。

2.解决方案

通过上面的分析,我们决定采用融合SignalR的WebAPI作为客户端,Unity3D融合BestHttp作为客户端,采用之前开发的OPCClient作为采集端,搭建一个实时监测温湿度等环境参数的例子。

融合SignalR的OPCClient实现环境参数实时监测

2.1安装BestHttp

打开Unity3D编辑器,新建项目MonitorDemo,选择菜单Window>Asset Store,打开应用商店查找BestHttp,导入到项目中。

这里写图片描述

导入完成后可以看到Assets目录下面多了一个BestHttp的文件夹,可以在文件夹下找到BestHttp的说明文档,本文中的例子都是基于这份说明文档进行开发的。

2.2搭建Unity3D界面

使用Unity3D的UI添加一个Canvas,添加两个Text,TextTempKey显示“温度”,TextTempValue显示温度数据。
这里写图片描述
为了看的清楚些,加了一个图片做背景。
将Canvas的Render Mode改为Screen Space-Camera,并将Render Camera改为MainCamera,使得Canvas始终以UI界面形式呈现。

2.3创建C#脚本

新建脚本SignalrHelper

public class SignalrHelper : MonoBehaviour
{
    public Text txt;
    private Connection _connection;
    private Hub _proxy;
    readonly Uri _uri=new Uri("http://localhost:49749/MessageBus");

    // Use this for initialization
    void Start () {

        //实例化代理Hub
        _proxy=new Hub("MessageHub");
        //实例化连接
        _connection=new Connection(_uri,_proxy);
        //接收数据事件注册
        _proxy.On("receiveData",ReceiveData);
        //启动连接
        _connection.Open();

    }

    private void ReceiveData(Hub hub, MethodCallMessage methodCall)
    {
        string arg0 = methodCall.Arguments[0].ToString();
        ItemValueModel item = Newtonsoft.Json.JsonConvert.DeserializeObject<ItemValueModel>(arg0);
        Debug.Log("I received data:"+arg0);
        txt.text=item.value;
    }


    // Update is called once per frame
    void Update () {

        }
    }

    void OnDestory()
    {
        if (_connection!=null)
        {
            _connection.Close();
        }
    }
}

其中ItemValueModel是我们定义的监测项数据类

/// <summary>
/// 监测标签数据类
/// </summary>
public class ItemValueModel 
{
    /// <summary>
    /// 标签唯一标识
    /// </summary>
    public string id { get; set; }
    /// <summary>
    /// 标签名称
    /// </summary>
    public string name { get; set; }

    /// <summary>
    /// 标签数据值
    /// </summary>
    public string value { get; set; }
}

将脚本SignalrHelper作为组件添加到Canvas上,并将所定义的Canvas的txt属性设为TextTemp_Value,使得Canvas初始化的时候就能与服务端建立连接,并监听服务端方法receiveData,当接收到数据时将TextTemp_Value的文本改为测点值。

2.4添加服务端方法

public class MessageHub:Hub
{
        public async Task ReceiveData(string data)
        {
            ItemValueModel item = JsonConvert.DeserializeObject<ItemValueModel>(data);
            await Clients.All.receiveData(item);
        }
}

完成以上工作后,启动服务端,然后依次启动采集端(OPCClient)和客户端(Unity3D),看到跳动的数字就说明成功了。

3.方案改进

在上面的步骤已经实现了一个测点的实时数据监测,但显然有点简单了,因为实际上我们在业务系统中不可能只有一个测点,而且并不仅仅只是读取数据,还会有写入数据的情况。另外在上面的示例运行的时候发现测点数据变化频率很快的时候,界面会变得卡顿,于是针对这些问题进行改进。

3.1添加数据处理队列

    private static Queue<ItemValueModel> _queue;

    private void ReceiveData(Hub hub, MethodCallMessage methodCall)
    {
        string arg0 = Newtonsoft.Json.JsonConvert.SerializeObject(methodCall.Arguments[0]);
        ItemValueModel item = Newtonsoft.Json.JsonConvert.DeserializeObject<ItemValueModel>(arg0);
        _queue.Enqueue(item);

    }

接收到测点数据变化时,并不立即处理,而是将数据添加到队列_quene中,在Update方法中去处理数据,这样处理数据就和帧刷新保持一致,避免出现卡顿的现象。

    void Update () {

        if (_queue.Any())
        {
            ItemValueModel item = _queue.Dequeue();
            string tmp = item.id.ToUpper();
            GameObject go = GameObject.FindWithTag(tmp);
            if (go != null)
            {
                Debug.Log(go.name);
                var target = go.GetComponent<Text>();
                if (target != null)
                {
                    target.text = item.value;
                    Debug.Log(target.name + ":" + item.value);
                }
            }

        }
    }

3.2添加数据写入方法

在脚本SignalrHelper中添加写入数据的方法,并通过BestHttp提供的Call方法发送给服务端。

    public Button button;
    public Text text_ID;
    public Text text_Value;
    void Start () {

        //数据处理队列
        _queue=new Queue<ItemValueModel>();
        //按钮事件
        button.onClick.AddListener(delegate()
        {            WriteItem(text_ID.text,text_Value.text);
        });

        //实例化代理Hub
        _proxy=new Hub("MessageHub");
        //实例化连接
        _connection=new Connection(_uri,_proxy);
        //接收数据事件注册
        _proxy.On("receiveData",ReceiveData);
        //启动连接
        _connection.Open();

    }

    private void WriteItem(string id,string value)
    {
        if (_connection!=null&&_proxy!=null)
        {
            _proxy.Call("WriteItem", id, value);
        }
    }

3.3添加界面控件

添加两个Input分别用来输入要写入的标签名称和值,添加一个Button用来发送写入命令,再多加几个Text来表示不同的标签。

这里写图片描述

通过以上的改造我们就能在Unity3D中实现多个测点实时数据的监测和测点数据写入了,也就意味着如果是一个设备监控系统的话,我们就能在Uinity3D里进行设备状态的监测和控制了。


在上面的步骤中,我们使用BestHttp在Unity3D里与SignalR服务端建立连接,实现了实时通信,但仍有部分问题需要解决:
如何将将SignalrHelper作为实时通信的总入口,实现数据传输的模块化?
如何实现测点数据与UI控件的动态绑定?
这些问题也许会在后面的实践中得到解决。

2016-11-23 00:37:00 djkje68462 阅读数 105
  • 基于.NET WPF+ASP.NET MVC4技术构建夜猫商务会所运营...

    本套课程的C/S框架部分主要是基本VS2012平台,采用WPF技术呈现界面,尤其是大量使用了第三方控件Telerik和AvalonDock让界面更加的炫彩夺目,再结合win7或win10操作系统,肯定是传统的WinForm所不能比拟的;录制课程过程中凡是第一次出现的代码99%采用现场手敲的方式

    3306 人正在学习 去看看 张勇

 

写在前面:

 

把Unity3D嵌入winform或者wpf程序,过去大部分使用UnityWebPlayer插件来实现,这个插件其实就是网页上播放unity页游的插件。

但是使用UnityWebPlayer嵌入桌面开发有各种问题,我认为最大的问题是效率问题(加载缓慢),毕竟是网页的加载方式,而且可以确认未来也不会得到任何优化。

由于WebGL的高速发展,unity公司认识到了webplayer十分鸡肋,毕竟WebGL不需要任何插件可以直接显示3d内容了,所以Unity3D在5.4.x版本以后明确表示

不再支持webplayer了,所以桌面上UnityWebPlayer插件能不用也就别用了吧。所以大家不要走弯路!!

 

主要内容:

 

将Unity嵌入桌面程序最好的方式是嵌入unity生成的exe程序,winform程序和unity之间通过socket进行通讯,我认为

这也是效率最高,效果最好和最好实现的方式。在Unity程序脚本中,嵌入socket内容,我推荐做成客户端(client),使用wpf程序做服务器端,这是一个谁是主体的问题。

这样wpf可以加载多个unity程序。

 

嵌入后的结果如下图所示(请无视具体内容):

下面简单写了一个脚本,其中man是游戏中的一个gameobject对象。就是上图中穿蓝衣服的男人。在他身上挂着socket脚本如下:

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

public class demoshows : MonoBehaviour {
    public GameObject man;



    const int portNo = 500;
    private TcpClient _client;
    byte[] data;

    string Error_Message;

    void Start () {
        try
        {
            this._client = new TcpClient();
            this._client.Connect("127.0.0.1", portNo);
            data = new byte[this._client.ReceiveBufferSize];
            //SendMessage(txtNick.Text);
            SendMessage("Unity Demo Client is Ready!");
            this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
        }
        catch (Exception ex)
        {


        }
       


    }
    

    void Update () {        
        transform.Rotate(new Vector3(0, 1, 0),0.1f);
    }


    public void rotation()

    {

        transform.Rotate(new Vector3(0, 10, 0));
        //targetRotation = Quaternion.Euler(45.0f, 45.0f, 45.0f);
        //// 直接设置旋转角度 
        //transform.rotation = targetRotation;
        ////man.transform.rotation.SetAxisAngle(new Vector3(0, 1, 0), 30);;                                                                                                                      
    }



    public void translateX(float x)

    {

        transform.Translate(new Vector3(x,0,0));
                                                                                                                      
    }

    public void translateY(float y)

    {

        transform.Translate(new Vector3(0, y, 0));
                                                                                                         
    }


    public void translateZ(float z)

    {

        transform.Translate(new Vector3(0, 0, z));
                                                                                                                   
    }



    void OnGUI()
    {
        GUI.Label(new Rect(50, 50, 150,50 ), Error_Message);
    }


    public new void SendMessage(string message)
    {
        try
        {
            NetworkStream ns = this._client.GetStream();
            byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
            ns.Write(data, 0, data.Length);
            ns.Flush();
        }
        catch (Exception ex)
        {
            Error_Message = ex.Message;
            //MessageBox.Show(ex.ToString());
        }
    }
    public void ReceiveMessage(IAsyncResult ar)
    {
        try
        {
            //清空errormessage
            Error_Message = "";
            int bytesRead;
            bytesRead = this._client.GetStream().EndRead(ar);
            if (bytesRead < 1)
            {
                return;
            }
            else
            {
                Debug.Log(System.Text.Encoding.ASCII.GetString(data, 0, bytesRead));
                string message = System.Text.Encoding.ASCII.GetString(data, 0, bytesRead);
                switch (message)
                {
                    case "1":
                        translateX(1);
                    break;

                    case "2":
                        translateX(-1);
                        break;
                    case "3":
                        translateY(1);
                        break;
                    case "4":
                        translateY(-1);
                        break;
                    case "5":
                        translateZ(1);
                        break;
                    case "6":
                        translateZ(-1);
                        break;
                    default:
                        Error_Message = "unknown command";
                        break;


                }




            }
            this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
        }
        catch (Exception ex)
        {
            Error_Message = ex.Message;
        }
    }


    void OnDestroy()
    {

        this._client.Close();
    }




}

 

脚本很简单,就是通过向unity程序发送消息(1~6)实现模型的平移。

 

服务器端,在wpf程序中简单建立一个socket类,ip和端口要和unity对应,在程序启动时先建立服务器端。

 

using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.Text;
using System;


namespace Demo_Song
{
    class TcpServer
    {
        //私有成员
        private static byte[] result = new byte[1024];
        private int myProt = 500;   //端口  
        static Socket serverSocket;
        static Socket clientSocket;

        Thread myThread;
        static Thread receiveThread;

        //属性

        public int port { get; set; }
        //方法


        
        internal void StartServer()
        {
            //服务器IP地址  
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(ip, myProt));  //绑定IP地址:端口  
            serverSocket.Listen(10);    //设定最多10个排队连接请求  

            Debug.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());
       
            //通过Clientsoket发送数据  
            myThread = new Thread(ListenClientConnect);
            myThread.Start();
            
        }


        internal void QuitServer()
        {

            serverSocket.Close();
            clientSocket.Close();
            myThread.Abort();
            receiveThread.Abort();


        }


        internal void SendMessage(string msg)
        {
                clientSocket.Send(Encoding.ASCII.GetBytes(msg));
        }



        /// <summary>  
        /// 监听客户端连接  
        /// </summary>  
        private static void ListenClientConnect()
        {
            while (true)
            {
                try
                {
                    clientSocket = serverSocket.Accept();
                    clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));
                    receiveThread = new Thread(ReceiveMessage);
                    receiveThread.Start(clientSocket);
                }
                catch (Exception)
                {

                }

            }
        }

        /// <summary>  
        /// 接收消息  
        /// </summary>  
        /// <param name="clientSocket"></param>  
        private static void ReceiveMessage(object clientSocket)
        {
            Socket myClientSocket = (Socket)clientSocket;
            while (true)
            {
                try
                {
                    //通过clientSocket接收数据  
                    int receiveNumber = myClientSocket.Receive(result);
                    Debug.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber));
                }
                catch (Exception ex)
                {
                    try
                    {
                        Debug.WriteLine(ex.Message);
                        myClientSocket.Shutdown(SocketShutdown.Both);
                        myClientSocket.Close();
                        break;
                    }
                    catch (Exception)
                    {
                    }

                }
            }
        }
    }
}  

 

使用socket一定要注意使用线程,并且退出时及时结束,我这里实现的也不是很好,谁有更好的方法可以告诉我。

下面大概就是server的启动和释放,效果还好,至少不卡死....

 

 

        TcpServer WpfServer;
        int size_state = 0;
        public MainWindow()
        {
            InitializeComponent();


            this.Closed += MainWindow_Closed;
            this.Activated += MainWindow_Activated;
            this.Deactivated += MainWindow_Deactivated;



            WpfServer = new TcpServer();
            WpfServer.StartServer();


        }


        void MainWindow_Closed(object sender, EventArgs e)
        {
            unityhost.Form1_FormClosed();


            WpfServer.QuitServer();

        }

 

 

 

 

关于socket通讯的问题大概就这样,大家估计更关系如何嵌入的问题

 

如何嵌入?

 

首先在wpf程序中建立一个winform的自定义控件(不是wpf控件)usercontrol

在usercontrol内新建一个panel(或者其他带有句柄的控件),并设置dock属性为fill。

启动unity.exe,通过几个api将unity窗口附加在panel句柄上。

(说明:借鉴别人的程序)

需要把unity程序命名为child.exe,放在下面指定位置(Debug\UnityApp\Child.exe,或者release)

process.StartInfo.FileName =Application.StartupPath +@"\UnityApp\Child.exe";

详细代码如下:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace Demo_Song
{
    public partial class UnityControl : UserControl
    {
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);

        internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
        [DllImport("user32.dll")]
        internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        private Process process;
        private IntPtr unityHWND = IntPtr.Zero;

        private const int WM_ACTIVATE = 0x0006;
        private readonly IntPtr WA_ACTIVE = new IntPtr(1);
        private readonly IntPtr WA_INACTIVE = new IntPtr(0);





        public UnityControl()
        {
            InitializeComponent();
            this.Load += UnityControl_Load;
            panel1.Resize+=panel1_Resize;
        }

        private void UnityControl_Load(object sender, EventArgs e)
        {
            try
            {
                process = new Process();
                process.StartInfo.FileName =Application.StartupPath +@"\UnityApp\Child.exe";
                process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
                process.StartInfo.UseShellExecute = true;
                process.StartInfo.CreateNoWindow = true;

                process.Start();

                process.WaitForInputIdle();
                // Doesn't work for some reason ?!
                //unityHWND = process.MainWindowHandle;
                EnumChildWindows(panel1.Handle, WindowEnum, IntPtr.Zero);

                unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
            }
            catch (Exception ex)
            {
                unityHWNDLabel.Text = ex.Message;
                //MessageBox.Show(ex.Message);
            }
        }

        internal void ActivateUnityWindow()
        {
            SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
        }

        internal void DeactivateUnityWindow()
        {
            SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
        }

        private int WindowEnum(IntPtr hwnd, IntPtr lparam)
        {
            unityHWND = hwnd;
            ActivateUnityWindow();
            return 0;
        }

        private void panel1_Resize(object sender, EventArgs e)
        {
            MoveWindow(unityHWND, 0, 0, panel1.Width, panel1.Height, true);
            ActivateUnityWindow();
        }

        // Close Unity application
        internal void Form1_FormClosed()
        {
            try
            {
                process.CloseMainWindow();

                Thread.Sleep(1000);
                while (process.HasExited == false)
                    process.Kill();
            }
            catch (Exception)
            {

            }
        }

        internal void Form1_Activated()
        {
            ActivateUnityWindow();
        }

        internal void Form1_Deactivate()
        {
            DeactivateUnityWindow();
        }
    }

}

 

 

最后附上源码,github重置密码死活连不上,现在就传压缩包到百度云把,vs版本较高(2012)以上可以打开,注意,低版本打不开工程。

 

博客地址迁移了,请到 这里

http://www.songshizhao.com/blog/blogPage/78.html

下载。

 

貌似写得有点长了,能看到这里的人不多吧哈哈,希望有做类似需求的人少走弯路吧,碎觉去,over~。

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/NuclearBoy/articles/6092221.html

unity3d 火焰燃烧

阅读数 802

Unity3D嵌入WPF教程

阅读数 86

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