Embed Unity3D app inside WPF application
Asked Answered
D

1

32

I want to develop a new CAD software in WPF and instead of using WPF 3D, is it possible to use Unity3D as my graphic engine that is capable of rotate, pan, zoom and view 3D graphic objects based on my data objects in WPF?

The reason I am asking this question is, Unity is a game engine, it uses C# as script but it does not provide any integration from WPF application (embeds Unity into WPF).

I asked the question in unity forum, could not find any good answer, so asking to a larger audience.

Durnan answered 18/5, 2017 at 23:18 Comment(8)
Yes, it is possible to do with Unity. Seriously though, you can pan,rotate and zoom with any 3D Game Engine so I can't tell the point of this question.Raffo
Thanks very much. I have modified my question, please have a look. Have you ever tried using WPF as your main application and call Unity3D to show the graphics display?Durnan
You can use TCP or Pipe to communicate between the two and this post describes how to embed Unity App into WPF.Raffo
Thanks very much for the link. My requirement is almost same. My additional requirement is add objects as per the actions in embedded Unity3d Window. As per your link, I assume it is a hard task to achieve this. Please post your last comment as answer then I can make it the correct answer.Durnan
I would check Eyeshot first. It's a WPF control designed for CAD applications.Tourneur
@Tourneur I am %100 sure that is not free.Raffo
Unity3D is not exactly free, more here: https://mcmap.net/q/8002/unity-is-unity-really-freeTourneur
Check MonoGame, it should be a bit easier to add to WPF, see MonoGame.Framework.WpfInteropSwagger
R
46

This can be done but it's worth noting that it will only work on Windows.

It used to be hard to do this but Unity recently(4.5.5p1) added -parentHWND command that can be used to embed its program into another program. All you have to do is build your Unity app, then from WPF, start it with the Process API. You can then pass the -parentHWND parameter to the Unity app.

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

For commutation between the two, you can either use TCP or Named Pipes.

Below is a complete sample of the embed code from Unity's website. You can get the whole project here. Make sure to name the Unity's build exe file "UnityGame.exe" then place it in the-same directory as the WPF exe program.

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();
        }
    }
}
Raffo answered 19/5, 2017 at 0:34 Comment(7)
Thanks for this solution. Looks like that this work with the example you provided, but with others it doesn't work. What could be the problem? Could it be the loading time of the unity project? What I noticed it's that the unity project is loaded but it is not shown in the Form. ThanksUnpolled
@AndreaPerissinotto Really don't know. File for a bug report with your project through the Editor. Maybe Unity can figure out why it's not working. Just make sure to explain that it works with this example.Raffo
I solved adding process.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;Unpolled
Great. I think this comment will help those who will into such issue.Raffo
Very Great! Your answer saved thousand of humankind life hours! very usefullAnthelion
Thank you very much. This is for a Windows Forms project though. To make it work in WPF use 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> The c# is nearly the same. Do "attachUnity();" in Window_Loaded instead of the constructor because Hwnd isn't ready there yet.Tessin
Then it worked but if I left focus from the window and came back, keyboard input would be disabled. For some reason the ActivateUnityWindow(); didn't work properly. I tried to make it sleep before activating, but didn't help. Instead I created a button and put the ActivateUnityWindow(); inside the click event. That worked for some reason. To automate it I added var peer = new ButtonAutomationPeer(btn_Test); IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider; invokeProv.Invoke(); to simulate a button click. You can hide the button, it will still workTessin

© 2022 - 2024 — McMap. All rights reserved.