JAVA开发类似冒险岛的游戏Part1

一、总结

二、JAVA开发类似冒险岛的游戏Part1

初学嘛) ,不过总的来说这个程序还是很有意思的。这里我重新再整理了一下,希望能帮助到其他想要开发类似程序的朋友,共同进步!

晒一下效果图:

哈,还是有模有样的。左边是自己写的冒险岛,右边是真的冒险岛。 
毕竟也是个游戏,方向键可以控制人物移动,然后可以攻击,可以打怪,升级,做任务。

先说一下素材,有一个专门提供冒险岛素材的纸娃娃系统,冒险岛中各种素材都可以从这个网站中获取: 
http://www.maplesimulator.com/programs/bannedstory

游戏中显示出这样的界面效果,可以用PS中的图层来理解,所谓图层就是含有文字或图形等元素的胶片,一张张按顺序叠放在一起,组合起来形成页面的最终效果。

而这个胶片,就是JAVA中的容器JPanel。JPanel可以设置背景图片,也可以多个JPanel放置于一个JPanel中,就拿游戏下方的状态条来说: 
整个状态条就是一个JPanel,我们就新建一个状态条类

public class StatusBar extends JPanel

然后让他继承JPanel。 
我们给这个JPanel设置这样的一个背景: 
 
给JPanel设置背景的方法是重写JPanel的paint函数,看下面代码应该很清楚。

public void paint(Graphics g){
g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\状态条\\状态条.png"),0,0,this);
}

接着,在这个JPanel上还要显示等级的数字、血条、蓝条、经验条,以及一个系统按钮(不过其实这个按钮并不属于StatusBar类,我们只是让他显示在这个位置)。 
这些都是StatusBar类的成员。我们只需要在固定的位置将相应的成员显示就可以了。 
先说这个显示等级的数字,很显然就是一个JLabel嘛,不过我用的不是JLabel。 
依旧是在paint函数中,加入这样一段:

g.setColor(Color.WHITE);
g.setFont(new Font(Font.DIALOG_INPUT, Font.BOLD, 25));//设置字体
g.drawString(Integer.toString(lv), 50,38);//等级

直接把字画在JPanel上的相应位置就可以了,颜色、字体、大小、字的内容、横纵坐标,就这些参数。可以查看API文档,这里就不细说。

血条蓝条经验条,这三个条条是大部分网络游戏都有的东西,主要用来直观的显示玩家状态,拿血条举例子,看最前面的效果图,当前血量800,总血量1000,所以血条的长度就是整个血槽的4/5,这样一想,用进度条这种组件去实现血条蓝条是很可行的,然而我查了才发现,JAVA中(至少是标准库吧。。)并没有进度条这个组件,所以还是要自己去实现它。 
还是在paint函数中:

g.setColor(Color.RED); //血条
g.fillRect(141, 23, (int)((double)hp/allhp*length), 15);
g.setColor(Color.BLUE); //蓝条
g.fillRect(340, 23, (int)((double)mp/allmp*length), 15);
g.setColor(Color.YELLOW); //经验条
g.fillRect(539, 23, (int)((double)exp/allexp*length), 15);

根据当前血量和总血量的比例去绘制相应长度相应颜色的矩形,这样一来动态的血条蓝条经验条的主体部分就完成了,我们还需要在上面显示具体的血量和血总量: 
 
有了前面绘制等级的经验,我们使用g.drawString函数就可以了,不过为了显示的美观吗,我们还需要考虑一些问题,文字的显示是与血条右边对其的,而绘制文字的参数中提供的参数是这样的一组x,y并不能符合我们右对其的要求。 
 
所以我用了另外一种很机智(shabi)的方法:

