Unity - Photon PUN 本地與網絡同步的邏輯分離 (一)


服務器大家可以使用Photon官網提供的,這樣會變得很簡單,直接搭建下就好。或者下載到本地開啟本地端Photon服務器

(大家也可以使用和我一樣方式有時間做了個winform 程序用來管理本地服務器開啟關閉等,不論用哪種方式下面要說的都是通用的)

在unity中我們使用 Photon Unity Networking Classic 這個官方免費的插件,地址 https://assetstore.unity.com/packages/tools/network/photon-unity-networking-classic-free-1786

 

下面我們先來簡單看下 PUN 的同步是怎么使用的

void PhotonView.RPC (string methodName, PhotonTargets target, params object[] parameters)

從上面方法看 我們需要使用掛載了PhotonView的對象去調用 PRC 方法

下面看下參數的具體意思:

string methodName

要同步調用的方法名 該方法名必須加上 [PunRPC] 特性

PhotonTargets target  

PhotonTargets 是個枚舉 表示消息以那種方式發送

public enum PhotonTargets
{
發送rpc消息給所有人包括自己
All,
向其他人發送rpc消息
Others,
僅向MasterClient發送rpc消息
MasterClient,
發送rpc消息給所有人包括自己,當新玩家加入會得到這個rpc緩沖
AllBuffered,
向其他人發送rpc消息,當新玩家加入會得到這個rpc緩沖
OthersBuffered,

一下兩個都是由服務器發送的方式
AllViaServer,
AllBufferedViaServer
}

 

 

params object[] parameters

可變參數,用來寫參數的,也就是methodName 方法的參數,如果沒有就不寫

下面舉例:

