从零开始手写mmo游戏从框架到爆炸(二十二)— 战斗系统三

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

       

目录

地图设定

战斗引擎 

服务端的BattleHandler

客户端的相关handler

战斗场景展示

执行效果


        文接上一章。我们把战斗系统demo应用到实际的项目中来。在第十九章(从零开始手写mmo游戏从框架到爆炸(十九)— 用户数据封装与存储-CSDN博客)中,客户端已经可以创建英雄并且进入游戏主界面了,下一步,我们需要选择地图,并且和地图中的野怪来一张遭遇战。

      首先我们把上一章中的test路径下的demo移动到正式路径下并进行修改。新增的几个类如下:

  

        其中大部分上一章已经基本讲过了,大家对着gitee上的分支源码来看即可,我们重点关注地图已经搜寻野怪的代码。

地图设定

地图:TownMap

@Data
public class TownMap implements Serializable {private String id;private FindMonstersEngine findMonsterEngine;// 级别 1 就是 0-9级  9 就是 90-99级private int level;private int floorId;private String name;private String desc;private String teamSize;// 可能出现的野怪private List<MonsterInMap> monsters;public TownMap(MapTemplate mapTemplate) {this.findMonsterEngine = new DefaultFindMonsters();this.name = mapTemplate.getName();this.level = mapTemplate.getLevel();this.monsters = MapFactory.createMonsters(mapTemplate);this.desc = mapTemplate.getDesc();this.teamSize = mapTemplate.getTeamSize();this.id = mapTemplate.getId();}
}

        地图中存储了野怪的等级已经出现的概率。同时增加了一个接口-FindMonstersEngine。

我们来看下这个接口:

public interface FindMonstersEngine {List<Monster> findMonsters(HeroWrapper hero, TownMap map) throws IOException, ClassNotFoundException, Exception;
}

这个接口就一个方法,寻找野怪。我们写一个默认的实现类:

public class DefaultFindMonsters extends AbstractFindMonsters{@Overridepublic List<Monster> findMonsters(HeroWrapper heroWrapper, TownMap map) throws Exception {List<Monster> result = new ArrayList<>();// 根据List<MonsterInMap> monsters = map.getMonsters();String[] teamSize = map.getTeamSize().split("-");int min = Integer.parseInt(teamSize[0]);int max = Integer.parseInt(teamSize[1]);int num;if(min == max){num = max;}else {num = min + new Random().nextInt(max - min);}for (int i = 0; i < num; i++) {Monster monster = RandomBalance.getMonsters(monsters);result.add(monster);}return BeanCopyUtils.deepCopy(result);}
}

战斗引擎 

