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

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-23 08:02:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-23 08:02:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-23 08:02:01       82 阅读
  4. Python语言-面向对象

    2024-02-23 08:02:01       91 阅读

热门阅读

  1. 力扣(leetcode)第455题分发饼干(Python)

    2024-02-23 08:02:01       50 阅读
  2. 嵌入式系统发展前景?

    2024-02-23 08:02:01       47 阅读
  3. mxonline安装总结

    2024-02-23 08:02:01       55 阅读
  4. Vue3学习——hooks

    2024-02-23 08:02:01       54 阅读
  5. C语言大小写转换

    2024-02-23 08:02:01       44 阅读
  6. 预防.locked.locked1勒索病毒攻击:保护数据安全

    2024-02-23 08:02:01       56 阅读
  7. 【Swift】NSSearchField用法和示例

    2024-02-23 08:02:01       44 阅读
  8. vue3封装接口(自测可用)

    2024-02-23 08:02:01       48 阅读
  9. 大数据专业python毕业设计题目分享

    2024-02-23 08:02:01       38 阅读
  10. 粉笔申论规范词积累(基层治理)

    2024-02-23 08:02:01       42 阅读
  11. mvcc 并发事务的控制

    2024-02-23 08:02:01       57 阅读
  12. websocket 实现原理和技术方案

    2024-02-23 08:02:01       53 阅读