从零开始搭建游戏服务器 第八节 角色创建登录流程开发

前言

上一节我们创建了GameServer用于处理具体游戏业务逻辑。
当GameServer启动时,会连接LoginServer进行注册。
这一节我们基于这个模型来对GameServer进行开发,并实现角色的创建和初步登录流程。

正文

拆分需求

在这一节中,我们需要做的事情如下:

  1. 创建角色创角协议。
  2. 在登陆服实现创角流程,创建一个角色数据并入库。
  3. 创建角色登录协议。
  4. 在登陆服解析角色登录,账号鉴权。
  5. 登陆服分配一个游戏服节点并通知角色登录。
  6. 游戏服收到角色登录信息,创建一个RoleActor并通知其进行初始化角色信息。
  7. 角色登录完毕,通过登录服下发登录成功协议给客户端。

在接到一个需求时,可以像这样先拆分需求,分析需求难点并一一实现,这样不仅思维更加清晰,代码写起来更快,也能更好地估算需求的难度和所需工时。

前置工作

在开始之前,先处理一下上一节中代码实现上不太合理的地方。

NettyClient多个实例启动的实现方式

之前我们的NettyClient在启动多个实例时,使用的是start(String hostStr, BaseNettyHandler handler) 这个方法,传入一个BaseNettyHandler,所有的NettyClient都共享这个实例。