//数字比例6.7619 /[比例3
String hptxt="["+Integer.toString(hp)+"/"+Integer.toString(allhp)+"]";
int hptxtnuml=Integer.toString(hp).length()+Integer.toString(allhp).length();//血量数字显示的数字长度
int hptxtx=272-(int)(hptxtnuml*6.7619);//相应横坐标 String mptxt="["+Integer.toString(mp)+"/"+Integer.toString(allmp)+"]";
int mptxtnuml=Integer.toString(mp).length()+Integer.toString(allmp).length();//蓝量数字显示的数字长度
int mptxtx=471-(int)(mptxtnuml*6.7619);//相应横坐标 String exptxt="["+Integer.toString(exp)+"/"+Integer.toString(allexp)+"]";
int exptxtnuml=Integer.toString(exp).length()+Integer.toString(allexp).length();//蓝量数字显示的数字长度
int exptxtx=670-(int)(exptxtnuml*6.7619);//相应横坐标

先把要绘制的字符串准备好,然后计算一下文本长度(这个长度是指真的长度。。显示出来要几个坐标。。别问我怎么测得,我用尺子量的。。),最后换算出相应的坐标绘制。

至此,状态条的显示就完成了。在我的设计中,人物的属性(血,蓝,经验,等级,攻击力等)是直接放在状态条类里作为成员变量的,换句话说,你玩游戏玩的不是那个人物,你的所有数据都不存在人物的类里,而是存在状态条类里,人物攻击力高,一下秒掉怪物,那只是配合着状态条里的属性显示给玩家看而已~

我们先把状态条放到一边,待会再来用它。

现在我们要实现方向键控制人物移动。 
依旧是前面图层的思想,人物也是一个图层。我们创建一个人物类:

public class Obj extends JPanel

先来分析一下人物的动作,人物有个朝向,脸朝左,脸朝右,站着不动的时候有站着不动的姿势,走路的时候有走路的姿势,按上键如果能抓到绳子,有爬绳子的姿势,按下有趴着的姿势。所以Obj类中需要有一个变量来控制人物的状态,(向左走、向右走、向左趴下等):

private int zhuangtai=2;//人物状态

然后在绘制背景图片的时候,switch(zhuangtai)来决定该绘制哪张图片就可以了。 
我们可以写一个设置状态的函数,以便控制人物的时候使用:

public void setzhuangtai(int a)//设置状态
{
zhuangtai=a;
}

这里说明一下,人物站立的时候,并不是一张静止的图片,而是提前准备好的GIF图像,站立的时候显示的就是自然摆手的动画分镜头如下: 
 
当然如果不想使用GIF图像的话,可以新建一个线程,专门在站立状态控制人物摆手动画的切换。 
类似攻击的动画显示也是如此。 
 
我们现在先来设想一下控制人物移动的思路: 
首先人物这个图层(JPanel)是显示在窗口上的,主类继承JFream 
然后人物作为主类的成员,就和创建一个JButton一样的写法

private Obj ren=new Obj(0,0);//创建角色

用两个整形变量x,y来代表人物的坐标 
接下来就是将,xy作为参数使用setBounds函数设置人物的显示坐标以及显示大小。 
对主窗口注册键盘监听和触发事件: 
我们按下方向键后,比如说方向右键,调用Obj类中的setzhuangtai函数,修改人物状态,再将x+5 (这个值自己定,值越大移动的越快)接着再次使用setBounds重新设置人物的显示坐标。然后调用repaint进行重绘。 
这样就可以实现人物的移动了。我这里没有附具体代码,因为我不是这样写的。。(噗!!)

在实际测试中,这样去实现人物移动会引入一个很麻烦的问题——当你设置了游戏地图之后,控制人物移动会出现背景跟不上人物,有残影的情形,具体的解决方案是使用双缓冲技术,不过我有另外的思路解决了这个问题,何乐而不为呢:

人物移动的时候,人物这个图层后面的背景总是拖着之前的背景,使得显示着很不协调,我的解决方案是设置人物这个图层的大小的时候,让他充满整个屏幕,这样的话,人物移动时,背景就是整个屏幕,而不用考虑旧背景的残影问题了。不过这样设计的话,人物移动的方式就要改变了,不能再是移动人物类的对象在主窗口中显示的位置,因为人物类的对象在主窗口中已经是全屏显示了,不能再改变,改变的只能是人物的图片,在人物类的这个JPanel中内部的位置。所以x,y这个坐标也是存于人物类中。

    private int zhuangtai=2;//人物状态

    //屏幕坐标
