任务23:【重要】后端-匹配进入游戏房间开发(二)
本节请反复看反复看,一定要真真正正理解到骨子里。
前面的课时可以跟着做,多做几次多梳理几遍,然后反复推敲本节的每一句话,每行代码。只要你真正明白了,你就上了台阶,是一个搞懂游戏服务器开发核心的人了,不再是一个纯粹的前端开发者了!
上一节做出来了吗?过关没?
如果你做出来了,是不是有几个地方好晕,实在不明白呢?
那我们就开始本节的挑战吧,搞明白了,我觉得你可以上天哦!
问题一:什么叫给网关上的user发了条消息呢?
登录到网关时A0003_LoginGateHanler
\Server\Hotfix\Landlords\Handler\Gate\A0003_LoginGateHanler.cs
user.AddComponent<MailBoxComponent>();
G2M_EnterMatch_Handler,向网关上的user发了一条Actor_MatchSucess_M2G的消息
\Server\Hotfix\Landlords\Handler\Map\G2M_EnterMatch_Handler.cs
//向Gate发送消息更新Gate上user的ActorID //这样不论玩家到了哪个地图服务器,Gate上的user都持有所在地图服务器上gamer的InstanceId ActorMessageSender actorProxy = Game.Scene.GetComponent<ActorMessageSenderComponent>().Get(gamer.GActorID); actorProxy.Send(new Actor_MatchSucess_M2G() { GamerID = gamer.InstanceId });
M2G_MatchSucess_Handler
\Server\Hotfix\Landlords\Handler\Gate\M2G_MatchSucess_Handler.cs
[ActorMessageHandler(AppType.Gate)] public class M2G_MatchSucess_Handler : AMActorHandler<User, Actor_MatchSucess_M2G> { protected override async ETTask Run(User user, Actor_MatchSucess_M2G message) { //gate更新ActorID user.ActorID = message.GamerID; Log.Info($"玩家{user.UserID}匹配成功 更新客户端Actor转发向Gamer"); await ETTask.CompletedTask; } }
当我们用一个ID数值构建的ActorMessageSender,发送消息时,MailBox就会找到这个ID数值的实体实例。
上面代码例子中,就是GActorID构建的ActorMessageSender。还记得不 GActorID 的值是(gate user's InstanceId),去上节课看看。
MailBox发现这个user实例是在网关上添加的MailBox,就去网关上找到了消息对应的M2G_MatchSucess_Handler。
执行了其中的Run方法,传入了user实例作为参数。
所以得出结论:你只要在某个服务器上给一个实体比如说user,session加上MailBox,然后在任何地方只要有这个实例的InstanceId,就能构建ActorMessageSender,向实例所在的服务器发消息,并且执行此服务器上的对应Handler,并把这个实例作为参数传入其中的Run方法。
问题二:为什么同样是在网关上添加的MailBox构建ActorMessageSender发消息。Actor_MatchSucess_M2G发给了网关,Actor_LandMatcherPlusOne_NTT 却直接转发给了客户端?
\Server\Hotfix\Landlords\Handler\Gate\A0003_LoginGateHanler.cs
user.AddComponent<MailBoxComponent>(); session.AddComponent<MailBoxComponent, string>(MailboxType.GateSession);
\Server\Hotfix\Landlords\Handler\Map\G2M_EnterMatch_Handler.cs
ActorMessageSender actorProxy = Game.Scene.GetComponent<ActorMessageSenderComponent>().Get(gamer.GActorID); actorProxy.Send(new Actor_MatchSucess_M2G() { GamerID = gamer.InstanceId });
\Server\Hotfix\Landlords\System\LandMatchComponentSystem.cs
ActorMessageSender actorProxy = Game.Scene.GetComponent<ActorMessageSenderComponent>().Get(gamer.CActorID); actorProxy.Send(new Actor_LandMatcherPlusOne_NTT() { MatchingNumber = self.MatchingQueue.Count });
首先CActorID的值是(gate user's GateSessionID),去上节课看看。
user.GateSessionID = session.InstanceId,此sesion实例正是网关与客户端的连接session。另外执行客户端Actor_LandMatcherPlusOne_NTT对应的Handler时,此session实例作为参数传入其中的Run方法。
因为在网关上给session添加MailBox时,加了MailboxType.GateSession参数,MailboxGateSessionHandler会直接将此消息转发给客户端。
问题三:上节课建立心中清晰的端与端通信原理中有服务端之间,可以在每个服务端定义一个构建和获取与其它服务端的连接session的方法。
那Actor_MatchSucess_M2G消息能不能不用ActorMessageSender,而是直接构建一个网关session发过去呢?
增加MapHelper \Server\Hotfix\Helper\MapHelper.cs
namespace ETHotfix { public static class MapHelper { public static Session GetGateSession() { StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>(); IPEndPoint gateIPEndPoint = config.GateConfigs[0].GetComponent<InnerConfig>().IPEndPoint; //Log.Debug(gateIPEndPoint.ToString()); Session gateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(gateIPEndPoint); return gateSession; } } }
修改Actor_MatchSucess_M2G的发送方式,消息改名为T_MatchSucess_M2G
\Server\Hotfix\Landlords\System\LandMatchComponentSystem.cs
//向Gate发送匹配成功 //ActorMessageSender actorProxy = Game.Scene.GetComponent<ActorMessageSenderComponent>().Get(gamer.GActorID); //actorProxy.Send(new Actor_MatchSucess_M2G() { GamerID = gamer.InstanceId }); Session GetGateSession = MapHelper.GetGateSession(); GetGateSession.Send(new T_MatchSucess_M2G() {UserID = gamer.UserID , GamerID = gamer.InstanceId });
网关上增加M2G_T_MatchSucess_Handler
\Server\Hotfix\Landlords\Handler\Gate\M2G_T_MatchSucess_Handler.cs
using System.Threading.Tasks; using ETModel; namespace ETHotfix { [MessageHandler(AppType.Gate)] public class M2G_T_MatchSucess_Handler : AMHandler<T_MatchSucess_M2G> { protected override async ETTask Run(Session session, T_MatchSucess_M2G message) { User user = Game.Scene.GetComponent<UserComponent>().Get(message.UserID); //gate更新ActorID user.ActorID = message.GamerID; Log.Info($"玩家{user.UserID}匹配成功 更新客户端Actor转发向Gamer"); await ETTask.CompletedTask; } } }
InnerMessage.proto 增加T_MatchSucess_M2G
//==>匹配玩家并进入斗地主游戏房间 4月18 //Map通知Gate匹配成功 message Actor_MatchSucess_M2G // IActorMessage { int64 ActorId = 94; int64 GamerID = 1; } message T_MatchSucess_M2G // IMessage { int32 RpcId = 90; int64 UserID = 1; int64 GamerID = 1; } //Gate通知Map 玩家请求匹配 message EnterMatchs_G2M // IMessage { int32 RpcId = 90; int64 UserID = 1; //Gate上User的UserID int64 GActorID = 2; //Gate上User的InstanceId int64 CActorID = 3; //Gate上User的GateSessionID } //匹配玩家并进入斗地主游戏房间 <==
这一节何其重要,何其关键,何其刻骨铭心,何其雾散云开见月明!!!
通信的模型与原理!!!
所以不论多少个端,Client,Gate,Map,Realm,只是在每个端都创建了同样UserID的user实例,和用同样UserID创建的gamer实例,每一个玩家就像在每个端都有分身或影子,玩家的分身之间把消息传来传去,改变着玩家的状态。另外,各类服务器(主要指map)也可以通过网关上客户端与网关的session连接,给一群玩家的本尊客户端广播消息。
注:Realm只需要认证账号,不需要分身
整个课程后面不论开发多少功能,都是在不断帮助你强化这个通信的模型与原理!!!
我们可以总结一下重要的总论:
有这几大实例id,就是几种实体(Entity)的InstanceId
User
Session
Gamer
- actorsender在构建时,传入一个实体的实例id,就可以给这个实体的实例发消息,不论他在哪个服务器。
- 只要有session,user,gamer的实例id就能给他们发actor消息。
- 而通过session的实例id给用户的网关session发actor消息,session会给你转发给客户端(用户的网关sesion正是网关与客户端的连接session)。