找回密码
 立即注册
查看: 569|回复: 0

WPF 中嵌入Unity

[复制链接]
发表于 2020-12-25 10:16 | 显示全部楼层 |阅读模式
最近,有做过一个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、编写用户控件相关代码
首先 打开 用户控件代码
代码如下:
  1.   public partial class UserControl1 : UserControl
  2.     {
  3.         [DllImport("User32.dll")]
  4.         static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
  5.         internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
  6.         [DllImport("user32.dll")]
  7.         internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
  8.         [DllImport("user32.dll")]
  9.         static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
  10.         private Process process;
  11.         private IntPtr unityHWND = IntPtr.Zero;
  12.         private const int WM_ACTIVATE = 0x0006;
  13.         private readonly IntPtr WA_ACTIVE = new IntPtr(1);
  14.         private readonly IntPtr WA_INACTIVE = new IntPtr(0);
  15.         public UserControl1()
  16.         {
  17.             InitializeComponent();
  18.             this.Load += UnityControl_Load;
  19.             panel1.Resize += panel1_Resize;
  20.         }
  21.         private void UnityControl_Load(object sender, EventArgs e)
  22.         {
  23.             try
  24.             {
  25.                 process = new Process();
  26.                 //注意此路径 为Unity程序导出路径
  27.                 process.StartInfo.FileName = Application.StartupPath + @"\UnityApp\WPFAndU3D.exe";
  28.                 process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
  29.                 process.StartInfo.UseShellExecute = true;
  30.                 process.StartInfo.CreateNoWindow = true;
  31.                 process.Start();
  32.                 process.WaitForInputIdle();
  33.                 // Doesn't work for some reason ?!
  34.                 //unityHWND = process.MainWindowHandle;
  35.                 EnumChildWindows(panel1.Handle, WindowEnum, IntPtr.Zero);
  36.                 label1.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
  37.             }
  38.             catch (Exception ex)
  39.             {
  40.                 // label1 为 上面新建的label
  41.                 label1.Text = ex.Message;
  42.                 //MessageBox.Show(ex.Message);
  43.             }
  44.         }
  45.         internal void ActivateUnityWindow()
  46.         {
  47.             SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
  48.         }
  49.         internal void DeactivateUnityWindow()
  50.         {
  51.             SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
  52.         }
  53.         private int WindowEnum(IntPtr hwnd, IntPtr lparam)
  54.         {
  55.             unityHWND = hwnd;
  56.             ActivateUnityWindow();
  57.             return 0;
  58.         }
  59.         private void panel1_Resize(object sender, EventArgs e)
  60.         {
  61.             MoveWindow(unityHWND, 0, 0, panel1.Width, panel1.Height, true);
  62.             ActivateUnityWindow();
  63.         }
  64.         // Close Unity application
  65.         internal void Form1_FormClosed()
  66.         {
  67.             try
  68.             {
  69.                 process.CloseMainWindow();
  70.                 Thread.Sleep(1000);
  71.                 while (process.HasExited == false)
  72.                     process.Kill();
  73.             }
  74.             catch (Exception)
  75.             {
  76.             }
  77.         }
  78.         internal void Form1_Activated()
  79.         {
  80.             ActivateUnityWindow();
  81.         }
  82.         internal void Form1_Deactivate()
  83.         {
  84.             DeactivateUnityWindow();
  85.         }
  86.         private void panel1_Paint(object sender, PaintEventArgs e)
  87.         {
  88.         }
  89.     }
