在 WPF 应用程序中嵌入 Unity3D 应用程序

Posted

技术标签:

【中文标题】在 WPF 应用程序中嵌入 Unity3D 应用程序【英文标题】:Embed Unity3D app inside WPF application 【发布时间】:2017-05-18 23:18:08 【问题描述】:

我想在 WPF 中开发一个新的 CAD 软件,而不是使用 WPF 3D,是否可以使用 Unity3D 作为我的图形引擎,它能够根据我的数据对象旋转、平移、缩放和查看 3D 图形对象WPF?

我问这个问题的原因是,Unity 是一个游戏引擎,它使用 C# 作为脚本,但它不提供来自 WPF 应用程序的任何集成(将 Unity 嵌入到 WPF 中)。

我在统一论坛上问过这个问题,找不到任何好的答案,所以问更多的观众。

【问题讨论】:

是的,可以使用 Unity。不过说真的,您可以使用任何 3D 游戏引擎进行平移、旋转和缩放,所以我无法说出这个问题的意义。 非常感谢。我修改了我的问题,请看一下。您是否尝试过使用 WPF 作为您的主应用程序并调用 Unity3D 来显示图形显示? 您可以使用 TCP 或 Pipe 在两者之间进行通信,this 的帖子描述了如何将 Unity App 嵌入 WPF。 我会先检查Eyeshot。这是一个专为 CAD 应用程序设计的 WPF 控件。 Unity3D 并不是完全免费的,更多内容在这里:answers.unity3d.com/questions/7720/is-unity-really-free-.html 【参考方案1】:

这是可以做到的,但值得注意的是它只适用于 Windows。

以前很难做到这一点,但 Unity 最近(4.5.5p1)添加了-parentHWND 命令,可用于将其程序嵌入到另一个程序中。您所要做的就是构建您的 Unity 应用程序,然后从 WPF 中使用 Process API 启动它。然后,您可以将 -parentHWND 参数传递给 Unity 应用程序。

process.StartInfo.FileName = "YourUnityApp.exe";
process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;

对于两者之间的通信,您可以使用 TCP 或Named Pipes。

以下是来自 Unity 网站的嵌入代码的完整示例。你可以得到整个项目here。确保将 Unity 的构建 exe 文件命名为 “UnityGame.exe”,然后将其放在与 WPF exe 程序相同的目录中。

namespace Container

    public partial class Form1 : Form
    
        [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 Form1()
        
            InitializeComponent();

            try
            
                process = new Process();
                process.StartInfo.FileName = "UnityGame.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)
            
                MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
            

        

        private void ActivateUnityWindow()
        
            SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
        

        private 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
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        
            try
            
                process.CloseMainWindow();

                Thread.Sleep(1000);
                while (!process.HasExited)
                    process.Kill();
            
            catch (Exception)
            

            
        

        private void Form1_Activated(object sender, EventArgs e)
        
            ActivateUnityWindow();
        

        private void Form1_Deactivate(object sender, EventArgs e)
        
            DeactivateUnityWindow();
        
    

【讨论】:

感谢您的解决方案。看起来这适用于您提供的示例,但对于其他示例则不起作用。可能是什么问题呢?会不会是unity项目的加载时间?我注意到的是统一项目已加载,但未显示在表单中。谢谢 我解决了添加 process.StartInfo.WindowStyle = ProcessWindowStyle.Maximized; 非常棒!您的回答拯救了人类数千小时的生命!很有用 非常感谢。不过,这是针对 Windows 窗体项目的。要使其在 WPF 中工作,请使用 xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms" c# 几乎相同。执行“attachUnity();”在 Window_Loaded 而不是构造函数中,因为 Hwnd 还没有准备好。 然后它起作用了,但是如果我从窗口离开焦点并返回,键盘输入将被禁用。出于某种原因,ActivateUnityWindow();没有正常工作。我试图在激活之前让它休眠,但没有帮助。相反,我创建了一个按钮并将 ActivateUnityWindow();在点击事件中。这出于某种原因。为了自动化它,我添加了 var peer = new ButtonAutomationPeer(btn_Test); IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;调用Prov.Invoke();模拟按钮点击。你可以隐藏按钮,它仍然可以工作

以上是关于在 WPF 应用程序中嵌入 Unity3D 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

将一个 Unity .exe 文件嵌入到多个 WPF 页面中

Unity3D嵌入WPF教程

WPF嵌入Unity3D之后,unity3D程序的键盘和鼠标事件无法触发(3D程序的焦点无法激活)的解决方案

unity3d 能否通过控件嵌入其他软件中

将 Windows 窗体嵌入到 WPF 应用程序中

WPF怎么能跨进程通信