用Java实现马里奥 您所在的位置:网站首页 描写食人花的场景 用Java实现马里奥

用Java实现马里奥

2024-07-13 15:37| 来源: 网络整理| 查看: 265

一、项目需求分析

1、实现基本的场景布局(背景、障碍、音乐); 2、实现基本的移动跳跃功能; 3、实现碰撞检测; 4、实现各类敌人的自己移动; 5、实现踩死栗子怪的能力; 6、实现Mario的各种死亡情况; 7、实现游戏的结束判定(降旗、Mario自动移到城堡);

二、重难点分析

1、构建地图;为了简单点,就没有采用数组的方法来构建地图了,而是通过paint画笔遍历存储在各个列表List里的物体(物体添加到集合里就定义好位置了)来达到绘制场景的目的。 2、碰撞检测;主要包括人与障碍物、人与敌人、敌人与障碍物之间的判断,可以通过获取背景中的物体的类型、尺寸(图片大小)、状态来进行判断;值的注意的是,当Mario处于两块砖块之间时,很难跳上去;解决方法是将判断距离稍微减小(小于Mario图片的格式),降低判断的灵敏度。此外,我这里还有一个bug没有修复,就是当Mario跳起来,然后以45°角度向砖块移动,会直接穿过砖块,再栗子怪面前以45°跳到栗子怪上面也被认为是踩到栗子怪,这些应该还是碰撞检测灵敏度的问题。 3、移动太快;通过调用线程的sleep()方法可以让敌人移动速度,降旗速度,以及Mario的速度随自己的想法调整。栗子怪被踩时切换成被踩扁的照片没有显示也是因为速度太快引起的。 4、地图重绘;由于Mario的动作引起的场景变化,都要在Mario死亡一次后,重新回到原来的位置和状态。这个问题可以通过利用一个列表List把消灭的敌人和障碍存储起来,Mario死亡后调用paint遍历列表即可重绘出来,记得要改变状态。 5、Mario死亡后状态;在调试过程中,发现一个bug,就是当Mario从上面掉下来碰到食人花的时候死亡,但是重新一条生命的时候,Mario一出现在界面中,就以之前碰到食人花的状态继续下落,然后掉到地下死亡,而不是以站立的状态出现在预先设定的复活点处。解决方法就是对Mario的各个属性都进行修改,更简单的就是让Mario复活在空中,就不会直接一复活就掉到地下。

三、源代码

全部代码分为六部分:Frame框架(MyFrame)、背景类(BackGround)、图片的导入类(StaticValue)、Mario类、敌人类、障碍物类。 由于代码繁复,所以一些get和set方法就不放上来了。

1、Frame框架

