MatchComponent
请大家关注我的微博:@NormanLin_BadPixel坏像素
作者注释到,此为匹配组件。
/// <summary>
/// 匹配组件,匹配逻辑在MatchComponentSystem扩展
/// </summary>
public class MatchComponent : Component
{
//游戏中匹配对象列表
public readonly Dictionary<long, long> Playing = new Dictionary<long, long>();
//匹配成功队列
public readonly Queue<Matcher> MatchSuccessQueue = new Queue<Matcher>();
//创建房间消息加锁,避免因为延迟重复发多次创建房间消息
public bool CreateRoomLock { get; set; }
}
我们快速的看一下Matcher。
/// <summary>
/// 匹配对象
/// </summary>
public sealed class Matcher : Entity
{
//用户ID(唯一)
public long UserID { get; private set; }
//玩家GateActorID
public long PlayerID { get; set; }
//客户端与网关服务器的SessionID
public long GateSessionID { get; set; }
...
}
很简单的一个类。我们来看看MatchComponent的扩展。
我们看到,它为MatchComponent组件订阅了Update事件。Update事件会在每帧都调用。
而Update里面有一个While循环。我们来看一下这个循环会在什么情况下结束。
- 当没有匹配玩家时直接结束
- 当没有空房间的时候会尝试创建一个新的房间并结束
注意这个循环结束只能说明这一帧的匹配逻辑结束,并不是不再匹配了。因为Update每帧都调用嘛。
好的,我们再来看看它是怎么进行匹配的。
MatcherComponent matcherComponent = Game.Scene.GetComponent<MatcherComponent>();
...
Room room = roomManager.GetReadyRoom();
...
if (room == null && matchers.Count >= 3)
{
//当还有一桌匹配玩家且没有可加入房间时使用空房间
room = roomManager.GetIdleRoom();
}
...
通过这段代码我们能知道什么呢?这个匹配机制会优先填充未满的房间,只有一个房间填满了才会往下一个房间填。也是一个很简单的机制。
在看接下的代码前,我们先去看看JoinRoom跟CreateRoom方法吧。
if (room != null)
{
//当有准备状态房间且房间还有空位时匹配玩家直接加入填补空位
while (matchers.Count > 0 && room.Count < 3)
{
self.JoinRoom(room, matcherComponent.Remove(matchers.Dequeue().UserID));
}
}
else if (matchers.Count >= 3)
{
//当还有一桌匹配玩家且没有空房间时创建新房间
self.CreateRoom();
break;
}
else
{
break;
}
//移除匹配成功玩家
while (self.MatchSuccessQueue.Count > 0)
{
matcherComponent.Remove(self.MatchSuccessQueue.Dequeue().UserID);
}
这些代码,作者注释的都非常详细。不过这里大家要注意,在每次匹配循环逻辑执行的最后一步,会遍历在这一次循环匹配成功的玩家列表,并把他们移除出matcherComponent组件。不过我们知道,当玩家加入房间的时候,用的是matcherComponent.Remove方法,已经移除了,这会不会是重复操作呢?在现在看来,我觉得是的。
public static async void JoinRoom(this MatchComponent self, Room room, Matcher matcher)
{
//玩家加入房间,移除匹配队列
self.Playing[matcher.UserID] = room.Id;
self.MatchSuccessQueue.Enqueue(matcher);
//向房间服务器发送玩家进入请求
ActorProxy actorProxy = Game.Scene.GetComponent<ActorProxyComponent>().Get(room.Id);
Actor_PlayerEnterRoom_Ack actor_PlayerEnterRoom_Ack = await actorProxy.Call(new Actor_PlayerEnterRoom_Req()
{
PlayerID = matcher.PlayerID,
UserID = matcher.UserID,
SessionID = matcher.GateSessionID
}) as Actor_PlayerEnterRoom_Ack;
Gamer gamer = GamerFactory.Create(matcher.PlayerID, matcher.UserID, actor_PlayerEnterRoom_Ack.GamerID);
room.Add(gamer);
ActorProxyComponent actorProxyComponent = Game.Scene.GetComponent<ActorProxyComponent>();
//向玩家发送匹配成功消息
ActorProxy gamerActorProxy = actorProxyComponent.Get(gamer.PlayerID);
gamerActorProxy.Send(new Actor_MatchSucess_Ntt() { GamerID = gamer.Id });
}
首先对自身的属性数据进行了修改。因为房间服务器是分布式的,所以我们要用ActorProxy把请求传过去。具体服务器会对这个消息做怎样的处理,我们之后讲整个游戏流程的时候再说。
而创建一个房间也是一样,会发送创建房间的请求,得到回复后再用回复的Id创建一个Room管理起来。不过这里需要注意的是,这里用到了CreateRoomLock这个“锁”。为什么要用到这个呢?我们之前讲过,每一帧的匹配逻辑都是一个无限循环,而且每帧都会启动一个新的循环。而我们创建房间的操作是异步的,需要网络传输。而在我们得到远端答复之前,匹配的逻辑还在跑,当它发现自己还是没有新的房间时,会不断发送创建房间的请求,所以,在这里我们在第一次发送创建请求时,会用这个锁标记,我已经发送创建请求了,还在等待,你不要再发送了。直到我们异步获取到了远端的回复,并且创建了一个新的房间后,才会给这个锁解锁,开始接收新的创建房间请求。
public static async void CreateRoom(this MatchComponent self)
{
if (self.CreateRoomLock)
{
return;
}
//消息加锁,避免因为延迟重复发多次创建消息
self.CreateRoomLock = true;
//发送创建房间消息
IPEndPoint mapIPEndPoint = Game.Scene.GetComponent<AllotMapComponent>().GetAddress().GetComponent<InnerConfig>().IPEndPoint;
Session mapSession = Game.Scene.GetComponent<NetInnerComponent>().Get(mapIPEndPoint);
MP2MH_CreateRoom_Ack createRoomRE = await mapSession.Call(new MH2MP_CreateRoom_Req()) as MP2MH_CreateRoom_Ack;
Room room = ComponentFactory.CreateWithId<Room>(createRoomRE.RoomID);
Game.Scene.GetComponent<MatchRoomComponent>().Add(room);
//解锁
self.CreateRoomLock = false;
}
对这些消息的具体处理,我们之后再讲。