复制代码
5、找到 MainWindow.xaml 设置主界面 格式,将自定义控件 内置到主界面中
  1. <Window x:Class="WpfApp.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.         xmlns:local="clr-namespace:WpfApp"
  7.         mc:Ignorable="d"
  8.         
  9.         WindowState="Maximized" WindowStyle="None"
  10.         
  11.         Title="MainWindow" Height="1080" Width="1920">
  12.     <Grid>
  13.         <Grid.RowDefinitions>
  14.             <RowDefinition Height="50px"/>
  15.             <RowDefinition Height="80px"/>
  16.             <RowDefinition Height="*"/>
  17.         </Grid.RowDefinitions>
  18.         <!--注释绘制这个标题栏-->
  19.         <Grid Name="toolBar" Height="50px" Width="Auto" Background="Black" MouseDown="Window_MouseDown" Margin="0" Grid.Row="0">
  20.             <!--标题文本-->
  21.             <StackPanel HorizontalAlignment="Left" VerticalAlignment="Center">
  22.                 <TextBlock Text="WPFTOU3D" FontSize="30" Foreground="White" ></TextBlock>
  23.             </StackPanel>
  24.             <!--右侧按钮-->
  25.             <StackPanel Name="buttons" Grid.ColumnSpan="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
  26.                 <!--最小化-->
  27.                 <Button  Name="btn_min" Width="50px" Height="50px" Padding="2" Margin="5" Background="Transparent" Click="btn_min_Click">
  28.                     <TextBlock FontFamily="Segoe MDL2 Assets" FontSize="30"  Text="" Foreground="White" />
  29.                 </Button>
  30.                 <!--normal/最大化-->
  31.                 <Button Name="btn_normal" Width="50px" Height="50px" Padding="2" Margin="5" Background="Transparent" Click="btn_normal_Click">
  32.                     <Grid>
  33.                         <TextBlock Name="toNormalSize" FontSize="30"  FontFamily="Segoe MDL2 Assets" Text="" Foreground="White" />
  34.                         <TextBlock Name="toMaxSize" Visibility="Hidden" FontSize="30" FontWeight="Bold" FontFamily="Segoe MDL2 Assets" Text="" Foreground="White" />
  35.                     </Grid>
  36.                 </Button>
  37.                 <!--关闭程序-->
  38.                 <Button Name="btn_close" Width="50px" Height="50px" Padding="2" Margin="5" Background="Transparent" Click="btn_close_Click">
  39.                     <TextBlock FontFamily="Segoe MDL2 Assets" Text="" FontSize="30" FontWeight="Bold" Foreground="White" />
  40.                 </Button>
  41.             </StackPanel>
  42.         </Grid>
  43.         <!--主要内容页-->
  44.         <Grid Grid.Row="2" Background="white" Margin="16,16,16,30">
  45.             <Grid.ColumnDefinitions>
  46.                 <ColumnDefinition Width="500"/>
  47.                 <ColumnDefinition Width="233*"/>
  48.                 <ColumnDefinition Width="118*"/>
  49.             </Grid.ColumnDefinitions>
  50.             <ListView>
  51.                 <ListViewItem Width="500">
  52.                     <Grid Height="100" Width="500" HorizontalAlignment="Center">
  53.                         <StackPanel HorizontalAlignment="Left">
  54.                             <Button Name="btn1" Click="btn1_Click">
  55.                                 <!--图片问题,可找随意图片代替,同名即可放到对应文件夹下-->
  56.                                 <Image Width="100" Height="100" Source="Assets/img/LeftArrow.png"></Image>
  57.                             </Button>
  58.                         </StackPanel>
  59.                         <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="30">X方向移动</TextBlock>
  60.                         <StackPanel HorizontalAlignment="Right">
  61.                             <Button Name="btn2" Click="btn2_Click">
  62.                                 <Image  Width="100" Height="100" Source="Assets/img/RightArrow.png" RenderTransformOrigin="3.842,0.494"></Image>
  63.                             </Button>
  64.                         </StackPanel>
  65.                     </Grid>
  66.                 </ListViewItem>
  67.                 <ListViewItem Width="500">
  68.                     <Grid Height="100" Width="500" HorizontalAlignment="Center">
  69.                         <StackPanel HorizontalAlignment="Left">
  70.                             <Button Name="btn3" Click="btn3_Click">
  71.                                 <Image Width="100" Height="100" Source="Assets/img/LeftArrow.png"></Image>
  72.                             </Button>
  73.                         </StackPanel>
  74.                         <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="30">Y方向移动</TextBlock>
  75.                         <StackPanel HorizontalAlignment="Right">
  76.                             <Button Name="btn4" Click="btn4_Click">
  77.                                 <Image  Width="100" Height="100" Source="Assets/img/RightArrow.png" RenderTransformOrigin="3.842,0.494"></Image>
  78.                             </Button>
  79.                         </StackPanel>
  80.                     </Grid>
  81.                 </ListViewItem>
  82.                 <ListViewItem Width="500">
  83.                     <Grid Height="100" Width="500" HorizontalAlignment="Center">
  84.                         <StackPanel HorizontalAlignment="Left">
  85.                             <Button Name="btn5" Click="btn5_Click">
  86.                                 <Image Width="100" Height="100" Source="Assets/img/LeftArrow.png"></Image>
  87.                             </Button>
  88.                         </StackPanel>
  89.                         <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="30">Z方向移动</TextBlock>
  90.                         <StackPanel HorizontalAlignment="Right">
  91.                             <Button Name="btn6" Click="btn6_Click">
  92.                                 <Image  Width="100" Height="100" Source="Assets/img/RightArrow.png" RenderTransformOrigin="3.842,0.494"></Image>
  93.                             </Button>
  94.                         </StackPanel>
  95.                     </Grid>
  96.                 </ListViewItem>
  97.             </ListView>
  98.             <!--建立一个winform的容器-->
  99.             <Grid Background="Azure"  Grid.Column="1" Name="Container" >
  100.                 <WindowsFormsHost>
  101.                     <local:UserControl1 x:Name="unityhost">
  102.                     </local:UserControl1>
  103.                 </WindowsFormsHost>
  104.             </Grid>
  105.         </Grid>
  106.     </Grid>
  107. </Window>
