1. 程式人生 > >基於Unity的編輯器開發(二): 程序間通訊

基於Unity的編輯器開發(二): 程序間通訊

共享程式碼

首先要做的, 是需要編輯器和Unity共享一部部分程式碼, 至少協議定義和解析我不想寫兩遍. 雖然有protobuf這樣的工具庫, 但是如果不是跨語言的話, 我覺得沒必要引入另一套流程. 所以我就想能不能讓一個C# dll庫可以同時被Winforms的編輯器和Unity指令碼引用呢? 考查了一下還是可以的:

  • Unity的.Net預設是Subset, 需要改成全的
  • Unity的.Net預設是C#3.5版本相容的, 一些新的語法(如async)不支援
  • Unity中如果要引用Visual Studio編譯出來的C# dll, 需要把Target framework改成”Unity 3.5 .net full Base Class Libraries”
  • 把VS編譯出來的dll, 拷到Unity的Asset目錄, mono腳本里就可以直接引用了

同時, 編輯器這邊也需要知道一些遊戲的資料型別和介面的定義, 實驗了一下, UnityEngine.dll, Assembly-CSharp.dll, Assembly-CSharp-firstpass.dll可以直接被VS這邊的C#工程引用, 只要不執行Unity特有的方法(會報”ECall 方法必須打包到系統模組中”的異常), 就可以在Winforms工程中安全地複用遊戲指令碼中的程式碼了.

經過這樣的設定, 編輯器和Unity遊戲可以共享一個dll庫, 從程式碼上做到了共享, 這就為我們的程式碼複用和通訊協議定義提供了基礎保障.

程序間通訊

為了達成這一通訊需求, 首先做了一些搜尋:

  • Unity這邊會報異常, Unity的mono對NamePipe支援不是很好
  • 有時候會連不上, 比如管道被佔用

所以又換了一個不依賴mono那個不靠譜.net framework的方案, 搜了搜看起來NNanomsg不錯, 使用起來夠簡單, 不過也遇到一些問題:

  • 使用ipc協議也會出現莫名其妙連不上的問題, 本質上底層還是走的NamePipe, 換成tcp協議就好了
  • 錯誤資訊不夠直觀, 所以我又在NNanomsg里加了nanomsg的一些除錯用的函式的介面
  • 一次性發送大量資料(比如幾MB), 會導致連結斷開或卡死, 問了作者說實現機制的問題, 讓我嘗試
    nanomsg next gen
    , 不過這個問題暫時可以繞過

用nanomsg的好處就是連線的建立/傳送/接收等不用自己操心, 可以直接連線UnityEditor進行雙端開發, 對於除錯修改非常方便:
這裡寫圖片描述

通訊協議

class IntMessage : Message
{
    public int Value = 100;
}
class StringMessage : Message
{
    public string Value = "a string";
}
static void Main(string[] args)
{
    MessageHandler.Subscribe((StringMessage m) => Console.WriteLine("String : " + m.Value));
    MessageHandler.Subscribe((StringMessage m) => Console.WriteLine("2nd String : " + m.Value));
    MessageHandler.Subscribe((IntMessage m) => Console.WriteLine("Int : " + m.Value));
    MessageHandler.Subscribe((IntMessage m) => Console.WriteLine("2nd Int : " + m.Value));
    MessageHandler.Publish(new IntMessage());
    MessageHandler.Publish(new StringMessage());
}

Message直接序列化後就可以傳送到另一端程序了. 不過這樣還是有點繁瑣, 所以我照著Unity的RPC山寨了個LPC:
這裡寫圖片描述
這裡寫圖片描述
本質上的實現也是把MethodCall給序列化了, 走的還是SendMessage的流程:

        public static void SendMessage(Message message)
        {
            var stream = new MemoryStream();
            binaryFormatter.Serialize(stream, message);
            if (!IPC.SendImmediate(stream.GetBuffer()))
            {
                string log = String.Format("{0} : {1}", message.ToString(), NN.StrError(NN.Errno()));
                LogCore(LogType.Error, log);
            }
        }

        public static void ProcedureCall(string className, string methodName, params object[] args)
        {
            SendMessage(new ProcedureCallMessage { ClassName = className, MethodName = methodName, Arguments = args });
        }

收到訊息後通過預先註冊好的MethodInfo直接Invoke呼叫執行就可以了.

其它

還有更高階的需求, 那就是跨程序的物件屬性編輯. 目前的思路是這樣的:

  • Unity這邊的資料物件序列化, 傳送到編輯器
  • 編輯器收到資料, 反序列化出資料物件(不能依賴Unity的方法, 否則會拋異常)
  • 編輯器修改後的物件序列化後傳送到Unity
  • Unity這邊反序列化出修改後的物件, 把屬性值拷貝到當前編輯物件上去

這裡寫圖片描述
雖然簡單暴力, 但也是行之有效的做法, IPC也不用過多考慮資料流量的問題, 當然比較極致一點是實現一套像WPF那樣的DataBinding, 針對每個變化的屬性做程序間同步, 有時間可以嘗試下.