        我们再看下战斗引擎 BattleEngine

public class BattleEngine {// 队列不变private final LinkedList<Attack> actions = new LinkedList<>();private int addAction(Attack action){actions.offer(action);return actions.size();}public BattleFightResult fight(List<? extends Character> listA, List<? extends Character> listB) throws InterruptedException {BattleFightResult result = new BattleFightResult();List<String> fightLogs = result.getFightLogs();// 先初始化listA.sort(Comparator.comparing(Character::getSpeed).reversed());for (int i = 0; i < listA.size(); i++) {addAction(new GroupAttack(listA.get(i),listB));}// 再放入listBlistB.sort(Comparator.comparing(Character::getSpeed).reversed());for (int i = 0; i < listB.size(); i++) {GroupAttack attack = new GroupAttack(listB.get(i), listA);insertAction(attack);}// 如果A集合和B集合的生命值都还大于0while(listA.stream().anyMatch(e -> e.getCurrentHp() > 0) && listB.stream().anyMatch(e -> e.getCurrentHp() > 0)) {Attack pop = actions.pop();AttackResult run = pop.run();if(run != null && run.getSuccess()) {System.out.println(run.getContent());fightLogs.addAll(run.getContent());// 再放进去if (pop.checkContinue()) {// 要重新计算interval的时间pop.setIntervalTime(pop.getIntervalTime() + pop.computeInterval(pop.speed()));insertAction(pop);}}}if(listA.stream().anyMatch(e -> e.getCurrentHp() > 0)) {result.setWin(true);}else{result.setWin(false);}return result;}private void insertAction(Attack attack) {int intervalTime = attack.getIntervalTime();// 如果第一个就大于attack的intervalif(actions.get(0).getIntervalTime() > attack.intervalTime()){// 在头插入一个actions.push(attack);}else {ListIterator<Attack> iterator = actions.listIterator();while (iterator.hasNext()) {Attack next = iterator.next();if (next.getIntervalTime() > intervalTime) {break;}}// 在指定位置插入新元素iterator.add(attack);}}public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {Hero heroA = JobFactory.createHero(JobFactory.getTemplates().get(0),"haha");Hero heroB = JobFactory.createHero(JobFactory.getTemplates().get(0),"erer");BattleEngine engine = new BattleEngine();BattleFightResult fight = engine.fight(Arrays.asList(heroA), Arrays.asList(heroB));}}

服务端的BattleHandler

@Component
@TopicListener(topic = ServerTopic.TOPIC_BATTLE)
public class BattleHandler extends BaseHandler {public static final Logger log = LoggerFactory.getLogger(BattleHandler.class);@TagListener(tag = ServerBattleTag.TAG_BATTLE_MAPS,messageClass = StringRequest.class)public StringMessage mapList(ChannelHandlerContext ctx, StringRequest data) throws Exception {List<MapTemplate> mapTemplates = MapFactory.mapTemplates();// 获取地图列表StringMessage message = StringMessage.create(ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_MAP_CHOOSE);message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);MapChoose mapChoose = new MapChoose(message);mapChoose.setMapList(MapTemplateConvert.INSTANCE.toMapItems(mapTemplates));message.setBody(JSON.toJSONString(mapChoose));return message;}@TagListener(tag = ServerBattleTag.TAG_BATTLE_COMMON_FIGHT,messageClass = FightChoose.class)public StringMessage commonFight(ChannelHandlerContext ctx, FightChoose data) throws Exception {TownMap townMap = MapFactory.createMap(MapFactory.getMapById(data.getMapId()));UserGameWrapper gameWrapper = ctx.channel().attr(SessionAttributeKey.GAME).get();HeroWrapper currentHero = gameWrapper.getCurrentHero();Hero hero = currentHero.getHero();// 计算野怪的类型 数目 等级 品质List<Monster> monsters = townMap.getFindMonsterEngine().findMonsters(currentHero, townMap);BattleEngine engine = new BattleEngine();BattleFightResult fight = engine.fight(Arrays.asList(hero), monsters);// 获取地图列表StringMessage message = StringMessage.create(data.getSuccessCallbackTopic(), data.getSuccessCallbackTag());message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);message.setBody(JSON.toJSONString(fight));return message;}
}

客户端的相关handler

选择地图:MenuHandler

