UnityってWPFの中に表示したりできひんよね?って聞かれたからドキュメント読んだらできるって書いてたからやってみたらできました。という話。
TL;DR
-parentHWND 0x****
で子ウィンドウにできる- HwndHostでUnityのプロセスを子ウィンドウとして起動するだけ
Unityを子ウィンドウで起動する
Windows向けにビルドしたUnityはコマンドライン引数で -parentHWND
を付けて起動するとUnity Playerは子ウィンドウとして起動します。その時の画面幅などはSTARTUPINFO
で指定されたものを使用します。と、Unityのドキュメントに書いてあります。
Windows スタンドアロンアプリケーションを別のアプリケーションに埋め込みます。これを使用する場合は、親アプリケーションのウィンドウハンドル (HWND) を Windows スタンドアロンアプリケーションに渡す必要があります。
これ、日本語だと特に書いてないんですけど、0xXXXXの形で16進数として渡すと成功します。 追記: 0xなしで10進でもOKでした
簡単にC#から起動するにはこんな感じ。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { StartUnity(); } void StartUnity() { var apppath = @"C:\doko\soko\unity-app.exe"; var cmdline = $"-parentHWND {new WindowInteropHelper(this).Handle}"; Process.Start(apppath, cmdline); } }
実行すると、UnityがWPFの中で動いているような感じになりました。が、リサイズしてもウィンドウサイズが追従しないのでいまいちです。
HwndHostをそれとなく実装していく
HwndHostを実装すれば子ウィンドウのサイズをWPFが面倒見てくれるようになります。Unityを子ウィンドウとして起動してくれるHwndHostを実装したクラスを書いてリサイズをいい感じに面倒見てもらいます。
class UnityHost : HwndHost { private Process _childProcess; private HandleRef _childHandleRef; public string AppPath { get; set; } public HandleRef Child => _childHandleRef; protected override HandleRef BuildWindowCore(HandleRef hwndParent) { var cmdline = $"-parentHWND {hwndParent.Handle}"; _childProcess = Process.Start(AppPath, cmdline); while (true) { var hwndChild = User32.FindWindowEx(hwndParent.Handle, IntPtr.Zero, null, null); if (hwndChild != IntPtr.Zero) { return (_childHandleRef = new HandleRef(this, hwndChild)); } Thread.Sleep(100); } } protected override void DestroyWindowCore(HandleRef hwnd) { _childProcess.Dispose(); } } static class User32 { [DllImport("user32.dll")] public static extern IntPtr FindWindowEx(IntPtr hParent, IntPtr hChildAfter, string pClassName, string pWindowName); }
急にコードが増えた気がしますが気のせいです。BuildWindowCore
の中でUnityを起動して、できたウィンドウのハンドルを返してるだけです。
このままだと、起動するパスがどこか明示されていないのでXAMLから指定します。
<Window x:Class="WpfApp4.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp4" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <local:UnityHost AppPath="C:\doko\soko\unity-app.exe" /> </Grid> </Window>
これでリサイズできるようになりました。
起動時にWPFのクライアントエリア全体にUnityが出ないようにする
ここまでのコードだと、XAMLでUnityHostをGridにいれてサイズを変えたりしていても、Unityを起動したときにクライアントエリア全体にUnityが一瞬表示されます。
これを回避するにはUnityのドキュメントに書いてあったようにSTARTUPINFOを指定すればいいんですが、残念ながらそれはP/Invokeです。ということでP/Invoke使っていろいろ実装したものがこちらです。
P/Invokeのせいでやたらと長くなった…やな感じ… 実際の処理はBuildWindowCoreを参照してください。STASRTUPINFO構造体を埋めて、CreateProcessを呼び出しているだけです。