但是笔者希望能在NettyHandler中存放一些连接相关的信息,比如游戏服所连接上的登录服信息LoginServerContext。如果共享一个handler实例,数据进来时要判断所属哪个LoginServer就会比较麻烦,所以这边修改一下NettyClient的启动方式,传入一个Class,通过反射为每个NettyClient实例创建一个Handler实例。

    public <T extends BaseNettyHandler> HashMap<String, Channel> start(String hostStr, Class<T> clz) {
        Constructor<T> declaredConstructor = null;
        try {
            declaredConstructor = clz.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        declaredConstructor.setAccessible(true);
        HashMap<String, Channel> map = new HashMap<>();
        String[] hostArray = hostStr.split(",");
        if (hostArray.length == 0) {
            log.error("hostStr split error! hostStr = {}", hostStr);
            return map;
        }
        for (String host : hostArray) {
            String[] split = host.split(":");
            if (split.length != 2) {
                log.error("host list config error! host = {}", host);
                return map;
            }
            String ip = split[0];
            int port = Integer.parseInt(split[1]);
            BaseNettyHandler nettyHandler;
            try {
                nettyHandler = declaredConstructor.newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
            Channel channel = start(ip, port, nettyHandler);
            map.put(host, channel);
        }
        return map;
    }

修改GameToLoginNettyHandler使用ProtoDispatcher进行协议分发

    /**
     * 收到协议数据
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
        Pack decode = PackCodec.decode(msg);
        int cmdId = decode.getCmdId();
        if (cmdId == ProtoEnumMsg.CMD.ID.GAME_SERVER_REGISTER_VALUE) {
            // login返回到登录服的协议
            InnerServerMsg.L2GRegister g2LRegister = InnerServerMsg.L2GRegister.parseFrom(decode.getData());
            int serverId = g2LRegister.getServerId();
            LoginServerManager loginServerManager = SpringUtils.getBean(LoginServerManager.class);
            loginServerManager.registerLoginServer(serverId, ctx.channel());
            log.info("登录服服注册,serverId={}", serverId);
            loginServerContext = loginServerManager.getLoginServerContext(serverId);
            return;
        }
        // 注册login以后都走分发
        if (loginServerContext == null) {
            log.error("登录服尚未注册");
            return;
        }
        ProtoDispatcher dispatcher = SpringUtils.getBean(ProtoDispatcher.class);
        dispatcher.dispatch(cmdId, decode.getData(), loginServerContext);
    }

Game服务注册到Login之后, 所有Game过来的协议,均使用ProtoHandler进行解码与分发, 减少工作量.

修改账号登录流程

我们需要在账号登录时, 将该账号所持角色发送给客户端, 这样玩家才能选择以哪个角色进行登录.

先修改账号登录协议AccountMsg.proto


// 角色简单信息
message SimpleRole {
    int64 roleId = 1;   // 角色id
    string name = 2;    // 角色名
    int32 level = 3;    // 等级
}

// 账号登录
message C2SAccountLogin {
    string accountName = 1; // 账号
    string password = 2;    // 密码
}
message S2CAccountLogin {
    bool success = 1;       // 是否成功
    int64 accountId = 2;    // 账号id
    repeated SimpleRole roles = 3; // 该账号拥有的角色信息
}

玩家发送账号密码,登录成功后返回账号Id以及所持有的角色信息.

先创建一个SimpleRoleCollection.java 他是用来保存角色基本信息的Mongo文档, 里面仅包含最低限度的角色信息


@Document
public class SimpleRoleCollection extends BaseCollection{
    @Id
    private long roleId;
    private long accountId;
    private String name;
    private int level;
	// getter & setter
}

生成协议, 修改LoginProtoHandler.java

    //region 账号登录
    @CMD(ProtoEnumMsg.CMD.ID.ACCOUNT_LOGIN_VALUE)
    public Pack onAccountLoginMsg(ConnectContext cc, AccountMsg.C2SAccountLogin up) {
        String accountName = up.getAccountName();
        String password = up.getPassword();
        boolean loginRes = accountLogin(cc, accountName.trim(), password.trim());
        AccountMsg.S2CAccountLogin.Builder builder = AccountMsg.S2CAccountLogin.newBuilder();
        builder.setSuccess(loginRes);
        if (loginRes) {
            builder.setAccountId(cc.getAccountId());
            // 查询该账号拥有的角色
            MongoService mongoService = SpringUtils.getBean(MongoService.class);
            Criteria criteria = Criteria.where("accountId").is(cc.getAccountId());
            List<SimpleRoleCollection> many = mongoService.findMany(criteria, SimpleRoleCollection.class);
            for (SimpleRoleCollection simpleRoleCollection : many) {
                AccountMsg.SimpleRole simpleRole = buildSimpleRoleMsg(simpleRoleCollection);
                builder.addRoles(simpleRole);
            }
        }
        return new Pack(ProtoEnumMsg.CMD.ID.ACCOUNT_LOGIN_VALUE, builder.build().toByteArray());
    }

	/**
     * 将db数据打包成Proto数据
     */
    private AccountMsg.SimpleRole buildSimpleRoleMsg(SimpleRoleCollection simpleRoleCollection) {
        AccountMsg.SimpleRole.Builder simpleRole = AccountMsg.SimpleRole.newBuilder();
        simpleRole.setRoleId(simpleRoleCollection.getRoleId());
        simpleRole.setName(simpleRoleCollection.getName());
        simpleRole.setLevel(simpleRoleCollection.getLevel());
        return simpleRole.build();
    }

    private boolean accountLogin(ConnectContext cc, String accountName, String password) {
        if (StringChecker.checkAccountNameAndPassword(accountName, password)) {
            return false;
        }

        MongoService mongoService = SpringUtils.getBean(MongoService.class);
        Criteria criteria = Criteria.where("accountName").is(accountName);
        AccountCollection accountCollection = mongoService.findOneByQuery(criteria, AccountCollection.class);
        if (accountCollection == null) {
            log.warn("login without account. accountName = {}", accountName);
            return false;
        } else if( !accountCollection.getPassword().equals(password) ) {
            log.warn("login password error. accountName = {}", accountName);
            return false;
        }
        log.info("login success. accountName = {}, accountId = {}", accountName, accountCollection.getAccountId());
        cc.setAccountId(accountCollection.getAccountId());
        cc.setAccountName(accountName);
        cc.setStatus(ConnectStatusEnum.ACCOUNT_LOGIN);
        // 添加accountId与actor映射
        ConnectActorManager connectActorManager = SpringUtils.getBean(ConnectActorManager.class);
        return connectActorManager.addConnectActorByAccountId(cc.getAccountId(), cc.getConnectId());
    }

这里在账号登陆时,读取该账号名下所有的角色SimpleRoleCollection数据,并将其组装成Protobuf协议数据,发送给客户端。

登陆成功后,设置账号登录状态并将ConnectActor与AccountId进行映射,后续可以使用AccountId进行消息转发。

修改客户端测试代码

在client下创建PlayerContext.java用来缓存账号信息,用来代表客户端的内存中存放的玩家数据。

package org.client;

import org.protobuf.AccountMsg;

import java.util.List;

public class PlayerContext {

    long accountId;

    long roleId;

    List<AccountMsg.SimpleRole> rolesList;

    public PlayerContext(long accountId) {
        this.accountId = accountId;
    }

    public void setRolesList(List<AccountMsg.SimpleRole> rolesList) {
        this.rolesList = rolesList;
    }
}

当账号登录成功时,创建PlayerContext,并将返回的角色列表保存起来用于选角登录。

修改ClientProtoHandler

@Slf4j
@Component
public class ClientProtoHandler extends BaseProtoHandler {

    PlayerContext playerContext;

    @CMD(ProtoEnumMsg.CMD.ID.ACCOUNT_REGISTER_VALUE)
    public void accountRegisterCallback(Channel channel, AccountMsg.S2CAccountRegister down) throws InvalidProtocolBufferException {
        log.info(JsonFormat.printer().print(down));
    }
    @CMD(ProtoEnumMsg.CMD.ID.ACCOUNT_LOGIN_VALUE)
    public void accountLoginCallback(Channel channel, AccountMsg.S2CAccountLogin down) throws InvalidProtocolBufferException {
        log.info(JsonFormat.printer().print(down));
        if (!down.getSuccess()) {
            return;
        } 
        // 登陆成功
        playerContext = new PlayerContext(down.getAccountId());
        // 保存角色列表
        List<AccountMsg.SimpleRole> rolesList = down.getRolesList();
        playerContext.setRolesList(rolesList);
    }
}

账号登录流程至此修改完毕,后面处理角色创建于登录流程。

编写角色创建与登录协议

角色创建

角色的创建流程依旧在LoginServer进行处理,当客户端创建角色时,在LoginServer创建一个SimpleRoleCollection并写入MongoDB。

增加角色创建的proto

创建角色的流程一般是由客户端界面先行对角色数据进行整理,如:捏脸数据、性别选择、职业选择、姓名选择。
最后一次性发送到服务端进行角色创建处理。
修改AccountMsg.proto,增加角色创建和登录的proto

// 创建角色
message C2SRoleCreate {
    string name = 1;        // 角色名
}
message S2CRoleCreate {
    SimpleRole simpleRole = 1;// 角色数据
}

// 角色登录
message C2SRoleLogin {
    int64 roleId = 1;   // 角色id
}
message S2CRoleLogin {
    bool success = 1;   // 是否成功
}

在ProtoEnumMsg中定义一下协议号

        // 角色创建
        ROLE_CREATE = 10201;
        // 角色登录
        ROLE_LOGIN = 10202;
创建角色业务代码开发

在LoginProtoHandler进行角色创建协议的监听以及业务逻辑开发。

	//region 创建角色
    @CMD(ProtoEnumMsg.CMD.ID.ROLE_CREATE_VALUE)
    public Pack onRoleCreateMsg(ConnectContext cc, AccountMsg.C2SRoleCreate up) {
        String name = up.getName();
        // TODO 重名判断 角色数量上限判断
        SimpleRoleCollection simpleRoleCollection = new SimpleRoleCollection();
        simpleRoleCollection.setAccountId(cc.getAccountId());
        simpleRoleCollection.setName(name);
        simpleRoleCollection.setLevel(1); // 默认1级
        long roleId = GenIdUtils.genRoleId();// 生成角色Id
        simpleRoleCollection.setRoleId(roleId);
        MongoService mongoService = SpringUtils.getBean(MongoService.class);
        mongoService.insert(simpleRoleCollection);
        AccountMsg.SimpleRole simpleRole = buildSimpleRoleMsg(simpleRoleCollection);
        AccountMsg.S2CRoleCreate.Builder builder = AccountMsg.S2CRoleCreate.newBuilder();
        builder.setSimpleRole(simpleRole);
        return new Pack(ProtoEnumMsg.CMD.ID.ROLE_CREATE_VALUE, builder.build().toByteArray());
    }
    //endregion

这边收到创角协议,创建SimpleRoleCollection,生成角色Id并写入Mongo中,然后返回给客户端更新角色列表。
我这边暂时没有写重名判断和角色数量上限判断,这些根据不同的策划需求会有不同的逻辑。

在LoginServer的角色创建中,我们只进行了SimpleRoleCollection的数据创建,这个数据仅包含最低限度的角色信息,而具体的角色信息我规划在RoleCollection(我称之为角色全量数据)中。而RoleCollection的数据初始化则放在角色登陆时,由GameServer节点进行创建。

角色登录

角色登录和之前相比会比较复杂,因为之前的所有协议,都只限定在LoginServer服务上,没有遇到分布式相关逻辑,而接下来的角色登录,我们会先在LoginServer进行基础的登录权限判断,然后发送到GameServer进行真正的登录流程。最后GameServer会将登陆成功返回到LoginServer,并通知客户端登陆成功。

登录服逻辑

在LoginProtoHandler进行角色登录协议的监听以及业务逻辑开发。

//region 角色登录
    @CMD(ProtoEnumMsg.CMD.ID.ROLE_LOGIN_VALUE)
    public Pack onRoleLogin(ConnectContext cc, AccountMsg.C2SRoleLogin up) {
        long roleId = up.getRoleId();
        MongoService mongoService = SpringUtils.getBean(MongoService.class);
        SimpleRoleCollection simpleRoleCollection = mongoService.findById(roleId, SimpleRoleCollection.class);
        if (simpleRoleCollection == null) {
            log.error("没有该角色, roleId = {}, accountId = {}", roleId, cc.getAccountId());
            sendLoginError(cc);
            return null;
        }
        if (simpleRoleCollection.getAccountId() != cc.getAccountId()) {
            log.error("所选角色不归属于该账号, roleId = {}, accountId = {}, cc.accountId = {}", roleId, simpleRoleCollection.getAccountId(), cc.getAccountId());
            sendLoginError(cc);
            return null;
        }
        // 分配一个game服务id
        GameServerManger gameServerManger = SpringUtils.getBean(GameServerManger.class);
        GameServerContext gameServerContext = gameServerManger.randomGameServer();
        if (gameServerContext == null) {
            log.error("游戏服节点尚未初始化完毕。");
            sendLoginError(cc);
            return null;
        }
        // 通知到GameServer
        InnerServerMsg.L2GRoleLogin.Builder builder = InnerServerMsg.L2GRoleLogin.newBuilder();
        builder.setRoleId(roleId);
        Pack pack = new Pack(ProtoEnumMsg.CMD.ID.NOTIFY_GAME_ROLE_LOGIN_VALUE, builder);
        gameServerContext.getChannel().writeAndFlush(PackCodec.encode(pack));

        return null;
    }

    /**
     * 向客户端推送角色登录失败
     */
    private void sendLoginError(ConnectContext cc) {
        AccountMsg.S2CRoleLogin.Builder builder = AccountMsg.S2CRoleLogin.newBuilder();
        builder.setSuccess(false);
        Pack pack = new Pack(ProtoEnumMsg.CMD.ID.ROLE_LOGIN_VALUE, builder);
        cc.getClientChannel().writeAndFlush(PackCodec.encode(pack));
    }

LoginServer做的事情不多,先对传过来的角色做一个基础的鉴权,然后分配一个已注册的GameServer,修改自己的RoleId和状态,将登录信息转发到GameServer。

然后我们给登录服也增加一个InnerServerProtoHandler,用来处理游戏服务发送过来的信息。
从GameServer复制一份InnerServerProtoHandler过来,然后内部业务代码都删掉。改成我们想要的。

    /**
     * 角色登录回调
     */
    @CMD(ProtoEnumMsg.CMD.ID.NOTIFY_GAME_ROLE_LOGIN_VALUE)
    public Pack notifyGameRoleLogin(GameServerContext gc, InnerServerMsg.G2LRoleLogin up) {
        long roleId = up.getRoleId();
        long accountId = up.getAccountId();
        log.info("角色登录GameServer成功, roleId = {}, accountId = {}", roleId, accountId);
        ConnectActorManager connectActorManager = SpringUtils.getBean(ConnectActorManager.class);
        ActorRef<BaseMsg> connectActor = connectActorManager.getConnectActorByAccountId(accountId);
        RoleLoginMsg roleLoginMsg = new RoleLoginMsg();
        roleLoginMsg.setRoleId(roleId);
        roleLoginMsg.setAccountId(accountId);
        connectActor.tell(roleLoginMsg);
        return null;
    }

当GameServer处理完登录流程,需要通知登录服,登录服处理账号状态修改。看看ConnectActor如何处理:

    @Override
    protected void onCreateReceive(ReceiveBuilder<BaseMsg> builder) {
        ...
        builder.onMessage(RoleLoginMsg.class, this::onRoleLoginMsg);
    }

    /**
     * 角色在GameServer登录成功
     */
    private Behavior<BaseMsg> onRoleLoginMsg(RoleLoginMsg msg) {
        connectContext.setRoleId(msg.getRoleId());
        connectContext.setStatus(ConnectStatusEnum.ROLE_LOGIN);
        // 返回数据给客户端
        AccountMsg.S2CRoleLogin.Builder builder = AccountMsg.S2CRoleLogin.newBuilder();
        builder.setSuccess(true);
        connectContext.push(new Pack(ProtoEnumMsg.CMD.ID.ROLE_LOGIN_VALUE, builder));
        return this;
    }

简单的设置一下角色id以及帐号状态改为角色已登录,并通知客户端角色登录完成,可以进行下一步。

最后处理一下LoginToGameNettyHandler,使得GameServer过来的协议除了注册协议,其他都转发到ProtoHandler中。


    // 登录服ctx自定义属性
    private static final AttributeKey<HashMap<String, Object>> gameServerSocketAttr = AttributeKey.valueOf("GameServerSocketAttr");

    /**
     * 收到协议数据
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
        HashMap<String, Object> attr = ctx.channel().attr(gameServerSocketAttr).get();
        if (attr == null) {
            attr = new HashMap<>();
            ctx.channel().attr(gameServerSocketAttr).set(attr);
        }
        Pack decode = PackCodec.decode(msg);
        int cmdId = decode.getCmdId();
        GameServerManger gameServerManger = SpringUtils.getBean(GameServerManger.class);
        // 游戏服注册
        if (cmdId == ProtoEnumMsg.CMD.ID.GAME_SERVER_REGISTER_VALUE) {
            InnerServerMsg.G2LRegister g2LRegister = InnerServerMsg.G2LRegister.parseFrom(decode.getData());
            int serverId = g2LRegister.getServerId();
            log.info("游戏服注册,serverId={}", serverId);
            gameServerManger.registerGameServer(serverId, ctx.channel());
            LoginConfig loginConfig = SpringUtils.getBean(LoginConfig.class);
            InnerServerMsg.L2GRegister.Builder builder = InnerServerMsg.L2GRegister.newBuilder();
            builder.setSuccess(true);
            builder.setServerId(loginConfig.getServerId());
            Pack pack = new Pack(cmdId, builder);
            ctx.writeAndFlush(PackCodec.encode(pack));
            attr.put("serverId", serverId);
            return;
        }
        int serverId = (int) attr.get("serverId");
        GameServerContext gameServer = gameServerManger.getGameServer(serverId);
        if (gameServer == null) {
            log.error("GameServer is null. serverId = {}", serverId);
            return;
        }
        ProtoDispatcher dispatcher = SpringUtils.getBean(ProtoDispatcher.class);
        dispatcher.dispatch(decode, gameServer);
    }

我们在ctx中开辟了一个hashmap用来存放gameServer的一些数据,这里只存放serverId。
服务注册后将其保存,后续过来的所有协议通过ctx中的serverId,转发到对应地协议处理模块中。

游戏服逻辑

游戏服接收到角色登陆协议,创建一个RoleActor,用来处理角色相关信息,然后具体的角色数据初始化放到RoleActor里面做。

以下的代码跟LoginServer的ConnectActor和ConnectActorManager的流程基本一致,大家可以对照着ConnectActor相关逻辑自己写一下。

我们先创建RoleActor以及角色管理器RoleManager

package org.game.obj;

import ...

/**
 * 角色Actor
 */
@Slf4j
public class RoleActor extends BaseActor {

    private final RoleContext rc;

    public RoleContext getRc() {
        return rc;
    }

    public RoleActor(ActorContext<BaseMsg> context, SimpleRoleCollection simpleRoleCollection, Channel channel) {
        super(context);
        this.rc = new RoleContext();
        this.rc.setSimpleRoleCollection(simpleRoleCollection);
        this.rc.setAccountId(simpleRoleCollection.getAccountId());
        this.rc.setRoleId(simpleRoleCollection.getRoleId());
        this.rc.setChannel(channel);
    }

    /**
     * 生成一个RoleActor
     */
    public static Behavior<BaseMsg> create(SimpleRoleCollection simpleRoleCollection, Channel channel) {
        return Behaviors.setup(context -> new RoleActor(context, simpleRoleCollection, channel));
    }

    @Override
    protected void onCreateReceive(ReceiveBuilder<BaseMsg> builder) {
    }

}

onRoleLoginMsg用来处理角色登录信息,这里去Mongo中找RoleCollection,当没有找到时,则创建一个。这边我们先给个空的结构,后面我们会对角色的全量数据这一块进行结构规范并统一管理。

RoleContext.java 用来存放玩家的数据在内存中。

package org.game.obj;

import ...

/**
 * 角色缓存
 */
public class RoleContext {

    private SimpleRoleCollection simpleRoleCollection;
    private long accountId;
    private long roleId;
    private Channel channel;

    /**
     * 角色初始化
     */
    public void initRoleContext() {
    }

    // getter & setter

    /**
     * 推送消息到客户端
     */
    public void push(Pack pack) {
        byte[] encode = PackCodec.encode(pack);
        channel.writeAndFlush(PackCodec.encode(ProtoEnumMsg.CMD.ID.PUSH_PROTO_TO_CLIENT_VALUE, encode));
    }
}

RoleManager.java 用来管理角色信息

@Slf4j
@Component
public class RoleManager {

    private final Map<Long, ActorRef<BaseMsg>> roleActorMap = new HashMap<>();

    public ActorRef<BaseMsg> createRoleActor(SimpleRoleCollection simpleRoleCollection, Channel channel) {
        long roleId = simpleRoleCollection.getRoleId();
        ActorRef<BaseMsg> actor = AkkaContext.createActor(RoleActor.create(simpleRoleCollection, channel), String.valueOf(roleId));
        roleActorMap.put(roleId, actor);
        return actor;
    }

    public ActorRef<BaseMsg> getRoleActor(long roleId) {
        return roleActorMap.get(roleId);
    }
}

以上的代码我们模仿ConnectActor创建与管理的方式,增加了RoleActor。后面从LoginServer过来的所有与角色相关的协议,均会被转发到RoleActor中进行处理。

最后我们修改一下InnerServerProtoHandler,监听LoginServer上报的登录协议。

@Slf4j
@Component
public class InnerServerProtoHandler extends BaseProtoHandler {

    /**
     * 角色登录
     */
    @CMD(ProtoEnumMsg.CMD.ID.NOTIFY_GAME_ROLE_LOGIN_VALUE)
    public Pack notifyGameRoleLogin(LoginServerContext lc, InnerServerMsg.L2GRoleLogin up) {
        long roleId = up.getRoleId();
        log.info("角色登录, roleId = {}", roleId);
        MongoService mongoService = SpringUtils.getBean(MongoService.class);
        // 创建actor
        SimpleRoleCollection simpleRoleCollection = mongoService.findById(roleId, SimpleRoleCollection.class);
        if (simpleRoleCollection == null) {
            log.error("没有该角色, roleId = {}", roleId);
            return null;
        }
        RoleManager roleManager = SpringUtils.getBean(RoleManager.class);
        ActorRef<BaseMsg> roleActor = roleManager.createRoleActor(simpleRoleCollection, lc.getChannel());
        RoleLoginMsg roleLoginMsg = new RoleLoginMsg();
        roleActor.tell(roleLoginMsg);
        return null;
    }
}

在登录协议中我们创建了一个RoleActor,然后向RoleActor发送了一个RoleLoginMsg进行角色登录通知。
我们需要在RoleActor中,增加对RoleLoginMsg消息的处理。

    @Override
    protected void onCreateReceive(ReceiveBuilder<BaseMsg> builder) {
        builder.onMessage(RoleLoginMsg.class, this::onRoleLoginMsg);
    }
    /**
     * 角色登录信息
     * 进行角色数据初始化
     */
    private Behavior<BaseMsg> onRoleLoginMsg(RoleLoginMsg msg) {
        MongoService mongoService = SpringUtils.getBean(MongoService.class);
        RoleCollection roleCollection = mongoService.findById(rc.getRoleId(), RoleCollection.class);
        if (roleCollection == null) {
            // 没有角色数据,创建一个
            roleCollection = new RoleCollection();
            roleCollection.setAccountId(msg.getAccountId());
            roleCollection.setRoleId(msg.getRoleId());
            mongoService.insert(roleCollection);
        }
        // 返回给Login
        InnerServerMsg.G2LRoleLogin.Builder builder = InnerServerMsg.G2LRoleLogin.newBuilder();
        builder.setRoleId(rc.getRoleId());
        builder.setAccountId(rc.getAccountId());
        rc.getChannel().writeAndFlush(PackCodec.encode(ProtoEnumMsg.CMD.ID.NOTIFY_GAME_ROLE_LOGIN_VALUE, builder.build().toByteArray()));
        log.info("roleActor,角色登录 roleId = {}", rc.getRoleId());
        return this;
    }

总结

本节我们对角色创建和登录做了基础开发,要实际使用还需要增加很多的逻辑处理。
比如创建角色时判断重名、角色数量。

本节的内容比较多,大家可以先大概地看一下实现思路,具体代码可以不看,过段时间我将该架构整理得大概能用时,会将git地址公开出来由大家一起学习。

再者,角色登录时数据初始化也是非常重要的一环,目前我对于角色的数据存储方式还在构思,希望db这一块能对开发人员做到无感,即能够自动收集修改进行写入,而无需业务开发人员过多考虑数据的读写。

db这块需要一些时间进行打磨,下一节可能没有那么快出来。感兴趣的伙伴可以点个关注或者收藏,这样有更新的时候会收到推送。

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值