基于JAVA平台的魔塔游戏设
   来源:智能计算机与应用     2018年09月09日 16:08

游戏和Java扩展工具-不锈钢拉丝面板 OPPO笑脸手机Z101评测

许益凡 薛益鸽

计文章编号: 2095-2163(2018)03-0235-06中图分类号: 文献标志码: A

摘要: 关键词: (College of Information Engineering, Wenzhou Business College, Wenzhou Zhejiang 325035, China)

Abstract: With the rise of the gaming market, various programming languages are widely used,JAVA language is the most widely used and the highest using proportion. This article takes the extremely classic magic tower game as an example, with the help of the JAVA languages object-oriented characteristic using the programming techniques: package and inherited.

Key words:

作者簡介: 许益凡(1997-),男,本科生,主要研究方向:JAVA的桌面应用、前端塔建; 薛益鸽(1990-),男,硕士,助教,主要研究方向:计算智能。

通讯作者: 收稿日期: 引言

游戏作为大众生活必不可少的一项娱乐方式,近年来发展势头良好。开发游戏的语言有很多种,JAVA是当前最流行最热门的一种编程语言, 因其是一种可以撰写跨平台应用程序的面向对象的程序设计语言\[3\],并具有卓越的通用性、高效性和平台移植性\[2\],同时,JAVA语言在游戏开发方面,又因为其语言严谨、可读性强、高度的便利性等优势,在游戏开发中具有非常重要的地位\[3\]。

魔塔游戏是一款策略类的RPG(角色扮演)游戏。游戏虽不大却充满难度,往往一步小小的失误就会让游戏失败,前功尽弃\[4\]。本文基于JAVA平台对魔塔游戏进行设计和开发。整个游戏在Microsoft Windows 7 64位操作系统下,利用 IntelliJ IDEA 2017.2.4 x64开发工具进行开发。游戏的菜单栏、操作栏和商品购买使用鼠标进行操作;勇士移动使用方向键上、下、左、右操作;打开/关闭怪物手册用快捷键A; 往上跳跃楼层用快捷键Z;往下跳跃楼层用快捷键X。

1需求分析及流程

1.1功能需求

通过对魔塔游戏的多次试玩和深入分析,可以发现,该款游戏虽然像容易通关的地牢游戏,但每一小小的决定都影响着能否救出公主顺利通关。如图1所示,此游戏有着繁多的属性和独有的伤害计算公式,伤害所扣除的血量并不是简单地直接攻击减去防御,而是需要经过伤害次数的参与计算。伤害次数是敌人的血量除去自身攻击与敌方防御之差。最终损失的血量又是需要次数乘以敌人的攻击与自身防御之差,若损失的血量超过拥有的血量则无法攻击此怪物,此时,若不提升自身能力则会卡在这个怪物面前,导致游戏失败。

本文设计的游戏主要涉及下列功能模块:战斗以及血量的计算模块、查看怪物手册模块、商店交易模块、游戏的存档与读取模块、重新开始游戏模块、退出游戏模块、与NPC对话模块、楼层跳转和金手指系统模块。功能结构如图2所示。

1.2游戏整体流程

游戏流程如下所述:游戏开始,勇士出现在魔塔的一层,没有配备装备,只被赋予了初始的属性和3把不同颜色的钥匙。勇士行动中遇到的事件如下:

(1)获取物品。可提升勇士的攻击防御等属性,增加勇士的综合实力,同时,有机会获取到特殊物品,特殊物品具有的特殊功能对游戏进展有很大帮助。

(2)遭遇怪物。怪物种类繁多,属性也不近相同,如“小怪物”、“士兵队长”、“蝙蝠怪”、“骷髅怪”、“法师”、“卫兵”等等。越高级的怪物属性越高,所需勇士的实力也就越高。其中,魔王是整个游戏中最强大也是最困难的一个怪物类NPC,魔王强具有强大的属性和华丽的外观,镇守着去往救助公主的通道,只有打败魔王才可以救出公主完成游戏。

(3)遇到商店。玩家可根据自身的购买能力提升自身的属性或等级,更好地进行游戏。

