导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客
到现在,我们切实需要一个客户端来完整的进行英雄选择,选择地图,打怪等等功能。所以我们需要把之前极为简陋的客户端改造一下。
首先再common模块中增加打印颜色的工具类:ConsoleColors
package com.loveprogrammer.utils;
/**
*
* @version 1.0.0
* @description:
* @author: eric
* @date: 2024-02-18 09:41
**/
public class ConsoleColors {
// Reset
public static final String RESET = "\033[0m"; // Text Reset
// Regular Colors
public static final String BLACK = "\033[0;30m"; // BLACK
public static final String RED = "\033[0;31m"; // RED
public static final String GREEN = "\033[0;32m"; // GREEN
public static final String YELLOW = "\033[0;33m"; // YELLOW
public static final String BLUE = "\033[0;34m"; // BLUE
public static final String PURPLE = "\033[0;35m"; // PURPLE
public static final String CYAN = "\033[0;36m"; // CYAN
public static final String WHITE = "\033[0;37m"; // WHITE
// Bold
public static final String BLACK_BOLD = "\033[1;30m"; // BLACK
public static final String RED_BOLD = "\033[1;31m"; // RED
public static final String GREEN_BOLD = "\033[1;32m"; // GREEN
public static final String YELLOW_BOLD = "\033[1;33m"; // YELLOW
public static final String BLUE_BOLD = "\033[1;34m"; // BLUE
public static final String PURPLE_BOLD = "\033[1;35m"; // PURPLE
public static final String CYAN_BOLD = "\033[1;36m"; // CYAN
public static final String WHITE_BOLD = "\033[1;37m"; // WHITE
// Underline
public static final String BLACK_UNDERLINED = "\033[4;30m"; // BLACK
public static final String RED_UNDERLINED = "\033[4;31m"; // RED
public static final String GREEN_UNDERLINED = "\033[4;32m"; // GREEN
public static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOW
public static final String BLUE_UNDERLINED = "\033[4;34m"; // BLUE
public static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLE
public static final String CYAN_UNDERLINED = "\033[4;36m"; // CYAN
public static final String WHITE_UNDERLINED = "\033[4;37m"; // WHITE
// Background
public static final String BLACK_BACKGROUND = "\033[40m"; // BLACK
public static final String RED_BACKGROUND = "\033[41m"; // RED
public static final String GREEN_BACKGROUND = "\033[42m"; // GREEN
public static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOW
public static final String BLUE_BACKGROUND = "\033[44m"; // BLUE
public static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLE
public static final String CYAN_BACKGROUND = "\033[46m"; // CYAN
public static final String WHITE_BACKGROUND = "\033[47m"; // WHITE
// High Intensity
public static final String BLACK_BRIGHT = "\033[0;90m"; // BLACK
public static final String RED_BRIGHT = "\033[0;91m"; // RED
public static final String GREEN_BRIGHT = "\033[0;92m"; // GREEN
public static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOW
public static final String BLUE_BRIGHT = "\033[0;94m"; // BLUE
public static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLE
public static final String CYAN_BRIGHT = "\033[0;96m"; // CYAN
public static final String WHITE_BRIGHT = "\033[0;97m"; // WHITE
// Bold High Intensity
public static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACK
public static final String RED_BOLD_BRIGHT = "\033[1;91m"; // RED
public static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREEN
public static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOW
public static final String BLUE_BOLD_BRIGHT = "\033[1;94m"; // BLUE
public static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLE
public static final String CYAN_BOLD_BRIGHT = "\033[1;96m"; // CYAN
public static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE
// High Intensity backgrounds
public static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACK
public static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// RED
public static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREEN
public static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOW
public static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUE
public static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLE
public static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m"; // CYAN
public static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m"; // WHITE
public static void main(String[] args) {
System.out.println(ConsoleColors.RED_BOLD_BRIGHT + "肩甲");
System.out.println(ConsoleColors.RED_BOLD + "肩甲");
}
}
增加统一打印工具类:ConsolePrint
package com.loveprogrammer.console;
import com.alibaba.fastjson2.util.DateUtils;
import java.util.Date;
/**
* @version 1.0.0
* @description: 输出类
* @author: eric
* @date: 2024-02-18 16:55
**/
public class ConsolePrint {
private static final String space = "\t\t\t\t\t\t\t\t";
public static void publishMessage(String content,int position) {
String format = DateUtils.format(new Date(),DateUtils.DateTimeFormatPattern.DATE_TIME_FORMAT_19_DASH.pattern);
String threadName = Thread.currentThread().getName();
if(position == 0) {
System.out.print(content);
}else if(position == 1) {
System.out.println(content);
}else {
System.out.println(space + content);
}
}
public static void publishMessage(String content) {
System.out.println(content);
}
public static void publishMessagePrint(String content,String placeholder) {
System.out.print(content + placeholder);
}
}
修改command模块的结构,把tag根据不同的topic拆分到不同的类中,方便维护。
之前的客户端就是简单的nettyclient,但是现在客户端也要解析topic和tag,所以我们根据server来改造客户端。大致结构如下:
客户端的监听类- NetworkClientListener
package com.loveprogrammer.network;
import com.alibaba.fastjson2.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.client.ClientMenuTag;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.handler.HandlerFactory;
import com.loveprogrammer.listener.INetworkEventListener;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class NetworkClientListener implements INetworkEventListener {
protected static final Logger logger = LoggerFactory.getLogger(NetworkClientListener.class);
private NetworkClientListener(){
}
private static final NetworkClientListener instance = new NetworkClientListener();
public static NetworkClientListener getInstance(){
return instance;
}
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
2,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder()
.setNameFormat("worker-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
/***
* 同客户端转发
* @param ctx
* @param topic
* @param tag
* @param msg
*/
public void forward(ChannelHandlerContext ctx, int topic, int tag, String msg) {
StringMessage data = new StringMessage();
data.setTopicId(topic);
data.setTagId(tag);
data.setBody(msg);
channelRead(ctx,data);
}
@Override
public void onConnected(ChannelHandlerContext ctx) {
}
@Override
public void onDisconnected(ChannelHandlerContext ctx) {
}
@Override
public void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
}
@Override
public void channelRead(ChannelHandlerContext ctx, StringMessage msg) {
int topicId = msg.getTopicId();
int tagId = msg.getTagId();
Object handler = HandlerFactory.handlerMap.get(topicId);
if (handler == null) {
logger.warn("未获取到指定的消息监听对象,topicId {}", topicId);
return;
}
String bodyValue = msg.getBody();
executor.execute(() -> {
try {
Class<?> handlerClass = handler.getClass();
// 找到tag 遍历methods
Method[] methods = handlerClass.getMethods();
for (Method method : methods) {
TagListener mqListener = method.getAnnotation(TagListener.class);
if (tagId == mqListener.tag()) {
Class<?> aClass = mqListener.messageClass();
String name = aClass.getName();
// 先处理基本类型
if ("java.lang.String".equals(name)) {
method.invoke(handler, ctx, bodyValue);
} else if ("java.lang.Long".equals(name)) {
Long object = Long.parseLong(bodyValue);
method.invoke(handler, ctx, object);
} else if ("java.lang.Integer".equals(name)) {
Integer object = Integer.parseInt(bodyValue);
method.invoke(handler, ctx, object);
} else if ("java.lang.Short".equals(name)) {
Short object = Short.parseShort(bodyValue);
method.invoke(handler, ctx, object);
} else if ("java.lang.Byte".equals(name)) {
Byte object = Byte.parseByte(bodyValue);
method.invoke(handler, ctx, object);
} else if ("java.lang.Double".equals(name)) {
Double object = Double.parseDouble(bodyValue);
method.invoke(handler, ctx, object);
} else if ("java.lang.Float".equals(name)) {
Float object = Float.parseFloat(bodyValue);
method.invoke(handler, ctx, object);
}
// 转对象类型
else {
Object object = JSON.parseObject(bodyValue, aClass);
method.invoke(handler, ctx, object);
}
break;
}
}
} catch (Exception e) {
logger.error("发生异常", e);
// 转发到首页
forward(ctx, ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_PORTAL, msg.getBody());
}
});
}
}
客户端菜单监听- MenuHandler
package com.loveprogrammer.handler.support;
import com.loveprogrammer.command.IHandler;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.anotation.TopicListener;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.command.client.ClientMenuTag;
import com.loveprogrammer.console.ConsolePrint;
import com.loveprogrammer.network.NetworkClientListener;
import com.loveprogrammer.utils.ScannerInput;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @ClassName MenuHandler
* @Description TODO
* @Author admin
* @Date 2024/2/18 17:37
* @Version 1.0
*/
@TopicListener(topic = ClientTopic.TOPIC_MENU)
public class MenuHandler implements IHandler {
public static final Logger log = LoggerFactory.getLogger(MenuHandler.class);
@TagListener(tag = ClientMenuTag.TAG_MENU_PORTAL,messageClass = String.class)
public void portalMenu(ChannelHandlerContext ctx, String msg){
// 展示首页数据
ConsolePrint.publishMessage("请选择您要进行的操作");
ConsolePrint.publishMessage("【1.打怪】 【2.装备】 【3.战兽】");
ConsolePrint.publishMessage("【4.冒险者工会】 【5.副本】 【6.工会】 ");
ConsolePrint.publishMessage("【8.配置】 【9.退出】");
ConsolePrint.publishMessage("请选择:");
int choose = ScannerInput.inputInt(1, 9, 9);
while (choose != 9) {
switch (choose) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
default:
ConsolePrint.publishMessage("暂未开放,敬请期待", 1);
break;
}
ConsolePrint.publishMessage("请选择您要进行的操作");
ConsolePrint.publishMessage("【1.打怪】 【2.装备】 【3.战兽】");
ConsolePrint.publishMessage("【4.冒险者工会】 【5.副本】 【6.工会】 ");
ConsolePrint.publishMessage("【8.配置】 【9.退出】");
ConsolePrint.publishMessage("请选择:");
choose = ScannerInput.inputInt(1, 9, 9);
}
// 这里不退出,而是返回首页,做一个重定向
NetworkClientListener.getInstance().forward(ctx,ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_PORTAL,msg);
}
}
剩余的改动这里就不一一赘述了,大家可以根据代码来看下调整的地方。本章对结构调整的有点大,会单独新增一个tag方便大家对比。
客户端运行后效果如下:
10:06:15.602 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.handler.HandlerFactory:32 --- 初始化消息监听器成功 com.loveprogrammer.handler.support.MenuHandler
请选择您要进行的操作
【1.打怪】 【2.装备】 【3.战兽】
【4.冒险者工会】 【5.副本】 【6.工会】
【8.配置】 【9.退出】
请选择:
1
暂未开放,敬请期待
请选择您要进行的操作
【1.打怪】 【2.装备】 【3.战兽】
【4.冒险者工会】 【5.副本】 【6.工会】
【8.配置】 【9.退出】
请选择:
全部源码详见:
gitee : eternity-online: 多人在线mmo游戏 - Gitee.com
分支:step-09
请各位帅哥靓女帮忙去gitee上点个星星,谢谢!