桌面WPF程序嵌入Unity3D引擎(standalone)並實現通訊。

把Unity3D嵌入winform或者wpf程序,過去大部分使用UnityWebPlayer插件來實現,這個插件其實就是網頁上播放unity頁遊的插件。

但是使用UnityWebPlayer嵌入桌面開發有各種問題,我認爲最大的問題是效率問題(加載緩慢),畢竟是網頁的加載方式,而且可以確認未來也不會得到任何優化。

由於WebGL的高速發展,unity公司認識到了webplayer十分雞肋,畢竟WebGL不需要任何插件可以直接顯示3d內容了,所以Unity3D在5.4.x版本以後明確表示,不再支持webplayer了,所以桌面上UnityWebPlayer插件能不用也就別用了吧。

將Unity嵌入桌面程序最好的方式是嵌入unity生成的exe程序,winform程序和unity之間通過socket進行通訊,我認爲

這也是效率最高,效果最好和最好實現的方式。在Unity程序腳本中,嵌入socket內容,我推薦做成客戶端(client),使用wpf程序做服務器端,這是一個誰是主體的問題。

這樣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對應,在程序啓動時先建立服務器端。


使用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();

        }

如何嵌入?

 

首先在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();
        }
    }  }




發佈了28 篇原創文章 · 獲贊 29 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章