复制代码
设置后如下图:

注意 : 添加用户自定义空间时 , xmal可能 会报错,说Wpf 程序 不支持 WindowsFormsHost
按如下操作可解决报错
在Visual Studio中,通过右键单击项目中的“引用”节点并选择“添加引用”来完成此操作:
在弹出的对话框中,选择“程序集”,然后勾选我们需要添加的两个程序集:


6、编写 MainWindow.xaml 的交互逻辑
  1.     /// <summary>
  2.     /// MainWindow.xaml 的交互逻辑
  3.     /// </summary>
  4.     public partial class MainWindow : Window
  5.     {
  6.         TcpServer WpfServer;
  7.         int size_state = 0;
  8.         public MainWindow()
  9.         {
  10.             InitializeComponent();
  11.             this.Closed += MainWindow_Closed;
  12.             this.Activated += MainWindow_Activated;
  13.             this.Deactivated += MainWindow_Deactivated;
  14.             WpfServer = new TcpServer();
  15.             WpfServer.StartServer();
  16.         }
  17.         private void btn_close_Click(object sender, RoutedEventArgs e)
  18.         {
  19.             this.Close();
  20.         }
  21.         void MainWindow_Closed(object sender, EventArgs e)
  22.         {
  23.             unityhost.Form1_FormClosed();
  24.             WpfServer.QuitServer();
  25.         }
  26.         void MainWindow_Deactivated(object sender, EventArgs e)
  27.         {
  28.             unityhost.Form1_Deactivate();
  29.         }
  30.         void MainWindow_Activated(object sender, EventArgs e)
  31.         {
  32.             unityhost.Form1_Activated();
  33.         }
  34.         private void btn_normal_Click(object sender, RoutedEventArgs e)
  35.         {
  36.             if (size_state == 0)
  37.             {
  38.                 this.WindowState = WindowState.Normal;
  39.                 this.Width = 1280;
  40.                 this.Height = 800;
  41.                 toNormalSize.Visibility = Visibility.Hidden;
  42.                 toMaxSize.Visibility = Visibility.Visible;
  43.                 size_state = 1;
  44.             }
  45.             else
  46.             {
  47.                 this.WindowState = WindowState.Maximized;
  48.                 toNormalSize.Visibility = Visibility.Visible;
  49.                 toMaxSize.Visibility = Visibility.Hidden;
  50.                 size_state = 0;
  51.             }
  52.         }
  53.         private void btn_min_Click(object sender, RoutedEventArgs e)
  54.         {
  55.             this.WindowState = WindowState.Minimized;
  56.         }
  57.         private void Window_MouseDown(object sender, MouseButtonEventArgs e)
  58.         {
  59.             try
  60.             {
  61.                 this.DragMove();
  62.             }
  63.             catch (Exception)
  64.             {
  65.             }
  66.         }
  67.         private void btn1_Click(object sender, RoutedEventArgs e)
  68.         {
  69.             WpfServer.SendMessage("1");
  70.         }
  71.         private void btn2_Click(object sender, RoutedEventArgs e)
  72.         {
  73.             WpfServer.SendMessage("2");
  74.         }
  75.         private void btn3_Click(object sender, RoutedEventArgs e)
  76.         {
  77.             WpfServer.SendMessage("3");
  78.         }
  79.         private void btn4_Click(object sender, RoutedEventArgs e)
  80.         {
  81.             WpfServer.SendMessage("4");
  82.         }
  83.         private void btn5_Click(object sender, RoutedEventArgs e)
  84.         {
  85.             WpfServer.SendMessage("5");
  86.         }
  87.         private void btn6_Click(object sender, RoutedEventArgs e)
  88.         {
  89.             WpfServer.SendMessage("6");
  90.         }
  91.     }
  92. }
