一、ecs并不影响使用oop思维
你应该是有一定经验积累的,组件思维只是用多个实例来给事物增加更多方法与属性。继承思维就是只给事物在内存中创建一个实例。
为什么要多在服务器多用组件呢?
- 内存释放灵活的考虑
- 可以运行时添加组件,可以根据配置文件创建实例
- 程序功能扩展性考虑
如果这个功能模块,不是大量玩家相关,就不需要灵活的释放内存,可以不用组件。我们做单机的时候最多一千几千个某种事物,但在服务器上可能要处理十万百万玩家事物,比如unit,必然要关注内存。
如果这个功能模块或组件中的某些方法不需要热更新,那方法可以直接写在组件模型上,并不用单再定义一个组件的System文件。
如果这个功能模块或组件没有,或没那么关系核心逻辑暂时不需要在意扩展性,就可以用继承来实现一些需求,比如有很多种状态同步组件,就可以抽象出一个基类 StateSync对象,MoveSyncComponent,NoticeUnitSyncComponent等状态同步组件就可以继承他。
二、AccountManager与AccountManagerSystem是一个组件
System中是AccountManager的扩展方法,上面说了有热更需求的方法放在扩展类中。
帐号管理组件,通常是帐号的缓存,获取,存库。
认证帐号的创建,和调用AccountManager的方法是在消息Handler或其它需要的组件逻辑中。
## 注册account存库并缓存账号在accountManager
## 确定并将gateScene信息缓存在account
public class C2R_RegisterRequestHandler : MessageRPC<C2R_RegisterRequest,R2C_RegisterResponse>
{
protected override async FTask Run(Session session, C2R_RegisterRequest request, R2C_RegisterResponse response, Action reply)
{
response.ErrorCode = await Check(session, request, response);
}
private async FTask<uint> Check(Session session, C2R_RegisterRequest request, R2C_RegisterResponse response)
{
string authName = request.AuthName?.Trim() ?? "";
string pw = request.Pw?.Trim() ?? "";
string pw2 = request.Pw2?.Trim() ?? "";
// 版本号不一致
if (request.Version != ConstValue.Version)
return ErrorCode.H_A2C_Login_Res_VersionERROR;
// 账号校验
var err = authName.IsAccountValid();
if (err != ErrorCode.Success)
return err;
// 两次密码不一致
if (pw != pw2)
return ErrorCode.Error_ReSetPwNotSame;
// 密码校验
err = pw.IsPasswordValid();
if (err != ErrorCode.Success)
return err;
var accountManager = session.Scene.GetComponent<AccountManager>();
var _authAccountLock = new CoroutineLockQueueType("AuthAccountLock");
using (await _authAccountLock.Lock(authName.GetHashCode()))
{
long now = TimeHelper.Now;
Account account = await accountManager.GetAccount(authName);
// 账号已注册
if (account != null)
return ErrorCode.Error_RegisterAccountAlreayRegister;
var zoneId = request.ZoneId;
// 根据区服id生成账号id,这样从账号id就能查出所在区服
var zoneAuthId = IdGenerate.GenerateId(zoneId);
// 所在区服随机一个Gate
// 存库记录这个网关SceneId,此账号以后都登录此网关
var gateSceneId = SceneHelper.GetSceneRandom(SceneType.Gate,zoneId).Id;
var ip = session.RemoteEndPoint.ToString();
account = Entity.Create<Account>(session.Scene,zoneAuthId);
account.GateSceneId = gateSceneId;
account.Phone = "";
account.AuthName = authName;
account.RegisterIp = ip;
account.RegisterTime = now;
account.LastLoginIp = ip;
account.LastLoginTime = now;
account.Pw = pw;
accountManager.AccountDic.Add(authName, account);
// 存库
await accountManager.SaveAccount(account);
return ErrorCode.Success;
}
}
}
类似的网关帐号管理组件GateAccountManager的任务,主要就是对网关帐号的添加缓存、入库,获取。
网关帐号的创建,和调用GateAccountManager的方法是在消息Handler或其它需要的组件逻辑中。
## 验证sessionKey,
## 创建、缓存、获取网关账号
## session上缓存玩家PlayerId,gateAccount
## gateAccount缓存LoginedGate状态,session.RuntimeId
public class C2G_LoginGateRequestHandler : MessageRPC<C2G_LoginGateRequest,G2C_LoginGateResponse>
{
protected override async FTask Run(Session session, C2G_LoginGateRequest request, G2C_LoginGateResponse response, Action reply)
{
response.ErrorCode = await Check(session, request, response);
}
private async FTask<uint> Check(Session session, C2G_LoginGateRequest request, G2C_LoginGateResponse response)
{
var SessionKeyComponent = session.Scene.GetComponent<SessionKeyComponent>();
// 用key查出他的Account id
var gateKey = SessionKeyComponent.Get(request.Key);
var accountId = gateKey.AccountId;
var authName = gateKey.AuthName;
// 验证登录Key是否正确
if (accountId == 0)
{
return ErrorCode.H_C2G_LoginGate_AccountIdIsNull;
}
var _LockGateAccountLock = new CoroutineLockQueueType("LockGateAccountLock");
using (await _LockGateAccountLock.Lock(accountId))
{
// 缓存有就获取网关账号,无则创建网关账号
var (err, gateAccount) = await LoginCheck(session, accountId,authName);
if (err != ErrorCode.Success) return err;
if (!session.IsDisposed)
{
// 在session上缓存玩家PlayerId,gateAccount
var sessionPlayer = session.GetComponent<SessionPlayerComponent>()
?? session.AddComponent<SessionPlayerComponent>();
sessionPlayer.playerId = accountId;
sessionPlayer.gateAccount = gateAccount;
// gateAccount缓存登录状态,GateRouteId
gateAccount.SessionRumtimeId = session.RuntimeId;
gateAccount.LoginedGate = true;
}
// 使Key过期
SessionKeyComponent.Remove(request.Key);
return ErrorCode.Success;
}
}
private async FTask<(uint, GateAccount)> LoginCheck(Session session, long accountId,string authName)
{
Scene scene = session.Scene;
var accountManager = scene.GetComponent<GateAccountManager>();
// 缓存有网关账号
if(accountManager.TryGetValue(accountId, out GateAccount gateAccount))
{
// 存在连接
if (gateAccount.TryGeySession(out Session existedSession))
{
// 同Session重复连接,异常直接断开
if (existedSession == session)
{
session.Disconnect(0).Coroutine();
return (ErrorCode.Error_LoginGateSameSession, null);
}
// 他处网关账号顶下线
existedSession.ForceDisconnect().Coroutine();
}
}
else
{
var db = scene.World.DateBase;
gateAccount = await db.Query<GateAccount>(accountId);
// 没有网关账号创建一个
if (gateAccount == null)
{
gateAccount = Entity.Create<GateAccount>(session.Scene, accountId);
gateAccount.AuthName = authName;
gateAccount.RegisterTime = TimeHelper.Now;
// 账号存库
await db.Save(gateAccount);
}
accountManager.GateAccounts.Add(accountId, gateAccount);
}
return (ErrorCode.Success, gateAccount);
}
}
三、整体的认证帐号,网关帐号管理逻辑
C2R_RegisterRequestHandler
注册account存库并缓存账号在accountManager
确定并将gateScene信息缓存在account
C2R_LoginRequestHandler
验证账号,
请求sessionKey,R2G_GetLoginKeyRequest
返回客户端网关地址
C2G_LoginGateRequestHandler
验证sessionKey,
创建、缓存、获取网关账号
session上缓存玩家PlayerId,gateAccount
gateAccount缓存LoginedGate状态,session.RuntimeId
H_G2C_RoleCreateHandler
检查角色性别,名字等
网关帐号有效,可创建角色数量
创建角色,存库