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