复制代码

wpf 端  通信类 (这个通信类,参考别人写,比较简单)
  1. using System.Net.Sockets;
  2. using System.Net;
  3. using System.Threading;
  4. using System.Diagnostics;
  5. using System.Text;
  6. using System;
  7.     class TcpServer
  8.     {
  9.         //私有成员
  10.         private static byte[] result = new byte[1024];
  11.         private int myProt = 500;   //端口  
  12.         static Socket serverSocket;
  13.         static Socket clientSocket;
  14.         Thread myThread;
  15.         static Thread receiveThread;
  16.         //属性
  17.         public int port { get; set; }
  18.         //方法
  19.         
  20.         internal void StartServer()
  21.         {
  22.             //服务器IP地址  
  23.             IPAddress ip = IPAddress.Parse("127.0.0.1");
  24.             serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  25.             serverSocket.Bind(new IPEndPoint(ip, myProt));  //绑定IP地址:端口  
  26.             serverSocket.Listen(10);    //设定最多10个排队连接请求  
  27.             Debug.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());
  28.       
  29.             //通过Clientsoket发送数据  
  30.             myThread = new Thread(ListenClientConnect);
  31.             myThread.Start();
  32.             
  33.         }
  34.         internal void QuitServer()
  35.         {
  36.             serverSocket.Close();
  37.             clientSocket.Close();
  38.             myThread.Abort();
  39.             receiveThread.Abort();
  40.         }
  41.         internal void SendMessage(string msg)
  42.         {
  43.                 clientSocket.Send(Encoding.ASCII.GetBytes(msg));
  44.         }
  45.         /// <summary>  
  46.         /// 监听客户端连接  
  47.         /// </summary>  
  48.         private static void ListenClientConnect()
  49.         {
  50.             while (true)
  51.             {
  52.                 try
  53.                 {
  54.                     clientSocket = serverSocket.Accept();
  55.                     clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));
  56.                     receiveThread = new Thread(ReceiveMessage);
  57.                     receiveThread.Start(clientSocket);
  58.                 }
  59.                 catch (Exception)
  60.                 {
  61.                 }
  62.             }
  63.         }
  64.         /// <summary>  
  65.         /// 接收消息  
  66.         /// </summary>  
  67.         /// <param name="clientSocket"></param>  
  68.         private static void ReceiveMessage(object clientSocket)
  69.         {
  70.             Socket myClientSocket = (Socket)clientSocket;
  71.             while (true)
  72.             {
  73.                 try
  74.                 {
  75.                     //通过clientSocket接收数据  
  76.                     int receiveNumber = myClientSocket.Receive(result);
  77.                     Debug.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber));
  78.                 }
  79.                 catch (Exception ex)
  80.                 {
  81.                     try
  82.                     {
  83.                         Debug.WriteLine(ex.Message);
  84.                         myClientSocket.Shutdown(SocketShutdown.Both);
  85.                         myClientSocket.Close();
  86.                         break;
  87.                     }
  88.                     catch (Exception)
  89.                     {
  90.                     }
  91.                 }
  92.             }
  93.         }
  94.     }
