RPG游戏

RPG冒险游戏(单机版)

前言:

这是一个基于Java语言的RPG冒险小游戏,使用maven构建管理,javafx进行绘图。玩家可以移动探索地图,自由切换皮肤,攻击怪物获得金币,拾取道具补充血量或拾取武器攻击敌人。玩家移动至相应位置可触发气泡提示,也可发送语音气泡显示在游戏角色头顶,增强玩家与游戏的互动性,提高趣味性。

此游戏还有联机版,网络多人作战,玩家可以通过语音输入与其它玩家进行互动。

以下是几张游戏截屏:

登录/注册界面:

play1

游戏场景:

play2

play3

play4

play5

play6

游戏逻辑设计:

  1. 玩家可通过WASD键进行八向移动,J键发射飞镖,C键进行换肤
  2. 玩家可通过移动拾取道具或武器,进行补充血量或攻击敌人
  3. 玩家击败敌人后可获得金币
  4. 玩家移动至地图传送口时可进行场景切换,前提是场景任务已完成
  5. 角色与地图、角色与角色间有碰撞检测
  6. 某些敌人具有追踪功能,当玩家靠近至一定范围内时会自发进行追踪

类设计:

GamePanel面板类:
游戏绘制主面板 也是启动类
启动后会先跳转至登录/注册页面 信息验证成功便进入游戏界面
初始化地图、玩家角色、敌人、道具、特效,并不断重绘(对象位置随时改变)
进行键盘监听,监听玩家操控、切换皮肤、发送语音气泡、查看教程等行为
布置当前场景任务,进行场景切换(杀完敌人才可以切换场景 道具不捡就会消失)

WordMap地图类:
读取地图文件数据 设置空气墙 设置怪物道具出生点等

Role角色类:
hero玩家类与enemy敌人类的父类
拥有移动线程,随时与空气墙的碰撞检测、角色间的碰撞检测逻辑

Hero玩家类:
继承Role角色类
根据角色朝向切换图像、具有切换角色皮肤功能
重写移动撞击线程,根据角色移动至传送口的位置与朝向切换游戏场景
具有发射子弹shoot逻辑

Enemy敌人类:
具有不同类型type,不同的敌人类型,主要分为近战型与远战型(发射子弹)
某些敌人具有追踪功能,当主角靠近至一定范围内即展开追踪

Arm道具类:
同样有自己的线程,有不同类型的道具或武器,可以回血、增强攻击力、伤害玩家自身等

Bomb炸弹特效类:
当道具中的炸弹被触发时,进行爆炸特效展示

Shot子弹类:
玩家与远战敌人所发射的子弹,自己拥有移动线程
子弹具有“生命”,超出界面范围或击中对手后则“死亡”,子弹消失

部分代码展示:

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
//游戏地图类

