同上一篇文章类似,对于GameMsgRecognizer我们也需要用同样的方式重构,直接上代码
重构GameMsgRecognizer
1.修改GameMsgRecognizer
package com.tk.tinygame.herostory;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.Message;
import com.tk.tinygame.herostory.msg.GameMsgProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* 消息识别器
*/
public final class GameMsgRecognizer {
/**
* 日志对象
*/
static private final Logger LOGGER = LoggerFactory.getLogger(GameMsgRecognizer.class);
/**
* 消息编号 -> 消息对象字典
*/
static private final Map<Integer, GeneratedMessageV3> _msgCodeAndMsgObjMap = new HashMap<>();
/**
* 消息类 -> 消息编号字典
*/
static private final Map<Class<?>, Integer> _msgClazzAndMsgCodeMap = new HashMap<>();
/**
* 私有化类默认构造器
*/
private GameMsgRecognizer() { }
/**
* 初始化
*/
static public void init() {
//_msgCodeAndMsgObjMap.put(GameMsgProtocol.MsgCode.USER_ENTRY_CMD_VALUE, GameMsgProtocol.UserEntryCmd.getDefaultInstance());
//_msgCodeAndMsgObjMap.put(GameMsgProtocol.MsgCode.WHO_ELSE_IS_HERE_CMD_VALUE, GameMsgProtocol.WhoElseIsHereCmd.getDefaultInstance());
//_clazzAndMsgCodeMap.put(GameMsgProtocol.UserEntryResult.class, GameMsgProtocol.MsgCode.USER_ENTRY_RESULT_VALUE);
//_clazzAndMsgCodeMap.put(GameMsgProtocol.WhoElseIsHereResult.class, GameMsgProtocol.MsgCode.WHO_ELSE_IS_HERE_RESULT_VALUE);
//_clazzAndMsgCodeMap.put(GameMsgProtocol.UserQuitResult.class, GameMsgProtocol.MsgCode.USER_QUIT_RESULT_VALUE);
LOGGER.info("==== 完成消息类与消息编号的映射 ====");
// 获取内部类
Class<?>[] innerClazzArray = GameMsgProtocol.class.getDeclaredClasses();
for (Class<?> innerClazz : innerClazzArray) {
if (null == innerClazz ||
!GeneratedMessageV3.class.isAssignableFrom(innerClazz)) {
// 如果不是消息类,
continue;
}
// 获取类名称并小写
String clazzName = innerClazz.getSimpleName();
clazzName = clazzName.toLowerCase();
for (GameMsgProtocol.MsgCode msgCode : GameMsgProtocol.MsgCode.values()) {
if (null == msgCode) {
continue;
}
// 获取消息编码
String strMsgCode = msgCode.name();
strMsgCode = strMsgCode.replaceAll("_", "");
strMsgCode = strMsgCode.toLowerCase();
if (!strMsgCode.startsWith(clazzName)) {
continue;
}
try {
// 相当于调用 UserEntryCmd.getDefaultInstance();
Object returnObj = innerClazz.getDeclaredMethod("getDefaultInstance").invoke(innerClazz);
LOGGER.info(
"{} <==> {}",
innerClazz.getName(),
msgCode.getNumber()
);
_msgCodeAndMsgObjMap.put(
msgCode.getNumber(),
(GeneratedMessageV3) returnObj
);
_msgClazzAndMsgCodeMap.put(
innerClazz,
msgCode.getNumber()
);
} catch (Exception ex) {
// 记录错误日志
LOGGER.error(ex.getMessage(), ex);
}
}
}
}
/**
* 根据消息编号获取消息构建器
*
* @param msgCode
* @return
*/
static public Message.Builder getBuilderByMsgCode(int msgCode) {
if (msgCode < 0) {
return null;
}
GeneratedMessageV3 defaultMsg = _msgCodeAndMsgObjMap.get(msgCode);
if (null == defaultMsg) {
return null;
} else {
return defaultMsg.newBuilderForType();
}
}
/**
* 根据消息类获取消息编号
*
* @param msgClazz
* @return
*/
static public int getMsgCodeByClazz(Class<?> msgClazz) {
if (null == msgClazz) {
return -1;
}
Integer msgCode = _msgClazzAndMsgCodeMap.get(msgClazz);
if (null == msgCode) {
return -1;
} else {
return msgCode.intValue();
}
}
}
至此,使用反射重构结束,为了验证效果,我们添加移动消息和攻击消息,测试我们的重构是否成功,当我们直接增加移动消息和攻击消息时,直接添加对应的消息处理类,应该是可以直接使用的。
1.增加移动消息:UserMoveToCmdHandler
package com.tk.tinygame.herostory.cmdhandler;
import com.tk.tinygame.herostory.Broadcaster;
import com.tk.tinygame.herostory.msg.GameMsgProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
/**
* 用户移动指令处理器
*/
public class UserMoveToCmdHandler implements ICmdHandler<GameMsgProtocol.UserMoveToCmd>{
@Override
public void handle(ChannelHandlerContext ctx, GameMsgProtocol.UserMoveToCmd cmd) {
if (null == ctx
|| null == cmd) {
return;
}
// 获取用户 Id
Integer userId = (Integer)ctx.channel().attr(AttributeKey.valueOf("userId")).get();
if (null == userId) {
return;
}
GameMsgProtocol.UserMoveToResult.Builder resultBuilder = GameMsgProtocol.UserMoveToResult.newBuilder();
resultBuilder.setMoveUserId(userId);
resultBuilder.setMoveToPosX(cmd.getMoveToPosX());
resultBuilder.setMoveToPosY(cmd.getMoveToPosY());
GameMsgProtocol.UserMoveToResult newResult = resultBuilder.build();
//把用户移动消息发送给所有用户
Broadcaster.broadcast(newResult);
}
}
2.增加攻击消息:UserAttkCmdHandler
package com.tk.tinygame.herostory.cmdhandler;
import com.tk.tinygame.herostory.Broadcaster;
import com.tk.tinygame.herostory.msg.GameMsgProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
/**
* 用户攻击指令处理器
*/
public class UserAttkCmdHandler implements ICmdHandler<GameMsgProtocol.UserAttkCmd>{
@Override
public void handle(ChannelHandlerContext ctx, GameMsgProtocol.UserAttkCmd cmd) {
if (null == ctx ||
null == cmd) {
return;
}
// 获取攻击者 Id
Integer attkUserId = (Integer) ctx.channel().attr(AttributeKey.valueOf("userId")).get();
if (null == attkUserId) {
return;
}
// 获取被攻击者 Id
int targetUserId = cmd.getTargetUserId();
GameMsgProtocol.UserAttkResult.Builder resultBuilder = GameMsgProtocol.UserAttkResult.newBuilder();
resultBuilder.setAttkUserId(attkUserId);
resultBuilder.setTargetUserId(targetUserId);
GameMsgProtocol.UserAttkResult newResult = resultBuilder.build();
Broadcaster.broadcast(newResult);
// 减血消息, 可以根据自己的喜好写...
// 例如加上装备加成, 暴击等等.
// 这些都属于游戏的业务逻辑了!
GameMsgProtocol.UserSubtractHpResult.Builder resultBuilder2 = GameMsgProtocol.UserSubtractHpResult.newBuilder();
resultBuilder2.setTargetUserId(targetUserId);
//简单的逻辑,打一下减10血
resultBuilder2.setSubtractHp(10);
GameMsgProtocol.UserSubtractHpResult newResult2 = resultBuilder2.build();
Broadcaster.broadcast(newResult2);
}
}
3.更新用户入场消息:UserEntryCmdHandler
在移动消息和攻击消息类中,我们需要通过ctx.channel().attr(AttributeKey.valueOf("userId")).get()获取用户id,此时我们需要先给其赋值才可以使用,所以,我们可以在用户入场消息时,添加对应的用户userId
代码如下:
在UserEntryCmdHandler类中添加:ctx.channel().attr(AttributeKey.valueOf("userId")).set(userId);
package com.tk.tinygame.herostory.cmdhandler;
import com.tk.tinygame.herostory.Broadcaster;
import com.tk.tinygame.herostory.User;
import com.tk.tinygame.herostory.UserManager;
import com.tk.tinygame.herostory.msg.GameMsgProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
/**
* 处理用户入场
*/
public class UserEntryCmdHandler implements ICmdHandler<GameMsgProtocol.UserEntryCmd>{
@Override
public void handle(ChannelHandlerContext ctx, GameMsgProtocol.UserEntryCmd cmd) {
//空值判断
if (null == ctx || null == cmd) {
return;
}
//
// 处理用户入场消息
//
int userId = cmd.getUserId(); //用户id
String heroAvatar = cmd.getHeroAvatar(); //英雄形象
//将登录的用户加入用户字典
User newUser = new User();
newUser.userId = userId;
newUser.heroAvatar = heroAvatar;
UserManager.addUser(newUser);
GameMsgProtocol.UserEntryResult.Builder resultBuilder = GameMsgProtocol.UserEntryResult.newBuilder();
resultBuilder.setUserId(userId);
resultBuilder.setHeroAvatar(heroAvatar);
// 将用户 Id 附着到 Channel
ctx.channel().attr(AttributeKey.valueOf("userId")).set(userId);
// 构建结果并广播
GameMsgProtocol.UserEntryResult newResult = resultBuilder.build();
Broadcaster.broadcast(newResult);
}
}
4.测试效果:http://cdn0001.afrxvk.cn/hero_story/demo/step010/index.html?serverAddr=127.0.0.1:12345&userId=2
可以发现:对应的入场,移动,攻击等消息都可以正确的显示,如果从后台修改一下攻击的血量,也可以做出对应的扣血操作