    /**** 选择地图* @param ctx* @param data*/@TagListener(tag = ClientMenuTag.TAG_MENU_MAP_CHOOSE, messageClass = MapChoose.class)public void chooseMaps(ChannelHandlerContext ctx, MapChoose data) {//ConsolePrint.publishMessage("请选择要进入的地图");List<MapChoose.MapItem> mapList = data.getMapList();for (int i = 0; i < mapList.size(); i++) {ConsolePrint.publishMessage((i+1) + ":" + mapList.get(i).toString(), 1);}Scanner input = new Scanner(System.in);int choose = input.nextInt();while(choose < 0 || choose > mapList.size()){ConsolePrint.publishMessage("请重新选择", 1);choose = input.nextInt();}MapChoose.MapItem item = mapList.get(choose - 1);ConsolePrint.publishMessage("选择的地图为" + item.getName());// 继续选择是打怪还是返回ConsolePrint.publishMessage("请选择您要进行的操作");ConsolePrint.publishMessage("【1.打怪寻宝】  【2.挑战BOSS】  【3.返回】");choose = ScannerInput.inputInt(1, 3, 3);if(choose != 3) {switch (choose) {case 1 :// 战斗StringMessage message = StringMessage.create(ServerTopic.TOPIC_BATTLE, ServerBattleTag.TAG_BATTLE_COMMON_FIGHT);message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);FightChoose request = new FightChoose(ClientTopic.TOPIC_SCENE, ClientSceneTag.TAG_SCENE_BATTLE, ClientTopic.TOPIC_MENU,ClientMenuTag.TAG_MENU_MAP_CHOOSE);request.setMapId(item.getId());request.setType(1);message.setBody(JSON.toJSONString(request));ChannelUtils.pushToServer(ctx.channel(), message);break;case 2:// 战斗ConsolePrint.publishMessage("暂未开放,敬请期待", 1);// break;default:// 返回上一页NetworkClientListener.getInstance().forward(ctx,ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_MAP_CHOOSE, JSON.toJSONString(data));}}else {// 返回上一页NetworkClientListener.getInstance().forward(ctx,ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_MAP_CHOOSE, JSON.toJSONString(data));}}

战斗场景展示

        SceneHandler

@TopicListener(topic = ClientTopic.TOPIC_SCENE)
public class SceneHandler extends BaseHandler {public static final Logger log = LoggerFactory.getLogger(SceneHandler.class);@TagListener(tag = ClientSceneTag.TAG_SCENE_BATTLE,messageClass = BattleFightResult.class)public void portalMenu(ChannelHandlerContext ctx, BattleFightResult result){ConsolePrint.publishMessage("战斗开始");List<String> fightLogs = result.getFightLogs();for (String fightLog : fightLogs) {ConsolePrint.publishMessage(fightLog);}if(result.isWin()){ConsolePrint.publishMessage("您胜利了");}else {ConsolePrint.publishMessage("您失败了");}}/**** 选择地图* @param ctx* @param data*/@TagListener(tag = ClientMenuTag.TAG_MENU_MAP_CHOOSE, messageClass = MapChoose.class)public void chooseMaps(ChannelHandlerContext ctx, MapChoose data) {//ConsolePrint.publishMessage("请选择要进入的地图");List<MapChoose.MapItem> mapList = data.getMapList();for (int i = 0; i < mapList.size(); i++) {ConsolePrint.publishMessage((i+1) + ":" + mapList.get(i).toString(), 1);}Scanner input = new Scanner(System.in);int choose = input.nextInt();while(choose < 0 || choose > mapList.size()){ConsolePrint.publishMessage("请重新选择", 1);choose = input.nextInt();}MapChoose.MapItem item = mapList.get(choose - 1);ConsolePrint.publishMessage("选择的地图为" + item.getName());// 继续选择是打怪还是返回ConsolePrint.publishMessage("请选择您要进行的操作");ConsolePrint.publishMessage("【1.打怪寻宝】  【2.挑战BOSS】  【3.返回】");choose = ScannerInput.inputInt(1, 3, 3);if(choose != 3) {switch (choose) {case 1 :// 战斗StringMessage message = StringMessage.create(ServerTopic.TOPIC_BATTLE, ServerBattleTag.TAG_BATTLE_COMMON_FIGHT);message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);FightChoose request = new FightChoose(ClientTopic.TOPIC_SCENE, ClientSceneTag.TAG_SCENE_BATTLE, ClientTopic.TOPIC_MENU,ClientMenuTag.TAG_MENU_MAP_CHOOSE);request.setMapId(item.getId());request.setType(1);message.setBody(JSON.toJSONString(request));ChannelUtils.pushToServer(ctx.channel(), message);break;case 2:// 战斗ConsolePrint.publishMessage("暂未开放,敬请期待", 1);// break;default:// 返回上一页NetworkClientListener.getInstance().forward(ctx,ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_MAP_CHOOSE, JSON.toJSONString(data));}}else {// 返回上一页NetworkClientListener.getInstance().forward(ctx,ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_MAP_CHOOSE, JSON.toJSONString(data));}}
}

执行效果

【1.登录已有账户】  【2.注册新账户】  【3.退出】
请选择:
1
请输入账户名:
eric
请输入密码:
111111
234:234
请选择要创建的职业
1:JobChoose.JobItem(id=1, name=战士, desc=剑类武器, strength=60, armature=5, constitution=80, magic=0, technique=15, speed=130)
1
选择的职业为战士
请输入角色的名称:
haer
124:124
英雄创建成功
请选择您要进行的操作
【1.打怪】  【2.装备】  【3.战兽】
【4.冒险者工会】   【5.副本】  【6.工会】 
【8.配置】  【9.退出】
请选择:
1
196:196
请选择要进入的地图
1:地图 { 名称 ='新手村', 级别 =1, 说明 ='新手练级的地方,适合等级1-10级'}
1
选择的地图为新手村
请选择您要进行的操作
【1.打怪寻宝】  【2.挑战BOSS】  【3.返回】
1
2831:2831
战斗开始
haer[800/800] 攻击了地精战士,造成伤害60点
地精头目[730/730] 攻击了haer,造成伤害65点
地精战士[160/220] 攻击了haer,造成伤害22点
haer[713/800] 攻击了地精战士,造成伤害60点
地精头目[730/730] 攻击了haer,造成伤害65点
地精战士[100/220] 攻击了haer,造成伤害22点
haer[626/800] 攻击了地精战士,造成伤害60点
地精头目[730/730] 攻击了haer,造成伤害65点
地精战士[40/220] 攻击了haer,造成伤害22点
haer[539/800] 攻击了地精战士,造成伤害60点
地精头目[730/730] 攻击了haer,造成伤害65点
haer[474/800] 攻击了地精头目,造成伤害60点
地精头目[670/730] 攻击了haer,造成伤害65点
haer[409/800] 攻击了地精头目,造成伤害60点
地精头目[610/730] 攻击了haer,造成伤害65点
haer[344/800] 攻击了地精头目,造成伤害60点
地精头目[550/730] 攻击了haer,造成伤害65点
haer[279/800] 攻击了地精头目,造成伤害60点
地精头目[490/730] 攻击了haer,造成伤害65点
haer[214/800] 攻击了地精头目,造成伤害60点
地精头目[430/730] 攻击了haer,造成伤害65点
haer[149/800] 攻击了地精头目,造成伤害60点
地精头目[370/730] 攻击了haer,造成伤害65点
haer[84/800] 攻击了地精头目,造成伤害60点
地精头目[310/730] 攻击了haer,造成伤害65点
haer[19/800] 攻击了地精头目,造成伤害60点
地精头目[250/730] 攻击了haer,造成伤害65点
您失败了

全部源码详见:

gitee : eternity-online: 多人在线mmo游戏 - Gitee.com

分支:step-12

请各位帅哥靓女帮忙去gitee上点个星星,谢谢!

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

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

相关文章

AI对话系统app开源

支持对接gpt&#xff0c;阿里云&#xff0c;腾讯云 具体看截图 后端环境&#xff1a;PHP7.4MySQL5.6 软件&#xff1a;uniapp 废话不多说直接上抗揍云链接&#xff1a; https://mny.lanzout.com/iKFRY1o1zusf 部署教程请看源码内的【使用教程】文档 欢迎各位转载该帖/源码

智慧公厕是什么?智慧公厕意义何在

随着城市化进程的加速&#xff0c;公厕成为城市管理中不容忽视的一环。智慧公厕传统的公厕管理方式已经无法满足当今社会的需求&#xff0c;因此智慧公厕的出现成为解决问题的利器。什么是智慧公厕&#xff1f;智慧公厕是实现公共厕所信息化、数字化、智慧化全方位管理与服务的…

啤酒:精酿啤酒与烧烤的热烈碰撞

在夏日的傍晚&#xff0c;烧烤与啤酒总是绝配。当Fendi Club啤酒遇上烧烤&#xff0c;它们将为我们带来一场热烈的美味碰撞。 Fendi Club啤酒&#xff0c;以其醇厚的口感和淡淡的麦芽香气而著称。这款啤酒在酿造过程中采用了特别的工艺&#xff0c;使得酒体呈现出诱人的金黄色&…

F2图例封装 - Bar

基于vue3 和 F2 3.5.0 <template><div :style"{minHeight: ${height}px,width: 100% }" ref"container"><canvas v-show"showChart" :id"chartId" class"chart-canval"></canvas><empty-box v-…

零感佩戴的开放式耳机,音质悦耳更耐听,西圣Air体验

每天都用蓝牙耳机的朋友应该不少&#xff0c;我平时也经常戴&#xff0c;不过最近我用的不是常规的入耳式耳机&#xff0c;因为它佩戴不舒适&#xff0c;戴久了耳朵特别难受。所以现在我换上了开放式耳机&#xff0c;这种耳机叫做OWS&#xff0c;我的这款是西圣Air&#xff0c;…

查看mysql数据库的版本

要查看MySQL数据库的版本&#xff0c;可以使用以下几种方法&#xff1a; 命令行&#xff08;已连接到MySQL服务器&#xff09;&#xff1a; 登录到MySQL服务器后&#xff0c;在MySQL提示符下执行&#xff1a; mysql> SELECT VERSION(); 或者&#xff0c;也可以执行 STATUS; …

Java异常梳理总结

目录 什么是异常 , 异常的分类 ? 异常的基本概念 什么是Throwable ? Throwable 类常用方法有哪些&#xff1f; Exception 和 Error 有什么区别&#xff1f; 运行时异常与一般异常有什么区别&#xff1f; 常见的RuntimeException 有哪些 ? NoClassDefFoundError 和 C…

面试总结之JVM入门

文章目录 &#x1f412;个人主页&#x1f3c5;JavaEE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;你为什么要学习JVM&#xff1f;&#x1f380;JVM的作用 &#x1f380;JVM的构成&#xff08;5大类&#xff09;&#x1f3e8;1.类加载系统&#x1f415;类什么时候会被加…

《业务建模驱动的企业架构转型白皮书》

当前&#xff0c;我国金融等国民经济重点行业和企业的数字化转型&#xff0c;仍存在战略落地难、业务技术协同难以及投入产出匹配难等问题&#xff0c;亟需通过实施企业架构&#xff0c;从顶层设计出发&#xff0c;制定符合自身需要的转型战略&#xff1b;从全局视角出发&#…

人工智能产生的幻觉问题真的能被看作是创造力的另一种表现形式吗?

OpenAI的首席执行官山姆奥特曼&#xff08;Sam Altman&#xff09;曾声称&#xff0c;人工智能产生的“幻觉”其实未尝不是一件好事&#xff0c;因为实际上GPT的优势正在于其非凡的创造力。 目录 一.幻觉问题的概念 二.幻觉产生的原因 三.幻觉的分类 四.减轻AI的幻觉问题到…

Windows部署WebDAV服务并映射到本地盘符实现公网访问本地存储文件

文章目录 前言1. 安装IIS必要WebDav组件2. 客户端测试3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网3.1 安装cpolar内网穿透3.2 配置WebDav公网访问地址 4. 映射本地盘符访问 前言 在Windows上如何搭建WebDav&#xff0c;并且结合cpolar的内网穿透工具实现在公网访…

Qt QWidget 简约美观的加载动画 第四季

&#x1f60a; 第四季来啦 &#x1f60a; 效果如下: 只有三个文件,可以直接编译运行的 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <QVBoxLayout> #include <QGridLayout> int main(int argc, char *argv[]) …

matlab 三质量-弹簧系统受激振力

1、内容简介 略 44-可以交流、咨询、答疑 建立系统运动方程&#xff0c;研究固有频率和对应主振型 2、内容说明 略 三质量&#xff0d;弹簧系统受激振力&#xff0c;并不考虑各自的阻尼。建立系统运动方程。 解&#xff1a;由于阻尼对固有频率没有影响&#xff0c;故本文不…

【蓝桥杯省赛真题25】python密室逃脱游戏 青少年组蓝桥杯比赛python编程省赛真题解析

目录 python密室逃脱游戏 一、题目要求 1、编程实现 2、输入输出

【深入理解设计模式】代理设计模式

代理设计模式&#xff1a; 代理设计模式是一种结构型设计模式&#xff0c;它允许你提供一个替代物或占位符来控制对其他对象的访问。在代理模式中&#xff0c;一个类代表另一个类的功能。这种类型的设计模式属于结构型模式&#xff0c;因为该模式涉及类和对象的组合。 概述 …

常见集合框架底层原理

常见集合框架底层原理 常见的集合有哪些 Java集合类主要由两个接口Collection和Map派生出来的&#xff0c;Collection有三个子接口: List、 Set、Queue List代表了有序可重复集合&#xff0c;可直接根据元素的索引来访问Set代表了无序集合&#xff0c;只能根据元素本身来访问…

代码随想录算法训练营第62天 | 739.每日温度 496.下一个更大元素I

每日温度 如果我们单纯的遍历数组&#xff0c;我们不知道当前元素是否比之前的元素大&#xff0c;所以需要维护一个容器来记录遍历过的元素。 什么时候用单调栈&#xff1f;通常是一维数组&#xff0c;要寻找任一个元素的右边或左边第一个比自己大或小的元素的位置。时间复杂度…

Linux-实用操作(黑马学习笔记)

各类小技巧&#xff08;快捷键&#xff09; ctrl c 强制停止 ● Linux某些程序的运行&#xff0c;如果想要强制停止它&#xff0c;可以使用快捷键ctrl c ● 命令输入错误&#xff0c;也可以通过快捷键ctrl c&#xff0c;退出当前输入&#xff0c;重新输入 ctrl d 退出或登…

客户端订阅服务端事件的机制

一、场景描述 产业大脑平台是一个典型的审核系统&#xff0c;用户发布到平台的信息需要经过审核员审核后生效。 用户发布信息->审核员审核信息->用户信息生效&#xff0c;这一流程可能发生在用户的同一次登录周期内。为了使客户端能实时响应信息的状态变化&#xff0c;…