public class MyFrame extends JFrame implements KeyListener, Runnable { // 存放背景的集合 private List allBG = new ArrayList(); private BackGround nowBG = null; private Mario mario = null; // 是否开始游戏的标记 private boolean isStart = false; //人物移动刷新地图的线程 private Thread t1 = new Thread(this); public static void main(String[] args) { new MyFrame(); } public MyFrame() { //定义窗体的尺寸 int width = Toolkit.getDefaultToolkit().getScreenSize().width; int height = Toolkit.getDefaultToolkit().getScreenSize().height; setSize(900, 600); setTitle("超級马里奥"); setLocation((width - 900) / 2, (height - 600) / 2); setDefaultCloseOperation(3); setResizable(false); // 初始化全部的图片 StaticValue.init(); // 创建全部的场景(3个) for (int i = 1; i = 840) { // 切换场景,改变Mario状态 this.nowBG = this.allBG.get(this.nowBG.getSort()); this.mario.setBg(this.nowBG); //Mario在下一个的位置 this.mario.setX(0); //此处场景的敌人开始移动 this.nowBG.enemyStartMove(); } //Mario死亡后在界面上的变化 if (this.mario.isDead()) { JOptionPane.showMessageDialog(this, "Game Over !"); this.mario.setDead(false); this.isStart = false; int result = JOptionPane.showConfirmDialog(this, "是否重新开始游戏?"); if(result == 0) { this.mario.setLife(3); this.mario.setScore(0); } } //Mario通关时界面的变化 if(this.mario.isWin()) { JOptionPane.showMessageDialog(this, "恭喜你,游戏通关!"); System.exit(0); } } catch (InterruptedException e) { e.printStackTrace(); } } } }

2、导入图片的类

public class StaticValue { //全部的Mario状态图片 public static List allMarioImage = new ArrayList(); //开局背景图片 public static BufferedImage startImage = null; //最后一关背景图片 public static BufferedImage endImage = null; //普通背景图片 public static BufferedImage bgImage = null; //全部的食人花图片(开,合) public static List allFlowerImage = new ArrayList(); //全部的栗子怪图片(抬左脚,抬右脚) public static List allTriangleImage = new ArrayList(); //全部的乌龟图片(左/右伸脚,左/右收脚,龟壳) public static List allTurtleImage = new ArrayList(); //全部的障碍物(各种砖块) public static List allObstructionImage = new ArrayList(); //Mario死亡图片 public static BufferedImage marioDeadImage = null; // System.getProperty("user.dir")返回当前项目的路径 // 图片初始化 public static void init() { // 导入Mario的图片 for (int i = 1; i this.y && ob.getY() - 50 < this.y)) { if (ob.getType() != 3 && ob.getType() != 11) { canLeft = false; } } // 在障碍物上才可以跳跃 if (ob.getY() == this.y + 60 && (ob.getX() + 60 > this.x && ob.getX() - 60 < this.x)) { if (ob.getType() != 3 && ob.getType() != 11) { onLand = true; } } // 判断Mario跳跃顶到砖块的情况 if (ob.getY() == this.y - 60 && (ob.getX() + 50 > this.x && ob.getX() - 50 < this.x)) { // 顶到砖块(顶的动) if (ob.getType() == 0) { // 砖块消失 this.bg.getAllObstruction().remove(ob); // 死后重置地图砖块还在,需要将移除的砖块保存到一个集合removeObstrution里 this.bg.getRemoveObstrution().add(ob); } // 顶到问号砖块或隐形砖块 if ((ob.getType() == 4 || ob.getType() == 3) && uptime > 0) { ob.setType(2); ob.setImage(); } // 顶到砖块就下落 uptime = 0; } } // 判断Mario与敌人的碰撞的情况 for (int i = 0; i < this.bg.getAllEnemy().size(); i++) { Enemy e = this.bg.getAllEnemy().get(i); // 跟敌人的左右碰撞 if (e.getX() + 50 > this.x && e.getX() - 50 < this.x && (e.getY() - 50 < this.y && e.getY() + 50 > this.y)) { this.dead(); } // 跟敌人的上面碰撞(要考虑重合的情况) if (e.getY() == this.y + 60 && (e.getX() + 60 > this.x && e.getX() - 60 < this.x)) { // 踩到蘑菇怪 if (e.getType() == 1) { e.dead(); // 踩到蘑菇怪上升高度 this.uptime = 3; // 上升速度 this.ymove = -5; // 踩到食人花 } else if (e.getType() == 2) { this.dead(); } } } // Mario掉到底死亡 if (this.y >= 600) { this.dead(); } // 下落时Mario状态的判断 if (onLand && uptime == 0) { if (this.status.indexOf("left") != -1) { if (xmove != 0) { this.status = "left--moving"; } else { // 往左静止下落状态 this.status = "left--standing"; } } else { if (xmove != 0) { this.status = "right--moving"; } else { // 往右静止下落状态 this.status = "right--standing"; } } } else { // 处于上升状态 if (uptime != 0) { uptime--; } else { this.down(); } y += ymove; } // 判断人物能移动且在移动才改变横坐标 if ((canLeft && xmove < 0) || (canRight && xmove > 0)) { x += xmove; if (x < 0) { x = 0; } } } // 定义一个图片取得的索引数 int temp = 0; // 当前为向左(对比字符串,相同是1) if (this.status.indexOf("left") != -1) { temp += 5; } // 通过循环显示图片实现走路的动态显示 // 判断当前是否为移动 if (this.status.indexOf("moving") != -1) { temp += this.moving; moving++; if (moving == 4) { moving = 0; } } // 显示跳跃图片 if (this.status.indexOf("jumping") != -1) { temp += 4; } // 改变显示移动图片 this.showImage = StaticValue.allMarioImage.get(temp); try { // 刷新速度(要跟MyFrame里的线程的sleep一起改) Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } }

5、敌人类

public class Enemy implements Runnable { // 障碍物坐标 private int x; private int y; // 食人花初始坐标 private int startX; private int startY; // 图片类型 private int type; // 显示图片 private BufferedImage showImage; // 移动方向 private boolean isLeftOrUp = true; // 移动的极限范围 private int upMax = 0; private int downMax = 0; //控制敌人开始移动的线程 private Thread t2 = new Thread(this); // 定义图片状态 private int imageType = 0; //背景 private BackGround bg; // 构造栗子怪 public Enemy(int x, int y, boolean isLeft, int type, BackGround bg) { this.x = x; this.y = y; this.startX = x; this.startY = y; this.isLeftOrUp = isLeft; this.type = type; this.bg = bg; if (type == 1) { this.showImage = StaticValue.allTriangleImage.get(0); } t2.start(); // 线程不能立即启动,要等按下空格开始才启动 t2.suspend(); } // 构造敌人 public Enemy(int x, int y, boolean isUp, int type, int upMax, int downMax, BackGround bg) { //初始化属性 this.x = x; this.y = y; this.startX = x; this.startY = y; this.isLeftOrUp = isUp; this.type = type; this.upMax = upMax; this.downMax = downMax; this.bg = bg; if (type == 2) { this.showImage = StaticValue.allFlowerImage.get(0); } //启动/挂起线程 t2.start(); t2.suspend(); } public void run() { while (true) { // 蘑菇怪左右移动 if (type == 1) { if (this.isLeftOrUp) { // 移动速度 this.x -= 2; } else { this.x += 2; } // 变换图片,形成走路的效果 if (imageType == 0) { imageType = 1; } else { imageType = 0; } //定义标记 boolean canLeft = true; boolean canRight = true; boolean onLand = false; // 判断当前敌人是否与障碍物碰撞 for (int i = 0; i < this.bg.getAllObstruction().size(); i++) { Obstrution ob = this.bg.getAllObstruction().get(i); // 碰撞不允许向右移动 if (ob.getX() == this.x + 60 && (ob.getY() - 50 < this.y && ob.getY() + 50 > this.y)) { canRight = false; } // 不允许向左移动 if (ob.getX() == this.x - 60 && (ob.getY() - 50 < this.y && ob.getY() + 50 > this.y)) { canLeft = false; } } // 敌人碰到障碍物自动转向 if (this.isLeftOrUp && !canLeft || this.x == 0) { this.isLeftOrUp = false; } else if (this.isLeftOrUp && !canRight || this.x == 840) { this.isLeftOrUp = true; } //改变转向后敌人图片 this.showImage = StaticValue.allTriangleImage.get(imageType); } // 食人花上下移动 if (type == 2) { if (this.isLeftOrUp) { this.y -= 2; } else { this.y += 2; } if (imageType == 0) { imageType = 1; } else { imageType = 0; } if (this.isLeftOrUp && this.y == this.upMax) { this.isLeftOrUp = false; } if (!this.isLeftOrUp && this.y == this.downMax) { this.isLeftOrUp = true; } //改变食人花图片 this.showImage = StaticValue.allFlowerImage.get(imageType); } // 同步移动速度 try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } } } //重置敌人的方法 public void reset() { // 将坐标重置 this.x = this.startX; this.y = this.startY; // 将显示图片重置 if (this.type == 1) { this.showImage = StaticValue.allTriangleImage.get(0); } else if (this.type == 2) { this.showImage = StaticValue.allFlowerImage.get(0); this.isLeftOrUp = true; } } // 敌人死亡 public void dead() { // 显示死亡图片 this.showImage = StaticValue.allTriangleImage.get(2); // 敌人消失 this.bg.getAllEnemy().remove(this); // 重置回来 this.bg.getRemoveEnemy().add(this); } // 敌人开局开始移动(打开线程) public void startMove() { t2.resume(); } }

6、障碍物类

public class Obstrution implements Runnable{ //坐标 private int x; private int y; //图片的类型 private int type; // 初始的类型 private int startType; // 显示图片 private BufferedImage showImage = null; //当前障碍物所在的背景 private BackGround bg; //创建线程控制旗帜降落 private Thread t = new Thread(this); // 构造方法(障碍物对象的初始化) public Obstrution(int x, int y, int type ,BackGround bg) { this.x = x; this.y = y; this.type = type; this.startType = type; this.bg = bg; setImage(); //碰到旗帜的时候启动线程 if(this.type == 11) { t.start(); } } // 重置方法 public void reset() { // 修改为初始类型 this.type = startType; // 改变显示图片 this.setImage(); } // 根据类型显示图片 public void setImage() { showImage = StaticValue.allObstructionImage.get(type); } //线程控制旗帜的降落 public void run() { while(true){ if(this.bg.isOver()) { if(this.y < 444) { this.y += 5; }else { this.bg.setDown(true); } } try { Thread.sleep(50); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 四、项目总结:

回顾整个项目的实现过程,我发现我又学到了很多,对如何通过Java来实现一些小游戏又有了更深入的理解。首先,在这个马里奥游戏项目里,我接触了一些新的(或者接触不多的)知识,比如:

//1、 获取整个界面Frame的尺寸 Toolkit.getDefaultToolkit().getScreenSize() //2、 初始化init()方法的使用 StaticValue.init();

也对一些内容有了更深的理解:

//1、迭代器Iterator Iterator iterEnemy = this.nowBG.getAllEnemy().iterator(); //2、 调用collection中的add()和addAll()来快速添加元素,而不用通过循环来添加。 this.allEnemy.addAll(this.removeEnemy); //3、 对一些只有两种可能发生的情况(只需要判定是否)的事件,使用布尔类型的标记可以更加清晰简便地判断问题。 //4、 在本项目中运用最多的就是this了,经常会把自己都搞混,但只要秉持谁调用、this就是谁的这一点就能避免出错。 Thread thread = new Thread(this);

然后,在这个项目中,我也发现了许多不足,其中最大的问题是我对API的不够熟悉,有时候明明想法有了,但是就是不知道调用什么方法来实现,用什么方法才更好。此外,还有就是当我构造好食人花后,虽然出现在背景中,但是食人花却没有动起来,其原因居然是由于直接复制栗子怪的部分代码,还忘记把x该成了y(苦笑不得);由此可见,做项目,写代码一定要思路清晰,知道自己在干什么,下一步又该干什么,一步一步地写,道长且艰,写代码一定要偶尔停一下,回头看看自己写了什么,又缺什么,才不容易出错。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有