课程设计:基于Java的超级玛丽游戏的设计与实现(素材+源码+论文+学习视频)

项目展示:

在这里插入图片描述

请添加图片描述
在这里插入图片描述

本软件是针对超级玛丽小游戏的JAVA程序,进入游戏后首先按空格键开始,利用方向键来控制的马里奥的移动,同时检测马里奥与场景中的障碍物和敌人的碰撞,并判断马里奥的可移动性和马里奥的生命值。当马里奥通过最后一个场景后游戏结束。

系统模块设计:

窗体类

该类主要用于存放游戏的场景以及其他各类,并且实现KeyListener接口,用于从键盘的按键中读取信息。该类中的一些属性主要包括了用于存放所有场景的list集合 allBG,马里奥类 mario,当前的场景 nowBG以及其他一些游戏中需要的标记等。而且在该类中,运用双缓存的技术使得游戏的流畅度更高,解决了游戏中出现的闪屏问题。
将该类的名字定义为MyFrame,并且要在该类中实现KeyListener接口和Runnable接口。然后首先要在该类中定义一个List集合,集合的泛型为背景类BackGround,集合的名字定义为allBG,用于存放所有的背景。接着定义一个Mario类属性,名字为mario,这个就是游戏运行时候的所需要的mario。接下来还要在类中定义一个BackGround属性,nowBG,默认值应当为空,会在构造方法中赋予该属性初值,这个属性主要是用来存放当前游戏运行时马里奥所处的游戏场景。另外该类中还应该有一个Thread类属性t,这个属性主要是为了在游戏运行的时候控制游戏的线程。然后就可以在类中定义main()方法,将该类实现就可以了。值得一提的是该类的构造方法相对来说是比较复杂的。
在该类的构造方法中,应当首先绘制窗体类的标题,以及窗体类的大小,并且要对窗体类在初始化的时候的位置,也就是在屏幕中显示的位置,最好是显示的时候居中,这样的话在游戏运行时会比较美观一些。其次还要对窗体的一个是否可拉升属性进行一下设置,这个设置的主要目的是因为游戏的界面都是开发者经过深思熟虑考虑出来的比较美观的界面,玩家随意改变游戏的窗口大小可能会对游戏的体验造成影响,所以在这里应该设置游戏的窗体默认不可以被拉伸。

public MyFrame(){
    
    
		this.setTitle("玛丽奥");
		this.setSize(900, 600);
           //这里是为了获得电脑屏幕的整体大小,以便于下面确定窗体的位置
		int width = Toolkit.getDefaultToolkit().getScreenSize().width;
		int height = Toolkit.getDefaultToolkit().getScreenSize().height;
		this.setLocation((width-900)/2, (height-600)/2);
       //设置窗体默认不可以被拉伸
		this.setResizable(false);
		//初始化图片
		StaticValue.init();

当这些都设置好以后,接下来就应当在构造方法中绘制了,当然最先应当将游戏的场景绘制到窗体类中,然后在窗体类中还应当绘制马里奥类,这是游戏中必不可少的。当然在绘制场景类的时候因为不知一个场景,所以可以使用循环,将所有的场景全部绘制。然后在将所需要的所有监视设置好以后就可以开启该类的线程了。

//使用循环创建全部场景
		for(int i=1;i<=7;i++){
    
    
			this.allBG.add(new BackGround(i, i==7?true:false));
		}
		//将第一个场景设置为当前场景
		this.nowBG = this.allBG.get(0);
		//初始化玛丽奥
		this.mario = new Mario(0, 480);
		//将玛丽奥放入场景中
		this.mario.setBg(nowBG);
		this.repaint();
		this.addKeyListener(this);
		this.t = new Thread(this);
		t.start();
      //使窗口在关闭的时候,程序也同时停止。
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setVisible(true);
	}

在这些最基本的东西设置完以后,还需要一个方法来解决游戏中经常会出现的闪屏问题。这个方法就是双缓存方法,现在类中定义一个BufferedImage的图片,然后从该图片中获取到图片的Graphics g2,然后利用画笔g2将所要绘制的东西绘制到这个空的图片中,然后在利用窗体类中的paint方法中的画笔g将这个已经绘制好的图片绘制到窗体类中,这样利用空白图片作为程序运行中的中转,就可以很好的解决游戏运行过程中出现的闪屏问题。

