本例子取自cping1982早期公开的一个AVG源码,loon-simple-20090212,里面带了6个游戏。这次我们要分析的是AVGSimple这个游戏。截图如下:
下载地址:http://code.google.com/p/loon-simple/downloads/list
声明一下,这个程序不是我写的,是cping1982写的。本人在这里斗胆分析一下高手5年前写的代码,一来是提高自己,二来也是给众多小白以信心和勇气,分析完源码你会发现用java写一个AVG游戏还是不难的。
如果你无法访问google code,也可在本文文末下载我已经加上了注释的版本。
原作者的博客上有3篇文章,可谓说相当好。说的东西都是点到为止,需要读者自己去结合源码细细品味。
Java版AVG游戏开发入门[0]——游戏模式转换中的事件交互
Java版AVG游戏开发入门[1] —— CG的绘制
Java版AVG游戏开发入门示例[3]——脚本引擎的制作及应用
我这里稍微再详细的分析一点源码。
1. 游戏模式转换
IControl的抽象化是关键,可以避免像STG雷霆行动那样冗长的if else分支判断现在处于哪种模式,使得扩展更简单。其实我们看一下这张序列图可以发现,一切的东西都是绘制到画布上的(AVGCanvas.paint方法),只是“如何画”这个逻辑给分散到了不同的IControl子类里。而AVG.setControl可以达到方便的切换模式的作用。
IControl有这些子类
Title 标题模式
Script 游戏中模式(采用了脚本系统)
AqueductGame 水渠贯通小游戏模式
IControl有如下这些方法可供子类实现
invoke 转换控制器接口
draw 画图
mouseMoved 鼠标处理
mousePressed 鼠标处理
keyPressed 键盘处理
这样添加新的模式就很简单了,各个模式之间相对做到解耦,而且功能也都有了。
2. CG
就是实现IControl各个子类的draw方法。如何画?主要步骤就是先画背景图,再画前景。
下面举1例
2.1 Title的绘制
public void draw(Graphics g) { //1.先绘制背景图 graphics.drawImage(title, 0, 0, null); //2.画窗体蓝色背景 Message.setWindowMessage(graphics, MESSAGE_LINE_X1, MESSAGE_LINE_Y1, MESSAGE_LINE_X2, MESSAGE_LINE_Y2); //3.画窗体边框 Message.setWindowFrame(graphics, MESSAGE_LINE_X1, MESSAGE_LINE_Y1, MESSAGE_LINE_X2, MESSAGE_LINE_Y2); //4.画菜单选项 for (int i = 0; i < messages.length; i++) { // 选中 if (i == selectFlag) { // 变更字体颜色。 graphics.setColor(Color.white); // 设定浮标。 Message.setWindowBuoyageMessage(selectFlag, 160, 34, graphics, MESSAGE_LINE_X1, MESSAGE_LINE_Y1, MESSAGE_LINE_X2 + fontSize + 2, MESSAGE_LINE_Y2 + fontSize + 40); // 未选中 } else { graphics.setColor(Color.gray); } //一个字一个字的画出来 for (int k = 0; k < messages[i].length(); k++) { Utility.drawDefaultString(messages[i].substring(k, k + 1).toString(), graphics, font * k + left, i * (font + fontSize) + font + top, 1, font); } } g.drawImage(screen, 0, 0, null); g.dispose(); }
用的是RPG Maker XP格式的窗体图。
第一步简单,把背景图画出来即可。
第二步如图,A区绘制窗体蓝色背景,会有一个比例扩大。
第三步如图,需要将B区的通过比例换算分8步画出来(上,下,左,右,还有4个角)
第四步画菜单选项。需要根据鼠标的移动高亮选中的菜单项。
同时,mouseMoved方法处理鼠标的移动,赋值selectFlag,以待下一次绘图时高亮菜单选项。
public void mouseMoved(MouseEvent e) { super.mouseMoved(e); int k = (Control.mouse.y - top) / (font + fontSize); //如果鼠标太上面 if (k < 0) { return; } //如果鼠标太下面 if (k >= messages.length) { return; } //赋值selectFlag,以待下一次绘图时高亮菜单选项 for (int l = 0; l < messages.length; l++) { if (l == k) { selectFlag = k; continue; } } //如果鼠标x轴在框内,则标记“选中” if ((double) Control.mouse.x > this.MESSAGE_LINE_X1 && (double) Control.mouse.x < this.MESSAGE_LINE_X1 + (double) this.MESSAGE_LINE_X2 && (double) Control.mouse.y > this.MESSAGE_LINE_Y1 && (double) Control.mouse.y < this.MESSAGE_LINE_Y1 + (double) this.MESSAGE_LINE_Y2) { select = true; } else { select = false; } }
3. 脚本系统
脚本系统的运用一方面可以使得开发效率提高,bug更少,另一方面可以使得后期维护简单。脚本系统一旦成熟,不懂java的人也可以维护甚至开发一个新的游戏。
比如下面脚本显示背景,再显示了2个人物(xiaoyanyan和ranmin)。
cg del gb image/ghost.png cg chara/xiaoyanyan.png 0 cg chara/ranmin.png 200 cgwait
原理和之前Title画面类似,主要就是nextScript方法执行脚本,赋给变量(修改model层),而draw方法根据变量不同来画图(view层)。两个方法搭配,比较好的实现了MVC模式。
修改model层
private synchronized int nextScript(final int index, final String s) { //...... for (j = index; j < scriptContent.length; j++) { //显示信息 if (messageFlag.equalsIgnoreCase("mes")) { isMessage = true; break; } //背景 if (messageFlag.equalsIgnoreCase("gb")) { if (objectFlag == null) { return index; } if (objectFlag.equalsIgnoreCase("none")) { cg.setBackgroundCG(null); } else { cg.setBackgroundCG(Utility.loadImage(objectFlag)); } continue; } //人物 if (messageFlag.equalsIgnoreCase("cg")) { if (objectFlag == null) { return index; } if (objectFlag.equalsIgnoreCase("del")) { //删除原有人物 if (orderFlag != null) { cg.removeImage(orderFlag); } else { cg.clear(); } } else { //添加人物 int x = 0, y = 0; if (orderFlag != null) { x = Integer.parseInt(orderFlag); } if (gotoFlag != null) { y = Integer.parseInt(gotoFlag); } cg.addImage(objectFlag, x, y); } continue; } } }
view层
public void draw(final Graphics g) { //1.画背景,带晃动 if (cg.getBackgroundCG() != null) { if (shakeNumber > 0) { //通过随机数,达到图片在小范围内晃动效果 graphics.drawImage(cg.getBackgroundCG(), shakeNumber / 2 - Control.rand.nextInt(shakeNumber), shakeNumber / 2 - Control.rand.nextInt(shakeNumber), null); } else { graphics.drawImage(cg.getBackgroundCG(), 0, 0, null); } } //2.画人物 for (int i = 0; i < cg.getCharas().size(); i++) { Chara chara = (Chara) cg.getCharas().get(i); graphics.drawImage(chara.getCharacterCG(), chara.getX(), chara .getY(), null); } }
附件有比较详细的注释,可以参考。