30

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.

Programmer
  • 121,791
  • 22
  • 236
  • 328
  • 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. – Programmer May 18 '17 at 23:25
  • 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? – OrionSoftTechnologiesSydney May 18 '17 at 23:56
  • 1
    You can use TCP or [Pipe](http://stackoverflow.com/questions/43062782/send-message-from-an-exe-to-another-exe-in-unity/43064120#43064120) to communicate between the two and [this](https://forum.unity3d.com/threads/how-to-integrate-unity3d-into-c-wpf-application.332276/) post describes how to embed Unity App into WPF. – Programmer May 19 '17 at 00:07
  • 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. – OrionSoftTechnologiesSydney May 19 '17 at 00:20
  • 1
    I would check [Eyeshot](http://www.devdept.com) first. It's a WPF control designed for CAD applications. – abenci May 19 '17 at 08:15
  • @abenci I am %100 sure that is not free. – Programmer May 19 '17 at 13:34
  • 1
    Unity3D is not exactly free, more here: http://answers.unity3d.com/questions/7720/is-unity-really-free-.html – abenci May 19 '17 at 15:00
  • Check [`MonoGame`](http://www.monogame.net/), it should be a bit easier to add to WPF, see [`MonoGame.Framework.WpfInterop`](https://gitlab.com/MarcStan/MonoGame.Framework.WpfInterop) – xmedeko Sep 25 '17 at 17:59

1 Answers1

44

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();
        }
    }
}
Community
  • 1
  • 1
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • 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. Thanks – Andrea Perissinotto Jul 02 '18 at 15:32
  • @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. – Programmer Jul 02 '18 at 15:36
  • 1
    I solved adding process.StartInfo.WindowStyle = ProcessWindowStyle.Maximized; – Andrea Perissinotto Jul 03 '18 at 10:01
  • Great. I think this comment will help those who will into such issue. – Programmer Jul 03 '18 at 13:51
  • 1
    Very Great! Your answer saved thousand of humankind life hours! very usefull – Bardia Feb 18 '19 at 09:01
  • 3
    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" The c# is nearly the same. Do "attachUnity();" in Window_Loaded instead of the constructor because Hwnd isn't ready there yet. – Flemming Bonde Kentved Jun 05 '20 at 11:46
  • 1
    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 work – Flemming Bonde Kentved Jun 05 '20 at 11:52