private int x;
private int y; public void setzhuangtai(int a)//设置状态
{
zhuangtai=a;
}
public void setxy(int m_x,int m_y)
{
x=m_x;
y=m_y;
}
//人物在屏幕中移动
public void movex(int m_x)
{
x+=m_x;
}
public void movey(int m_y)
{
y+=m_y;
}

显示的地方就通过x和y的值来调整位置,不过还是有一个地方要注意,显示图片时的坐标参数是指以左上角为起点的,但是每一张图片的大小都不一样,所以如果不加换算的话就会出现这样的问题: 
 
 
人物的显示坐标自然是要以脚为坐标,所以我们对于每一张图都要测定它的偏移(很幸运的是纸娃娃系统可以直接导出偏移的值),然后显示的时候根据偏移去计算换算后的坐标。

paint函数如下:

public void paint(Graphics g)
{ switch(zhuangtai)
{
case 0:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向右走0.png"),x-15,y-68,this);break;
case 1:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向右走1.png"),x-15,y-68,this);break;
case 2:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向右走2.png"),x-15,y-68,this);break;
case 3:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向右走3.png"),x-15,y-68,this);break;
case 4:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向左走0.png"),x-76,y-68,this);break;
case 5:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向左走1.png"),x-76,y-68,this);break;
case 6:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向左走2.png"),x-76,y-68,this);break;
case 7:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\走路\\向左走3.png"),x-76,y-68,this);break;
case 8:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\站立\\面朝右站.gif"),x-40,y-86,this);break;
case 9:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\站立\\面朝左站.gif"),x-24,y-86,this);break;
case 10:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\跳跃\\向右跳.png"),x-40,y-90,this);break;
case 11:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\跳跃\\向左跳.png"),x-25,y-90,this);break;
case 12:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\趴下\\面朝左趴下.png"),x-106,y-40,this);break;
case 13:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\趴下\\面朝右趴下.png"),x,y-40,this);break;
case 14:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\0.png"),x-165,y-110,this);break;
case 15:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\1.png"),x-165,y-110,this);break;
case 16:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\2.png"),x-165,y-110,this);break;
case 17:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\3.png"),x-165,y-110,this);break;
case 18:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\4.png"),x-165,y-110,this);break;
case 19:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\5.png"),x-165,y-110,this);break;
case 20:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\6.png"),x-165,y-110,this);break;
case 21:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\7.png"),x-165,y-110,this);break;
case 22:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\左\\8.png"),x-165,y-110,this);break;
case 23:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\0.png"),x-115,y-110,this);break;
case 24:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\1.png"),x-115,y-110,this);break;
case 25:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\2.png"),x-115,y-110,this);break;
case 26:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\3.png"),x-115,y-110,this);break;
case 27:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\4.png"),x-115,y-110,this);break;
case 28:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\5.png"),x-115,y-110,this);break;
case 29:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\6.png"),x-115,y-110,this);break;
case 30:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\7.png"),x-115,y-110,this);break;
case 31:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\技能\\轻舞飞扬\\右\\8.png"),x-115,y-110,this);break;
case 32:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\爬绳子\\0.png"),x-27,y-70,this);break;
case 33:g.drawImage(Toolkit.getDefaultToolkit().getImage("F:\\Code\\JAVA\\MapleStory\\图片素材\\主角\\爬绳子\\1.png"),x-27,y-70,this);break; }
g.drawString("屏幕位置:"+"x="+Integer.toString(x)+"y="+Integer.toString(y),645,525);
}

控制移动的代码示例:

if(e.getKeyCode()==KeyEvent.VK_LEFT)//左
{
if(Drop!=2)
{
Direction=0;
if(Drop==0)
ren.setzhuangtai(movetime++%4+4);
if(map.AllowMoveL()==1)
{
//if(ren.getx()<=600||map.getrenx()>mapxmax-200)
if(ren.getx()>=200||map.getrenx()<210)
{
ren.movex(-5);
map.moverenx(-5);
}
else
{
map.movex(5);
map.moverenx(-5);
NpcOffsetx-=5;
NPCshow();
}
}
MainWindow.this.repaint();
new Thread(DropTest).start();
}
}

