Java小程序之山寨版超级玛丽
夜听春雨
8年前
<p>Java小程序之山寨版超级玛丽</p> <p><strong>一、游戏基本功能</strong></p> <p>1、能够向左向右行走(键盘监听)</p> <p>2、能够跳跃</p> <p>3、能够发射子弹</p> <p>4、能够检测和障碍物之间的碰撞</p> <p>5、背景图片的移动</p> <p><strong>二、游戏运行界面</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6a24cb67ed085eed4aa14fee4de57012.png"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/5b6d0d12cec871d966f2638f6931a3d3.png"></p> <p><strong>三、游戏大致实现思路:</strong></p> <p>1.窗体</p> <p>2.自己角色的添加</p> <p>3.背景图片的添加</p> <p>4.背景图片的移动</p> <p>5.人物的移动和跳跃</p> <p>6.砖头、水管等等障碍物的添加</p> <p>7.任务和障碍物的碰撞</p> <p>难点分析:</p> <p>1.人物的多键控制</p> <p>1)给人物设定方向boolean变量:向左、向右、向上、向下</p> <p>2)通过键盘监听来修改方向的变量值</p> <p>按下某个键的时候,我们把相应方向改为true,释放的时候改false</p> <p>2.地图配置</p> <p>自定义文件读取方式实现:文件流的使用和字符串String类的方法调用</p> <p>3.碰撞检测</p> <p>封装一个Rectangle类的对象</p> <p>4.子弹添加</p> <p>1)先定义一个容器,这个用于封装所有的子弹对象</p> <p>2)按下某个键的时候,创建一个子弹对象(以角色的坐标为基准初始化)</p> <p>3)把子弹对象添加到容器当中</p> <p>4)在paint方法中,遍历容器,取出子弹对象并进行绘制</p> <p>5)检测子弹如果超出了窗体边界,则需要把当前子弹从容器当中移除掉</p> <p><strong>四、程序源代码:</strong></p> <p>代码结构图:分了三个包、敌人类包、游戏界面类包、游戏地图配置包</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/60482899f5db26be9d97f33d85643bfe.png"></p> <p>com.huaxin.mario包:</p> <pre> <code class="language-java">package com.huaxin.mario; import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.ArrayList; import javax.swing.ImageIcon; import javax.swing.JFrame; import com.huaxin.enery.Enery; import com.huaxin.enery.Pipe; import Util.Map; public class GameFrame extends JFrame{ public Mario mario; public Enery pipe,cion,brick; //背景图片 public BackgroundImage bg ; //容器装敌人 public ArrayList<Enery> eneryList = new ArrayList<Enery>(); //子弹容器 public ArrayList<Boom> boomList = new ArrayList<Boom>(); //子弹的速度 public int bspeed=0; //画地图,制定规则,是1画砖头,是2画金币,是3画水管 public int [][] map =null; //构造函数里面初始化背景图片和马里奥对象 public GameFrame() throws Exception { mario = new Mario(this); mario.start(); Map mp= new Map(); bg = new BackgroundImage(); //窗体重绘线程 new Thread(){ public void run(){ while(true){ //重绘窗体 repaint(); //检查子弹是否出界 checkBoom(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); map=mp.readMap(); //读取地图,并配置地图 for (int i = 0; i < map.length; i++) { for (int j = 0; j < map[0].length; j++) { //读取到的是1,画砖头 if(map[i][j]==1){ brick = new Pipe(j*30,i*30,30,30,new ImageIcon("image/brick.png").getImage()); eneryList.add(brick); } //读到2画金币 if(map[i][j]==2){ cion = new Pipe(j*30,i*30,30,30,new ImageIcon("image/coin_brick.png").getImage()); eneryList.add(cion); } //读到3画水管 if(map[i][j]==3){ pipe = new Pipe(j*30,i*30,60,120,new ImageIcon("image/pipe.png").getImage()); eneryList.add(pipe); } } } //设置背景音乐 com.huaxin.music.Util.startMusic("music/bg1.wav"); } public void initFrame(){ //设置窗体相关属性 this.setSize(800,450); this.setTitle("山寨版超级玛丽"); this.setResizable(false); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(3); this.setVisible(true); //该窗体添加键盘监听 KeyListener kl = new KeyListener(this); this.addKeyListener(kl); } public void paint(Graphics g) { //利用双缓冲画背景图片和马里奥 BufferedImage bi =(BufferedImage)this.createImage(this.getSize().width,this.getSize().height); Graphics big =bi.getGraphics(); big.drawImage(bg.img, bg.x, bg.y, null); for (int i = 0; i <eneryList.size(); i++) { Enery e =eneryList.get(i); big.drawImage(e.img, e.x, e.y, e.width, e.height,null); } //画子弹 for (int i = 0; i < boomList.size(); i++) { Boom b =boomList.get(i); Color c =big.getColor(); big.setColor(Color.red); big.fillOval(b.x+=b.speed, b.y, b.width, b.width); big.setColor(c); } //画人物 big.drawImage(mario.img, mario.x, mario.y, mario.width, mario.height,null); g.drawImage(bi,0,0,null); } //检查子弹是否出界,出界则从容器中移除,不移除的话,内存会泄漏 public void checkBoom(){ for (int i = 0; i < boomList.size(); i++) { Boom b = boomList.get(i); if(b.x<0 || b.x>800){ boomList.remove(i); } } } }</code></pre> <pre> <code class="language-java">package com.huaxin.mario; import java.awt.Image; import java.awt.Rectangle; import javax.swing.ImageIcon; import com.huaxin.enery.Enery; //自己的角色类 public class Mario extends Thread{ public GameFrame gf; public boolean jumpFlag=true; //马里奥的坐标 public int x=0,y=358; //马里奥的速度 public int xspeed=5,yspeed=1; //马里奥的宽高 public int width=30,height=32; //马里奥的图片 public Image img = new ImageIcon("image/mari1.png").getImage(); public boolean left=false,right=false,down=false,up=false; public String Dir_Up="Up",Dir_Left="Left",Dir_Right="Right",Dir_Down="Down"; public Mario (GameFrame gf) { this.gf=gf; this.Gravity(); } public void run(){ while(true){ //向左走 if(left){ //碰撞到了 if(hit(Dir_Left)){ this.xspeed=0; } if(this.x>=0){ this.x-=this.xspeed; this.img=new ImageIcon("image/mari_left.gif").getImage(); } this.xspeed=5; } //向右走 if(right){ if(hit(Dir_Right)){ this.xspeed=0; } //任人物向右移动 if(this.x<400){ this.x+=this.xspeed; this.img=new ImageIcon("image/mari_right.gif").getImage(); } if(this.x>=400){ //背景向左移动 gf.bg.x-=this.xspeed; //障碍物项左移动 for (int i = 0; i <gf.eneryList.size(); i++) { Enery enery = gf.eneryList.get(i); enery.x-=this.xspeed; } this.img=new ImageIcon("image/mari_right.gif").getImage(); } this.xspeed=5; } //向上跳 if(up){ if(jumpFlag && !isGravity){ jumpFlag=false; new Thread(){ public void run(){ jump(); jumpFlag=true; } }.start(); } } try { this.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } //向上跳的函数 public void jump(){ int jumpHeigh=0; for (int i = 0; i < 150; i++) { gf.mario.y-=this.yspeed; jumpHeigh++; if(hit(Dir_Up)){ break; } try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i <jumpHeigh; i++) { gf.mario.y+=this.yspeed; if(hit(Dir_Down)){ this.yspeed=0; } try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } this.yspeed=1;//还原速度 } //检测碰撞 public boolean hit(String dir){ Rectangle myrect = new Rectangle(this.x,this.y,this.width,this.height); Rectangle rect =null; for (int i = 0; i < gf.eneryList.size(); i++) { Enery enery = gf.eneryList.get(i); if(dir.equals("Left")){ rect = new Rectangle(enery.x+2,enery.y,enery.width,enery.height); } else if(dir.equals("Right")){ rect = new Rectangle(enery.x-2,enery.y,enery.width,enery.height); } else if(dir.equals("Up")){ rect = new Rectangle(enery.x,enery.y+1,enery.width,enery.height); }else if(dir.equals("Down")){ rect = new Rectangle(enery.x,enery.y-2,enery.width,enery.height); } //碰撞检测 if(myrect.intersects(rect)){ return true; } } return false; } //检查是否贴地 public boolean isGravity=false; public void Gravity(){ new Thread(){ public void run(){ while(true){ try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if(!jumpFlag){ } while(true){ if(!jumpFlag){ break; } if(hit(Dir_Down)){ break; } if(y>=358){ isGravity=false; } else{ isGravity=true; y+=yspeed; } try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } }.start(); } }</code></pre> <pre> <code class="language-java">package com.huaxin.mario; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.ImageIcon; //键盘按下监听类 public class KeyListener extends KeyAdapter{ public GameFrame gf; public boolean jumpFlag=true; public KeyListener(GameFrame gf) { this.gf=gf; } //键盘监听 public void keyPressed(KeyEvent e) { int code = e.getKeyCode(); switch(code){ //向右走 case 39: gf.mario.right=true; break; //向左走 case 37: gf.mario.left=true; break; case 66: addBoom(); break; //向上跳 case 74: gf.mario.up=true; break; } } //添加子弹 public void addBoom() { Boom b = new Boom(gf.mario.x,gf.mario.y+5,10); if(gf.mario.left) b.speed=-2; if(gf.mario.right) b.speed=2; gf.boomList.add(b); } //键盘释放监听 public void keyReleased(KeyEvent e) { int code=e.getKeyCode(); if(code==39){ gf.mario.right=false; gf.mario.img=new ImageIcon("image/mari1.png").getImage(); } if(code==37){ gf.mario.left=false; gf.mario.img=new ImageIcon("image/mari_left1.png").getImage(); } if(code==74){ gf.mario.up=false; } } }</code></pre> <pre> <code class="language-java">package com.huaxin.mario; import java.awt.Image; import javax.swing.ImageIcon; public class BackgroundImage { public int x=0,y=0; public int ox=0,oy=0; public Image img=new ImageIcon("image/startBack.jpg").getImage(); }</code></pre> <pre> <code class="language-java">package com.huaxin.mario; //子弹类 public class Boom { //子弹的坐标,大小,速度 int x,y; int width; int speed=1; public Boom(int x, int y, int width) { super(); this.x = x; this.y = y; this.width = width; } }</code></pre> <p>主函数类,作为整个程序的入口</p> <pre> <code class="language-java">package com.huaxin.mario; public class Test { //主函数,程序入口 public static void main(String[] args) throws Exception { GameFrame gf = new GameFrame(); gf.initFrame(); } }</code></pre> <p>com.huaxin.enery包:</p> <pre> <code class="language-java">package com.huaxin.enery; import java.awt.Image; //障碍物的抽象父类 public abstract class Enery { public int x,y; public int width,height; public Image img; public Enery(int x, int y, int width, int height,Image img) { this.x = x; this.y = y; this.width = width; this.height = height; this.img=img; } } </code></pre> <pre> <code class="language-java">package com.huaxin.enery; import java.awt.Image; //金币类 public class Coin extends Enery{ public Coin(int x, int y, int width, int height, Image img) { super(x, y, width, height, img); } }</code></pre> <pre> <code class="language-java">package com.huaxin.enery; import java.awt.Image; //砖头类 public class Brick extends Enery { public Brick(int x, int y, int width, int height, Image img) { super(x, y, width, height, img); } }</code></pre> <pre> <code class="language-java">package com.huaxin.enery; import java.awt.Image; //水管类 public class Pipe extends Enery { public Pipe(int x, int y, int width, int height, Image img) { super(x, y, width, height, img); } }</code></pre> <p>com.huaxin.util包:</p> <pre> <code class="language-java">package Util; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.util.ArrayList; //地图配置类 public class Map { //数据容器 public ArrayList<String> list = new ArrayList<String>(); public int [][] map=null; public int[][] readMap() throws Exception { // 构造文件输入流 FileInputStream fis = new FileInputStream("map.txt"); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); //直接读取一行数据 String value =br.readLine(); while(value!=null){ //将读取到的一行数据加入到容器中 list.add(value); value =br.readLine(); } br.close(); //得到多少行多少列 int row=list.size(); int cloum=0; for (int i = 0; i < 1; i++) { String str=list.get(i); String [] values=str.split(","); cloum=values.length; } map = new int [row][cloum]; //将读到的字符创转换成整数,并赋值给二位数组map for (int i = 0; i < list.size(); i++) { String str=list.get(i); String [] values=str.split(","); for (int j = 0; j < values.length; j++) { map[i][j]=Integer.parseInt(values[j]); } } return map; } }</code></pre> <p><strong>五、项目总结:</strong></p> <p>这个小项目看起来挺简单的,其实里面也有很多功能需要仔细去思考的,以前觉得游戏人物在窗体走的时候,觉得一直是人在走,其实,人物走到中间或者走到某一位置的时候,人物已经是在原地踏步了,取而代之的是背后那张长度很长的背景的图片在移动,任务的左右移动相对而言比较简答实现,跳跃功能就不是那么简单了,需要永达线程,跟后面障碍物碰撞检测又有关系,当没有碰到的时候,人物要往下掉,一不注意就会出现各种bug,比如,可以一直跳,在跳的同时还能再跳,还有就是下降可能不会落地面的同一高度,可能会高于地面的高度,可能会低于地面的高度;障碍物的添加,我们是通过配置地图的方式,配置地图有时通过读入文件的操作,大致的原理是将后面的那张的背景图片划分成很多的小格子,将这些小格子用代码转换为二位数组,二维数组可以存入不同数字,代表不同的障碍物,通过操作文件就可以轻易配置地图;首先通过文件输入流将文件数据读入,将字符转换为数字,并存储在二维数组中,后面,通过遍历二维数组的不同位置的值,在相应的格子填充相应的障碍物;地图就这样配置好了;觉得配地图很炫酷,因为以前有位学长在演示的时候,觉得好难,现在自己掌握了觉得也不是很难了,当然了,这里离不开老师的指导;碰撞检测类似之前小球的碰撞检测,这里主要需要处理碰撞之后的一些逻辑,如何避免一直碰撞(人物贴着障碍物不能动)等;发射子弹的话,也比较简单了,利用容器装子弹,不过需要注意的是要及时将容器中子弹移除,不然内存会很快被吃完的;</p> <p>写程序,一定要按照思路一步一步来,一点一点来实现!不要太过急于求成!以前老想着自己做一个小东西出来,可是自己总是很急,希望很快做出来,结果,思路没有理清,写起来一团糟!前车之鉴!</p> <p>好了,就先写到这吧!共勉!</p> <p> </p> <p>来自:http://blog.csdn.net/bluesky_usc/article/details/54115406</p> <p> </p>