public void paint(Graphics g) {
    
    
		//先定义一个图片,然后利用双缓存解决闪屏问题
		BufferedImage image = new BufferedImage(900, 600, BufferedImage.TYPE_3BYTE_BGR);
		Graphics g2 = image.getGraphics();
      //利用上面图片中得到的画笔g2,将所需绘制到图片中
		if(this.isStart){
    
    
			//绘制背景
			g2.drawImage(this.nowBG.getBgImage(), 0, 0, this);
			//绘制生命
			g2.drawString("生命:    "+this.mario.getLife(), 800, 50);
			//绘制怪物敌人
			Iterator<Enemy> iterEnemy = this.nowBG.getAllEnemy().iterator();
			while(iterEnemy.hasNext()){
    
    
				Enemy e = iterEnemy.next();
				g2.drawImage(e.getShowImage(), e.getX(), e.getY(), this);
			}
		//把缓存图片绘制进去
		g.drawImage(image, 0, 0, this);
	}

当然游戏的宗旨是让玩家和电脑之间的互动,那么就又涉及到一个问题,就是玩家对游戏中的马里奥的控制。我们前面已经说过了该类中必须要实现KeyListener接口,这个接口的作用就是使该类中实现一些方法,以便于达到玩家在游戏进行时可以对游戏中的马里奥进行控制。我们这里拟定对于马里奥的控制可以使用我们常见的四个方向键,即我们说的上下左右。并且通过控制台打印,可以知道上对应的是38,右对应的是39,左对应的是37。并且游戏的设定是开始后游戏不会直接运行,而是要使用空格键以后游戏才会真正开始,所以还要加入当按空格键的时候游戏正式开始,空格键对应的是32。

public void keyPressed(KeyEvent e) {
    
    
		if(this.isStart){
    
    
			//玛丽奥的移动控制
			if(e.getKeyCode()==39){
    
    
				this.mario.rightMove();
			}
			if(e.getKeyCode()==37){
    
    
				this.mario.leftMove();
			}
			//跳跃控制
			if(e.getKeyCode()==38){
    
    
				this.mario.jump();
			}
		}else if(e.getKeyCode()==32){
    
    
			this.isStart = true;
		}
	}

对于按键,那么相对应的就是当抬起建的时候。因为你向右移动的时候,如果这时候突然停止,那么很可能马里奥会保持一个运动的状态停下来,那么就必须在玛丽奥停止的时候给他一个指令,让他的移动图片变为静止。相对于运动的时候是类似的,这里不做累述。

public void keyReleased(KeyEvent e) {
    
    
		if(this.isStart){
    
    
			//控制玛丽奥的停止
			if(e.getKeyCode()==39){
    
    
				this.mario.rightStop();;
			}
			if(e.getKeyCode()==37){
    
    
				this.mario.leftStop();;
			}
		}

当这一切都做好以后,那么最后就应该在类中重写一下run方法了,在这个方法中应当提一下游戏的通关和死亡后的状态。即游戏通关,或者马里奥死亡时应当弹出一个窗口,说明游戏通关或者马里奥死亡,并且点击了这个窗口以后,游戏应当结束,而且整个游戏也应当关闭。

if(this.mario.isDead()){
    
    
		JOptionPane.showMessageDialog(this, "游戏结束");
		System.exit(0);
	}
	if(this.mario.isClear()){
    
    
		JOptionPane.showMessageDialog(this, "恭喜游戏通关!");
		System.exit(0);
		}

初始化类

用于存放游戏所需要的所有静态文件,在游戏开始的时候将所有文件导入,提高游戏的运行速度。并且在该类中将所有需要用到的图片进行分类,分为障碍物类,马里奥类,敌人类以及背景图片。当游戏运行时可以直接调用这些集合中的图片进行遍历,在调用的时候更加方便,而且可以使马里奥或者敌人在移动的时候产生动态效果。
首先在类中应当定义一个静态的List,泛型为BufferedImage,属性名字为allMarioImage,这个属性的作用在于存放所有的马里奥图片,里面包括了马里奥的移动图片,站立图片以及马里奥跳跃的图片。这样在程序运行的时候就可以从该类中的这个属性里面将所需要的马里奥图片直接调用出来,并且还可以在马里奥移动时不断遍历里面的图片,这样就可以使马里奥产生移动的动态效果。接下来要在该类中定义开始图片,结束图片以及背景图片,默认的初始值都为null。注意这些所有的属性都是静态的,包括下面要提到的所有的属性,这样做的目的是为了在程序运行时先加载这些图片。然后应当定义存放食人花的List集合allFlowerImage,这个集合将食人花的不同形态,张嘴、闭嘴图片存放进去,这样在运行的时候进行遍历就可以打到动态效果。同理存放蘑菇怪的集合allTrangleImage,以及存放所有障碍物的集合allObstructionImage。

public class StaticValue {
    
    
	public static List<BufferedImage> allMarioImage = new ArrayList<BufferedImage>();
	public static BufferedImage startImage = null;
	public static BufferedImage endImage = null;
	public static BufferedImage bgImage = null;
	public static List<BufferedImage> allFlowerImage = new ArrayList<BufferedImage>();
	public static List<BufferedImage> allTriangleImage = new ArrayList<BufferedImage>();
	public static List<BufferedImage> allObstructionImage = new ArrayList<BufferedImage>();
定义完这些属性之后,剩下的就是初始化了,在该类中定义一个init()方法,这个方法在执行的时候会将所需的所有图片放入到之前定义好的各个集合中。因为图片存放的路径都是一样的,所以为了减少代码量会定义一个公共路径ImagePath。然后就可以利用循环,将存放的图片全部导入进去。
   //介绍代码量,定义公共路径
	public static String ImagePath = System.getProperty("user.dir")+"/bin/";
	//定义方法init(),将图片初始化
	public static void init(){
    
    
		//利用循环将玛丽奥图片初始化
		for(int i=1;i<=10;i++){
    
    
			try {
    
    
				allMarioImage.add(ImageIO.read(new File(ImagePath+i+".png")));
			} catch (IOException e) {
    
    
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//导入背景图片
		try {
    
    
			startImage = ImageIO.read(new File(ImagePath+"start.jpg"));
			bgImage = ImageIO.read(new File(ImagePath+"firststage.jpg"));
			endImage = ImageIO.read(new File(ImagePath+"firststageend.jpg"));
		} catch (IOException e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//导入玛丽奥死亡图片
		try {
    
    
			mariDeadImage = ImageIO.read(new File(ImagePath+"over.png"));
		} catch (IOException e) {
    
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

背景类

该类表示马里奥及障碍物和敌人所处的场景,并且将障碍物和敌人绘制到场景中。在该类中包括用于存放敌人和障碍物的list集合,以及当敌人或者障碍物被消灭后用于存放已经消失的敌人和障碍物的集合,这样做是为了在马里奥死亡时重置场景所用的。其次在该类中还使用了控制敌人移动的方法,是为了在程序之初控制敌人静止,然后在玩家点击空格以后在使得敌人开始移动。并且在第六个关卡处设置了一个隐形通关要点,只有当马里奥顶到这个隐形砖块时才会出现,马里奥就可以借助这个砖块通过关卡。
首先背景类中肯定要有一个标记来表示现在是第几个场景,因为不同的背景中所绘制的场景,障碍物等也不同,所以该类中要有一个int类型的场景顺序sort。并且在游戏的设定中,如果玩家玩到最有一关的时候马里奥会失去玩家的控制,自己走向城堡。那么这里就要这几一个标记,是否为最后的场景,类型为boolean类型。如果马里奥失去所有生命值,或者游戏通关的话,那么游戏就会结束,这里还应当加一个boolean的标记isOver判断游戏是否结束。

public class BackGround {
    
    
	//当前场景图片
	private BufferedImage bgImage = null;
	//场景顺序
	private int sort;
	//是否为最后的场景
	private boolean flag;
	//游戏结束标记
	private boolean isOver = false;

在最后一个关卡中,马里奥到达旗杆的位置后就会失去控制,同时旗子将会开始下降,只有等旗子下降完毕后,马里奥才能开始移动,所以这里还要定义一个旗子是否下降完毕的boolean类型的属性isDown,用于判断马里奥什么时候移动。

//定义降旗结束
	private boolean isDown = false;

当马里奥失去生命的时候,但是并没有失去所有的生命,那么这个时候应当重置这个场景,将所有消灭掉的障碍物和敌人全部还原。因此除了在该类中除了要定义存放敌人和障碍物的List集合以外,还应当有存放被消灭的敌人或者障碍物的List,当敌人或者障碍物被消灭的时候先放入到这个List中,这样在充值的时候就可以直接将这个集合中的数据在还原到原先的集合里面。

//用集合保存敌人
	private List<Enemy> allEnemy = new ArrayList<Enemy>();
	//用集合保存障碍物
	private List<Obstruction> allObstruction = new ArrayList<Obstruction>();
	//被消灭的敌人
	private List<Enemy> removeEnemy = new ArrayList<Enemy>();
	//被消灭的障碍物
	private List<Obstruction> removeObstruction = new ArrayList<Obstruction>();

在游戏的设定中,应当是游戏开始的时候,所有的敌人其实是静止的,而且玩家也不能控制马里奥,必须要等到玩家按空格键开始以后游戏才会进行,那么这里就应当在定义一个方法,即当玩家空格键的时候会调用这个方法,同时游戏中的敌人开始移动,游戏正式开始。这个方法也就是相当于控制敌人开始移动的方法,所以命名为enemyStartMove()方法。

//敌人开始移动
	public void enemyStartMove(){
    
    
       //遍历当前场景中的敌人,使之开始移动
		for(int i=0;i<this.allEnemy.size();i++){
    
    
			this.allEnemy.get(i).startMove();
		}
	}

接下来就应当定义背景类的构造方法了,通过获取场景的顺序,即场景的sort,来判断是哪一个场景,同时将场景绘制好。

//构造方法
	public BackGround(int sort,boolean flag){
    
    
		//第一个场景
		if(sort==1){
    
    
			for(int i=0;i<15;i++){
    
    
				this.allObstruction.add(new Obstruction(i*60, 540, 9,this));
			}
			//绘制砖块和问号
			this.allObstruction.add(new Obstruction(120, 360, 4,this));
			this.allObstruction.add(new Obstruction(300, 360, 0,this));
			......
	}

前面提到,如果马里奥死亡,但是却没有失去所有的生命值,那么游戏应当重置,当前场景中的所有敌人和障碍物,也包括马里奥都应当回到初始位置。为了达到这个效果,那么我们的场景类中就必须要定义一个reset()方法,来调用障碍物和场景还有马里奥的各自的重置方法,来使当前的场景还原。并且在这之前我们还要将消灭掉的敌人和障碍物从消灭掉的存放的List中提出来,放回到原来的List中。然后遍历障碍物和敌人的List,使用循环调用他们的重置方法。

//重置方法,重置障碍物和敌人
	public void reset(){
    
    
		//将移除的障碍物和敌人还原
		this.allEnemy.addAll(this.removeEnemy);
		this.allObstruction.addAll(this.removeObstruction);
		//调用障碍物和敌人的重置方法
		for(int i=0;i<this.allEnemy.size();i++){
    
    
			this.allEnemy.get(i).reset();
		}
		for(int i=0;i<this.allObstruction.size();i++){
    
    
			this.allObstruction.get(i).reset();
		}
	}	

马里奥类

用来控制马里奥的行动,并且在该类中加入碰撞检测,判断马里奥是否与障碍物或者敌人发生碰撞。该类中的属性主要定义了马里奥所在的场景,马里奥的移动和跳跃的速度,以及马里奥在移动过程中需要显示的图片。另外该类中还定义了玩家的生命值和所获得的分数。并且在run()方法中还定义了当马里奥到达最后一关的旗子时,玩家将失去对马里奥的控制,剩下的由程序控制走到城堡,完整全部游戏。
在游戏中,玛丽奥要在玩家的控制下完成移动、跳跃等动作,那么这些动作首先肯定要涉及到坐标,那么我们在该类中首先要定义两个属性,这两个属性即为马里奥的坐标x和y。并且该类还要实现Runnable接口,在run()方法中写马里奥的移动规则。

public class Mario implements Runnable{
    
    
	//坐标
	private int x;
	private int y;
	//定义玛丽奥所在场景
	private BackGround bg;
	//加入线程
	private Thread t = null;

为了玩家在游戏过程中的良好体验,那么对于马里奥的移动速度和跳跃速度就必须要定义好。所以该类里面还应当定义马里奥的移动速度和跳跃速度,其本质就是马里奥在移动过程中坐标加减的值。当然初始值为零,必须等到马里奥构造的时候,再将这些属性赋予相对应的值。在本类中还要定义游戏的分数以及马里奥的生命数,这些都是必不可少的。

//移动速度
	private int xmove = 0;
	//跳跃速度
	private int ymove = 0;
	//状态
	private String status;
	//显示图片
	private BufferedImage showImage;
	//生命和分数
	private int score;
	private int life;

在马里奥这个类中,还要定义马里奥的移动和跳跃方法,以便玩家在按下方向键后调用这些方法,来达到控制马里奥的移动。下面是马里奥向左移动的方法,其他方法同理。

	public void leftMove(){
    
    
		//移动速度
		xmove = -5;
		//改变状态
		//如果当前已经是跳跃,应该保持原有状态,不能再改变
		if(this.status.indexOf("jumping") != -1){
    
    
			this.status = "left-jumping";
		}else{
    
    
			this.status = "left-moving";
		}
	}
 ......

在定义马里奥的跳跃方法的时候,不单单定义一个方法就行,而且还要判断马里奥的状态。如果马里奥是在地面或者是在障碍物的上方,那么马里奥可以进行跳跃,如果马里奥处于空中,那么马里奥就不可以继续跳跃。

public void jump(){
    
    
//判断马里奥是否可以进行跳跃
		if(this.status.indexOf("jumping") == -1){
    
    
			if(this.status.indexOf("left") != -1){
    
    
				this.status = "left-jumping";
			}else{
    
    
				this.status = "right-jumping";
			}
			ymove = -5;
			upTime = 36;
		}
	}

接下来就要写马里奥中的run()方法了,这个方法中的内容相对来说比较麻烦,因为要在这个方法中对马里奥和障碍物或者敌人之间进行逻辑判断,即所谓的碰撞检测。首先在这个类中对马里奥是否处于最后一个场景进行判断,如果马里奥处于最后一个场景,并且坐标大于520,那么说明马里奥已经撞到的旗杆,这个时候马里奥将不会由玩家控制。并且同时调用旗子的移动方法,使旗子进行下落,当旗子下落完毕后给马里奥一个标记,马里奥开始移动到城堡。当马里奥的坐标大于780,即马里奥到达城堡的门口的时候,这个时候游戏结束。

public void run() {
    
    
		while(true){
    
    
			//判断是否与障碍物碰撞
			//定义标记
			if(this.bg.isFlag() && this.x >= 520){
    
    
				this.bg.setOver(true);
				if(this.bg.isDown()){
    
    
					//降旗后玛丽奥开始移
					this.status = "right-moving";
					if(this.x < 580){
    
    
						//向右
						this.x += 5;
					}
						if(this.x >= 780){
    
    
							//游戏结束
							this.setClear(true);
						}

然后对当前马里奥所处的场景中的所有障碍物进行遍历,获取到所有障碍物的坐标,通过障碍物的坐标和马里奥的坐标的之间的关系的判断,来决定马里奥是否与障碍物发生了碰撞,并且通过判断的结果来对马里奥和障碍物的状态进行相应的变化。

for(int i=0;i<this.bg.getAllObstruction().size();i++){
    
    
		Obstruction ob = this.bg.getAllObstruction().get(i);
		//不能向右移动
	if(ob.getX()==this.x+60&&(ob.getY()+50>this.y&&ob.getY()-50<this.y)){
    
    
			if(ob.getType() != 3){
    
    
				canRight = false;
			}
		}
		......

当马里奥撞到障碍物的时候,那么就要根据障碍物的类型进行接下来的判断,如果是砖块或者是问号的话,那么障碍物消失,马里奥被弹回,即马里奥的状态由上升状态变为下落状态,并且将消失掉的障碍物放入相对应的消失的List集合当中。如果障碍物的类型为其他,比如说是石头的话,那么障碍物不变,马里奥直接被弹回。

//判断玛丽奥跳跃时是否撞到障碍物
	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);
			//保存到移除的障碍物中
			this.bg.getRemoveObstruction().add(ob);
		}

为了游戏的可玩性,将会在游戏中加入一个隐藏的陷阱,或者是隐藏的通关点。这个隐藏的障碍物在游戏进行的时候不会显示出来,当然马里奥从他的左右两边过去的时候也不会触发这个隐藏的障碍物,必须是从下方撞到这个障碍物时才会显示出来。同时马里奥由上升状态变为下落状态。而且他和砖块障碍物相同,被顶到后会变为石头,改变类型。

//如果是问号||隐藏的砖块
		if((ob.getType()==4 || ob.getType()==3) && upTime > 0){
    
    
			ob.setType(2);
			ob.setImage();
		}
      //马里奥开始下落
		upTime = 0;

在游戏中敌人大致可以分为两类。一类是蘑菇怪,这种敌人是可以被杀死的,当马里奥从蘑菇怪的正上方踩到蘑菇怪时,那么蘑菇怪就会被消灭,同时马里奥向上跳起一小段距离。而消失掉的蘑菇怪就会被放到消失掉的敌人的List集合中,等到重置的时候在调用出来。但是如果马里奥从蘑菇怪的左右两边碰到蘑菇怪的话就会失去一条生命,并且重置游戏。第二类是食人花,这种敌人不会被马里奥消灭掉,不论马里奥从哪个方向去碰撞食人花,食人花都不会消失,而且如果马里奥碰到了食人花,自身还会失去一条生命,并且游戏重置,当然前提是马里奥没有失去所有的生命值,否则的话游戏就结束。
首先马里奥对于所有的敌人,如果从左右两边碰撞到敌人,那么马里奥死亡,失去一条生命,游戏重置。

```java
//对敌人的判断
	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()+60>this.y && e.getY()-60<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 = 10;
				this.ymove = -5;
			}else if(e.getType() == 2){
    
    
				this.dead();
			}
		}					

障碍物类

绘制场景中所需要的障碍物,例如地面、砖块、水管等等。该类中的属性包括了障碍物的坐标,障碍物所需要显示的图片等。并且在该类中也定义了障碍物类的重置方法,当马里奥死亡时,场景类会调用该方法。
游戏中的场景是由背景中的障碍物绘制而成的,不同的障碍物所在的位置肯定也不相同,那么对于障碍物而言,就必须要有坐标属性来使绘制的时候将不同的障碍物绘制到不同的位置,所以必须要有两个int属性x和y来表示障碍物的坐标。同时该类也必须要实现Runnable接口,实现这个接口的作用主要是为了在最有一个场景中控制旗子的运动,当然同时还要为该类加入线程。

public class Obstruction implements Runnable{
    
    
	//坐标
	private int x;
	private int y;
	//控制旗子
	private Thread t = new Thread(this);

前面说过,当马里奥顶到问好或者是隐藏的砖块时,那么这个障碍物的类型就会改变,变为石头。那么在障碍物这个类里面就必须要定义一个属性stype,这个属性用于表示当前障碍物的类型,以便于变化形态的时候调用。这个类型的值就可以用初始化类中的相对应的List集合里面的下标表示。既然有改变,就要有恢复,所以还要定义一个不变的type,命名为starttype,这个属性是为了当游戏重置的时候,障碍物可以通过调用这个属性恢复到最初始的状态。而且不同的状态对应不同的显示图片,所以还要有showImage属性。

//类型
	private int type;
	//初始类型
	private int starttype;
	//显示图片
	private BufferedImage showImage = null;
	//取得场景
	private BackGround bg;

在该类中还要写入reset()方法,这个方法是为了当马里奥死的时候调用重置方法,对已经被消灭掉的障碍物进行重置。因为有的障碍物被顶掉以后会给变类型和图片,所有还要定义一个setImage()方法,用来改变障碍物的显示图片。

//重置方法
public void reset(){
    
    
	this.type = starttype;
	this.setImage();
}
//根据状态改变显示图片
public void setImage(){
    
    
	showImage = StaticValue.allObstructionImage.get(type);
}

最后该类中的run方法主要是为了控制最后一个场景中的旗子的移动,并且在旗子移动完毕后要设置一个标记,并且将该标记表示给马里奥类,这样马里奥就可以开始自主移动了。

if(this.bg.isOver()){
    
    
				if(this.y < 420){
    
    
					this.y += 5;
				}else{
    
    
                  //设计标记为true,即表示马里奥可以开始移动了
					this.bg.setDown(true);
				}

敌人类

该类中主要设置了两种敌人,一种是蘑菇怪,可以被马里奥踩死,另一种是食人花,不能被踩死。该类中的属性包括了敌人的坐标,敌人的初始坐标,需要显示的图片,以及敌人的移动方向和移动范围等。敌人的初始坐标主要是为了当敌人执行重置方法后将敌人的位置还原。
在该类中首先要实现Runnable接口,因为在游戏中的敌人是可以移动的,所以一定要通过重写run()方法来达到敌人可以移动的效果。当然还要在该类中定义一个Thread属性,用于控制线程。然后说说到移动,必然少不了坐标问题,那么在该类中就要定义两个int属性x和y,用于控制敌人的位置以及敌人的移动。

public class Enemy implements Runnable{
    
    
	//坐标
	private int x;
	private int y;
   //加入线程
	private Thread t = null;

当马里奥失去一条生命值的时候,游戏会被重置,敌人回回到初始的位置,所以还要定义另外两个int属性startx和starty,用来当游戏进行重置的时候,可以根据这个初始坐标回复敌人的位置。

//初始坐标
	private int startx;
	private int starty;

对于不同的敌人,所显示的图片肯定是不同的,所以要定义一个现实的图片属性showImage,并且在马里奥中,马里奥要通过判断敌人的类型,来决定是马里奥死亡,还是敌人死亡,对于不同的敌人有不同的反应,所以还要在该类中定义一个type属性,用来表示敌人的类型。

//怪物类型
private int type;
//显示图片
private BufferedImage showImage;

对于敌人里面的食人花而言,他是在水管中直上直下的,所以他的上下移动应当有一个界限,不论是向上移动还是向下移动,都不能超过这个界限,否则的话食人花就会从水管中飞出来或者是移动到MyFrame外面了。

//移动范围
	private int upMax = 0;
	private int downMax = 0;

在这个类中应当有两个构造方法,对于不同的敌人,所需要的属性都是不同的。并且在两个类中都有一个共同的代码,那就是要在开启线程后应当先将线程挂起。这是为了配合游戏在开始的时候敌人不移动,必须要等到玩家按空格键的时候才会开始,所以先将线程挂起来,当点击空格键以后在将线程开启。

//蘑菇怪的构造方法
	public Enemy(int x,int y,boolean isLeft,int type,BackGround bg){
    
    
		... ...
		this.t = new Thread(this);
		t.start();
		t.suspend();
	}

接下来是写敌人类中的run()方法了,该方法主要是为了控制蘑菇怪以及食人花敌人的移动的。因为不同的敌人在不同的场景中有不同的移动方法,所以对于敌人的移动而且,首先要判断敌人的类型和敌人所处的场景。

public void run() {
    
    
	while(true){
    
    
		//判断怪物类型
		if(type==1){
    
    
			if(this.isLeftOrUp){
    
    
				this.x -= 5;
			}else{
    
    
				this.x += 5;
			}
	    

在游戏中,当马里奥死亡的时候会对整个场景中的障碍物进行重置,当然敌人也不例外。当马里奥死亡的时候,不仅要将所有被消灭的敌人全部显示出来,即从消灭的List中还原到原来的敌人List中,并且敌人的状态和坐标也要进行重置。要将敌人的坐标还原到最开始的坐标,而且把图片进行还原。并且在重置方法中也要对敌人的类型进行判断,使得敌人的类型和他的显示图片相对应。

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);
	}
}

最后在该类中定义一个死亡方法,主要是针对蘑菇怪被消灭的时候所调用的方法。在这个方法中要定义蘑菇怪死亡的时候的显示图片,也就是蘑菇怪被踩扁的图片。并且要将这个敌人从相对应的场景的敌人集合中除去,放入别消灭的敌人的List集合。

public void dead(){
    
    
		//死亡图片
		this.showImage = StaticValue.allTriangleImage.get(2);
		//从原来的List集合中删除,让入被消灭的List集合中
		this.bg.getAllEnemy().remove(this);
		this.bg.getRemoveEnemy().add(this);
	}	
}

素材+源码+论文+学习视频)下载

链接:打包下载点这里
提取码:vypf

猜你喜欢

转载自blog.csdn.net/qq_45928272/article/details/113454984