|
最近,有做过一个wpf 工程中嵌入Unity 的Demo。
引用其他博主的一段话:把Unity3D嵌入winform或者wpf程序,过去大部分使用UnityWebPlayer插件来实现,这个插件其实就是网页上播放unity页游的插件。
但是使用UnityWebPlayer嵌入桌面开发有各种问题,我认为最大的问题是效率问题(加载缓慢),毕竟是网页的加载方式,而且可以确认未来也不会得到任何优化。
由于WebGL的高速发展,unity公司认识到了webplayer十分鸡肋,毕竟WebGL不需要任何插件可以直接显示3d内容了,所以Unity3D在5.4.x版本以后明确表示不再支持webplayer了,所以桌面上UnityWebPlayer插件能不用也就别用了吧。 大该意思 就是 UnityWebPlayer Unity3D在5.4.x版本以后被弃用了。所以我们使用其他方式来嵌入。
首先根据Unity官方文档,选择嵌入方式。
文档中 介绍,可以使用两种 不同方式启动在 window 程序中启动Unity
在这里 我使用的是最简单的方式:将Unity作为外部进程启动,并为其指定一个窗口,使用-parentHWND命令行参数对Unity程序进行初始化和呈现。
通信
我是使用socket进行通讯,Unity程序做客户端,使用wpf程序做服务器端。(参考文章中有更加规范的做法)
版本:
Unity 版本: Unity 2018.4.1f1
Vs 版本:vs2017
WPF端相关操作
1、新建 WPF 工程
2、新建 Winform 自定义控件
3、在用户自定控件上 新建一个 Panel 来承接 Unity.exe 画面
打开工具箱 视图→工具箱 (注:需要在设计界面 才会有控件展示)
如下图:
Panel 拖入到 空白区域即可。然后设置Panel 自适应 如下图。
新建一个label 来显示 将错误日志可视化。
4、编写用户控件相关代码
首先 打开 用户控件代码
代码如下:- public partial class UserControl1 : 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 UserControl1()
- {
- InitializeComponent();
- this.Load += UnityControl_Load;
- panel1.Resize += panel1_Resize;
- }
- private void UnityControl_Load(object sender, EventArgs e)
- {
- try
- {
- process = new Process();
- //注意此路径 为Unity程序导出路径
- process.StartInfo.FileName = Application.StartupPath + @"\UnityApp\WPFAndU3D.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);
- label1.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
- }
- catch (Exception ex)
- {
- // label1 为 上面新建的label
- label1.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();
- }
- private void panel1_Paint(object sender, PaintEventArgs e)
- {
- }
- }
复制代码 5、找到 MainWindow.xaml 设置主界面 格式,将自定义控件 内置到主界面中设置后如下图:
注意 : 添加用户自定义空间时 , xmal可能 会报错,说Wpf 程序 不支持 WindowsFormsHost 按如下操作可解决报错
在Visual Studio中,通过右键单击项目中的“引用”节点并选择“添加引用”来完成此操作:
在弹出的对话框中,选择“程序集”,然后勾选我们需要添加的两个程序集:
6、编写 MainWindow.xaml 的交互逻辑- /// <summary>
- /// MainWindow.xaml 的交互逻辑
- /// </summary>
- public partial class MainWindow : Window
- {
- 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();
- }
- private void btn_close_Click(object sender, RoutedEventArgs e)
- {
- this.Close();
- }
- void MainWindow_Closed(object sender, EventArgs e)
- {
- unityhost.Form1_FormClosed();
- WpfServer.QuitServer();
- }
- void MainWindow_Deactivated(object sender, EventArgs e)
- {
- unityhost.Form1_Deactivate();
- }
- void MainWindow_Activated(object sender, EventArgs e)
- {
- unityhost.Form1_Activated();
- }
- private void btn_normal_Click(object sender, RoutedEventArgs e)
- {
- if (size_state == 0)
- {
- this.WindowState = WindowState.Normal;
- this.Width = 1280;
- this.Height = 800;
- toNormalSize.Visibility = Visibility.Hidden;
- toMaxSize.Visibility = Visibility.Visible;
- size_state = 1;
- }
- else
- {
- this.WindowState = WindowState.Maximized;
- toNormalSize.Visibility = Visibility.Visible;
- toMaxSize.Visibility = Visibility.Hidden;
- size_state = 0;
- }
- }
- private void btn_min_Click(object sender, RoutedEventArgs e)
- {
- this.WindowState = WindowState.Minimized;
- }
- private void Window_MouseDown(object sender, MouseButtonEventArgs e)
- {
- try
- {
- this.DragMove();
- }
- catch (Exception)
- {
- }
- }
- private void btn1_Click(object sender, RoutedEventArgs e)
- {
- WpfServer.SendMessage(&#34;1&#34;);
- }
- private void btn2_Click(object sender, RoutedEventArgs e)
- {
- WpfServer.SendMessage(&#34;2&#34;);
- }
- private void btn3_Click(object sender, RoutedEventArgs e)
- {
- WpfServer.SendMessage(&#34;3&#34;);
- }
- private void btn4_Click(object sender, RoutedEventArgs e)
- {
- WpfServer.SendMessage(&#34;4&#34;);
- }
- private void btn5_Click(object sender, RoutedEventArgs e)
- {
- WpfServer.SendMessage(&#34;5&#34;);
- }
- private void btn6_Click(object sender, RoutedEventArgs e)
- {
- WpfServer.SendMessage(&#34;6&#34;);
- }
- }
- }
复制代码
wpf 端 通信类 (这个通信类,参考别人写,比较简单)
Wpf 端我们搞定了,接下来我们来配置Unity端。
Unity 部分
1、新建一个工程,在场景中 新建一个Cube
在Cube上面 添加上如下脚本。- using UnityEngine;
- using System.Collections;
- using System.Net.Sockets;
- using System;
- public class Demo : 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(&#34;127.0.0.1&#34;, portNo);
- data = new byte[this._client.ReceiveBufferSize];
- //SendMessage(txtNick.Text);
- SendMessage(&#34;Unity Demo Client is Ready!&#34;);
- this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
- }
- catch (Exception ex)
- {
- }
- }
- 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 = &#34;&#34;;
- 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 &#34;1&#34;:
- translateX(1);
- break;
- case &#34;2&#34;:
- translateX(-1);
- break;
- case &#34;3&#34;:
- translateY(1);
- break;
- case &#34;4&#34;:
- translateY(-1);
- break;
- case &#34;5&#34;:
- translateZ(1);
- break;
- case &#34;6&#34;:
- translateZ(-1);
- break;
- default:
- Error_Message = &#34;unknown command&#34;;
- 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();
- }
- }
复制代码 2、导出工程
工程设置 如下: 其中Display ResolutionDialog 需要关闭,否则启动Unity导出exe文件 会有选择分辨率提示,导致wpf端 不显示。
导入 上面 新建Wpf项目中文件夹的 bin/Debug/UnityApp 文件夹下。
exe 文件名称需要与 wpf 端用户控件中 代码 保持一致。
如下图
至此,Unity 端设置完毕。
返回,WPF项目,点击运行,可得到下图效果:
参考文章:
git仓库地址: |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|