服務器大家可以使用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(); }
這個類只是看着內容多,其實就是三個函數,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]這個繁瑣的標記。