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();
}
这个类只是看着内容多,其实就是三个函数,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]这个繁琐的标记。
Unity - Photon PUN 本地与网络同步的逻辑分离 (一)的更多相关文章
- Unity - Photon PUN 本地与网络同步的逻辑分离 (二)
上篇实现了事件系统的设计,这篇就来结合发送RPC消息 并且不用标记 [PunRPC] 先来看下上编的代码 GameEnvent.cs private static Dictionary<Comm ...
- 实现一个简易的Unity网络同步引擎——netgo
实现一个简易的Unity网络同步引擎Netgo 目前GOLANG有大行其道的趋势,尤其是在网络编程方面.因为和c/c++比较起来,虽然GC占用了一部分机器性能,但是出错概率小了,开发效率大大提升,而且 ...
- Unite 2017 | 从《闹闹天宫》看MOBA游戏里的网络同步技术
http://mp.weixin.qq.com/s/0v0EU79Q6rFafrh8ptlmhw 在Unite 2017 Shanghai案例分享专场,来自蓝港互动<闹闹天宫>项目组的主程 ...
- MOBA游戏的网络同步技术
转自:http://www.gameres.com/750888.html 在5月13日Unite 2017 案例分享专场上,蓝港互动<闹闹天宫>项目组的主程序陈实分享了MOBA游戏的网络 ...
- Photon PUN 一 介绍
有句话说的好 , 官网永远是最好的学习地方 . 虽然国内的资料不多 , 但是官网的资料还是很充足 , 这就带着英汉词典就着作阅读理解的劲头去官网学习吧 https://doc.photonengine ...
- 游戏中的网络同步机制——Lockstep(帧同步)
本文来自: https://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockste ...
- 手游后台PVP系统网络同步方案总结
游戏程序 平台类型: 程序设计: 编程语言: 引擎/SDK: 概述 PVP系统俨然成为现在新手游的上线标配,手游Pvp系统体验是否优秀,很大程度上决定了游戏的品质.从最近半年上线的新手 ...
- Photon PUN 三 RPCs & RaiseEvent
官方文档地址 https://doc.photonengine.com/en-us/pun/current/manuals-and-demos/rpcsandraiseevent 一, RPC P ...
- 百度云+ KeePass 网络同步你的密码
百度云+ KeePass 网络同步你的密码 百度云一个目前不限流量不限格式能直链的网盘,速度在我这里很快,难得了!KeePass(小众介绍过 KeePass.) 是一个免费开源的密码管理类软件, ...
随机推荐
- Spring @Autowired注解在非Controller中注入为null
问题描述 今天在写一个工具类,里面用了@Autowired注入了StringRedisTemplate以及RedisTemplate时,在template.opsForValue().set(key, ...
- Java的注解总结
java 1.5开始引入了注解和反射,正确的来说注解是反射的一部分,没有反射,注解无法正常使用,但离开注解,反射依旧可以使用.Java的注解详解和自定义注解: https://blog.csdn.ne ...
- SQL注入--SQLMap过WAF
单引号被过滤情况: 空格.等号未被过滤情况: select被过滤情况: 以此类推,当sqlmap注入出现问题时,比如不出数据,就要检查对应的关键词是否被过滤. 比如空格被过滤可以使用space2com ...
- pytorch 生成随机数
在使用PyTorch做实验时经常会用到生成随机数Tensor的方法,比如: torch.rand() torch.randn() torch.normal() torch.linespace() 在很 ...
- 每日一练之排序算法(P1097 统计数字)
某次科研调查时得到了n个自然数,每个数均不超过1500000000(1.5×10^9).已知不相同的数不超过10000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果. ...
- springboot+vue前后端分离,nginx代理配置 tomcat 部署war包详细配置
1.做一个小系统,使用了springboot+vue 基础框架参考这哥们的,直接拿过来用,链接https://github.com/smallsnail-wh/interest 前期的开发环境搭建就不 ...
- (ZT)算法杂货铺——分类算法之贝叶斯网络(Bayesian networks)
https://www.cnblogs.com/leoo2sk/archive/2010/09/18/bayes-network.html 2.1.摘要 在上一篇文章中我们讨论了朴素贝叶斯分类.朴素贝 ...
- java mybatis后台判断表是否存在mysql
<select id="isTableExist" parameterType="string" resultType="int"&g ...
- inpuy只能输入数字,并且禁止输入e
<el-input type="number" onkeyup="this.value=this.value.replace(/[^\d]/g,'')" ...
- 初学python---排序
1.永久性排序 sort() a = [12,45,1,25,3] a.sort() print(a) ----[1, 3, 12, 25, 45] 2.临时排序 sorted() a = [12, ...