ET框架实现匹配功能(服务器端)
目录
一、定义房间中的玩家实体
二、定义房间的实体
三、实现房间管理组件
3.1房间管理组件定义
3.2房间管理组件行为
四、实现匹配功能
4.1匹配接口
4.2 房间实体的行为
注:本篇文章记录用ET框架模拟实现游戏中的匹配功能,代码和思路仅供学习使用。
回想一下我们打王者的时候:
- 先在大厅界面选择游戏模式(例如:匹配、排位等)
- 然后进入到一个虚拟的房间
- 点击开始匹配按钮
- 匹配过程中的等待时间
- 匹配成功点击进入游戏正式游玩
拿排位来举例,正式开始游戏需要十个人,所以我称匹配之前的那个所谓的房间为虚拟房间,成功匹配十个人后组成的房间为正式房间。我们按照上面罗列出的顺序用ET来实现。
一、定义房间中的玩家实体
我们把玩家在房间中需要的信息定义成一个新的实体带入房间供我们使用,比如头像、昵称、英雄列表、皮肤列表、段位信息、玩家经验、等级、是否是机器人等游戏过程中所需要的字段。
namespace ET.Server
{
public class RoomPlayerInfo : Entity, IAwake,IDestroy
{
public long UnitId = 1;
public long GateSessionActorId = 1;
//是否掉线
public int OfflineFlag = 0;
public string Name = "";
public int Exp = 1000;
public int RobotFlag = 0;
public long AddMatchingTime = 0;
//玩家拥有的英雄列表
public List<Hero> CardList = new List<Hero>();
//玩家拥有的皮肤列表
public List<Skin> CardList = new List<Skin>();
//段位
public int StartCount = 0;
public int HeadImgId = 1;
public int FrameId = 1;
//单排、三排、五排
public int FriendRoomPlayFlag = 1;
public int Level = 0;
}
}
二、定义房间的实体
玩家匹配成功之后进入到房间中进行游戏,我们来定义房间实体的基本信息;比如房间状态(等待、匹配中、匹配成功、全军出击、龙王刷新、风暴龙王降临、结算...)、房间类型(匹配玩法、娱乐模式玩法、排位玩法)、房间ID、房间中玩家信息、玩家数量等......
namespace ET.Server
{
public enum RoomType: ushort
{
Wait = 0,// 等待
Matchmaking = 1,//匹配中
MatchSuccessfully = 2,//匹配成功
GameStart = 3,//游戏开始,全军出击
DragonKingRefresh = 4,//龙王刷新
StormDragonLord = 5,//风暴龙王刷新
Settle = 6//结算
}
[ChildOf(typeof (RoomManagerComponent))]
public class RoomInfo: Entity, IAwake, IDestroy
{
public int RoomId = 1;
/// <summary>
/// 房间类型
/// </summary>
public int Type = 1001;
public int PlayerCount = 3;
public int PlayCountFlag = 1;
/// <summary>
/// 房间状态
/// </summary>
public RoomType Status = RoomType.Wait;
/// <summary>
/// 玩家信息
/// </summary>
public Dictionary<int, RoomPlayerInfo> Players = new Dictionary<int, RoomPlayerInfo>();
public int RoomProcessNum = 1;
public int PlayCount = 0;
//创建时间
public long CreateTime = 0;
//房主
public long OwnerId = 0;
}
}
三、实现房间管理组件
玩家的实体信息通过玩家本身进行创建,创建完成后匹配放入到房间中,那房间实体信息怎么创建呢?
[ChildOf(typeof (RoomManagerComponent))]
在上段房间实体的代码中我们用到了ChildOf,说明房间是通过 RoomManagerComponent 也就是房间管理组件来进行管理的。
房间管理组件管理着所有房间的生成创建和销毁。比如有人开始匹配了,没有空余房间,房间管理组件就创建一个房间把玩家放进去;游戏结束房间管理组件就把房间销毁。房间管理组件中设置定时任务不停地执行监控各个房间、玩家的各种状态。
3.1房间管理组件定义
namespace ET.Server
{
[ComponentOf(typeof(Scene))]
public class RoomManagerDDZComponent:Entity,IAwake,IDestroy
{
public long Timer;
//房间字典
public Dictionary<int, RoomInfo> RoomInfosDic = new Dictionary<int, RoomInfo>();
//匹配队列字典
public Dictionary<int, List<RoomPlayerInfo>> MatchList = new Dictionary<int, List<RoomPlayerInfo>>();
public int NowId = 100001;
public int MatchTime = 30000;
}
}
3.2房间管理组件行为
RoomManagerComponentSystem
namespace ET.Server
{
public class RoomManagerComponentDestroy : DestroySystem<RoomManagerComponent>
{
protected override void Destroy(RoomManagerComponent self)
{
TimerComponent.Instance.Remove(ref self.Timer);
}
}
public class RoomManagerComponent: AwakeSystem<RoomManagerComponent>
{
protected override void Awake(RoomManagerComponent self)
{
self.Timer = TimerComponent.Instance.NewRepeatedTimer(1000, TimerInvokeType.RoomUpdate, self);
}
}
[Invoke(TimerInvokeType.RoomUpdate)]
public class RoomManagerComponentTimer : ATimer<RoomManagerComponent>
{
protected override void Run(RoomManagerComponent self)
{
try
{
if ( self.IsDisposed || self.Parent == null )
{
return;
}
self?.Update();
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
}
[FriendOf(typeof(RoomInfo))]
[FriendOf(typeof(RoomManagerComponent))]
[FriendOf(typeof(RoomPlayerInfo))]
[FriendOf(typeof(ItemComponent))]
[FriendOf(typeof(BaseInfoComponent))]
[FriendOf(typeof(UnitComponent))]
[FriendOf(typeof(Unit))]
[FriendOf(typeof(Account))]
public static class RoomManagerComponentSystem
{
//定时任务,每秒执行
public static void Update(this RoomManagerComponent self)
{
//匹配方法
self.Match();
UnitComponent uc = self.DomainScene().GetComponent<UnitComponent>();
foreach (var room in self.RoomInfosDic)
{
//同时也执行现有房间的Update方法
room.Value.Update().Coroutine();
}
}
//执行匹配操作
public static void Match(this RoomManagerComponent self)
{
//遍历玩家匹配列表进行匹配操作
foreach (var playerList in self.MatchList)
{
long nowT = TimeHelper.ClientNow();
int playerCount = 10;
if (playerList.Value.Count >= playerCount || (playerList.Value.Count >= 1 && nowT > playerList.Value[0].AddMatchingTime + self.MatchTime) || (playerList.Value.Count >= 1 &&playerList.Value[0].PlayWithRobotFlag == 1))
{
self.GetRoomIdAndLocate(playerList.Key,0,1,0,out int locate, out int roomId);
RoomInfo room = self.GetRoom(roomId);
for (int i = 0; i< playerCount; i++)
{
if (playerList.Value.Count > i)
{
//i+1key自增
room.Players.Add(i+1,playerList.Value[i]);
self.PlayerAddMatch(playerList.Value[i].UnitId,playerList.Key,0,roomId).Coroutine();
self.ChangePlayerCount(playerList.Key, 0, 1);
//安排机器人
if (playerList.Value[i].PlayWithRobotFlag == 1)
{
for (int j = 2; j <= room.PlayerCount; j++)
{
RoomPlayerInfo robot = RoomHelper.GetRobot(roomConfig.RoleType,roomConfig.WinningOrLosingCap);
room.Players.Add(j,robot);
}
break;
}
}else
{
RoomPlayerInfo robot = RoomHelper.GetRobot(roomConfig.RoleType,roomConfig.WinningOrLosingCap);
room.Players.Add(i+1,robot);
}
}
if (playerList.Value.Count > playerCount)
{
playerList.Value.RemoveRange(0,playerCount);
}
else
{
playerList.Value.Clear();
}
//匹配成功,通知给房间中的各个玩家
room.MatchSuccess();
return;
}
}
}
public static async ETTask PlayerAddMatch(this RoomManagerComponent self,long unitId,int type,int rankFlag,Dictionary<int,long> macthUse,int gameGroupId,int roomId,int controlFlag)
{
UnitComponent unitComponent = self.DomainScene().GetComponent<UnitComponent>();
Unit unit = unitComponent.Get(unitId);
if (unit == null)
{
unit = await UnitCacheHelper.GetUnitCache(self.GetParent<Scene>(),unitId);
}
//告诉玩家基本信息组件BaseInfoComponent,玩家已经开始了游戏
unit.GetComponent<BaseInfoComponent>().PlayGame(type, rankFlag, gameGroupId, 0, roomId, controlFlag);
}
public static void AddMatch(this RoomManagerComponent self,int type,RoomPlayerInfo player)
{
//如果玩家已经在匹配队列则return
if (self.CheckInMatch(player.UnitId))
{
return;
}
//如果匹配队列没有此类型的玩法比如排位,则匹配队列会增加
if (!self.MatchList.ContainsKey(type))
{
self.MatchList.Add(type, new List<RoomPlayerInfo>());
}
//将玩家加入到匹配队列中
self.MatchList[type].Add(player);
}
//检查玩家是否在匹配队列中
public static bool CheckInMatch(this RoomManagerComponent self,long unitId)
{
foreach (var info in self.MatchList)
{
foreach (var p in info.Value)
{
if (p.UnitId == unitId)
{
return true;
}
}
}
return false;
}
//根据房间ID返回房间信息
public static RoomInfo GetRoom(this RoomManagerComponent self,int roomId)
{
return self.RoomInfosDic[roomId];
}
//返回房间ID和玩家位置
public static void GetRoomIdAndLocate(this RoomManagerComponent self,int type,int rId,int playCount,int rankFlag,out int locate, out int roomId)
{
locate = 0;
roomId = 0;
foreach (var room in self.RoomInfosDic)
{
if (room.Value.Type == type)
{
if (room.Value.Players.Count == 0)
{
roomId = room.Key;
locate = 1;
break;
}
}
}
//检查玩家是否在房间中如果在返回房间ID
public static void CheckInRoom(this RoomManagerComponent self,long unitId, out int locate , out int roomId)
{
locate = 0;
roomId = 0;
foreach (var room in self.RoomInfosDic)
{
foreach (var playerInfo in room.Value.Players)
{
if (playerInfo.Value.UnitId == unitId)
{
roomId = room.Key;
locate = playerInfo.Key;
break;
}
}
}
}
//创建并返回RoomPlayerInfo对象
public static RoomPlayerInfo SelfToRoomPlayerInfo(this RoomManagerComponent self,long unitId,string name,long sessionId,....//生成玩家实体所需参数)
{
return new RoomPlayerInfo()
{
UnitId = unitId,
GateSessionActorId = sessionId,
AddMatchingTime = TimeHelper.ClientNow(),
Name = name,
....//生成玩家实体所需参数
};
}
}
}
当然我们想要在匹配中加点其他逻辑,比如根据隐藏分来匹配也是可以的。
四、实现匹配功能
有了前面的实体组件做铺垫,我们来实现匹配功能。
虚拟房间中房主点击匹配按钮 ——> 客户端请求服务端进行匹配 ——> 玩家进入匹配队列 ——> 匹配到十个人组成房间 ——> 服务端通知房间中的所有玩家匹配成功。
4.1匹配接口
定义接口编写Proto文件
//ResponseType M2C_Matching_DDZ
message C2M_MatchingGame // IActorLocationRequest
{
int32 RpcId = 1;
int32 RoomType = 2;
}
message M2C_MatchingGame // IActorLocationResponse
{
int32 RpcId = 1;
int32 Error = 2;
string Message = 3;
}
编写接口
namespace ET.Server
{
[ActorMessageHandler(SceneType.Map)]
[FriendOf(typeof(UnitGateComponent))]
[FriendOf(typeof(BaseInfoComponent))]
[FriendOf(typeof(RoomInfo))]
public class C2M_MatchingGameHandler : AMActorLocationRpcHandler<Unit,C2M_MatchingGame,M2C_MatchingGame>
{
protected override async ETTask Run(Unit unit, C2M_MatchingGame request, M2C_MatchingGame response)
{
//获取房间管理组件
RoomManagerComponent rm = unit.DomainScene().GetComponent<RoomManagerComponent>();
//获取玩家基本信息组件
BaseInfoComponent baseInfoComponent = unit.GetComponent<BaseInfoComponent>();
if (baseInfoComponent.NowPlayGame > 0)
{
response.Error = ErrorCode.ERR_All;
response.Message = "已在游戏中";
return;
}
using (await CoroutineLockComponent.Instance.Wait(CoroutineLockType.Matching, unit.Id.GetHashCode()))
{
//生成房间中的玩家实体信息RoomPlayerInfo
//这个方法做的就是把需要的参数传进去返回RoomPlayerInfo
RoomPlayerInfo player = rm.SelfToRoomPlayerInfo(unit.Id, baseInfoComponent.Name, unit.GetComponent<UnitGateComponent>().GateSessionActorId,
score,baseInfoComponent.NowUseCharacter,
baseInfoComponent.NowUseAvatar, baseInfoComponent.NowUseAvatarFrame, baseInfoComponent.ip, baseInfoComponent.PhoneNumber,
Level);
//把生成好的玩家信息加入到房间管理组件的玩家匹配队列中进行匹配
rm.AddMatch(request.RoomType, player);
await ETTask.CompletedTask;
}
}
}
}
4.2 房间实体的行为
根据房间管理组件中定时任务执行的Update方法,玩家匹配成功会自动通知房间内玩家匹配成功,房间状态发生变化正式进入游戏。
RoomInfoSystem
namespace ET.Server
{
public class RoomInfoDestroy : DestroySystem<RoomInfo>
{
protected override void Destroy(RoomInfo self)
{
}
}
public class RoomInfoAwake : AwakeSystem<RoomInfo>
{
protected override void Awake(RoomInfo self)
{
}
}
[FriendOf(typeof(RoomInfo))]
[FriendOf(typeof(RoomPlayerInfo))]
[FriendOf(typeof(ItemComponent))]
[FriendOf(typeof(RoomManagerComponent))]
public static class RoomInfoSystem
{
//房间管理组件延伸过来的定时任务
public static async ETTask Update(this RoomInfo self)
{
}
public static void MatchSuccess(this RoomInfo self)
{
//获取房间内所有的玩家列表
List<PlayerInRoomProto> info = self.GetPlayers();
//匹配成功后更新房间状态逻辑
self.UpdateRoomStatus(TimeHelper.ServerNow(),1,RoomType.Wait);
foreach (var playerInfo in self.Players)
{
M2C_MatchSuccessNotice g = new M2C_MatchSuccessNotice()
{
Players = info, RoomStatus = self.GetRoomStatus()//获取房间状态
};
//正式通知房间内玩家匹配成功
GameNoticeHelper.NoticeHelper(g,playerInfo.Value.GateSessionActorId,playerInfo.Value.OfflineFlag);
}
}
}
}