1:
public
void WelcomeNewPlayer(string welcomeData) { GetComponent<PhotonView>().RPC("ConsoleNewPlayer", PhotonTargets.AllBuffered, welcomeData); } [PunRPC] public void ConsoleNewPlayer(string strData) { //TODO:邏輯 }

2:
public void WelcomeNewPlayer()
        {
            GetComponent<PhotonView>().RPC("ConsoleNewPlayer", PhotonTargets.AllBuffered); 
        }

        [PunRPC]
        public void ConsoleNewPlayer()
        {
           //TODO:邏輯
        }


 

 

總結:發送同步 rpc消息 要

1.進行發送的對象(使用Rpc這個方法的對象)需要掛在PhotonView 組件

2.要同步的方法 需要加上 [PunRPC] 特性

當然參數也只能發送一些基本參數,字典一類的就需要序列化了

由此我們可以想象 每個同步方法都要加上PunRPC 不僅如此由於同步方法必須還要由 掛載PhotonView 的對象來調用才行,這樣做的話就要為每一個同步方法 再寫個發送的方法,這樣算下來着實麻煩,且容易混亂。

下面我們就來分析一下:

”使用掛載PhotonView 的對象發送一個帶有 [PunRPC] 標識的方法可以實現同步“

以此思考我們就得出了結論 :

 使用一個專門負責發送rpc消息的單利對象用來作為消息傳輸的中介者。

但問題還是有的,使用其他還是麻煩,其實我們要達到的目標是這樣的:

1.使用一個通用的靜態類來調用(比單利使用起來方便)。

2. 我們不想寫 [PunRPC]這個特性。

3. 為什么還要單獨為一個同步方法寫個調用它的方法,不能接受。

 

下面我們就對這3點要求進行實現:

為了不到處添加 [PunRPC] 這個特性就考慮使用 Delegate代替所有要同步的方法,將delegate放入字典中,通過傳key與參數 實現跳過[PunRPC],當然實際還是通過punRPC調用的只不過我們開發中調用的僅僅只是同一個方法,簡單說就是通過一個帶有[PunPRC]的方法

來調用所有需要同步的方法。下面我們就開始了:

既然要使用Delegate綁定所有同步方法那就自然的想到了使用事件系統的方式來做,下面完整介紹下這個簡單事件系統,下一篇說下事件系統與rpc同步的結合使用來跳過一系列麻煩的操作實現本地與同步邏輯分離。

事件系統通過字符串或枚舉作為key,來調用value中保存的所有方法

1.寫個枚舉

 public enum CommandType { Test, }

 

2.由於我們是想把所有同步方法都儲存起來因此需要寫個泛型的delegate來綁定不同類型的方法

    public delegate void CallFunction(); public delegate void CallFunction<T>(T arg); public delegate void CallFunction<T, U>(T arg1, U arg2); public delegate void CallFunction<T, U, O>(T arg1, U arg2, O arg3); public delegate void CallFunction<T, U, O, P>(T arg1, U arg2, O arg3, P arg4);

 

3.下面要做的就是要寫個用來綁定和調用的類,類名: GameEnvent

        private static Dictionary<CommandType, Delegate> EvnDic = new Dictionary<CommandType, Delegate>(); //保存所有函數方法的字典

        public static List<CommandType> CommandTypeList = new List<CommandType>(); //注冊監聽____________________________________
        public static void Listen(CommandType command, CallFunction call)     //通過傳遞參數枚舉 和方法 進行綁定到EvnDic字典中
 { if (!CommandTypeList.Contains(command)) //如果不包含就添加進去
 { CommandTypeList.Add(command); EvnDic.Add(command, call); } else                                   //如果包含1.判斷是否是null 2.不是null則進行綁定(+=)
 { if (EvnDic[command] == null) { Consoles.WriteError("Delegate對象異常為NULL Key:" + command); return; } EvnDic[command] = (CallFunction)EvnDic[command] + call; } } public static void Listen<T>(CommandType command, CallFunction<T> call) { if (!CommandTypeList.Contains(command)) { CommandTypeList.Add(command); EvnDic.Add(command, call); } else { if (EvnDic[command] == null || EvnDic[command].GetType() != call.GetType()) { Consoles.WriteError("Delegate對象異常為NULL Key:" + command); return; } EvnDic[command] = (CallFunction<T>)EvnDic[command] + call; } } public static void Listen<T, U>(CommandType command, CallFunction<T, U> call) { if (!CommandTypeList.Contains(command)) { CommandTypeList.Add(command); EvnDic.Add(command, call); } else { if (EvnDic[command] == null || EvnDic[command].GetType() != call.GetType()) { Consoles.WriteError("Delegate對象異常為NULL Key:" + command); return; } EvnDic[command] = (CallFunction<T, U>)EvnDic[command] + call; } } public static void Listen<T, U, O>(CommandType command, CallFunction<T, U, O> call) { if (!CommandTypeList.Contains(command)) { CommandTypeList.Add(command); EvnDic.Add(command, call); } else { if (EvnDic[command] == null || EvnDic[command].GetType() != call.GetType()) { Consoles.WriteError("Delegate對象異常為NULL Key:" + command); return; } EvnDic[command] = (CallFunction<T, U, O>)EvnDic[command] + call; } } public static void Listen<T, U, O, P>(CommandType command, CallFunction<T, U, O, P> call) { if (!CommandTypeList.Contains(command)) { CommandTypeList.Add(command); EvnDic.Add(command, call); } else { if (EvnDic[command] == null || EvnDic[command].GetType() != call.GetType()) { Consoles.WriteError("Delegate對象異常為NULL Key:" + command); return; } EvnDic[command] = (CallFunction<T, U, O, P>)EvnDic[command] + call; } } private static void CheckCommad(CommandType command) { if (EvnDic[command] == null) { EvnDic.Remove(command); CommandTypeList.Remove(command); } } //移除事件--------------------------------------------------------
        public static void Remove(CommandType command, CallFunction call)   //通過枚舉 和 方法 從EvnDic字典中移除綁定
 { if (!CommandTypeList.Contains(command)) return; Delegate @delegate = EvnDic[command]; if (@delegate == null) { Consoles.WriteError("Delegate結果為NULL Key:" + command); return; } else if (@delegate.GetType() != call.GetType()) { Consoles.WriteError("Delegate對象不匹配 Key:" + command); return; } EvnDic[command] = (CallFunction)EvnDic[command] - call;; CheckCommad(command); } public static void Remove<T>(CommandType command, CallFunction<T> call) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate = EvnDic[command]; if (@delegate == null) { Consoles.WriteError("Delegate結果為NULL Key:" + command); return; } else if (@delegate.GetType() != call.GetType()) { Consoles.WriteError("Delegate對象不匹配 Key:" + command); return; } EvnDic[command] = (CallFunction<T>)EvnDic[command] - call; CheckCommad(command); } public static void Remove<T, U>(CommandType command, CallFunction<T, U> call) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate = EvnDic[command]; if (@delegate == null) { Consoles.WriteError("Delegate結果為NULL Key:" + command); return; } else if (@delegate.GetType() != call.GetType()) { Consoles.WriteError("Delegate對象不匹配 Key:" + command); return; } EvnDic[command] = (CallFunction<T, U>)EvnDic[command] - call; CheckCommad(command); } public static void Remove<T, U, O>(CommandType command, CallFunction<T, U, O> call) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate = EvnDic[command]; if (@delegate == null) { Consoles.WriteError("Delegate結果為NULL Key:" + command); return; } else if (@delegate.GetType() != call.GetType()) { Consoles.WriteError("Delegate對象不匹配 Key:" + command); return; } EvnDic[command] = (CallFunction<T, U, O>)EvnDic[command] - call; CheckCommad(command); } public static void Remove<T, U, O, P>(CommandType command, CallFunction<T, U, O, P> call) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate = EvnDic[command]; if (@delegate == null) { Consoles.WriteError("Delegate結果為NULL Key:" + command); return; } else if (@delegate.GetType() != call.GetType()) { Consoles.WriteError("Delegate對象不匹配 Key:" + command); return; } EvnDic[command] = (CallFunction<T, U, O, P>)EvnDic[command] - call; CheckCommad(command); } public static void Remove<T, U, O, P, Q>(CommandType command, CallFunction<T, U, O, P, Q> call) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate = EvnDic[command]; if (@delegate == null) { Consoles.WriteError("Delegate結果為NULL Key:" + command); return; } else if (@delegate.GetType() != call.GetType()) { Consoles.WriteError("Delegate對象不匹配 Key:" + command); return; } EvnDic[command] = (CallFunction<T, U, O, P, Q>)EvnDic[command] - call; CheckCommad(command); } //執行事件-------------------------------------------------------------
        public static void Broadcast(CommandType command)                  //通過枚舉 和要執行方法參數 從EvnDic中獲取對象方法並調用 
 { if (!CommandTypeList.Contains(command)) return; Delegate @delegate; if (EvnDic.TryGetValue(command, out @delegate)) { CallFunction call = @delegate as CallFunction; if (call != null) call(); else Consoles.WriteError("對應key的De'le'gate為空 Key:" + command); } } public static void Broadcast<T>(CommandType command, T arg1) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate; if (EvnDic.TryGetValue(command, out @delegate)) { CallFunction<T> call = @delegate as CallFunction<T>; if (call != null) call(arg1); else Consoles.WriteError("對應key的De'le'gate為空 Key:" + command); } } public static void Broadcast<T, U>(CommandType command, T arg1, U arg2) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate; if (EvnDic.TryGetValue(command, out @delegate)) { CallFunction<T, U> call = @delegate as CallFunction<T, U>; if (call != null) call(arg1, arg2); else Consoles.WriteError("對應key的De'le'gate為空 Key:" + command); } } public static void Broadcast<T, U, O>(CommandType command, T arg1, U arg2, O arg3) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate; if (EvnDic.TryGetValue(command, out @delegate)) { CallFunction<T, U, O> call = @delegate as CallFunction<T, U, O>; if (call != null) call(arg1, arg2, arg3); else Consoles.WriteError("對應key的De'le'gate為空 Key:" + command); } } public static void Broadcast<T, U, O, P>(CommandType command, T arg1, U arg2, O arg3, P arg4) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate; if (EvnDic.TryGetValue(command, out @delegate)) { CallFunction<T, U, O, P> call = @delegate as CallFunction<T, U, O, P>; if (call != null) call(arg1, arg2, arg3, arg4); else Consoles.WriteError("對應key的De'le'gate為空 Key:" + command); } } public static void Broadcast<T, U, O, P, Q>(CommandType command, T arg1, U arg2, O arg3, P arg4, Q arg5) { if (!CommandTypeList.Contains(command)) return; Delegate @delegate; if (EvnDic.TryGetValue(command, out @delegate)) { CallFunction<T, U, O, P, Q> call = @delegate as CallFunction<T, U, O, P, Q>; if (call != null) call(arg1, arg2, arg3, arg4, arg5); else Consoles.WriteError("對應key的De'le'gate為空 Key:" + command); } } //清空事件--------------------------------------------------------
        public static void Cleanup() { EvnDic.Clear(); }
View Code

 這個類只是看着內容多,其實就是三個函數,Listen (注冊) , Remove (移除) Broadcast(執行),其他的都是根據他們各自的泛型擴展而已

下面演示下調用

  

void Start() {       //注冊 GameEnvent.Listen(CommandType.Test, TestPrintLocalHost); //執行 } [PunRPC] public void TestPrintLocalHost() { print("LocalHOST:************"); }

private void Update()
{
  if (Input.GetKeyDown(KeyCode.T))
  GameEnvent.Broadcast(CommandType.Test);
}

 

//這樣就可以按T輸出

 

//”LocalHOST:************“

 

--------------------------------------------------

//泛型

 

void Start()
        {
       //注冊
            GameEnvent.Listen<string>(CommandType.Test, TestPrintLocalHost1);
            //執行
        }

        [PunRPC]
        public void TestPrintLocalHost1(string str)
        {
            print(str);
        }

 

private void Update()
{
  if (Input.GetKeyDown(KeyCode.T))
  GameEnvent.Broadcast<string>(CommandType.Test,"Hello");
}

 

// 這樣就可以輸出 "Hello"

 


現在我們只是使用了事件系統來綁定和調用方法,下一篇我們來跳過 [PunRPC]這個繁瑣的標記。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM