将Unity3D应用程序嵌入WPF应用程序

31
我想开发一个基于 WPF 的 CAD 软件,但不想使用 WPF 3D,是否可以使用 Unity3D 作为图形引擎,根据我的 WPF 数据对象旋转、平移、缩放和查看 3D 图形对象?
我提出这个问题的原因是,Unity 是一个游戏引擎,它使用 C# 作为脚本语言,但它并没有提供将 Unity 嵌入到 WPF 应用程序中的任何集成方式。
我在 Unity 论坛上提出了这个问题,但没有找到什么好的答案,所以现在向更广大的观众求助。

是的,使用Unity可以实现。但说真的,任何3D游戏引擎都可以进行平移、旋转和缩放,所以我不知道这个问题的意义所在。 - Programmer
1
你可以使用TCP或Pipe在两者之间进行通信,而这篇文章描述了如何将Unity应用程序嵌入到WPF中。 - Programmer
非常感谢您提供的链接。我的需求几乎相同。我的额外需求是根据嵌入式Unity3d窗口中的操作添加对象。根据您提供的链接,我认为这是一个难以实现的任务。请将您的最后一条评论发布为答案,那我可以将其设为正确答案。 - OrionSoftTechnologiesSydney
1
我会先检查 Eyeshot。它是专为 CAD 应用程序设计的 WPF 控件。 - abenci
1
Unity3D并不完全免费,更多信息请参见:http://answers.unity3d.com/questions/7720/is-unity-really-free-.html - abenci
显示剩余3条评论
1个回答

44
这是可以实现的,但值得注意的是它只能在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或命名管道进行通信。
以下是Unity网站中嵌入代码的完整示例。你可以从这里获取整个项目。确保将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();
        }
    }
}

1
我解决了添加过程。StartInfo.WindowStyle = ProcessWindowStyle.Maximized; - Andrea Perissinotto
太好了。我认为这个评论会帮助那些遇到类似问题的人。 - Programmer
1
非常棒!你的回答节省了成千上万人类的时间!非常有用。 - Bardia
3
非常感谢。不过这是给一个Windows Forms项目的内容。如果要在WPF中使用,请添加: xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"<WindowsFormsHost Grid.Row="1"> <wf:Panel x:Name="panel1" Resize="panel1_Resize" Height="500" Width="500"/> </WindowsFormsHost> C#代码几乎相同。请在Window_Loaded中执行 "attachUnity();",而不是在构造函数中执行,因为此时Hwnd还没有准备好。 - Flemming Bonde Kentved
1
然后它能够工作,但是如果我离开窗口并回来,键盘输入将被禁用。由于某种原因,ActivateUnityWindow()没有正常工作。我尝试让它在激活之前休眠,但没有帮助。相反,我创建了一个按钮并将ActivateUnityWindow()放在单击事件中。由于某种原因,这起作用了。为了自动化它,我添加了 var peer = new ButtonAutomationPeer(btn_Test); IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider; invokeProv.Invoke(); 来模拟按钮点击。您可以隐藏按钮,它仍然可以工作。 - Flemming Bonde Kentved
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接