毕竟移动的时候不能只考虑人物的移动,玩游戏的时候,人物始终会显示在屏幕上,而不会走出窗口,当人物坐标临近窗口时,移动的就不是人物,而是地图的背景了(地图的图片是比窗口大的多的,窗口只有800*600,只显示地图的一部分,当玩家向右走,走到接近窗口边缘时,地图的图片向左移动,玩家坐标不变,动作保持向右走的姿势)。这样可以保证人物始终在窗口内移动。 
 
 
同样的NPC的位置也要改变,NPC是对于地图的独立存在,但是相对于地图,NPC是静止的。 
 
NPC的显示,我们需要明确一下两点,第一,NPC显示在哪张地图上(或是说NPC显示在哪个场景中),第二,该NPC在该地图上的坐标是多少。 
这里所需的NPC在地图上的坐标而不是NPC在窗口上的坐标,并不是说要将NPC作为地图类的成员,在地图这个JPaenl中显示(这样做不可取,原因后面说),而是因为NPC与地图是相对静止的,我们有了NPC在地图上的坐标,也有了地图在窗口的显示坐标,就可以将NPC在地图上的显示坐标换算出来,我们还可以设置一个NPC坐标的偏移参数,地图移动时,就改变这个偏移值,然后NPC显示的时候就将偏移值包含进去。这样一来会更加直观,后期若先要添加新的NPC也十分方便。 
那么我们把所有的NPC显示专门放到一个函数里:

public void NPCshow()//显示NPC
{
switch(mapNo)
{
case 0:
{
for(int i=0;i<7;i++)
npc[i].setVisible(true);
npc[0].setBounds(2350-NpcOffsetx,720+NpcOffsety,123,101);//希纳斯
npc[1].setBounds(1800-NpcOffsetx,707+NpcOffsety,48,109);//南哈特
npc[2].setBounds(732-NpcOffsetx,515+NpcOffsety,97,103);//米哈尔
npc[3].setBounds(465-NpcOffsetx,705+NpcOffsety,97,109);//伊莉娜
npc[4].setBounds(788-NpcOffsetx,695+NpcOffsety,78,122);//奥兹
npc[5].setBounds(302-NpcOffsetx,420+NpcOffsety,77,116);//伊卡尔特
npc[6].setBounds(663-NpcOffsetx,635+NpcOffsety,77,119);//胡克
break;
}
case 1:
{
for(int i=0;i<7;i++)
npc[i].setVisible(false);
for(Monster tempgw:gw[0])
tempgw.setBounds(map.getx(), map.gety(), mapxmax, mapymax);
//gw[0][0].setBounds(map.getx(), map.gety(), mapxmax, mapymax);
//gw[0][1].setBounds(map.getx(), map.gety(), mapxmax, mapymax);
break;
}
case 2:
{
for(int i=0;i<7;i++)
npc[i].setVisible(false);
for(Monster tempgw:gw[1])
tempgw.setBounds(map.getx(), map.gety(), mapxmax, mapymax);
break;
}
case 3:
{
for(int i=0;i<7;i++)
npc[i].setVisible(false);
for(Monster tempgw:gw[2])
tempgw.setBounds(map.getx(), map.gety(), mapxmax, mapymax);
break;
}
}
}

整个函数的原理很简单,先switch地图编号,让其他地图的NPC不显示,然后再对当前地图的NPC一个一个的设置位置。

我们把游戏理解成一个一个的图层组成的场景,那么图层与图层之间是有叠放次序的,处于最顶端的图层会将后面的图层盖住。 
最顶端的图层是系统菜单(如下图),然后是状态条上面的系统按钮,再然后是状态条,再再然后是NPC的图层,再再再然后是玩家控制的人物图层,最后是地图。 
我们看这张图,很清楚的反应了他们的关系: 
 