(4)NPC。在本文设计的游戏中,去除了一些购买钥匙的NPC,将其整合在商店和金手指中。同时,保留了公主这个重要角色并添加了新的剧情:击杀魔王后需解除公主手上的锁链才能将她救走,则游戏成功,否则游戏失败。流程如图3所示。

2程序实现

本款游戏具有以下3种特性:可靠性,即程序执行时不会出现故障;可维护性,即在不破坏玩家数据的前提下进行维护修复程序BUG;扩展性,即在原有基础上有更多的创新,使用户不产生使用疲劳,开发新的功能模块\[5\]。下面,详细介绍游戏中各功能的实现。

2.1游戏界面的实现

游戏界面是游戏开发的点睛之笔。本文开发的游戏界面在原版的基础上增加了金手指菜单。游戏界面由以下3个板块构成:游戏主界面,显示游戏的内容,大部分游戏的操作全在这里操作并显示;游戏控制面板,显示勇者的属性及携带的钥匙数量,并提供了保存、读取、退出、重玩游戏的按钮;游戏菜单栏,集合了所有可进行的操作,并添加了可玩性极高的金手指,给初玩者一个良好的体验。图4示出了游戏界面。

游戏主页面中地图复杂多样、各个楼层的地图都不相同,怪物NPC也是层出不穷,因此本文将图片以数字形式储存在一个二维数组里,需要时读取该数组并“画”在界面上,不同数字代表不同图片,即代表不同的角色、不同的游戏环境。同时,也为勇士行动操作提供更好的判断。第0层地图的储存图的代码如下:

map1 = new int\[\]\[\]{

{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},

{-1, 1, 3, 3, 3, 3, 1000, 3, 3, 3, 3, 1, -1},

.................. }

魔塔有很多层,每一層创建一个数组,同时,创建了一个专门用来储存初始地图的类MapPackage,以方便调用读取显示。以下是部分代码(第0、1层):

