第一个 Java 小游戏(pro)

OOP h17

某一款游戏,其主要角色如下:

  • 游戏中每个对象有当前 x,y 坐标,坐标值取值范围为整数
  • 每种对象的攻击力 strength、攻击范围 range、初始健康值 health 通过BattleField.init(String fileName) 从配置文件中初始化
  • 非建筑物可以通过 move(dx,dy) 来移动坐标值,dx,dy 表示 x轴,y轴增量, 取值范围为整数
  • 对象 A 攻击 B 的时候,要满足两个对象之间直线距离小于等于 A 的攻击范围,否则攻击无效(被攻击方不减健康值)
  • 任何对象有 getHealth() 方法,返回当前生命值,如果已经死亡则返回 <=0 的一个数字
  • 任何对象有 isDestroyed() 方法,如果生命值 <=0 则 true,否则 false
  • GameBase 初始 x,y 值在创建时指定
  • Barrack 兵营,可以训练出步枪兵、 RPG兵、军犬,初始 x,y 值在创建时指定
  • RifleSoldier 步枪兵,初始 x,y 值就是兵营的 x,y
  • RPGSoldier 火箭兵,初始 x,y 值就是兵营的 x,y
  • Dog 军犬,初始 x,y 值就是兵营的 x,y
  • A.attack(B),表示 A 攻击 B 对象,B.health=B.health-A.strength,注意这次没有一击毙命方法
  • A.attack(),表示 A 寻找距离他最近、非己方、且活着的对象 B 进行攻击,如果攻击范围内没有符合要求对象则什么也不做
  • 如果 A.isDestroyed() 则 A.attack() 没有任何效果

需要在原来的基础上实现读取文件、创建玩家和自定义攻击的功能。

0x00 BattleField

新的类,用于初始化属性值(生命值、攻击力和攻击范围),还包括玩家。

在上一次程序中,属性值由 Param 类中的一些 public 常量来定义。这次需要实现根据文件更改属性值。

成员变量包括所有玩家列表(new)。

init 方法中,可以调用 readLines 读取文件转换格式,将信息存入字符串数组列表中,遍历每一个字符串数组(即每一行)赋值。

createPlayer 方法中,传入玩家名字,就新建一个玩家,加入列表中。

getAllPlayer 列出所有玩家,只需返回所有玩家列表。

readLines 和之前用的一样,格式化的时候需要注意细节。

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package com.huawei.classroom.student.h17;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
* @author super
*/
public class BattleField {
private static List<Player> players = new ArrayList<>();

public BattleField() {
players = null;
}

public static void init(String filename) {
List<String[]> rule = readLines(filename);
if (rule == null) {
return;
}
for (String[] ruleEle : rule) {
setValue(ruleEle);
}
}

public static void createPlayer(String playerName) {
if (playerName == null) {
return;
}
Player newPlayer = new Player(playerName);
players.add(newPlayer);
}

public static List<Player> getAllPlayer() {
return players;
}

public static GameBase createGameBase(Player player, int x, int y) {
return new GameBase(player, x, y);
}

private static List<String[]> readLines(String filename) {
String line = "";
Reader reader = null;
List<String[]> result = new ArrayList<>();
try {
reader = new FileReader(filename);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (reader == null) {
return null;
}
LineNumberReader lineReader = new LineNumberReader(reader);

try {
while (true) {
line = lineReader.readLine();// 读一行
if (line == null) {// 空的,文件末尾
break;
}
if (line.trim().length() == 0 || line.startsWith("#")) {// 去掉一行的空格,判断是空行或者开头是#,跳过
continue;
}
result.add(line.trim().split("\\.|="));// 正则表达式,"."需要转义(?)前面加\\,|可以代表多个分隔符
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}

private static void setValue(String[] rule) {// 设置属性,这一定不是好的写法
if (EnumObjectType.base.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.baseHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[0])) {
Param.baseStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[0])) {
Param.baseRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.heavyTank.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.heavyTankHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.heavyTankStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.heavyTankRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.mediumTank.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.mediumTankHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.mediumTankStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.mediumTankRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.rifleSoldier.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.rifleSoldierHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.rifleSoldierStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.rifleSoldierRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.RPGSoldier.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.RPGSoldierHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.RPGSoldierStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.RPGSoldierRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.dog.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.dogHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.dogStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.dogRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.warFactory.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.warFactoryHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.warFactoryStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.warFactoryRange = Integer.parseInt(rule[2]);
}
} else if (EnumObjectType.barrack.toString().equals(rule[0])) {
if (EnumObjectType.health.toString().equals(rule[1])) {
Param.barrackHealth = Integer.parseInt(rule[2]);
} else if (EnumObjectType.strength.toString().equals(rule[1])) {
Param.barrackStrength = Integer.parseInt(rule[2]);
} else if (EnumObjectType.range.toString().equals(rule[1])) {
Param.barrackRange = Integer.parseInt(rule[2]);
}
}
}
}

0x01 Player

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.huawei.classroom.student.h17;

/**
* @author super
*/
public class Player {
private String name;

public Player(String name) {
this.name = name;
}

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

0x02 GameObject

增加自定义攻击方法。

因为要在范围内找所有对象,需要新建成员变量 gameObject(GameObject 列表)用于遍历,每新建对象是存入(add)到此列表。

新建成员变量 player 代表所属玩家、objectType 代表所属类型。分别新建 getter 方法和 setter 方法。

重写 toString 方法,用于 debug:

1
2
3
4
@Override
public String toString() {
return "["+player.getName() + "." + objectType +" live=" + (!isDestroyed()) + " x=" + x + " y=" + y+ " health=" + lifeValue+"]";
}

修改 attack(GameObject) 方法(不再有一击毙命):

1
2
3
4
5
6
7
8
9
10
11
public void attack(GameObject obj) {
if (obj.isDestroyed() || this.isDestroyed()) {// 攻击者或被攻击者死了
return;
}
if (this.attackRange >= this.getDistance(obj)) {// 在范围内,可以攻击
String debug=this+" 攻击 "+obj;// debug 需要
obj.changeHealth(this.attackPower);
debug=debug+" 攻击后 health="+obj.getHealth();// debug 需要
System.out.println(debug);// debug 需要
}
}

新建 attack() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void attack() {
if (this.isDestroyed()) {// 判断,此处可省
return;
}
double min = this.attackRange;// 可攻击的最大距离
for (GameObject obj : GameObject.gameObjects) {// 遍历所有对象,找最近的
if (obj.isDestroyed() || obj.player == this.player) {// 被攻击者死了或者是友军
continue;// 继续找,这里不能 return
}
if (this.getDistance(obj) <= min) {// 更近的
min = this.getDistance(obj);// 更近的距离
}
}
for (GameObject obj : GameObject.gameObjects) {// 上面找到了最近的距离
if (obj.isDestroyed() || obj.player == this.player) {// 被攻击者死了或者是友军
continue;// 继续找那个距离是最近距离的对象
}
if (this.getDistance(obj) - min < 0.000001) {// 找到了
attack(obj);// 直接调用上面的方法攻击
}
}
}

0x03 Others

Param 中变量不再是 final。

继承 GameObject 的类均需在构造方法中调用 setObjectType 方法,指明自身类型,便于 debug。

继承 GameObject 的类的构造方法均需传入参数「玩家」。

gameBase 创建 barrack 和 warFactory 、barrack 和 warFactory 创建 soldier、dog 和 tank 时,需传入自身的 getPlayer。

building、tank 和 soldier 起传递作用。