需求是这样的 —— 一个编辑器。既能够方便得编辑各种静态数据表(Excel),又能够对表中指定的资源进行预览(Spine骨骼动画)。
问题在于适合做表编辑器的软件框架,如WPF、Winform等等,都没有相应的Spine渲染库;而支持Spine渲染的框架,如、MonoGame、Cocos2D等,又或存在Excel库不好用或者缺少软件向控件的问题。
我们采取的方案是,使用WPF做它擅长的软件功能,而使用Unity作为“渲染控件”。
为什么不用WPF嵌入?
将Unity嵌入WPF(或Winform),比较常见的做法是通过ActiveX将UnityWebPlayer包装成一个真正的控件。但由于UnityWebPlayer运行在安全沙箱中,它至少有以下两个问题:
如何实现Standalone与WPF双向通信
需要能在WPF中设定Standalone的当前Spine,也要能在Standalone上操作由WPF维护的Excel。一个可行的方案是pipes。
这篇讨论中说Unity不初始化NamedPipelineServer。那么就拿它当NamedPipelineClient好了。
核心代码如下:
// 定义一个WPF用户控件,来定义Standalone的尺寸
ProcessStartInfo info = new ProcessStartInfo(“UnityControl.exe”);
info.UseShellExecute = true;
info.WindowStyle = ProcessWindowStyle.Minimized;
m_AppProcess = System.Diagnostics.Process.Start(info);
m_AppProcess.WaitForInputIdle();
this.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.ApplicationIdle,
appIdleEvent, this, null);
// Application Idle 事件处理
WindowInteropHelper helper = new WindowInteropHelper(Window.GetWindow(this));
IntPtr ptr = helper.Handle;
SetParent(app.MainWindowHandle, helper.Handle);
SetWindowLong(new HandleRef(this, app.MainWindowHandle), GWL_STYLE, WS_VISIBLE);
MoveWindow(app.MainWindowHandle, (int)control.Margin.Left, (int)control.Margin.Top, (int)control.Width, (int)control.Height, true);
// WPF
NamedPipeServerStream pipeServer = new NamedPipeServerStream(
"testpipe",
PipeDirection.InOut,
1);
pipeServer.WaitForConnection();
string str = "hello from server!";
byte[] outBuffer = Encoding.Unicode.GetBytes(str);
int len = outBuffer.Length;
pipeServer.WriteByte((byte)(len / 256));
pipeServer.WriteByte((byte)(len % 256));
pipeServer.Write(outBuffer, 0, len);
pipeServer.Flush();
len = pipeServer.ReadByte() * 256;
len += pipeServer.ReadByte();
byte[] inBuffer = new byte[len];
pipeServer.Read(inBuffer, 0, len);
string remoteInfo = Encoding.Unicode.GetString(inBuffer);
lbRemoteInfo.COntent= remoteInfo;
// Unity
pipeClient = new NamedPipeClientStream(
".", "testpipe",
PipeDirection.InOut);
pipeClient.Connect(10000);
int len = pipeClient.ReadByte() * 256;
len += pipeClient.ReadByte();
byte[] inBuffer = new byte[len];
pipeClient.Read(inBuffer, 0, len);
remoteInfo = Encoding.Unicode.GetString(inBuffer);
pipeClient.Flush();
string str = "hello from client!";
byte[] outBuffer = Encoding.Unicode.GetBytes(str);
len = outBuffer.Length;
pipeClient.WriteByte((byte)(len / 256));
pipeClient.WriteByte((byte)(len % 256));
pipeClient.Write(outBuffer, 0, len);
pipeClient.Flush();
做了一个简单的测试,看上去效果不错:
MSDN管道通讯Demo
如何:使用命名管道进行网络进程间通信
Mono不支持管道安全级别
不用就可以了,NamedPipeServerStream构造函数里,不要填TokenImpersonationLevel
StackOverflow
Unity无法启动NamedPipelineServer
StackOverflow
在Unity中启用System.IO.Pipes命名空间
在Player Settings中,将.net版本设置为.net2.0,默认是.net2.0 subset
StackOverflow
如何将窗体嵌入为控件
C#自定义控件:WinForm将其它应用程序窗体嵌入自己内部