复制代码

Wpf 端我们搞定了,接下来我们来配置Unity端。
Unity 部分

1、新建一个工程,在场景中 新建一个Cube
在Cube上面 添加上如下脚本。
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Net.Sockets;
  4. using System;
  5. public class Demo : MonoBehaviour
  6. {
  7.     public GameObject man;
  8.     const int portNo = 500;
  9.     private TcpClient _client;
  10.     byte[] data;
  11.     string Error_Message;
  12.     void Start()
  13.     {
  14.         try
  15.         {
  16.             this._client = new TcpClient();
  17.             this._client.Connect("127.0.0.1", portNo);
  18.             data = new byte[this._client.ReceiveBufferSize];
  19.             //SendMessage(txtNick.Text);
  20.             SendMessage("Unity Demo Client is Ready!");
  21.             this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
  22.         }
  23.         catch (Exception ex)
  24.         {
  25.         }
  26.     }
  27.     public void translateX(float x)
  28.     {
  29.         transform.Translate(new Vector3(x, 0, 0));
  30.     }
  31.     public void translateY(float y)
  32.     {
  33.         transform.Translate(new Vector3(0, y, 0));
  34.     }
  35.     public void translateZ(float z)
  36.     {
  37.         transform.Translate(new Vector3(0, 0, z));
  38.     }
  39.     void OnGUI()
  40.     {
  41.         GUI.Label(new Rect(50, 50, 150, 50), Error_Message);
  42.     }
  43.     public new void SendMessage(string message)
  44.     {
  45.         try
  46.         {
  47.             NetworkStream ns = this._client.GetStream();
  48.             byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
  49.             ns.Write(data, 0, data.Length);
  50.             ns.Flush();
  51.         }
  52.         catch (Exception ex)
  53.         {
  54.             Error_Message = ex.Message;
  55.             //MessageBox.Show(ex.ToString());
  56.         }
  57.     }
  58.     public void ReceiveMessage(IAsyncResult ar)
  59.     {
  60.         try
  61.         {
  62.             //清空errormessage
  63.             Error_Message = "";
  64.             int bytesRead;
  65.             bytesRead = this._client.GetStream().EndRead(ar);
  66.             if (bytesRead < 1)
  67.             {
  68.                 return;
  69.             }
  70.             else
  71.             {
  72.                 Debug.Log(System.Text.Encoding.ASCII.GetString(data, 0, bytesRead));
  73.                 string message = System.Text.Encoding.ASCII.GetString(data, 0, bytesRead);
  74.                 switch (message)
  75.                 {
  76.                     case "1":
  77.                         translateX(1);
  78.                         break;
  79.                     case "2":
  80.                         translateX(-1);
  81.                         break;
  82.                     case "3":
  83.                         translateY(1);
  84.                         break;
  85.                     case "4":
  86.                         translateY(-1);
  87.                         break;
  88.                     case "5":
  89.                         translateZ(1);
  90.                         break;
  91.                     case "6":
  92.                         translateZ(-1);
  93.                         break;
  94.                     default:
  95.                         Error_Message = "unknown command";
  96.                         break;
  97.                 }
  98.             }
  99.             this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
  100.         }
  101.         catch (Exception ex)
  102.         {
  103.             Error_Message = ex.Message;
  104.         }
  105.     }
  106.     void OnDestroy()
  107.     {
  108.         this._client.Close();
  109.     }
  110. }
复制代码
2、导出工程
工程设置 如下: 其中Display ResolutionDialog  需要关闭,否则启动Unity导出exe文件 会有选择分辨率提示,导致wpf端 不显示。
导入  上面 新建Wpf项目中文件夹的 bin/Debug/UnityApp 文件夹下。
exe 文件名称需要与  wpf 端用户控件中 代码 保持一致。
如下图


至此,Unity 端设置完毕。
返回,WPF项目,点击运行,可得到下图效果:


参考文章:


git仓库地址:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-6-17 05:29 , Processed in 0.092376 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表