【IT168技术文档】2.5D游戏,虽然在外观上近似于3D游戏,却又不是严格意义上讲的3D游戏,故此2.5D游戏又常被称为[伪3D游戏]。
在笔者的观念中,2.5D严格上说并不能算是一种技术,而只是一种实现方式或者说应用手段。大多数时候,游戏公司之所以会采取2.5D方式开发游戏,常是为解决3D及2D技术混用而采取的一种折中,而并不是说这种手段有多么先进。2.5D游戏的实现方式虽然很多,但主要无非有三类,即:2D角色+3D场景(比如RO1)、3D角色+2D场景(比如生化复刻版)、2D角色+2D场景(比如仙剑),另外有些纯3D游戏出于操作性考虑而固定视角,勉强擦了个2.5D的边,但严格上讲依旧是3D。
除了2D角色+2D场景是利用斜45度的2D图片来“冒充”3D效果以外,其它组合方式大多有真正的3D演算参与其中,不过是出于开发效率或者游戏风格等外部因素考虑而混入2D,可以说是一种产生在2D向3D演变过程中的过渡物。
使用2.5D方式开发游戏的好处在于,能够免去纯3D图像渲染所涉及的海量运算,从而减低不必要的系统资源损耗,并且在处理单纯的2D画面时,贴图也明显较系统渲染为快,最主要的是——开发周期明显较3D游戏更短。
当然,缺点也很明显,最核心的一点就够——不伦不类,非驴非马。
具体到2.5D游戏开发,根据选择的技术不同,也会产生不同的实现手段,笔者在[Java中2.5D游戏(斜45度角)的设计与实现]系列博文中内所要介绍的,是最简单,同时也最方便的2D角色+2D场景——伪45度角方式,不需要的可以无视此文了,具体到真3D与2D的混合,笔者将留到介绍JME时再去讲解。
在正式开始本回之前,我们先来简要介绍下不同维度空间的特点:
零维,简单讲就是没有维度,表现形式上就是一个[点],没有长度,更没有高度。实际上,很多人认为宇宙最初就是个零维空间。
一维,多体现为一条[直线],也可以理解为一个只有长度,而没有高度的无限线性空间。
二维,即我们常说的[平面],由X及Y两点交织而成,即只有长与宽,典型的二维空间存在方式是数据表格。
三维,三维即[立体],若谁不能理解三维,请随意向显示器四周看看,举目所及全是三维空间。从技术角度解释,三维就是在二维的长、宽基础上再加个高度(厚度)形成的体积面,典型的三维存在方式就是我们这个世界。
四维,四维空间是个非常模糊的概念,实际上它象征着[n维]。在物理学及数学概念中,一个n的序列可以被理解为一个n维空间中的位置,当n=4时,所有这样的位置的集合都叫做四维空间,但是当n-1或者n+1时,它又会自然过渡成其它空间。这种空间与我们熟悉并在其中居住的三维空间不同,因为它多一个维度。这个额外的维度既可以理解成时间,也可以直接理解为空间的第四维,即第四空间维度。当我们说到四维空间时,常会扯出天堂、地狱、阴阳界等超自然理论,实在玄之又玄,笔者也不知道它究竟怎样表示才好……
由于人类生存在三维空间,我们周围的空间自然也都具有三个维度(上下(长)、左右(宽)、前后(厚度)),用中式思维解释就是世有八荒(又称八方,即“东、西、南、北、东南、东北、西南、西北”八个方向),人居六合(即“上、下、东、西、南、北”六个方位),我们也都很自然的会用“八荒六合”来进行方位判定,当然,最先决的方位判定条件是“我在其中”。
如果一个人能在“八荒六合”之内与我一样行动,我当然会认为他与我一样练成了八荒六合唯我独尊功……咳,我是说认为他与我同样是一个立体的人,而不是一张纸片,嗯嗯(=_=|||)。
同样的道理,在游戏中如果一个2D Sprite的行动模式与3D Sprite一致,那么用户便很容易“误认”此单元为3D,而非2D。原因就在于,人眼是极好蒙蔽的,比如好莱坞早期大片中就经常使用纸制建筑来冒充城镇或者某个名声古迹;即便游戏2.5D游戏中没有实际的3D坐标及多面计算,只要能给人眼以“距离感”或者说“立体感”,我们也会认为这个游戏是三维存在的,而没人会去关心它是否真正使用了3D渲染方式。换句话说,只要我们创建的游戏单元“看上去”能“行八荒”,“游六合”,也就是看上去它的行为是3D立体的,玩家就会认为角色正处于一个三维空间之内,而不是穿越到某个2D世界。
那么,这个“看上去移动”的效果要如何达到呢?
我们用下图作为示例:
通过上图中我们可以发现,此图中角色的单元动作被分解为八种不同类型,即上、左、右、下、左上、右上、左下、右下,而此八种动作正好对应着“八荒”中的“北、西、东、南、西北、东北、西南、东南”。由于人眼所能观测到的运行是相对的,只要背景或者角色中任意一者发生移动,我们都会产生“移动”的“错觉”。所以我们并不追求角色的实际3D运动,而只是令角色单元能做出对应“上、左、右、下、左上、右上、左下、右下”这八方向的动作,在普通人眼中,便与角色移动向“北、西、东、南、西北、东北、西南、东南”这八个方向无异。
因此,在2D制作2.5D效果时,每个角色的动作单元实际上都只是一幅分帧小图罢了。
如何判定对应的单元图像呢?
具体到单元动作的显示判定,和游戏采取的斜视角产生方式息息相关,大体上分可分两类,即非等距坐标实现与等距坐标实现。
1、非等距坐标实现:
逻辑如下图:
以0点为常模(参照物),在2D地图上“错位”绘制角色,每次角色移动时偏移相应坐标,配合图片产生45度移动错觉。
优点:在编程上极容易实现,并且更利于地图及角色的联合操作。
缺点:如果不对单元与地图的交织部分进行细节处理,很容易产生移动“生硬”感,并且很难融入一些较复杂的地形判定。
2、等距坐标实现:
逻辑如下图:
实际上此图没有任何坐标变化,而是在不改变2D图形X,Y坐标系的基础上,等距转换坐标点位置为斜45度时的状态,由于坐标系经过等距转换,所以实际操作中与2D无疑。
优点:除了坐标转换外,基本上还是2D那一套,纯2D时怎样处理便怎样处理。
缺点:为了配合倾斜后的X,Y坐标拼接,大部分平面图必须转换为45度图,当然这是美工的事(^^)(PS:虽然也可以在平面图上自动换算出所需的斜视图形,但细节处通常不够理想,而且耗费不必要的运算资源,还是交给美工直接切出成品图最好,鄙人大原则就是能麻烦美工就不劳驾程序员(^^)),不过象笔者这样个人研究就超麻烦……
在本系列博文在后面会涉及到此部分,在这里先给出一个基本概念。
首先,要转换坐标为斜45度时位置,至少需要以下参数。
1、mapX(地图X坐标)
2、mapY(地图Y坐标)
3、mapMaxY (Y轴的最大纵深)
4、tileWidth(每块小图宽度)
5、tileHeight(每块小图高度)
而后我们才可以根据基础参数换算坐标位置:
screenX (屏幕坐标X)
screenY (屏幕坐标Y)
screenX = (mapX - mapY + mapMaxY) * (tileWidth / 2);
screenY = (mapX + mapY) * (tileHeight / 2);
bevelMapX (倾视的X坐标)
bevelMapY (倾视的Y坐标)
bevelMapX = ((screenY / tileHeight) + (screenX - (mapMaxY * tileWidth/2)) / tileWidth);
bevelMapY = ((screenY / tileHeight) - (screenX - (mapMaxY * tileWidth/2)) / tileWidth);
这时得到的bevelMapX及bevelMapY,就是斜45度时的绘图位置,以此坐标绘制准备好的斜视图,就自然会呈现在斜视情况下的X,Y点位置上。
由于本例中为非等距实现,也就是在2D地图上直接位移角色单元到斜点,故此不需要额外的进行地图坐标换算处理,但是人物坐标则需偏移。
下面开始我们用代码示例说话:
Role.java(负责描述一个角色单元的行为)
2 import java.awt.Color;
3 import java.awt.Graphics;
4 import java.util.List;
5 import org.loon.game.simple.alldirection.GraphicsUtils;
6 public class Role implements Config {
7 private static final int SPEED = 4;
8 public static final double PROB_MOVE = 0.02;
9 private int x, y;
10 private int px, py;
11 private int direction;
12 private int count;
13 private boolean isMoving;
14 private int movingLength;
15 private int moveType;
16 private String message;
17 private Thread threadAnime;
18 private RpgSprite sprite;
19 private RpgMap map;
20 private String name;
21 private String partyName;
22 private int ioffsetX;
23 private int ioffsetY;
24 private boolean autoFinder;
25 private boolean isLoop;
26 public Role(String fileName, int x, int y, int direction, int moveType,
27 RpgMap map) {
28 sprite = new RpgSprite(fileName);
29 this.x = x;
30 this.y = y;
31 px = x * CS;
32 py = y * CS;
33 ioffsetX = sprite.getImageWidth() - CS;
34 ioffsetY = sprite.getImageHeight() - CS;
35 this.direction = direction;
36 this.count = 0;
37 this.moveType = moveType;
38 this.map = map;
39 this.roleLoop();
40 }
41 private void roleLoop() {
42 isLoop = true;
43 threadAnime = new Thread(new AnimationThread());
44 threadAnime.start();
45 }
46 public void stop() {
47 isLoop = false;
48 threadAnime = null;
49 }
50 public void setXandY(Cell2D cell) {
51 setXandY(cell.x(), cell.y());
52 }
53 public void setXandY(int x, int y) {
54 this.x = x;
55 this.y = y;
56 }
57 public Cell2D getCell2D() {
58 return new Cell2D(x, y);
59 }
60 private synchronized void redress() {
61 if (autoFinder) {
62 move();
63 }
64 if (px < 0) {
65 px = 0;
66 }
67 if (py < 0) {
68 py = 0;
69 }
70 if (px > map.getWidth() - CS) {
71 px = map.getWidth() - CS;
72 x = map.getRow() - 1;
73 }
74 if (py > map.getHeight() - CS) {
75 py = map.getHeight() - CS;
76 y = map.getCol() - 1;
77 }
78 }
79 public synchronized void draw(Graphics g, int offsetX, int offsetY) {
80 redress();
81 int aspect = 0;
82 switch (direction) {
83 case UP:
84 aspect = RpgSprite.UPPER_RIGHT;
85 break;
86 case DOWN:
87 aspect = RpgSprite.LOWER_LEFT;
88 break;
89 case LEFT:
90 aspect = RpgSprite.UPPER_LEFT;
91 break;
92 case RIGHT:
93 aspect = RpgSprite.LOWER_RIGHT;
94 break;
95 case TUP:
96 aspect = RpgSprite.UP;
97 break;
98 case TDOWN:
99 aspect = RpgSprite.DOWN;
100 break;
101 case TLEFT:
102 aspect = RpgSprite.LEFT;
103 break;
104 case TRIGHT:
105 aspect = RpgSprite.RIGHT;
106 break;
107 }
108 int nx = px + offsetX - ioffsetX;
109 int ny = py + offsetY - ioffsetY;
110 g.drawImage(sprite.getMove(aspect)[count], nx, ny, null);
111 if (name != null) {
112 int fontHeight = g.getFontMetrics().getHeight();
113 int nameFontWidth = g.getFontMetrics().stringWidth(name);
114 int size = 20;
115 int mx = nx + ioffsetX - nameFontWidth / 2;
116 int my = ny + ioffsetY + size + fontHeight;
117 GraphicsUtils.drawStyleString(g, name, mx, my, Color.black,
118 Color.white);
119 GraphicsUtils.drawStyleString(g, partyName, mx, my + size,
120 Color.black, Color.white);
121 }
122 }
123 public synchronized void autoDirection(List startPath) {
124 Cell2D cell1 = (Cell2D) startPath.get(0);
125 try {
126 if (startPath.size() > 1) {
127 Cell2D cell2 = (Cell2D) startPath.get(1);
128 int sx = cell2.x() - cell1.x();
129 int sy = cell2.y() - cell1.y();
130 direction = Field2D.getDirection(sx, sy);
131 }
132 } finally {
133 startPath.remove(0);
134 }
135 }
136 public synchronized boolean move() {
137 switch (direction) {
138 case LEFT:
139 if (moveLowerLeft()) {
140 return true;
141 }
142 break;
143 case RIGHT:
144 if (moveLowerRight()) {
145 return true;
146 }
147 break;
148 case UP:
149 if (moveUpperRight()) {
150 return true;
151 }
152 break;
153 case DOWN:
154 if (moveUpperLeft()) {
155 return true;
156 }
157 break;
158 case TLEFT:
159 if (moveLeft()) {
160 return true;
161 }
162 break;
163 case TRIGHT:
164 if (moveRight()) {
165 return true;
166 }
167 break;
168 case TUP:
169 if (moveUp()) {
170 return true;
171 }
172 break;
173 case TDOWN:
174 if (moveDown()) {
175 return true;
176 }
177 break;
178 }
179 return false;
180 }
181 protected boolean moveLeft() {
182 int nextX = x - 1;
183 int nextY = y;
184 if (nextX < 0) {
185 nextX = 0;
186 }
187 if (!map.isHit(nextX, nextY)) {
188 px -= Role.SPEED;
189 movingLength += Role.SPEED;
190 if (movingLength >= CS) {
191 x--;
192 px = x * CS;
193 isMoving = false;
194 return true;
195 }
196 } else {
197 isMoving = false;
198 px = x * CS;
199 py = y * CS;
200 }
201 return false;
202 }
203 protected boolean moveRight() {
204 int nextX = x + 1;
205 int nextY = y;
206 if (nextX > map.getCol() - 1) {
207 nextX = map.getCol() - 1;
208 }
209 if (!map.isHit(nextX, nextY)) {
210 px += Role.SPEED;
211 movingLength += Role.SPEED;
212 if (movingLength >= CS) {
213 x++;
214 px = x * CS;
215 isMoving = false;
216 return true;
217 }
218 } else {
219 isMoving = false;
220 px = x * CS;
221 py = y * CS;
222 }
223 return false;
224 }
225 protected boolean moveUp() {
226 int nextX = x;
227 int nextY = y - 1;
228 if (nextY < 0) {
229 nextY = 0;
230 }
231 if (!map.isHit(nextX, nextY)) {
232 py -= Role.SPEED;
233 movingLength += Role.SPEED;
234 if (movingLength >= CS) {
235 y--;
236 py = y * CS;
237 isMoving = false;
238 return true;
239 }
240 } else {
241 isMoving = false;
242 px = x * CS;
243 py = y * CS;
244 }
245 return false;
246 }
247 protected boolean moveDown() {
248 int nextX = x;
249 int nextY = y + 1;
250 if (!map.isHit(nextX, nextY)) {
251 py += Role.SPEED;
252 movingLength += Role.SPEED;
253 if (movingLength >= CS) {
254 y++;
255 py = y * CS;
256 isMoving = false;
257 return true;
258 }
259 } else {
260 isMoving = false;
261 px = x * CS;
262 py = y * CS;
263 }
264 return false;
265 }
266 protected boolean moveLowerLeft() {
267 int nextX = x - 1;
268 int nextY = y - 1;
269 if (nextX < 0) {
270 nextX = 0;
271 }
272 if (nextY < 0) {
273 nextY = 0;
274 }
275 if (!map.isHit(nextX, nextY)) {
276 px -= Role.SPEED;
277 py -= Role.SPEED;
278 movingLength += Role.SPEED;
279 if (movingLength >= CS) {
280 x--;
281 px = x * CS;
282 y--;
283 py = y * CS;
284 isMoving = false;
285 return true;
286 }
287 } else {
288 isMoving = false;
289 px = x * CS;
290 py = y * CS;
291 }
292 return false;
293 }
294 protected boolean moveLowerRight() {
295 int nextX = x + 1;
296 int nextY = y + 1;
297 if (nextX > map.getRow() - 1) {
298 nextX = map.getRow() - 1;
299 }
300 if (nextY > map.getCol() - 1) {
301 nextY = map.getCol() - 1;
302 }
303 if (!map.isHit(nextX, nextY)) {
304 px += Role.SPEED;
305 py += Role.SPEED;
306 movingLength += Role.SPEED;
307 if (movingLength >= CS) {
308 x++;
309 px = x * CS;
310 y++;
311 py = y * CS;
312 isMoving = false;
313 return true;
314 }
315 } else {
316 isMoving = false;
317 px = x * CS;
318 py = y * CS;
319 }
320 return false;
321 }
322 protected boolean moveUpperLeft() {
323 int nextX = x - 1;
324 int nextY = y + 1;
325 if (nextX < 0) {
326 nextX = 0;
327 }
328 if (nextY > map.getCol() - 1) {
329 nextY = map.getCol() - 1;
330 }
331 if (!map.isHit(nextX, nextY)) {
332 px -= Role.SPEED;
333 py += Role.SPEED;
334 movingLength += Role.SPEED;
335 if (movingLength >= CS) {
336 x--;
337 px = x * CS;
338 y++;
339 py = y * CS;
340 isMoving = false;
341 return true;
342 }
343 } else {
344 isMoving = false;
345 px = x * CS;
346 py = y * CS;
347 }
348 return false;
349 }
350 protected boolean moveUpperRight() {
351 int nextX = x + 1;
352 int nextY = y - 1;
353 if (nextX > map.getRow() - 1) {
354 nextX = map.getRow() - 1;
355 }
356 if (nextY < 0) {
357 nextY = 0;
358 }
359 if (!map.isHit(nextX, nextY)) {
360 px += Role.SPEED;
361 py -= Role.SPEED;
362 movingLength += Role.SPEED;
363 if (movingLength >= CS) {
364 x++;
365 px = x * CS;
366 y--;
367 py = y * CS;
368 isMoving = false;
369 return true;
370 }
371 } else {
372 isMoving = false;
373 px = x * CS;
374 py = y * CS;
375 }
376 return false;
377 }
378 /**
379 * 触发事件
380 *
381 * @return
382 */
383 public Role talkWith() {
384 int nextX = 0;
385 int nextY = 0;
386 switch (direction) {
387 case LEFT:
388 nextX = x - 1;
389 nextY = y;
390 break;
391 case RIGHT:
392 nextX = x + 1;
393 nextY = y;
394 break;
395 case UP:
396 nextX = x;
397 nextY = y - 1;
398 break;
399 case DOWN:
400 nextX = x;
401 nextY = y + 1;
402 break;
403 }
404 Role chara;
405 chara = map.getRoles().roleCheck(nextX, nextY);
406 if (chara != null) {
407 switch (direction) {
408 case LEFT:
409 chara.setDirection(RIGHT);
410 break;
411 case RIGHT:
412 chara.setDirection(LEFT);
413 break;
414 case UP:
415 chara.setDirection(DOWN);
416 break;
417 case DOWN:
418 chara.setDirection(UP);
419 break;
420 }
421 }
422 return chara;
423 }
424 public int getX() {
425 return x;
426 }
427 public int getY() {
428 return y;
429 }
430 public int getPx() {
431 return px;
432 }
433 public int getPy() {
434 return py;
435 }
436 public void setDirection(int dir) {
437 direction = dir;
438 }
439 public synchronized boolean isMoving() {
440 return isMoving;
441 }
442 public synchronized void setMoving(boolean flag) {
443 isMoving = flag;
444 movingLength = 0;
445 }
446 public String getMessage() {
447 return message;
448 }
449 public void setMessage(String message) {
450 this.message = message;
451 }
452 public int getMoveType() {
453 return moveType;
454 }
455 private class AnimationThread extends Thread {
456 public void run() {
457 while (isLoop) {
458 if (count < sprite.getSize()) {
459 count++;
460 } else {
461 count = 0;
462 }
463 try {
464 Thread.sleep(300);
465 } catch (InterruptedException e) {
466 }
467 }
468 }
469 }
470 public boolean isAutoFinder() {
471 return autoFinder;
472 }
473 public void setAutoFinder(boolean autoFinder) {
474 this.autoFinder = autoFinder;
475 }
476 public int getIoffsetX() {
477 return ioffsetX;
478 }
479 public int getIoffsetY() {
480 return ioffsetY;
481 }
482 public String getName() {
483 return name;
484 }
485 public void setName(String name) {
486 this.name = name;
487 }
488 public String getPartyName() {
489 return partyName;
490 }
491 public void setPartyName(String partyName) {
492 this.partyName = partyName;
493 }
494 }
495
RprMap.java(负责描述角色单元集合在地图上的具体位置)
2 import java.awt.Color;
3 import java.awt.Graphics;
4 import java.io.IOException;
5 import java.util.List;
6 import org.loon.game.simple.alldirection.GraphicsUtils;
7 import org.loon.game.simple.alldirection.LSystem;
8 public class RpgMap implements Config {
9 private ImageMapFactory imageMap;
10 private Roles roles;
11 private boolean showGrid;
12 private Field2D map2d;
13 private int firstTileX;
14 private int firstTileY;
15 private int lastTileX;
16 private int lastTileY;
17 public RpgMap(String imageFile, String mapFile) {
18 try {
19 imageMap = new ImageMapFactory(imageFile, mapFile);
20 } catch (IOException e) {
21 throw new RuntimeException(e);
22 }
23 this.map2d = new Field2D(imageMap.getMap());
24 this.roles = new Roles();
25 }
26 public void addRole(Role role) {
27 roles.addChara(role);
28 }
29 public Role getHero() {
30 return roles.getHero();
31 }
32 public void setupHero(Role hero) {
33 roles.mainHero(hero);
34 }
35 public synchronized void draw(Graphics g, int offsetX, int offsetY) {
36 firstTileX = pixelsToTiles(-offsetX);
37 lastTileX = firstTileX + pixelsToTiles(LSystem.WIDTH) + 1;
38 lastTileX = Math.min(lastTileX, getRow());
39 firstTileY = pixelsToTiles(-offsetY);
40 lastTileY = firstTileY + pixelsToTiles(LSystem.HEIGHT) + 1;
41 lastTileY = Math.min(lastTileY, getCol());
42 for (int i = firstTileX; i < lastTileX; i++) {
43 for (int j = firstTileY; j < lastTileY; j++) {
44 g.drawImage(imageMap.getImages()[i][j], tilesToPixels(i)
45 + offsetX, tilesToPixels(j) + offsetY, null);
46 if (showGrid) {
47 if (imageMap.getMap()[j][i] == 1) {
48 g.setColor(Color.white);
49 g.drawRect(tilesToPixels(i) + offsetX, tilesToPixels(j)
50 + offsetY, CS - 2, CS - 2);
51 GraphicsUtils.setAlpha(g, 0.5d);
52 g.fillRect(tilesToPixels(i) + offsetX, tilesToPixels(j)
53 + offsetY, CS - 2, CS - 2);
54 GraphicsUtils.setAlpha(g, 1.0d);
55 } else if (imageMap.getMap()[j][i] == -1) {
56 g.setColor(Color.blue);
57 g.drawRect(tilesToPixels(i) + offsetX, tilesToPixels(j)
58 + offsetY, CS - 2, CS - 2);
59 GraphicsUtils.setAlpha(g, 0.3d);
60 g.fillRect(tilesToPixels(i) + offsetX, tilesToPixels(j)
61 + offsetY, CS - 2, CS - 2);
62 GraphicsUtils.setAlpha(g, 1.0d);
63 }
64 }
65 }
66 }
67 roles.draw(g, offsetX, offsetY);
68 }
69 public int getSelfFirstX() {
70 return firstTileX;
71 }
72 public int getSelfFirstY() {
73 return firstTileY;
74 }
75 public int getSelfLastX() {
76 return lastTileX;
77 }
78 public int getSelfLastY() {
79 return lastTileY;
80 }
81 public int getSelfFirstWidth() {
82 return tilesToPixels(firstTileX);
83 }
84 public int getSelfFirstHeight() {
85 return tilesToPixels(firstTileY);
86 }
87 public int getSelfLastWidth() {
88 return tilesToPixels(lastTileX);
89 }
90 public int getSelfLastHeight() {
91 return tilesToPixels(lastTileY);
92 }
93 public List findPath(Role hero, Cell2D goal) {
94 return AStarFinder.find(map2d, hero.getCell2D(), goal);
95 }
96 public void showGrid(boolean show) {
97 this.showGrid = show;
98 }
99 public ImageMapFactory getFactory() {
100 return imageMap;
101 }
102 public boolean isHit(int x, int y) {
103 try {
104 int[][] map = imageMap.getMap();
105 if (map[y][x] == 1) {
106 return true;
107 }
108 if (roles.isHit(x, y)) {
109 return true;
110 }
111 return false;
112 } catch (Exception e) {
113 return false;
114 }
115 }
116 public static int pixelsToTiles(double pixels) {
117 return (int) Math.floor(pixels / CS);
118 }
119 public static int tilesToPixels(int tiles) {
120 return tiles * CS;
121 }
122 public int getRow() {
123 return imageMap.getMapWidth();
124 }
125 public int getCol() {
126 return imageMap.getMapHeight();
127 }
128 public int getWidth() {
129 return imageMap.getImageWidth();
130 }
131 public int getHeight() {
132 return imageMap.getImageHeight();
133 }
134 public Roles getRoles() {
135 return roles;
136 }
137 }
Main.java(初始配置及程序运行)
2 import org.loon.game.simple.alldirection.GameCursor;
3 import org.loon.game.simple.alldirection.GameFrame;
4 import org.loon.game.simple.alldirection.rpg.Config;
5 import org.loon.game.simple.alldirection.rpg.Role;
6 import org.loon.game.simple.alldirection.rpg.RpgLayout;
7 import org.loon.game.simple.alldirection.rpg.RpgMap;
8 /**
9 * Copyright 2008 - 2009
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
12 * use this file except in compliance with the License. You may obtain a copy of
13 * the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
20 * License for the specific language governing permissions and limitations under
21 * the License.
22 *
23 * @project loonframework
24 * @author chenpeng
25 * @email:ceponline@yahoo.com.cn
26 * @version 0.1
27 */
28 public class Main {
29 public static void main(String[] args) {
30 java.awt.EventQueue.invokeLater(new Runnable() {
31 public void run() {
32 GameFrame frame = new GameFrame(
33 "Java 2.5D游戏开发中的八方走法实现<LoonFramework-Game>", 640, 480);
34 // 设定游标
35 frame.setCursor(GameCursor.getCursor("image/cursor.png"));
36 // 游戏地图
37 RpgMap rpgMap = new RpgMap("image/map/maze.jpg",
38 "./image/map/maze.map");
39 // 显示网格
40 rpgMap.showGrid(false);
41 // 创建主角
42 Role hero = new Role("image/role/gm.png", 7, 8, Config.DOWN, 0,
43 rpgMap);
44 hero.setName("妈妈说坏孩子长大以后就是GM");
45 hero.setPartyName("曾经的北S商人");
46 // 创建NPC1
47 Role npc1 = new Role("image/role/assassin.png", 3, 14,
48 Config.LEFT, 1, rpgMap);
49 npc1.setName("炼妖狐(爆刺、爆刺、一拍即死)");
50 npc1.setPartyName("此人已死,有事烧纸");
51 // //创建NPC2
52 Role npc2 = new Role("image/role/rogue.png", 15, 21, Config.LEFT,
53 1, rpgMap);
54 npc2.setName("猫猫(巴帽小偷)");
55 npc2.setPartyName("病猫不发威你还拿我当老虎了");
56 // 设定主角
57 rpgMap.setupHero(hero);
58 // 设定NPC
59 rpgMap.addRole(npc1);
60 rpgMap.addRole(npc2);
61 frame.getGame().setControl(new RpgLayout(rpgMap));
62 // 游戏全屏
63 // frame.updateFullScreen();
64 frame.setFPS(true);
65 frame.mainLoop();
66 frame.showFrame();
67 }
68 });
69 }
70 }
示例截取图如下所示:
至于脚本处理、事件触发,对话框、场景转换、角色对战等部分将在以后逐步讲解。
下载地址如下:http://code.google.com/p/loon-simple/downloads/list