从零开始手写mmo游戏从框架到爆炸(十五)— 命令行客户端改造

本文主要是介绍从零开始手写mmo游戏从框架到爆炸(十五)— 命令行客户端改造,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客         

       到现在,我们切实需要一个客户端来完整的进行英雄选择,选择地图,打怪等等功能。所以我们需要把之前极为简陋的客户端改造一下。

首先再common模块中增加打印颜色的工具类:ConsoleColors

package com.loveprogrammer.utils;/**** @version 1.0.0* @description:* @author: eric* @date: 2024-02-18 09:41**/
public class ConsoleColors {// Resetpublic static final String RESET = "\033[0m";  // Text Reset// Regular Colorspublic static final String BLACK = "\033[0;30m";   // BLACKpublic static final String RED = "\033[0;31m";     // REDpublic static final String GREEN = "\033[0;32m";   // GREENpublic static final String YELLOW = "\033[0;33m";  // YELLOWpublic static final String BLUE = "\033[0;34m";    // BLUEpublic static final String PURPLE = "\033[0;35m";  // PURPLEpublic static final String CYAN = "\033[0;36m";    // CYANpublic static final String WHITE = "\033[0;37m";   // WHITE// Boldpublic static final String BLACK_BOLD = "\033[1;30m";  // BLACKpublic static final String RED_BOLD = "\033[1;31m";    // REDpublic static final String GREEN_BOLD = "\033[1;32m";  // GREENpublic static final String YELLOW_BOLD = "\033[1;33m"; // YELLOWpublic static final String BLUE_BOLD = "\033[1;34m";   // BLUEpublic static final String PURPLE_BOLD = "\033[1;35m"; // PURPLEpublic static final String CYAN_BOLD = "\033[1;36m";   // CYANpublic static final String WHITE_BOLD = "\033[1;37m";  // WHITE// Underlinepublic static final String BLACK_UNDERLINED = "\033[4;30m";  // BLACKpublic static final String RED_UNDERLINED = "\033[4;31m";    // REDpublic static final String GREEN_UNDERLINED = "\033[4;32m";  // GREENpublic static final String YELLOW_UNDERLINED = "\033[4;33m"; // YELLOWpublic static final String BLUE_UNDERLINED = "\033[4;34m";   // BLUEpublic static final String PURPLE_UNDERLINED = "\033[4;35m"; // PURPLEpublic static final String CYAN_UNDERLINED = "\033[4;36m";   // CYANpublic static final String WHITE_UNDERLINED = "\033[4;37m";  // WHITE// Backgroundpublic static final String BLACK_BACKGROUND = "\033[40m";  // BLACKpublic static final String RED_BACKGROUND = "\033[41m";    // REDpublic static final String GREEN_BACKGROUND = "\033[42m";  // GREENpublic static final String YELLOW_BACKGROUND = "\033[43m"; // YELLOWpublic static final String BLUE_BACKGROUND = "\033[44m";   // BLUEpublic static final String PURPLE_BACKGROUND = "\033[45m"; // PURPLEpublic static final String CYAN_BACKGROUND = "\033[46m";   // CYANpublic static final String WHITE_BACKGROUND = "\033[47m";  // WHITE// High Intensitypublic static final String BLACK_BRIGHT = "\033[0;90m";  // BLACKpublic static final String RED_BRIGHT = "\033[0;91m";    // REDpublic static final String GREEN_BRIGHT = "\033[0;92m";  // GREENpublic static final String YELLOW_BRIGHT = "\033[0;93m"; // YELLOWpublic static final String BLUE_BRIGHT = "\033[0;94m";   // BLUEpublic static final String PURPLE_BRIGHT = "\033[0;95m"; // PURPLEpublic static final String CYAN_BRIGHT = "\033[0;96m";   // CYANpublic static final String WHITE_BRIGHT = "\033[0;97m";  // WHITE// Bold High Intensitypublic static final String BLACK_BOLD_BRIGHT = "\033[1;90m"; // BLACKpublic static final String RED_BOLD_BRIGHT = "\033[1;91m";   // REDpublic static final String GREEN_BOLD_BRIGHT = "\033[1;92m"; // GREENpublic static final String YELLOW_BOLD_BRIGHT = "\033[1;93m";// YELLOWpublic static final String BLUE_BOLD_BRIGHT = "\033[1;94m";  // BLUEpublic static final String PURPLE_BOLD_BRIGHT = "\033[1;95m";// PURPLEpublic static final String CYAN_BOLD_BRIGHT = "\033[1;96m";  // CYANpublic static final String WHITE_BOLD_BRIGHT = "\033[1;97m"; // WHITE// High Intensity backgroundspublic static final String BLACK_BACKGROUND_BRIGHT = "\033[0;100m";// BLACKpublic static final String RED_BACKGROUND_BRIGHT = "\033[0;101m";// REDpublic static final String GREEN_BACKGROUND_BRIGHT = "\033[0;102m";// GREENpublic static final String YELLOW_BACKGROUND_BRIGHT = "\033[0;103m";// YELLOWpublic static final String BLUE_BACKGROUND_BRIGHT = "\033[0;104m";// BLUEpublic static final String PURPLE_BACKGROUND_BRIGHT = "\033[0;105m"; // PURPLEpublic static final String CYAN_BACKGROUND_BRIGHT = "\033[0;106m";  // CYANpublic static final String WHITE_BACKGROUND_BRIGHT = "\033[0;107m";   // WHITEpublic 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);}@Overridepublic void onConnected(ChannelHandlerContext ctx) {}@Overridepublic void onDisconnected(ChannelHandlerContext ctx) {}@Overridepublic void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {}@Overridepublic 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 遍历methodsMethod[] 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上点个星星,谢谢!

这篇关于从零开始手写mmo游戏从框架到爆炸(十五)— 命令行客户端改造的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/728561

相关文章

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

国产游戏崛起:技术革新与文化自信的双重推动

近年来,国产游戏行业发展迅猛,技术水平和作品质量均得到了显著提升。特别是以《黑神话:悟空》为代表的一系列优秀作品,成功打破了过去中国游戏市场以手游和网游为主的局限,向全球玩家展示了中国在单机游戏领域的实力与潜力。随着中国开发者在画面渲染、物理引擎、AI 技术和服务器架构等方面取得了显著进展,国产游戏正逐步赢得国际市场的认可。然而,面对全球游戏行业的激烈竞争,国产游戏技术依然面临诸多挑战,未来的

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

ZooKeeper 中的 Curator 框架解析

Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细介绍 Curator 框架,包括它的设计哲学、核心组件以及如何使用 Curator 来简化 ZooKeeper 的操作。 1

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

Spring Framework系统框架

序号表示的是学习顺序 IoC(控制反转)/DI(依赖注入): ioc:思想上是控制反转,spring提供了一个容器,称为IOC容器,用它来充当IOC思想中的外部。 我的理解就是spring把这些对象集中管理,放在容器中,这个容器就叫Ioc这些对象统称为Bean 用对象的时候不用new,直接外部提供(bean) 当外部的对象有关系的时候,IOC给它俩绑好(DI) DI和IO

Java Websocket实例【服务端与客户端实现全双工通讯】

Java Websocket实例【服务端与客户端实现全双工通讯】 现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发 出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏 览器需要不断的向服务器发出请求,然而HTTP

Sentinel 高可用流量管理框架

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。 Sentinel 具有以下特性: 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应