public class WordMap {
//游戏面板对象(获取画笔)
private GamePanel gp;
//当前场景
private int nowScene;
//空气墙集合
public static int[][] airWall;
//空气墙地图
private File map = map1;
//空气墙文件集合
private static File map1 = new File("src/main/java/org/example/wordMaps/map1.txt");
private static File map2 = new File("src/main/java/org/example/wordMaps/map2.txt");
private static File map3 = new File("src/main/java/org/example/wordMaps/map3.txt");
private static File map4 = new File("src/main/java/org/example/wordMaps/map4.txt");
private static File map5 = new File("src/main/java/org/example/wordMaps/map5.txt");
private static File map6 = new File("src/main/java/org/example/wordMaps/map6.txt");
private static File map7 = new File("src/main/java/org/example/wordMaps/map7.txt");

public WordMap(GamePanel gp) {
this.gp = gp;
nowScene = 1;
//初始读取一次地图
try {
readMap(40, 40, map);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

//读取地图数据
public void readMap(int rows, int cols, File map) throws Exception {
BufferedReader br = new BufferedReader(new FileReader(map));
String line;
airWall = new int[40][40];
int rowIndex = 0;
while ((line = br.readLine()) != null && rowIndex < rows) {
String[] values = line.split(",");
for (int collIndex = 0; collIndex < cols; collIndex++) {
airWall[rowIndex][collIndex] = Integer.parseInt(values[collIndex].trim());
}
rowIndex++;
}
br.close();

// //debug
// for (int i = 0; i < 40; i++) {
// for (int j = 0; j < 40; j++) {
// System.out.print(airWall[i][j]+" ");
// }
// System.out.println();
// }
}

public void setAirWall(int[][] airWall) {
this.airWall = airWall;
}

public File getMap() {
return map;
}

public void setMap(int num) {
//先切换空气墙数据地图
switch (num){
case 1:
map = map1;
nowScene = 1;
break;
case 2:
map = map2;
nowScene = 2;
break;
case 3:
map = map3;
nowScene = 3;
break;
case 4:
map = map4;
nowScene = 4;
break;
case 5:
map = map5;
nowScene = 5;
break;
case 6:
map = map6;
nowScene = 6;
break;
case 7:
map = map7;
nowScene = 7;
break;
}
//再重新读取空气墙
try {
readMap(40,40,map);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public int getNowScene() {
return nowScene;
}

public void setNowScene(int nowScene) {
this.nowScene = nowScene;
}
}
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//玩家类
public class Hero extends Role {
private String name = "吴亦涵";//玩家名字
private Image hero1 = hero11;
private Image hero2 = hero12;
private Image hero3 = hero13;
private Image hero4 = hero14;
private Image hero5 = hero15;
private Image hero6 = hero16;
private Image hero7 = hero17;
private Image hero8 = hero18;
private Image image = hero1;//默认方向
private GamePanel gp;//面板类

private Vector<Shot> shots = new Vector<>();//子弹集合

public Hero(int x, int y, GamePanel gp) {
super(x, y, gp);
this.setBlood(500);
this.setOriginBlood(1000);
setAttack(0);
setSpeed(10);
setType(0);
this.gp = gp;
}

//设置主角不同方位图片
public void setImg(int num) {
switch (num) {
case 1:
this.image = hero1;
break;
case 2:
this.image = hero2;
break;
case 3:
this.image = hero3;
break;
case 4:
this.image = hero4;
break;
case 5:
this.image = hero5;
break;
case 6:
this.image = hero6;
break;
case 7:
this.image = hero7;
break;
case 8:
this.image = hero8;
break;
}
}

//重写判断是否撞击方法(撞击到切换口则场景切换)
@Override
public boolean isHit() {//此处x y已为假设运动后的坐标
int aX = this.getX() / 20;
int aY = this.getY() / 20;

//检查是否在数组边界内
if (aX < 0 || aX >= WordMap.airWall.length || aY < 0 || aY >= WordMap.airWall[0].length) {
return true; // 假设越界等同于撞到空气墙
}

int num = WordMap.airWall[aY][aX];//x y 翻转?

int sceneNum = gp.wordMap.getNowScene();
//检查是否撞到切换口进行场景切换
if (num == 2) {
switch (sceneNum) {
case 1:
if (state[2] == 1)
gp.changeScene(2);
break;
case 2:
if (state[0] == 1) {
if (getX() < 400)//左边房子
gp.changeScene(1);
else gp.changeScene(3);
} else if (state[2] == 1 && getY() > 700) {
gp.changeScene(5);
}
break;
case 3:
if (state[0] == 1)
if (getX() < 400)//左边山洞
gp.changeScene(4);
else gp.changeScene(5);
else if (state[2] == 1 && getY() > 700) {
gp.changeScene(2);
}
break;
case 4:
if (state[2] == 1)
gp.changeScene(3);
break;
case 5:
if (state[0] == 1 && getX() > 400)//上侧出口
gp.changeScene(2);
else if (state[2] == 1 && getX() < 400) {//下侧出口
gp.changeScene(3);
}
if (state[1] == 1 && getX() > 750) {//右侧铁轨
gp.changeScene(6);
}
break;
case 6:
if (state[3] == 1 && getX() < 400) {
gp.changeScene(5);
} else if (state[0] == 1 && getY() < 400) {
gp.changeScene(7);
}
if (state[1] == 1 && getX() > 400) {
//通关逻辑
if (gp.getEnemies().isEmpty())
gp.setIsWin(1);
}
break;
case 7:
if (state[2] == 1)
gp.changeScene(6);
break;
}
}
return num == 0;
}

//射击
public void shoot() {
if (getAttack() == 0) {
System.out.println("攻击力为0 没有子弹");
return;
}
Shot s = new Shot(this.getX() + 10, this.getY() + 10, this.state);
s.setImg(new Image("file:src/main/java/org/example/image/feibiao.png"));
s.setWidth(20);
s.setHeight(20);
String direction = "0000";
if (this.getImg().equals(hero1)) {
direction = "0010";
} else if (this.getImg().equals(hero2)) {
direction = "0110";
} else if (this.getImg().equals(hero3)) {
direction = "0100";
} else if (this.getImg().equals(hero4)) {
direction = "1100";
} else if (this.getImg().equals(hero5)) {
direction = "1000";
} else if (this.getImg().equals(hero6)) {
direction = "1001";
} else if (this.getImg().equals(hero7)) {
direction = "0001";
} else if (this.getImg().equals(hero8)) {
direction = "0011";
}
for (int i = 0; i < 4; i++) {
s.state[i] = direction.charAt(i) - 48;
}
s.setType(0);
shots.add(s);
s.setSpeed(30);
new Thread(s).start();
}

//设置主角不同皮肤
public void changeSkin(int num) {
switch (num) {
case 1:
hero1 = hero11;
hero2 = hero12;
hero3 = hero13;
hero4 = hero14;
hero5 = hero15;
hero6 = hero16;
hero7 = hero17;
hero8 = hero18;
break;
case 2:
hero1 = hero21;
hero2 = hero22;
hero3 = hero23;
hero4 = hero24;
hero5 = hero25;
hero6 = hero26;
hero7 = hero27;
hero8 = hero28;
break;
case 3:
hero1 = hero31;
hero2 = hero32;
hero3 = hero33;
hero4 = hero34;
hero5 = hero35;
hero6 = hero36;
hero7 = hero37;
hero8 = hero38;
break;
case 4:
hero1 = hero41;
hero2 = hero42;
hero3 = hero43;
hero4 = hero44;
hero5 = hero45;
hero6 = hero46;
hero7 = hero47;
hero8 = hero48;
break;
}
}

public Image getImg() {
return image;
}

public Vector<Shot> getShots() {
return shots;
}

public void setShots(Vector<Shot> shots) {
this.shots = shots;
}

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

public String getName() {
return this.name;
}

private static Image hero11 = new Image("file:src/main/java/org/example/image/hero/hero11.png");
private static Image hero12 = new Image("file:src/main/java/org/example/image/hero/hero12.png");
private static Image hero13 = new Image("file:src/main/java/org/example/image/hero/hero13.png");
//...省略