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

news2025/1/14 0:59:39

 导航:从零开始手写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上点个星星,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1459185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初识ONLYOFFICE 8.0:办公软件的革命性升级

引言 随着数字化时代的到来&#xff0c;办公软件已经成为我们日常生活和工作的重要组成部分。在这个充满竞争的市场中&#xff0c;ONLYOFFICE凭借其卓越的性能和功能&#xff0c;脱颖而出&#xff0c;成为许多企业和个人用户的优选。近期&#xff0c;ONLYOFFICE推出了全新的8.…

美格智能联合罗德与施瓦茨完成5G RedCap模组SRM813Q验证,推动5G轻量化全面商用

全球5G发展进入下半场&#xff0c;5G RedCap以其低成本、低功耗的特性成为行业焦点。近日&#xff0c;中国移动携手合作伙伴率先完成全球最大规模、最全场景、最全产业的RedCap现网规模试验&#xff0c;推动首批芯片、终端具备商用条件&#xff0c;RedCap端到端产业已全面达到商…

【Docker】有用的命令

文章目录 DockerDocker 镜像与容器的差异Docker的好处Hypervisor运维 一、安装docker二、启动docker三、获取docker镜像四、创建镜像使用命令行创建镜像使用dockerfile创建镜像 五、docker报错 Docker docker镜像&#xff08;Image&#xff09; docker镜像类似于虚拟机镜像&…

Unity3D中刚体、碰撞组件、物理组件的区别详解

前言 Unity3D提供了丰富的功能和组件&#xff0c;其中包括刚体、碰撞组件和物理组件。这些组件在游戏开发中起着非常重要的作用&#xff0c;能够让游戏世界更加真实和有趣。本文将详细介绍这三种组件的区别以及如何在Unity3D中实现它们。 对惹&#xff0c;这里有一个游戏开发…

【AI学习】LangChain学习

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

java8 中文指南3

java8 中文指南3 文章目录 java8 中文指南3Parallel Streams(并行流)Sequential Sort(串行排序)Parallel Sort(并行排序) MapsDate API(日期相关 API)ClockTimezones(时区)LocalTime(本地时间)LocalDate(本地日期)LocalDateTime(本地日期时间) Annotations(注解) 文章来自Java …

目录的共享与访问的实现

给用户机赋予读取文件的权利 创建文件夹&文件 点击属性–>共享–>共享&#xff08;S&#xff09; 点击添加–》给需要赋权的用户赋予相应的权限–>应用–确定 在赋权的用户机里winR–>‘\’‘IP地址&#xff08;主机&#xff09;’

IP协议及相关技术协议

一、IP基本认识 1. IP的作用 IP在TCP/IP模型中处于网络层&#xff0c;网络层的主要作用是实现主机与主机之间的通信&#xff0c;而IP的作用是在复杂的网络环境中将数据包发送给最终目的主机。 2. IP与MAC的关系 简单而言&#xff0c;MAC的作用是实现“直连”的两个设备之通信…

后端开发怎么学?

后端开发怎么学&#xff1f; 后端开发可以简单地理解为与前端开发相对应的开发方向。前端开发主要负责构建用户界面、维护用户体验等方面的工作&#xff0c;而后端开发则主要负责处理数据、逻辑和算法等方面的工作。后端开发旨在为前端应用程序提供支持&#xff0c;以帮助实现可…

Internet Download Manager 6.42.3 (IDM) 中文免激活绿色版

相信很多网友都遇到过一种情况&#xff0c;网页有些视频资源或者音频资源不知道如何下载&#xff0c;一直不知道如何解决&#xff0c;为此小编特意带来了这款&#xff1a;Internet Download Manager电脑版&#xff0c;这是一款非常专业且十分好用的下载工具&#xff0c;也就是大…

力扣55. 跳跃游戏(动态规划)

Problem: 55. 跳跃游戏 文章目录 题目描述思路复杂度Code 题目描述 思路 我们将问题稍做转换每次求取当前位置可以走到的最远位置&#xff0c;在此基础上我们将最终判断是否能走出整个nums&#xff1b;同时我们要判断中途会不会遇到某个位置是0使得不能继续走下去 复杂度 时间…

nginx 日志改为json格式

nginx 日志改为json格式 场景描述效果变更旧样式新样式 场景描述 正常使用nginx时&#xff0c;使用默认的日志输出格式&#xff0c;对于后续日志接入其他第三方日志收集、清洗环节&#xff0c;因分隔符问题可能不是很友好。 xxxx - - [19/Feb/2024:11:16:48 0800] "GET …

Linux篇:指令

一 基本常识&#xff1a; 1. 文件文件内容文件的属性 2. 文件的操作对文件内容的操作对文件属性的操作 3. 文件的类型&#xff1a; d&#xff1a;目录文件 -&#xff1a;普通文件 4. 指令是可执行程序&#xff0c;指令的代码文件在系统的某一个位置存在的。/u…

每日学习总结20240220

每日总结 20240220 岁月极美&#xff0c;在于它必然的流逝&#xff1b;春花&#xff0c;秋月&#xff0c;夏日&#xff0c;冬雪。 ——三毛 1.svn操作 通过svn创建一个仓库 请写出一套配置 配置文件包括svnserve.conf passwd authz 三个文件 添加用户xiaoming 密码为lx,使得能…

离线升级esp32开发板升级包esp32-2.0.14(最新版已经3.0alpha了)

1.Arduino IDE 2.3.2最新 2024.2.20升级安装:https://www.arduino.cc/en/software 2.开发板地址 地址&#xff08;esp8266,esp32&#xff09; http://arduino.esp8266.com/stable/package_esp8266com_index.json,https://raw.githubusercontent.com/espressif/arduino-esp32…

Java 面向对象进阶 07 继承中成员变量,成员方法的访问特点(黑马)

一、继承中成员变量的访问特点&#xff1a; 打印结果为&#xff1a;zishow 这种情况打印出来的结果是Zi 这种情况打印的是Fu 这种情况就会报错 对于重名的情况&#xff0c;没有关键字&#xff0c;那么就是就近原则&#xff0c;打印出的是ziShow&#xff1b; this.name 指的是Zi…

Rancher实用篇-使用rancher,部署微服务应用

说到rancher&#xff0c;我们必须先了解一下k8s 一、k8s简介 Kubernetes&#xff08;通常简写为 K8s&#xff09;是一个开源的容器管理系统&#xff0c;由Google于2014年发起&#xff0c;并在2015年贡献给Cloud Native Computing Foundation (CNCF)进行维护。它基于Borg项目的…

spring boot3参数校验基本用法

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途。 目录 前置条件 前言 导入依赖 使用介绍 配置检验规则 开启校验 使用注意 全局异常捕获返回友好提示信息 常用的校…

python celery使用队列

在celery的配置方法中有个参数叫task_routes&#xff0c;是用来设置不同的任务 消费不同的队列&#xff08;也就是路由&#xff09;。 格式如下&#xff1a; { ‘task name’: { ‘queue’: ‘queue name’ }}直接上代码&#xff0c;简单明了&#xff0c;目录格式如下&#x…

LabVIEW读取excel日期

LabVIEW读取excel日期 | Excel数据表格中有日期列和时间列&#xff0c;如下表所示&#xff1a; 通过LabVIEW直接读取Excel表格数据&#xff0c;读出的日期列和时间列数据与原始表格不一致&#xff0c;直接读出来的数据如下表所示&#xff1a; 日期、时间列数据异常 问题产生原因…