public classSaveMap {

public Map map;

public int\[\]\[\] map1;

public SaveMap() {

map1 = new int\[\]\[\]{

{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},

{-1, 1, 3, 3, 3, 3, 1000, 3, 3, 3, 3, 1, -1},

....................... };

为了在界面上画出地图,本文首先把所有图片和怪物分类存入数组,然后根据所在楼层找到相应的二维数组,使用2个嵌套的for循环对其进行遍历,将数字与实例化的图片对象进行匹配,一个一个“画”在界面上。部分代码如下:

importjavax.swing.*;

public class Map {

public MapPackge mapPackge;

public Map() {

mapPackge = new MapPackge();

map = new int\[13\]\[13\];

wall = Toolkit.getDefaultToolkit().getImage("image/05.jpg");

////NPC

NPC01a = Toolkit.getDefaultToolkit().getImage("image/NPC01a.png");

Princess = Toolkit.getDefaultToolkit().getImage("image/Princess.png");

////怪物ID、属性、图片

monsters\[0\] = new Monster(49, "黄金BOSS", 1000, 500, 250, 22, 19, Toolkit.getDefaultToolkit().getImage("image/Yellow.png"));

.............

for (int i = 0; i < 13; i++) {

for (int j = 0; j < 13; j++) {

map\[i\]\[j\] = mapPackge.map1\[i\]\[j\];

} }}

public void DrawMap(Graphics g, JPanel i) {

for (int z = 1; z < 12; z++) {

for (int j = 1; j < 12; j++) {

if (map\[z\]\[j\] == 1) {

g.drawImage(wall, (j - 1) * 32, (z - 1) * 32, 32, 32, (ImageObserver) i);

}

...........

if (map\[z\]\[j\] >= 1000 && map\[z\]\[j\] < 2000) {

g.drawImage(road, (j - 1) * 32, (z - 1) * 32, 32, 32, (ImageObserver) i);

g.drawImage(up, (j - 1) * 32, (z - 1) * 32, 32, 32, (ImageObserver) i);

}......}} }}

2.2地图改变的实现

随着游戏的进行,地图上的一些门被判定为已经打开,一些怪物被判定为已经被击杀,因此,保存的原地图包不能被读取。为了原地图进行区分,需创建一个新的类SaveMap用以储存改变后的地图包。由于SaveMap的结构与MapPackage相同,这里就不对代码进行详细描述。

2.3勇士移动的实现

游戏的主要目的是让勇士动起来去突破艰难险阻,击杀怪物解救公主。由于勇士在行动中会遇到不同的事物,从而使得勇士在行动的每一步都要进行判断。本文运用常见的动作监听事件,并将所有判断的代码打包在新的类KeyPress里。部分代码如下:

public classKeyPress {

public void Keypress(MSprite mSprite, Map map, Fight fight, FloorJump floorJump, SaveMap saveMap, int a, int b) {

if (mSprite.m_posY >= 0 && mSprite.m_posY <= 320 && mSprite.m_posX >= 0 && mSprite.m_posX <= 320) {

if (map.map\[mSprite.m_posY / 32 + 1 + a\]\[mSprite.m_posX / 32 + 1 + b\] == 100 && mSprite.YKey > 0) {

map.map\[mSprite.m_posY / 32 + 1 + a\]\[mSprite.m_posX / 32 + 1 + b\] = 0;

mSprite.YKey--;

} } } }

上述代码在主菜单MCanvas的动作监听中被调用,在按下不同按键的同时进行判断操作。创建动作监听的部分代码如下:

@Override

public void keyPressed(KeyEvent e) {

if (VK1 == "UP") {

VK1 = "";

shop.addHP.setVisible(false);

}

if (VK == "") {

if (e.getKeyCode() == KeyEvent.VK_DOWN) {

int a = 1;

int b = 0;

keyPress.Keypress(mSprite, map, fight, floorJump, saveMap, a, b);

} else if (e.getKeyCode() == KeyEvent.VK_UP) {

int a = -1;

int b = 0;

keyPress.Keypress(mSprite, map, fight, floorJump, saveMap, a, b);

if (map.map\[mSprite.m_posY / 32\]\[mSprite.m_posX / 32 + 1\] == 46) {

if (VK1 == "UP") {

VK1 = "";

shop.addHP.setVisible(false);

} else {

VK1 = "UP";

shop.addHP.setVisible(true);

}}

2.4战斗过程的实现

勇士移动的路上布满了怪物的防线,同时,前面提到的战斗公式也需要用代码实现。为此,创造一个动作类Fight用来进行战斗部分的操作。首先,遇到怪物时调用该类并打包在KeyPress中。然后,从怪物类Monster中提取所遭遇怪物的属性与勇士类MSprite的现有数据进行计算判断,如果符合公式则扣除血量,如果不符合则无法进行操作,勇士无法继续前进。部分代碼如下:

public voidFighting(MSprite sprite, int monID, Map map, int a, int b) {

for (int i = 0; i < 1; i++) {

if (monID <= 70 && monID >= 49) {

xmonster = map.monsters\[monID - 49\];

monster = new Monster(monID, null, xmonster.HP, xmonster.DF, xmonster.ATK, xmonster.coin, xmonster.Exp, null);

HP = sprite.HP;

MHP = monster.HP;

if (sprite.ATK > monster.DF) {

while (MHP > 0) {

if (sprite.DF < monster.ATK && MHP > 0) {

MHP -= (sprite.ATK - monster.DF);

HP -= (monster.ATK - sprite.DF);

} else if (sprite.DF >= monster.ATK && MHP > 0) {

MHP = 0;

} }}

if (sprite.ATK > monster.DF && HP > 0) {

while (monster.HP > 0) {

if (sprite.DF >= monster.ATK && sprite.ATK > monster.DF) {

monster.HP = 0;

map.map\[sprite.m_posY / 32 + 1 + a\]\[sprite.m_posX / 32 + 1 + b\] = 0;

sprite.coin += monster.coin;

}

if (sprite.DF < monster.ATK && monster.HP > 0) {

monster.HP -= (sprite.ATK - monster.DF);

sprite.HP -= (monster.ATK - sprite.DF);

}

if (monster.HP <= 0) {

map.map\[sprite.m_posY / 32 + 1 + a\]\[sprite.m_posX / 32 + 1 + b\] = 0;

sprite.coin += monster.coin;

sprite.exp += monster.Exp;

} }} } }2.5楼层跳转的实现

跳转楼层后每个楼层的出发点都不同,并且要确保跳转的楼层已经保存在SaveMap类中,否则会出现地图重复或者不更新的问题。本文采用如下方法解决以上问题,将勇士放在要去的楼层的上一个楼层的楼梯上,根据KeyPress里打包的判断,勇士会自动到下一层的出发点,这样就能找到所要到达楼层的出发点。部分代码如下:

public classFloorJump {

public void Jump(int i, Map map, SaveMap saveMap, MSprite mSprite) {

if (i >= 1000 && i < 2000) {

int z = i - 1000;

mSprite.floor = z + 1;

for (int j = 0; j < 13; j++) {

for (int h = 0; h < 13; h++) {

Group\[z\]\[j\]\[h\] = map.map\[j\]\[h\];

map.map\[j\]\[h\] = Group\[z + 1\]\[j\]\[h\];

}}

int x = 0, y = 0;

for (int j = 1; j < 13; j++) {

for (int h = 1; h < 13; h++) {

if (Group\[z + 1\]\[j\]\[h\] >= 2000 && Group\[z + 1\]\[j\]\[h\] < 3000) {

x = h;

y = j;

}} }

2.6游戏存取的实现

魔塔虽然是一个小游戏,但作为经典的地牢闯关类游戏,想要闯过层层关卡,取得游戏成功也并非容易实现,想要闯关成功需要花费大量时间,因此,游戏的存档与读取就显得尤为重要。本文使用的存档技术是JAVA比较常见的IO流技术。将游戏所有能读取到且需要改变的属性数值,通过IO流保存在一个外部文件中,并在需要时判断是否存在,如果存在则读取这些属性,从而达到地图和数据的保存功能。保存代码和读取代码如下:

importjava.io.DataOutputStream;

import java.io.FileOutputStream;

public class Save {

private SaveMap saveMap = new SaveMap();

try {

DataOutputStream localDataOutPutStream = new DataOutputStream(new FileOutputStream("save.jpg" >

for (int i = 0; i < Group.length; i++) {

for (int j = 0; j < 13; j++) {

for (int k = 0; k < 13; k++) {

Group\[i\]\[j\]\[k\] = mCanvas.map.map\[j\]\[k\];

localDataOutPutStream.writeInt(Group\[i\]\[j\]\[k\]);

} } }

} catch (Exception e) {

e.printStackTrace();

} } }

importjavax.swing.*;

public class Read {

try {

DataInputStream localDataInputStream = new DataInputStream(new FileInputStream("save" + a + ".dat"));

for (int i = 0; i < Group.length; i++) {

for (int j = 0; j < 13; j++) {

for (int k = 0; k < 13; k++) {

Group\[i\]\[j\]\[k\] = localDataInputStream.readInt();

mCanvas.map.map\[j\]\[k\] = Group\[i\]\[j\]\[k\];

} } }

mSprite.m_posX = localDataInputStream.readInt();

localDataInputStream.close();

} catch (Exception e) {

e.printStackTrace();

JOptionPane.showMessageDialog(null, "讀取失败!此存档为空或损坏!", "读取失败", JOptionPane.WARNING_MESSAGE);

} } }

3结束语

魔塔是一个十分经典的RPG(角色扮演)游戏,(下转封三)本文在实现原魔塔游戏功能的基础上创建了新的功能。经过测试,该游戏能够运行流畅,体验良好。通过设计该款游戏,体验到了JAVA在制作游戏方面的便捷性。

参考文献

[1] 孙卫琴. Java面向对象编程[M]. 电子工业出版社, 2006.

[2] 牛芳. 异构存储系统中的节点失效并行化修复研究[D]. 中国科学技术大学, 2014.

[3] 杨丰盛. Android应用开发揭秘[M]. 机械工业出版社, 2010.

[4] 庞萍.基于java的魔塔游戏的设计与实现[J]. 电脑知识与技术,2016,12(34):246-247.

[5] Julian Gold. 面向对象的游戏开发[M]. 电子工业出版社, 2005.

文章 游戏 怪物