JavaFX战旗游戏开发 第七课 回合逻辑(完)

时间:2021-11-06 20:04:22

  上一节课中,我们讲述了SLG中获取移动范围的算法(获取攻击范围也是同理),相对如自动寻径来说,简单不少。由于个人时间问题,这一节课将会把内容讲完,将这个系列完结,并给出示例下载地址。

  项目下载地址:JavaFX战旗类游戏开发示例

  注意:该项目为e(fx)clipse项目

  在战旗游戏开发中,最基本的回合逻辑就是敌方回合和我方回合。当然,在如今的SLG游戏中,往往是根据我方角色和敌方角色的某些数值计算(例如速度之类的),来排列角色操控的列表,而且某些技能还能中断某个角色的操作,将他往操作列表的后面移动(例如著名的SLG游戏《英雄传说》)。

  当然,在这里我们只是讲述最简单的回合逻辑。

  下面是我们需要定义的一些简单枚举:

	// 游戏状态
	enum Status {
		NONE, SHOW_ENEMY_PROPERTY, SHOW_MENU, PREPARE_MOVE, MOVE, PREPARE_ATTACK, ATTACK, WAIT, GAME_WIN, GAME_OVER
	}

	enum GameTurn {
		PLAYER, ENEMY
	}

	// 当前游戏状态
	private Status nowStatus = Status.NONE;
	// 当前游戏回合
	private GameTurn nowTurn = GameTurn.PLAYER;

  我们在这里定义了游戏状态。游戏状态是在游戏开发中很重要的一个参数,我们需要根据不同的游戏状态,来显示不同的游戏界面,并直接影响到当前的角色能够进行哪些操作,正在进行的是哪些操作。

  我们这里的Status里包含游戏状态:显示敌方属性,显示菜单,准备移动,移动,准备攻击,攻击,等待,游戏胜利,游戏失败。

  另外,定义了一个回合枚举,分为我方回合和敌方回合。当然,有的SLG中阵营分类不止两个,例如以前的FC上的《第四次机器人大战》,里面有我方,敌方和中立阵营(游戏中的名字叫联邦,自盟和什么来着,忘了。这个游戏是外星科技山寨改版的)。

  接下来,上一课中,我们创建的定时器就要派上用场了。

		moveTimer = WTimer.createWTimer(50, new WTimer.OnTimerListener() {

			@Override
			public void onTimerRunning(WTimer mTimer) {
				if (nowStatus == Status.MOVE) {
					int nowPlayerX = (int) (nowControllPlayer.getX() / tileWidth);
					int nowPlayerY = (int) (nowControllPlayer.getY() / tileHeight);
					if (nowPlayerX != moveToX) {
						nowControllPlayer.moveX(nowPlayerX > moveToX ? -tileWidth : tileWidth);
					} else {
						if (nowPlayerY != moveToY) {
							nowControllPlayer.moveY(nowPlayerY > moveToY ? -tileHeight : tileHeight);
						} else {
							nowControllPlayer.setWaitToAttack(false);
							nowControllPlayer.setWaitToMove(false);
							nowControllPlayer.setCanMove(false);
							nowControllPlayer = null;
							nowStatus = Status.NONE;
							moveTimer.stop();
						}
					}
				}
			}
		});

		actioTimer = WTimer.createWTimer(50, new WTimer.OnTimerListener() {
			@Override
			public void onTimerRunning(WTimer mTimer) {
				//敌方回合
				if (nowTurn == GameTurn.ENEMY) {
					if (nowStatus == Status.NONE) {
						//当前敌方角色索引
						if (nowActionIndex < enemys.size()) {
							//选择操作的敌方角色
							nowControllPlayer = enemys.get(nowActionIndex);
							nowControllPlayer.setChoose(true);
							// 没有就近角色
							if (!nowControllPlayer.isHasNearBP(players)) {
								//如果可以移动
								if (nowControllPlayer.isCanMove()) {
									//获取最近的一个角色(这里可以更改规则,比如HP最低的等等)
									BasePlayer player = nowControllPlayer.getNearestBP(players);
									path.clear();
									// path.map_sprite = createMapSprite();
									//搜索移动范围
									LinkedList<WNode> nodeList = path.SearchMoveScan(
											new Point2D(nowControllPlayer.getX() / tileWidth, nowControllPlayer.getY()
													/ tileHeight), nowControllPlayer.getMove());
									// 删选当前可移动范围内 如果有角色,则把该移动点删除
									List<WNode> deleteList = new ArrayList<>();
									for (WNode node : nodeList) {
										if (isPointHasPlayer((int) node.getPoint().getX(), (int) node.getPoint().getY())) {
											deleteList.add(node);
										}
									}
									for (WNode node : deleteList) {
										nodeList.remove(node);
									}
									nowControllPlayer.nodeList = nodeList;
									//获取可移动范围里距离 最近角色的最近的点
									Point2D point = player.getNearestNode(nodeList);
									moveToX = (int) (point.getX());
									moveToY = (int) (point.getY());
									//状态更改为移动
									nowStatus = Status.MOVE;
									moveTimer.start();
								} else {
									//状态更改为准备攻击
									nowStatus = Status.PREPARE_ATTACK;
								}
							} else {
								//状态更改为准备攻击
								nowStatus = Status.PREPARE_ATTACK;
							}
						} else {
							//当操作角色的索引大于 敌方角色集合的大小时,回合结束 并重置所有敌方角色状态
							nowStatus = Status.NONE;
							nowTurn = GameTurn.PLAYER;
							nowActionIndex = 0;
							for (BasePlayer enemy : enemys) {
								enemy.reset();
							}
						}
					} else if (nowStatus == Status.PREPARE_ATTACK) {
						// 敌人周边四个方格是否有角色
						if (nowControllPlayer.isHasNearBP(players)) {
							//敌人获取最近的我方角色,并攻击
							BasePlayer bp = nowControllPlayer.getNearestBP(players);
							nowControllPlayer.attack(bp);
							nowBeAttackedPlayer = bp;
							bp.setFlash(true);
							nowStatus = Status.ATTACK;
						} else {
							//没有攻击对象,则操作下一个敌方角色
							nowControllPlayer.setCanAction(false);
							nowActionIndex++;
							nowControllPlayer.setChoose(false);
							nowStatus = Status.NONE;
						}
					} else if (nowStatus == Status.ATTACK) {
						//角色死亡
						if (!nowBeAttackedPlayer.isFlash()) {
							if (nowBeAttackedPlayer.getHp() <= 0) {
								players.remove(nowBeAttackedPlayer);
							}
							nowControllPlayer.setCanAction(false);
							nowActionIndex++;
							nowControllPlayer.setChoose(false);
							nowStatus = Status.NONE;
							//当没有我方角色时,游戏结束
							if (players.size() == 0) {
								nowStatus = Status.GAME_OVER;
								nowTurn = GameTurn.PLAYER;
							}
						}
					}
				} else if (nowTurn == GameTurn.PLAYER) {
					if (nowStatus == Status.ATTACK) {
						if (!nowBeAttackedPlayer.isFlash()) {
							//敌人死亡
							if (nowBeAttackedPlayer.getHp() <= 0) {
								nowControllPlayer.getExp(nowBeAttackedPlayer.getExp());
								enemys.remove(nowBeAttackedPlayer);
							}
							waitToNextPlayer();
							//当没有敌方角色时,游戏胜利
							if (enemys.size() == 0) {
								nowStatus = Status.GAME_WIN;
							}
						}
					}
				}
			}
		});
		actioTimer.start();

  这一段代码比较的多,主要是创建了两个定时器,MoveTimer和ActionTimer。

  MoveTimer的作用是控制角色的移动(根据时间间隔一个单元格一个单元格的移动)。

  ActionTimer的作用是敌方回合或者我方回合的一些操作。

  由于我方回合主要是菜单的鼠标事件和简单的判断,这里我描述一下敌方回合的步骤。

  敌方回合

  1.当前敌方操作角色索引小于敌方角色链表大小,如果CanMove,则获取最近的我方角色(也可以根据HP等来判断),并获取敌方角色可移动的范围。

  2.从敌方角色可移动范围中,获取距离“上个步骤中得到的我方最角色”最近的一个点,并将状态改为Move,启动MoveTimer开始移动。

  3.如果!CanMove(就是已经移动过了),则将状态更改为PREPARE_ATTACK,准备攻击。

  4.如果状态为PREPARE_ATTACK,则判断敌方周围四个单元格有无可攻击对象,如果有,则获取对象并将状态更改为ATTACK并攻击,如果无,则操作下一个敌方角色。

  5.如果状态为ATTACK,则判断被攻击的对象是否死亡,死亡则移出链表。当我方角色链表为空,则游戏结束。


  以上为敌方回合的逻辑步骤,代码中的注释比较多,就不做过多说明。可以对照项目代码来看。

  然后,在鼠标事件和绘制中,也是通过状态来管理的,这里列出绘制代码来说明。

	public void draw() {
		gameMap.drawMap(gContext);
		drawPlayer();
		switch (nowStatus) {
		case SHOW_MENU:
			if (nowControllPlayer != null && nowControllPlayer.isCanAction()) {
				actionMenu.draw(gContext);
			}
			propertyMenu.draw(gContext);
			break;
		case SHOW_ENEMY_PROPERTY:
			propertyMenu.draw(gContext);
			break;
		case PREPARE_MOVE:

			break;
		case GAME_WIN:
			gContext.setFont(Font.font(18));
			gContext.setFill(Color.WHITE);
			gContext.fillText("游戏胜利!", 250, 150);
			break;
		case GAME_OVER:
			gContext.setFont(Font.font(18));
			gContext.setFill(Color.RED);
			gContext.fillText("游戏失败!", 250, 150);
			break;
		default:
			break;
		}
		gContext.save();
		gContext.setFont(Font.font(18));
		switch (nowTurn) {
		case PLAYER:
			gContext.setFill(Color.WHITE);
			gContext.fillText("我方回合", 15, getHeight() - 15);
			break;
		case ENEMY:
			gContext.setFill(Color.RED);
			gContext.fillText("敌方回合", 15, getHeight() - 15);
			break;
		}
		gContext.restore();
	}
   

  在绘制中,我们根据不同的状态来绘制不同的内容。
   鼠标事件原理是如此,如下所示:

		// 事件处理
		setOnMouseClicked(e -> {
			if (e.getButton() == MouseButton.PRIMARY) {
				switch (nowStatus) {
				case NONE:
					for (BasePlayer player : players) {
						//判断点击的角色,显示属性框和操作菜单
						if (player.isCollisionWith(e.getX(), e.getY())) {
							actionMenu.setLocation(player.getX() + tileWidth, player.getY());
							actionMenu.getTextObjects()[0].setColor(player.isCanMove() != true ? Color.DARKGRAY
									: Color.WHITE);
							actionMenu.getTextObjects()[1].setColor(player.isCanAttack() != true ? Color.DARKGRAY
									: Color.WHITE);
							propertyMenu.initPlayer(player);
							nowControllPlayer = player;
							nowControllPlayer.setChoose(true);
							nowStatus = Status.SHOW_MENU;
						}
					}
                     //如果点击的是敌方角色,则只显示敌方属性框
					for (BasePlayer enemy : enemys) {
						if (enemy.isCollisionWith(e.getX(), e.getY())) {
							propertyMenu.initPlayer(enemy);
							nowStatus = Status.SHOW_ENEMY_PROPERTY;
						}
					}
					break;
				case SHOW_MENU:
					actionMenu.onMousePressed(e);
					break;
				case PREPARE_MOVE:
					moveToX = (int) (e.getX() / tileWidth);
					moveToY = (int) (e.getY() / tileHeight);
					isCanMove = false;
					// 判断点击的是否是可移动的范围
					if (!isPointHasPlayer(moveToX, moveToY)) {
						for (WNode node : nowControllPlayer.nodeList) {
							if (((int) node.getPoint().getX()) == moveToX && ((int) node.getPoint().getY()) == moveToY) {
								isCanMove = true;
							}
						}
						//如果可以移动,则启动移动定时器
						if (isCanMove) {
							nowStatus = Status.MOVE;
							moveTimer.start();
							nowControllPlayer.setChoose(false);
						}
					}
					break;
				case PREPARE_ATTACK:
					//准备攻击状态时,点击要攻击的角色
					for (BasePlayer enemy : enemys) {
						if (enemy.isCollisionWith(e.getX(), e.getY())) {
							nowControllPlayer.attack(enemy);
							nowControllPlayer.setWaitToAttack(false);
							enemy.setFlash(true);
							nowBeAttackedPlayer = enemy;
							nowStatus = Status.ATTACK;
						}
					}
					break;
				default:
					break;
				}
			} else if (e.getButton() == MouseButton.SECONDARY) {
				//右键状态还原
				if (nowControllPlayer != null) {
					nowControllPlayer.setChoose(false);
					nowControllPlayer.setWaitToAttack(false);
					nowControllPlayer.setWaitToMove(false);
				}
				nowStatus = Status.NONE;
				nowControllPlayer = null;
			}
		});
  

  具体的大家可以研究项目代码。

  截图如下:

JavaFX战旗游戏开发 第七课 回合逻辑(完)

JavaFX战旗游戏开发 第七课 回合逻辑(完)

    本文章为个人原创,版权所有,转载请注明出处:http://blog.csdn.net/ml3947。另外我的个人博客:http://www.wjfxgame.com. 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

   其实这个示例已经完成很久了,之前是一个CSDN的朋友说想看看JavaFX战旗类游戏开发的示例。于是业余花了一点点时间写了这个例子。随后开始写了这个《JavaFX战旗类游戏开发》的系列博文,可惜,中间间间断断时不时写一篇,已经过去了好几个月了,汗,感觉自己变得有点懒散了,于是总共七课的课程算是完结了,之前的系列文章有很多坑,不知道何时会想到去填。。今年下半年算是不平凡的半年,考了驾照,然后结婚,老婆家酒已经办了,我家还没办。之后还要度蜜月,空余的时间也没多少心思写着写那,无奈。

  另外,很多朋友发私信加QQ。但是现在本人QQ非特殊情况基本不加技术讨论的。毕竟有时候还是想休息之类的。如果有任何问题发我的邮件Gmail:wingfourever@gmail.com。谢谢。

--------------------------------------------------------------------------------------------------------------