前面说到NPC不能是作为地图类中的成员,虽然说把NPC放到地图类中去显示会很方便,但是这样一来NPC就没有交互性了,游戏中如何与NPC交互?当然是要点她。。如果把NPC作为地图类的成员,那么就永远也点不到NPC了,因为人物的图层(JPanel)大小是占满整个屏幕(这在前面已经说到),这样就像是一个JButton处在一个透明的JPaenl后面,看得到点不到。

现在来做系统按钮,游戏中的按钮自然不能用JButton,因为太丑了嘛,我们还是用JPanel来做,就和人物的显示一样,按钮也分三个状态,一个是未激活,再是激活,再是按下,激活就是鼠标放到按钮上的时候按钮边缘发光。 
 
从左到右一目了然,要做到这些,只需要给它注册和设置鼠标监听事件:

public void mouseEntered(MouseEvent e)//鼠标进入
public void mousePressed(MouseEvent e)//鼠标按下
public void mouseReleased(MouseEvent e)//鼠标释放
public void mouseExited(MouseEvent e)//鼠标离开

将系统菜单的是否显示,以及系统按钮的状态切换分别写到这些事件之中。同理,系统菜单上的按钮也是如此。

至此,我们的游戏已经是初见雏形,可以控制人物做各种动作,也有状态栏去显示人物的状态,还有一个菜单提供各项操作选择。

该对游戏的完整性更进一步了,现在的地图,说到底也只是一张图片,设置成地面的图片,人物就在地面上走,设置成天空的图片,人物就在天空上走,我们要对地图和人物进一步编写,引入“重力下坠”和“地面”。

先说下坠,这个动作执行起来人物是什么样的呢?如图: 
 
人物保持跳跃的动作,从高空落下,坐标值y值不断增加,期间我们按左键 ,右键,可以使得人物脸的朝向改变,并且可以往左或者右飘动,也可以在空中的时候发动攻击(我尝试了N遍才截到这个图!!)。 
 
所以很显然啊,这个坠落,是一个多线程的函数,始玩家y坐标持续增大,并且设置玩家的状态动画为“跳跃”。

if(droping==0)//确保同一时间只执行一个下坠线程
{
droping=1;//代表已经在下坠
jumping=1;//下坠中禁止跳跃
if(Drop!=2)
Drop=map.ToGround();
while(Drop==1)
{
if(attacking==0)
ren.setzhuangtai(Direction==0?11:10);
try
{
Thread.sleep(3);
}
catch(Exception e1)
{
e1.printStackTrace();
}
if(ren.gety()<=430/*||map.getreny()>=mapymax-200*/)
ren.movey(1);
else
{
map.movey(-1);
NpcOffsety-=1;
}
map.movereny(1);
Drop=map.ToGround();
NPCshow();
MainWindow.this.repaint();
if(attacking==0)
ren.setzhuangtai(Direction==0?9:8);
}
droping=0;//下坠结束
jumping=0;//跳跃结束
//MainWindow.this.repaint();
}

代码中很容易看出来的,坠落的条件是Drop的值,每下落一点,就调用地图的ToGround函数,检测是否到达地面,然后给Drop重新赋值。 
来看一下ToGround函数,比较长所以只贴一部分:

public int ToGround()//到地面
{
switch(mapNo)
{
case 0:
{
if(reny==790
||(renx>=597&&renx<=736&&reny==757)
||(renx>=600&&renx<=710&&reny==727)
||(renx>=589&&renx<=694&&reny==695)
||(renx>=586&&renx<=686&&reny==668)
||(renx>=589&&renx<=677&&reny==643)
||(renx>=594&&renx<=674&&reny==619)
||(renx>=600&&renx<=682&&reny==595))
return 0;
break;
}…

很显然,人物坐标处于这些范围,就不用继续掉了,这里的坐标说的是人物的脚的坐标。 
而这些范围,就是一条条的线,代表地面,或者台阶,或者别的能站的地方如图(这张地图费了我3个小时啊!!): 

在所有可能发生坠落的操作后面都启动一个坠落线程。类似的地图元素还有传送门,绳子,爬梯,墙壁,都可以在地图类中用类似的函数来写,以便于检测。 
同样的,跳跃也是一个线程,先是将y值减小,到了最高点再执行坠落线程:

if(jumping==0)//确保同一时间只能执行一个跳跃线程
{
jumping=1;//代表正在跳跃
Drop=1;//代表人物以离开地面
droping=1;//跳跃中途禁止启动下坠线程
for(int i=0;i<100;i++)
{
try
{
Thread.sleep(3);
}
catch(Exception e1)
{
e1.printStackTrace();
}
if(ren.gety()>=100||map.getreny()<=100)
ren.movey(-1);
else
{
map.movey(1);
NpcOffsety+=1;
}
map.movereny(-1);
NPCshow();
MainWindow.this.repaint();
}
try
{
Thread.sleep(5);
}
catch(Exception e1)
{
e1.printStackTrace();
}
droping=0;//允许下坠
new Thread(DropTest).start();//启动下坠
}

至此,人物终于可以在地图上自由走动了。 
玩过冒险岛的人都知道,换地图的动作就是在光门的位置按上键,所以对上键注册键盘监听,然后检测是否在光门上,在哪个光门上,以及切换到哪个地图。 
以下是部分代码:

public void gomap(int x,int y)//进入地图 切换地图时,要给地图一个初始位置
{
map.setmapNo(mapNo);//切换地图
switch(mapNo)
{
case 0:
{
mapymax=941;
mapxmax=3095;
//new Thread(newMonster).start();
break;
}
case 1:
{
mapymax=535;
mapxmax=2170;
new Thread(newMonster).start();
break;
} … ren.setxy((x>mapxmax-403?x-mapxmax+806:(x<806?x:403)),y>mapymax-315?y-mapymax+629:(y<629?y:315));
//map.moverenx(x>mapxmax-403?mapxmax-806:(x<806?0:x-403));
//map.moverenx(x);
// map.movereny(y);
map.setrenxy(x, y);
//map.movex(-x);
map.setxy(-(x>mapxmax-403?mapxmax-806:(x<806?0:x-403)),-(y>mapymax-315?mapymax-629:(y<629?0:y-315)));
//map.movex(x>mapxmax-403?mapxmax-806:(x<806?0:x-403));
//map.movey(-y);
NpcOffsetx=x>mapxmax-403?mapxmax-806:(x<806?0:x-403);
NpcOffsety=-(y>mapymax-315?mapymax-629:(y<629?0:y-315));
menu.setVisible(false);
for(ButtonBase temp:menubutton)//菜单按钮
temp.setVisible(false);;
MainWindow.this.repaint();
new Thread(DropTest).start();
}

切换地图之后让人物从空中掉下了就OK了。

接下来就是怪物系统: 
 
怪物的主体和人物基本一致,不同的是,怪物的属性,比如hp,攻击力等都是存在于怪物自己的类中。所有的怪物由一个怪物线程进行控制。怪物能做什么呢?可以左右移动,可以攻击,可以被玩家打,被打死了玩家能获得经验。

    private int MonsterID;
private int zhuangtai=0;
private int Target;//代表是否有攻击目标
private int HP;//怪物血量
private int Defense;//防御力
private int DHP=0;//掉血量
private int x=1370;
private int y=400;
private int bx;
private int by;
private int xmax;
private int xmin;
private int Offsetx;
private int Offsety;

如果怪物没有被攻击过,那么它会漫无目的的随机在自己的移动范围内移动,如果玩家攻击了怪物,那么怪物就会朝着玩家移动。游戏中对于属性,有很多可以设置的地方,最简单的就是一个攻击力一个血量,不过为了增强游戏的多样性(keng qian)吧,陆续出来了防御力,回避率,恢复速度,XX属性抗性等等等。。我这里也就是随便设定了一下攻防计算规则。 
对于怪物的显示位置,我们可以将其与NPC的显示位置类比,怪物静止的时候,就和NPC一样,与地图是相对静止的,怪物还可以在自己的活动范围自己移动,所以在NPC显示的基础上,再去进一步的增加一个移动函数就可以了。 
为了节约资源,我将一张地图上的所有怪物共用的同一个线程,首先获取地图号,然后用一个循环来遍历该地图内所有的怪物,切换地图之后,就开始执行这张地图上的怪物的线程,线程根据怪物的状态,有没有被攻击,有没有目标来控制怪物做出各种动作。

任务系统 
 
任务是游戏的精髓部分,它拉着玩家去展开游戏剧情,这里说一下我的单线任务设计的思路。一个整型变量来代表任务的进度,给NPC注册鼠标监听,点击NPC后,根据当前的进度,以及NPC的编号,来确定NPC对话框中的显示内容。 
示例如下面的代码,先switch NPC编号,然后再switch 任务进度,最后就可以得到具体的内容了:

case 8:
{
g.drawString("圣地中到处都是黑魔法师的傀儡", 165, 30);
g.drawString("这对女皇来说是很大的威胁", 165, 45);
g.drawString("不能杀掉太多,不然会引起黑魔法师注意的", 165, 60);
g.drawString("(请再和我对话)", 165, 75);
break;
}
case 9:
{
g.drawString("往左一直走,前往 [前线1]、[前线2]", 165, 30);
g.drawString("消灭痩刺客、胖刺客各 30 只吧!", 165, 45);
break;
}
case 10:
{
g.drawString("你确认已经消灭了 痩刺客、胖刺客各 30 只吗?", 165, 30);
g.drawString("(打开任务窗口可以查看任务完成情况)", 165, 45);
break;
}
case 11:
{
g.drawString("你完成的很好!", 165, 30);
g.drawString("(请再和我对话)", 165, 45);
break;
}

 
然后显示对话框的时候就可以判断,当前进度,这个NPC是否有内容,如果有的话,就显示提前设置好的,没有的话就显示默认值。 
 
至此,一个单机的冒险岛的主要功能就基本实现了,剩下的就是多添几张地图,多加几任务之类的了。

注:第一次写博客。。(其实因为之前发了一次这个,不过写的是乱七八糟,所以删了重写,这次算是正式的第一次!)前面写的很详细,后面比较简略。。这篇博客我还会慢慢修改(排版什么的还不熟嘛)。 
然后是,大二结束的时候JAVA课程设计,我最后把它改成了联网版,可以局域网一起玩。。空了我再整理一下也发出来(所以这个是Part1嘛)。

项目文件下载地址: 
http://download.csdn.net/detail/jdk999/9042405

参考:

JAVA开发类似冒险岛的游戏Part1的更多相关文章

  1. 用Java开发50个棋类游戏

    眼下已经开发完了两个 1A2B 24点 打算开发以下的.直接在QQ上玩. QQ机器人已经有了.我们直接写业务即可.有兴趣的參与.机器人婷婷体验群 Java技术交流 207224939 四棋 小枪大炮 ...

  2. 博主有偿带徒 《编程语言设计和实现》《MUD游戏开发》《软件破解和加密》《游戏辅助外挂》《JAVA开发》

    <考研专题>操作系统原理 理论解答:8K 实战 1.5W CPU设计 理论解答:1W 实战 2.5W <编程语言设计和实现>初窥门径<5K>:编译原理.编译设计小试 ...

  3. 给Java开发人员的Play Framework(2.4)介绍 Part1:Play的优缺点以及适用场景

    1. 关于这篇系列 这篇系列不是Play框架的Hello World,由于这样的文章网上已经有非常多. 这篇系列会首先结合实际代码介绍Play的特点以及适用场景.然后会有几篇文章介绍Play与Spri ...

  4. 打造Linux三流娱乐环境,二流办公环境,一流Java开发环境

    写这篇文章的目的首先是为让自己以后再装linux环境时候,不用再通宵google+百度,做个备忘录,其次,给新入Linux环境的同学分享一点个人经验,再高尚点的动机也算是想做为开源技术的传播布道者.我 ...

  5. [翻译]现代java开发指南 第二部分

    现代java开发指南 第二部分 第二部分:部署.监控 & 管理,性能分析和基准测试 第一部分,第二部分 =================== 欢迎来到现代 Java 开发指南第二部分.在第一 ...

  6. 用 Java 写个塔防游戏「GitHub 热点速览 v.21.37」

    作者:HelloGitHub-小鱼干 本周 GitHub Trending 的主题词是:多语言.本周特推的 C 语言教程是大家都知道的阮一峰编写的,想必和他之前的技术文章类似,能起到科普作用.再来时 ...

  7. Libgdx游戏开发(2)——接水滴游戏实现

    原文:Libgdx游戏开发(2)--接水滴游戏实现 - Stars-One的杂货小窝 本文使用Kotlin语言开发 通过本文的学习可以初步了解以下基础知识的使用: Basic file access ...

  8. 使用HTML5开发Kinect体感游戏

    一.简介 我们要做的是怎样一款游戏? 在前不久成都TGC2016展会上,我们开发了一款<火影忍者手游>的体感游戏,主要模拟手游章节<九尾袭来 >,用户化身四代,与九尾进行对决, ...

  9. 【搬砖】安卓入门(1)- Java开发入门

    01.01_计算机基础知识(计算机概述)(了解) A:什么是计算机?计算机在生活中的应用举例 计算机(Computer)全称:电子计算机,俗称电脑.是一种能够按照程序运行,自动.高速处理海量数据的现代 ...

随机推荐

  1. Mysql学习总结(3)——MySql语句大全:创建、授权、查询、修改等

    一.用户创建.权限.删除 1.连接MySql操作 连接:mysql -h 主机地址 -u 用户名 -p 用户密码 (注:u与root可以不用加空格,其它也一样) 断开:exit (回车) 打开cmd, ...

  2. jmind-redis一个redis的nio客户端

    Redis是一个基于key/value的系统.Redis目前最新版本是2.2.4,用着很不错,不过java版本的客户端比较的不给力,目前redis 客户端jedis 是基于io 的socket . 而 ...

  3. sed的一些tricks

    1.sed -f xx.sed input_file 可以将一系列操作放在一个xx.sed脚本里执行 ``` #!/bin/sed -f ``` 2.在匹配字符串后面或行尾添加内容 在text后面添加 ...

  4. cocos2d-x的声音控制

    声音控制SimpleAudioEngine是单例.下面是其方法. [cpp] view plaincopy //获得SimpleAudioEngine的实例 static SimpleAudioEng ...

  5. 下载eclipse详细步骤

    先登陆eclipse的官网 然后点击红色箭头进行选择你电脑是32还是64位的 根据自己的需求下载 然后点击下载 这里下载的是安装包,你要进行压缩.安装时一定要好相应的jdk要不然就会报错 这上面的错误 ...

  6. ubuntu-删除内核

    今天进入公司第一天,公司需要给电脑安装ubuntu,这个是由it部门帮忙安装的.但是,我不小心升级了内核版本,接下来就悲剧了,因为内核版本升级以后,直接导致了环境错误,很多公司内部使用的工具都不能用了 ...

  7. 最大子段和 模板题 51Nod 1049

    N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值.当所给的整数均为负数时和为0. 例如:-2,11,-4,13,-5,- ...

  8. easyui 前端实现分页 复制就能用

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. 微信小程序从零开始开发步骤(三)底部导航栏

    上一章节,我们分享了如何创建一个新的页面和设置页面的标题,这一章我们来聊聊底部导航栏是如何实现的.即点击底部的导航,会实现不同对应页面之间的切换. 我们先来看个我们要实现的底部导航栏的效果图:(三个导 ...

  10. spring之AOP(转)

    Spring之AOP篇: AOP框架是Spring的一个重要组成部分.但是Spring IOC 并不依赖于AOP,这就意味着你有权力选择是否使用AOP,AOP作为Spring IOC容器的一个补充,使 ...