出處:http://www.cnblogs.com/NuclearBoy/articles/6092221.htmlcss
寫在前面:html
另百度文庫裏有一篇好的文章:https://wenku.baidu.com/view/d8be1b183c1ec5da51e27007.html git
把Unity3D嵌入winform或者wpf程序,過去大部分使用UnityWebPlayer插件來實現,這個插件其實就是網頁上播放unity頁遊的插件。github
可是使用UnityWebPlayer嵌入桌面開發有各類問題,我認爲最大的問題是效率問題(加載緩慢),畢竟是網頁的加載方式,並且能夠確認將來也不會獲得任何優化。web
因爲WebGL的高速發展,unity公司認識到了webplayer十分雞肋,畢竟WebGL不須要任何插件能夠直接顯示3d內容了,因此Unity3D在5.4.x版本之後明確表示api
再也不支持webplayer了,因此桌面上UnityWebPlayer插件能不用也就別用了吧。因此你們不要走彎路!!服務器
主要內容:markdown
將Unity嵌入桌面程序最好的方式是嵌入unity生成的exe程序,winform程序和unity之間經過socket進行通信,我認爲app
這也是效率最高,效果最好和最好實現的方式。在Unity程序腳本中,嵌入socket內容,我推薦作成客戶端(client),使用wpf程序作服務器端,這是一個誰是主體的問題。socket
這樣wpf能夠加載多個unity程序。
嵌入後的結果以下圖所示(請無視具體內容):
下面簡單寫了一個腳本,其中man是遊戲中的一個gameobject對象。就是上圖中穿藍衣服的男人。在他身上掛着socket腳本以下:
using UnityEngine;
using System.Collections;
using System.Net.Sockets;
using System;
public class demoshows : MonoBehaviour {
public GameObject man;
const int portNo = 500;
private TcpClient _client;
byte[] data;
string Error_Message;
void Start () {
try
{
this._client = new TcpClient();
this._client.Connect("127.0.0.1", portNo);
data = new byte[this._client.ReceiveBufferSize];
//SendMessage(txtNick.Text);
SendMessage("Unity Demo Client is Ready!");
this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
}
catch (Exception ex)
{
}
}
void Update () {
transform.Rotate(new Vector3(0, 1, 0),0.1f);
}
public void rotation()
{
transform.Rotate(new Vector3(0, 10, 0));
//targetRotation = Quaternion.Euler(45.0f, 45.0f, 45.0f);
//// 直接設置旋轉角度
//transform.rotation = targetRotation;
////man.transform.rotation.SetAxisAngle(new Vector3(0, 1, 0), 30);; }
public void translateX(float x){
transform.Translate(new Vector3(x,0,0));
}
public void translateY(float y){
transform.Translate(new Vector3(0, y, 0));
}
public void translateZ(float z){
transform.Translate(new Vector3(0, 0, z)); }
void OnGUI()
{
GUI.Label(new Rect(50, 50, 150,50 ), Error_Message);
}
public new void SendMessage(string message)
{
try
{
NetworkStream ns = this._client.GetStream();
byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
ns.Write(data, 0, data.Length);
ns.Flush();
}
catch (Exception ex)
{
Error_Message = ex.Message;
//MessageBox.Show(ex.ToString());
}
}
public void ReceiveMessage(IAsyncResult ar)
{
try
{
//清空errormessage
Error_Message = "";
int bytesRead;
bytesRead = this._client.GetStream().EndRead(ar);
if (bytesRead < 1)
{
return;
}
else
{
Debug.Log(System.Text.Encoding.ASCII.GetString(data, 0, bytesRead));
string message = System.Text.Encoding.ASCII.GetString(data, 0, bytesRead);
switch (message)
{
case "1":
translateX(1);
break;
case "2":
translateX(-1);
break;
case "3":
translateY(1);
break;
case "4":
translateY(-1);
break;
case "5":
translateZ(1);
break;
case "6":
translateZ(-1);
break;
default:
Error_Message = "unknown command";
break;
}
}
this._client.GetStream().BeginRead(data, 0, System.Convert.ToInt32(this._client.ReceiveBufferSize), ReceiveMessage, null);
}
catch (Exception ex)
{
Error_Message = ex.Message;
}
}
void OnDestroy()
{
this._client.Close();
}
}
腳本很簡單,就是經過向unity程序發送消息(1~6)實現模型的平移。
服務器端,在wpf程序中簡單創建一個socket類,ip和端口要和unity對應,在程序啓動時先創建服務器端。
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.Text;
using System;
namespace Demo_Song
{
class TcpServer
{
//私有成員
private static byte[] result = new byte[1024];
private int myProt = 500; //端口
static Socket serverSocket;
static Socket clientSocket;
Thread myThread;
static Thread receiveThread;
//屬性
public int port { get; set; }
//方法
internal void StartServer()
{
//服務器IP地址
IPAddress ip = IPAddress.Parse("127.0.0.1");
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(ip, myProt)); //綁定IP地址:端口
serverSocket.Listen(10); //設定最多10個排隊鏈接請求
Debug.WriteLine("啓動監聽{0}成功", serverSocket.LocalEndPoint.ToString());
//經過Clientsoket發送數據
myThread = new Thread(ListenClientConnect);
myThread.Start();
}
internal void QuitServer()
{
serverSocket.Close();
clientSocket.Close();
myThread.Abort();
receiveThread.Abort();
}
internal void SendMessage(string msg)
{
clientSocket.Send(Encoding.ASCII.GetBytes(msg));
}
/// <summary>
/// 監聽客戶端鏈接
/// </summary>
private static void ListenClientConnect()
{
while (true)
{
try
{
clientSocket = serverSocket.Accept();
clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));
receiveThread = new Thread(ReceiveMessage);
receiveThread.Start(clientSocket);
}
catch (Exception)
{
}
}
}
/// <summary>
/// 接收消息
/// </summary>
/// <param name="clientSocket"></param>
private static void ReceiveMessage(object clientSocket)
{
Socket myClientSocket = (Socket)clientSocket;
while (true)
{
try
{
//經過clientSocket接收數據
int receiveNumber = myClientSocket.Receive(result);
Debug.WriteLine("接收客戶端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber));
}
catch (Exception ex)
{
try
{
Debug.WriteLine(ex.Message);
myClientSocket.Shutdown(SocketShutdown.Both);
myClientSocket.Close();
break;
}
catch (Exception)
{
}
}
}
}
}
}
使用socket必定要注意使用線程,而且退出時及時結束,我這裏實現的也不是很好,誰有更好的方法能夠告訴我。
下面大概就是server的啓動和釋放,效果還好,至少不卡死....
TcpServer WpfServer;
int size_state = 0;
public MainWindow()
{
InitializeComponent();
this.Closed += MainWindow_Closed;
this.Activated += MainWindow_Activated;
this.Deactivated += MainWindow_Deactivated;
WpfServer = new TcpServer();
WpfServer.StartServer();
}
void MainWindow_Closed(object sender, EventArgs e)
{
unityhost.Form1_FormClosed();
WpfServer.QuitServer();
}
關於socket通信的問題大概就這樣,你們估計更關係如何嵌入的問題
如何嵌入?
首先在wpf程序中創建一個winform的自定義控件(不是wpf控件)usercontrol
在usercontrol內新建一個panel(或者其餘帶有句柄的控件),並設置dock屬性爲fill。
啓動unity.exe,經過幾個api將unity窗口附加在panel句柄上。
(說明:借鑑別人的程序)
須要把unity程序命名爲child.exe,放在下面指定位置(Debug\UnityApp\Child.exe,或者release)
process.StartInfo.FileName =Application.StartupPath +@"\UnityApp\Child.exe";
詳細代碼以下:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
namespace Demo_Song
{
public partial class UnityControl : UserControl
{
[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 UnityControl()
{
InitializeComponent();
this.Load += UnityControl_Load;
panel1.Resize+=panel1_Resize;
}
private void UnityControl_Load(object sender, EventArgs e)
{
try
{
process = new Process();
process.StartInfo.FileName =Application.StartupPath +@"\UnityApp\Child.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)
{
unityHWNDLabel.Text = ex.Message;
//MessageBox.Show(ex.Message);
}
}
internal void ActivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
internal 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
internal void Form1_FormClosed()
{
try
{
process.CloseMainWindow();
Thread.Sleep(1000);
while (process.HasExited == false)
process.Kill();
}
catch (Exception)
{
}
}
internal void Form1_Activated()
{
ActivateUnityWindow();
}
internal void Form1_Deactivate()
{
DeactivateUnityWindow();
}
}
}
最後附上源碼,github重置密碼死活連不上,如今就傳壓縮包到百度雲把,vs版本較高(2012)以上能夠打開,注意,低版本打不開工程。
博客地址遷移了,請到 這裏
http://www.songshizhao.com/blog/blogPage/78.html
下載。
貌似寫得有點長了,能看到這裏的人很少吧哈哈,但願有作相似需求的人少走彎路吧,碎覺去,over~。