题目:
本程序是针对超级玛丽小游戏的 JAVA 程序,进入游戏后首先用鼠标点击 GUI 窗口,然后开始游 戏,利用方向键来控制的马里奥的移动,同时检测马里奥与场景中的障碍物和敌人的碰撞,并判断马里 奥的可移动性和马里奥的生命。踩死蘑菇敌人与撞击金币砖块可获得积分与金币。记录马里奥获得的分 数与金币个数。当马里奥通过最后一个场景后游戏结束。利用多线程技术,给游戏分别添加背景音乐、 跳跃音乐、死亡音乐、顶金币音乐、游戏胜利音乐。
运行窗口
代码解析:
窗体类–MyF rame.java
该类主要用于存放游戏的场景以及其他各类,并且实现 KeyListener 接口,用于从键盘的按键中 读取信息。该类中的一些属性主要包括了用于存放所有场景的 list 集合 allBG,马里奥类 mario,当前 的场景 nowBG 以及其他一些游戏中需要的标记等。而且在该类中,运用双缓存的技术使得游戏的流畅 度更高,解决了游戏中出现的闪屏问题。 将该类的名字定义为 MyF rame,并且要在该类中实现 KeyListener 接口和 Runnable 接口。然 后首先要在该类中定义一个 List 集合,集合的泛型为背景类 BackGround,集合的名字定义为 allBG, 用于存放所有的背景。接着定义一个 M ario 类属性,名字为 mario,这个就是游戏运行时候的所需要 的 mario。接下来还要在类中定义一个 BackGround 属性,nowBG,默认值应当为空,会在构造方法 中赋予该属性初值,这个属性主要是用来存放当前游戏运行时马里奥所处的游戏场景。另外该类中还应 该有一个 T hread 类属性 thread,这个属性主要是为了在游戏运行的时候控制游戏的线程。然后就可以 在类中定义 main() 方法,将该类实现就可以了。 在该类的构造方法中,应当首先绘制窗体类的标题,以及窗体类的大小,并且要对窗体类在初始化 的时候的位置,也就是在屏幕中显示的位置,最好是显示的时候居中,这样的话在游戏运行时会比较美 观一些。考虑到玩家随意改变游戏的窗口大小可能会对游戏的体验造成影响,所以在这里应该设置游戏 的窗体默认不可以被拉伸。
当这些都设置好以后,接下来就应当在构造方法中绘制了,当然最先应当将游戏的场景绘制到窗体 类中,然后在窗体类中还应当绘制马里奥类,这是游戏中必不可少的。当然在绘制场景类的时候因为不 知一个场景,所以可以使用循环,将所有的场景全部绘制。然后在将所需要的所有监视设置好以后就可 以开启该类的线程了, 然后创建一个 Music 类播放背景主题音乐
在这些最基本的东西设置完以后,还需要一个方法来解决游戏中经常会出现的闪屏问题。这个方法 就是双缓存方法,现在类中定义一个 BufferedImage 的图片,然后从该图片中获取到图片的 Graphics graphics,然后利用画笔 graphics 将所要绘制的东西绘制到这个空的图片中,然后在利用窗体类中的 paint 方法中的画笔 graphics 将这个已经绘制好的图片绘制到窗体类中,这样利用空白图片作为程序运 行中的中转,就可以很好的解决游戏运行过程中出现的闪屏问题。
对于玩家对游戏中的马里奥的控制。在该类中实现 KeyListener 接口,这个接口的作用就是使该类 中实现一些方法,以便于达到玩家在游戏进行时可以对游戏中的马里奥进行控制。我们这里拟定对于马 里奥的控制可以使用我们常见的四个方向键,即我们说的上下左右。并且通过控制台打印,可以知道上 对应的是 38,右对应的是 39,左对应的是 37。并且游戏的设定是开始后游戏不会直接运行,而是要使 用鼠标点击以后游戏才会真正开始,所以还要加入鼠标监听
对于按键,那么相对应的就是当抬起按键的时候。因为你向右移动的时候,如果这时候突然停止, 那么很可能马里奥会保持一个运动的状态停下来,那么就必须在玛丽奥停止的时候给他一个指令,让他 的移动图片变为静止。相对于运动的时候是类似的,。
package ClassPackage;
import javazoom.jl.decoder.JavaLayerException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
// KeyListener接口 键盘事件类
public class MyFrame extends JFrame implements KeyListener, Runnable {
// 用于存储所有背景
private List<BackGround> allBg = new ArrayList<>();
//用于存储当前的背景
private BackGround nowBg = new BackGround();
//用于双缓存
private Image offScreenImage = null;
//马里奥对象
private Mario mario = new Mario();
//定义一个线程对象用于实现马里奥的运动
private Thread thread = new Thread(this);
public static final int START = 0;
public static final int RUNNING = 1;
private int state = START;
public MyFrame() {
//设置窗口的大小
this.setSize(800, 600);
// 设置窗口居中显示
this.setLocationRelativeTo(null);
//设置窗口可见
this.setVisible(true);
//设置点击窗口关闭见关闭程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗口大小不可变
this.setResizable(false);
//向键盘添加键盘监听器 KeyListener接口
this.addKeyListener(this);
//设置窗口名称
this.setTitle("超级玛丽");
// 初始化图片
StaticValue.init();
//初始化马里奥对象
mario = new Mario(10, 355);
//创建全部场景
for (int i = 1; i <= 3; i++) {
allBg.add(new BackGround(i, i == 3 ? true : false));
}
// 将第一个场景设置为当前场景
nowBg = allBg.get(0);
mario.setBackGround(nowBg);
//调用鼠标点击方法
action();
//若游戏未开始 无限 停止线程
while (state == START) {
Thread.yield();
}
//绘制图像
repaint();
thread.start();
//加入音乐
try {
Music m1 = new Music(1);
} catch (FileNotFoundException | JavaLayerException | InterruptedException e) {
e.printStackTrace();
}
}
private void action() {
//鼠标侦听器
MouseAdapter m = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
//鼠标点击时若为游戏未开始状态则开始游戏
if (state == START) {
state = RUNNING;
}
}
};
this.addMouseListener(m);
this.addMouseMotionListener(m);
}
public void paint(Graphics g) {
//窗口
if (offScreenImage == null) {
offScreenImage = createImage(800, 600);
}
//画笔
Graphics graphics = offScreenImage.getGraphics();
//画窗口
graphics.fillRect(0, 0, 800, 600);
//画背景
graphics.drawImage(nowBg.getBgImage(), 0, 0, this);
//绘制敌人
for (Enemy o : nowBg.getEnemyList()) {
graphics.drawImage(o.getShow(), o.getX(), o.getY(), this);
}
//绘制障碍物
//画障碍物
for (Obstacle ob : nowBg.getObstacleList()) {
graphics.drawImage(ob.getShow(), ob.getX(), ob.getY(), this);
}
//画城堡 旗杆
graphics.drawImage(nowBg.getTower(), 620, 270, this);
graphics.drawImage(nowBg.getGan(), 500, 220, this);
//绘制马里奥
graphics.drawImage(mario.getShow(), mario.getX(), mario.getY(), this);
// 添加分数
Color c = graphics.getColor();
graphics.setColor(Color.GRAY);
graphics.setFont(new Font("楷体", Font.BOLD, 18));
graphics.drawString("当前分数:" + mario.getScore(), 16, 100);
graphics.drawString("当前金币:" + mario.getGold(), 16, 123);
graphics.setColor(c);
//如果游戏未开始 画封面覆盖游戏画面
if (state == START) {
graphics.drawImage(BackGround.getStart(), 0, 0, this);
}
// 将图像绘制到窗口中
g.drawImage(offScreenImage, 0, 0, this);
}
public static void main(String[] args) {
MyFrame myFrame = new MyFrame();
}
@Override
public void keyTyped(KeyEvent e) {
}
//当键盘按下时调用
@Override
public void keyPressed(KeyEvent e) {
//向右移动
if (e.getKeyCode() == 39) {
mario.rightMove();
}
//向左移动
if (e.getKeyCode() == 37) {
mario.leftMove();
}
//跳跃
if (e.getKeyCode() == 38) {
mario.jump();
}
}
//当键盘松开时
@Override
public void keyReleased(KeyEvent e) {
//向左停止
if (e.getKeyCode() == 37) {
mario.leftStop();
}
//向右停止
if (e.getKeyCode() == 39) {
mario.rightStop();
}
}
@Override
public void run() {
System.out.println(this.getName());
while (true) {
//调用鼠标点击方法
action();
//若游戏未开始 无限 停止线程
while (state == START) {
Thread.yield();
}
//如果马里奥达到了屏幕的最右 那么切换场景
repaint();
try {
Thread.sleep(50);
if (mario.getX() >= 775) {
nowBg = allBg.get(nowBg.getSort());
mario.setBackGround(nowBg);
mario.setX(10);
mario.setY(355);
}
//判断马里奥是否死亡
if (mario.isDeath()) {
//弹窗
JOptionPane.showMessageDialog(this, "马里奥死亡!!!");
System.exit(0);
}
// 判断游戏是否结束
if (mario.isOK()) {
JOptionPane.showMessageDialog(this, "恭喜你!成功通关了");
System.exit(0);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常量类–StaticV alue.java
用于存放游戏所需要的所有图片文件,在游戏开始的时候将所有图片导入,提高游戏的运行速度。 并且在该类中将所有需要用到的图片进行分类,分为障碍物类,马里奥类,敌人类以及背景图片。当游 戏运行时可以直接调用这些集合中的图片进行遍历,在调用的时候更加方便,而且可以使马里奥或者敌 人在移动的时候产生动态效果。 首先在类中应当几个定义 BufferedImage 类型,属性名字为 jump_L,jump_R,stand_L,stand_R, 列表 run_L 和 run_R,这些属性的作用在于存放所有的马里奥图片,里面包括了马里奥的移动图片, 站立图片以及马里奥跳跃的图片。这样在程序运行的时候就可以从该类中的这个属性里面将所需要的马 里奥图片直接调用出来,并且还可以在马里奥移动时不断遍历里面的图片,这样就可以使马里奥产生移 动的动态效果。接下来要在该类中定义开始图片,结束图片以及背景图片,默认的初始值都为 null。注 意这些所有的属性都是静态的,包括下面要提到的所有的属性,这样做的目的是为了在程序运行时先加 载这些图片。然后应当定义存放食人花的 List 集合 flower,这个集合将食人花的不同形态,张嘴、闭嘴 图片存放进去,这样在运行的时候进行遍历就可以打到动态效果。同理存放蘑菇怪的集合 mogu,以及 存放所有障碍物的集合 obstacle。
package ClassPackage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class StaticValue {
//背景
public static BufferedImage bg = null;
public static BufferedImage bg2 = null;
//跳跃
public static BufferedImage jump_L = null;
public static BufferedImage jump_R = null;
//站立
public static BufferedImage stand_L = null;
public static BufferedImage stand_R = null;
//城堡
public static BufferedImage tower = null;
//旗杆
public static BufferedImage gan = null;
//旗杆
public static BufferedImage gold1 = null;
//障碍物
public static List<BufferedImage> obstacle = new ArrayList<>();
//跑
public static List<BufferedImage> run_L = new ArrayList<>();
public static List<BufferedImage> run_R = new ArrayList<>();
//蘑菇
public static List<BufferedImage> mogu = new ArrayList<>();
//食人花
public static List<BufferedImage> flower = new ArrayList<>();
//乌龟
public static List<BufferedImage> turtle = new ArrayList<>();
//路径
public static String path = System.getProperty("user.dir") + "/src/imgs/";
//未开始界面
public static BufferedImage start = null;
//初始化方法
public static void init() {
try {
//加载未开始图片
start = ImageIO.read(new File(path + "start.jpg"));
//加载背景图片
bg = ImageIO.read(new File(path + "bg.png"));
bg2 = ImageIO.read(new File(path + "bg2.png"));
//加载马里奥向左跳跃
jump_L = ImageIO.read(new File(path + "s_mario_jump1_L.png"));
//加载马里奥向右跳跃
jump_R = ImageIO.read(new File(path + "s_mario_jump1_R.png"));
//加载马里奥向左站立
stand_L = ImageIO.read(new File(path + "s_mario_stand_L.png"));
//加载马里奥向右站立
stand_R = ImageIO.read(new File(path + "s_mario_stand_R.png"));
//加载城堡
tower = ImageIO.read(new File(path + "tower.png"));
//加载旗杆
gan = ImageIO.read(new File(path + "gan.png"));
//加载金币
gold1 = ImageIO.read(new File(path + "gold1.png"));
} catch (IOException e) {
e.printStackTrace();
}
//加载马里奥向左跑
for (int i = 1; i <= 2; i++) {
try {
run_L.add(ImageIO.read(new File(path + "s_mario_run" + i + "_L.png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载马里奥向右跑
for (int i = 1; i <= 2; i++) {
try {
run_R.add(ImageIO.read(new File(path + "s_mario_run" + i + "_R.png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载障碍物
try {
obstacle.add(ImageIO.read(new File(path + "questionmark1.png")));
obstacle.add(ImageIO.read(new File(path + "soil_up.png")));
obstacle.add(ImageIO.read(new File(path + "soil_base.png")));
} catch (IOException e) {
e.printStackTrace();
}
//加载水管
for (int i = 1; i <= 4; i++) {
try {
obstacle.add(ImageIO.read(new File(path + "pipe" + i + ".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载不可破坏的砖块和旗子
try {
obstacle.add(ImageIO.read(new File(path + "brick2.png")));
obstacle.add(ImageIO.read(new File(path + "flag.png")));
// 加载金币
obstacle.add(ImageIO.read(new File(path + "gold1.png")));
// 加载无金币砖块
obstacle.add(ImageIO.read(new File(path + "questionmark2.png")));
} catch (IOException e) {
e.printStackTrace();
}
//加载蘑菇敌人
for (int i = 1; i <= 3; i++) {
try {
mogu.add(ImageIO.read(new File(path + "fungus" + i + ".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载食人花敌人
for (int i = 1; i <= 2; i++) {
try {
flower.add(ImageIO.read(new File(path + "flower1." + i + ".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
//加载w乌龟敌人
for (int i = 1; i <= 6; i++) {
try {
turtle.add(ImageIO.read(new File(path + "tortoise" + i + ".png")));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
背景类–BackGround.java
该类表示马里奥及障碍物和敌人所处的场景,并且将障碍物和敌人绘制到场景中。在该类中包括用 于存放敌人和障碍物的 list 集合,在敌人或者马里奥死亡时,将对应图像移出列表,以达到重新绘制图 像效果。 首先背景类中肯定要有一个标记来表示现在是第几个场景,因为不同的背景中所绘制的场景,障碍 物等也不同,所以该类中要有一个 int 类型的场景顺序 sort。并且在游戏的设定中,如果玩家玩到最后 一关的时候马里奥碰到棋子会失去玩家的控制,自己走向城堡。那么这里就要这几一个标记,是否为最 后的棋子与城堡,类型为 boolean 类型。。 接下来就应当定义背景类的构造方法了,通过获取场景的顺序,即场景的 sort,来判断是哪一个场 景,同时将场景绘制好。下面为绘制第一关的图像
package ClassPackage;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class BackGround {
//显示当前场景的图片
private BufferedImage bgImage = null;
//记录当前第几个场景
private int sort;
//判断是否为最后一个场景
private boolean flag;
//用于显示旗杆
private BufferedImage gan = null;
//用于显示城堡
private BufferedImage tower = null;
//判断马里奥是否到达旗杆位置
private boolean isReach = false;
//判断棋子是否落地
private boolean isBase = false;
// 存放金币
private static BufferedImage start = null;
//用于存放所有的敌人
private List<Enemy> enemyList = new ArrayList<>();
//用于存放我们所有的障碍物
private List<Obstacle> obstacleList = new ArrayList<>();
// 空参构造方法
public BackGround() {
}
// 两个参数构造方法
public BackGround(int sort, boolean flag) {
this.sort = sort;
this.flag = flag;
start = StaticValue.start;
if (flag) {
// 最后一个场景
bgImage = StaticValue.bg2;
} else {
bgImage = StaticValue.bg;
}
if (sort == 1) {
//地面
//type是StaticValue类的障碍物
//绘制第一关的地面 上地面type=1 下地面type=2
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 420, 1, this));
}
for (int j = 0; j <= 120; j += 30) {
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 570 - j, 2, this));
}
}
//绘制砖块A
for (int i = 120; i <= 150; i += 30) {
obstacleList.add(new Obstacle(i, 300, 7, this));
}
//绘制砖块B-F
for (int i = 300; i <= 570; i += 30) {
if (i == 360 || i == 390 || i == 480 || i == 510 || i == 540) {
obstacleList.add(new Obstacle(i, 300, 7, this));
} else {
// 普通砖块
obstacleList.add(new Obstacle(i, 300, 0, this));
}
}
//砖块G
for (int i = 420; i <= 450; i += 30) {
obstacleList.add(new Obstacle(i, 240, 7, this));
}
//水管
for (int i = 360; i <= 600; i += 25) {
if (i == 360) {
obstacleList.add(new Obstacle(620, i, 3, this));
obstacleList.add(new Obstacle(645, i, 4, this));
} else {
obstacleList.add(new Obstacle(620, i, 5, this));
obstacleList.add(new Obstacle(645, i, 6, this));
}
}
// 绘制第一关的蘑菇敌人
enemyList.add(new Enemy(580, 385, true, 1, this));
//食人花
enemyList.add(new Enemy(635, 420, true, 2, 328, 428, this));
//乌龟
//enemyList.add(new Enemy(570, 390, 3, this));
}
if (sort == 2) {
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 420, 1, this));
}
for (int j = 0; j <= 120; j += 30) {
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 570 - j, 2, this));
}
}
//第一个水管
for (int i = 360; i <= 600; i += 25) {
if (i == 360) {
obstacleList.add(new Obstacle(60, i, 3, this));
obstacleList.add(new Obstacle(85, i, 4, this));
} else {
obstacleList.add(new Obstacle(60, i, 5, this));
obstacleList.add(new Obstacle(85, i, 6, this));
}
}
//第二个水管
for (int i = 330; i <= 600; i += 25) {
if (i == 330) {
obstacleList.add(new Obstacle(620, i, 3, this));
obstacleList.add(new Obstacle(645, i, 4, this));
} else {
obstacleList.add(new Obstacle(620, i, 5, this));
obstacleList.add(new Obstacle(645, i, 6, this));
}
}
//砖块C
obstacleList.add(new Obstacle(300, 330, 0, this));
//砖块B,E,G
for (int i = 270; i <= 330; i += 30) {
if (i == 270 || i == 330) {
// 普通砖块
obstacleList.add(new Obstacle(i, 360, 0, this));
} else {
obstacleList.add(new Obstacle(i, 360, 7, this));
}
}
//砖块A,D,F,H,I
for (int i = 240; i <= 360; i += 30) {
if (i == 240 || i == 360) {
obstacleList.add(new Obstacle(i, 390, 0, this));
} else {
obstacleList.add(new Obstacle(i, 390, 7, this));
}
}
//妨碍砖块
obstacleList.add(new Obstacle(240, 300, 0, this));
for (int i = 360; i <= 540; i += 60) {
obstacleList.add(new Obstacle(i, 270, 7, this));
}
//食人花敌人
enemyList.add(new Enemy(75, 420, true, 2, 328, 418, this));
enemyList.add(new Enemy(635, 420, true, 2, 298, 388, this));
//蘑菇敌人
enemyList.add(new Enemy(200, 385, true, 1, this));
enemyList.add(new Enemy(500, 385, true, 1, this));
}
if (sort == 3) {
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 420, 1, this));
}
for (int j = 0; j <= 120; j += 30) {
for (int i = 0; i < 27; i++) {
obstacleList.add(new Obstacle(i * 30, 570 - j, 2, this));
}
}
//A-O砖块
int temp = 290;
for (int i = 390; i >= 270; i -= 30) {
for (int j = temp; j <= 410; j += 30) {
obstacleList.add(new Obstacle(j, i, 7, this));
}
temp += 30;
}
//P-R砖块
temp = 60;
for (int i = 390; i >= 360; i -= 30) {
for (int j = temp; j <= 90; j += 30) {
obstacleList.add(new Obstacle(j, i, 7, this));
}
temp += 30;
}
//绘制旗杆
gan = StaticValue.gan;
//绘制城堡
tower = StaticValue.tower;
//将棋子绘制到旗杆上
obstacleList.add(new Obstacle(515, 220, 8, this));
//蘑菇
enemyList.add(new Enemy(150, 385, true, 1, this));
}
}
// 顶金币
public void createMoney(int x, int y, int type) {
obstacleList.add(new Obstacle(x, y, type, this));
}
public BufferedImage getBgImage() {
return bgImage;
}
public int getSort() {
return sort;
}
public boolean isFlag() {
return flag;
}
public List<Obstacle> getObstacleList() {
return obstacleList;
}
public BufferedImage getGan() {
return gan;
}
public BufferedImage getTower() {
return tower;
}
public boolean isReach() {
return isReach;
}
public void setReach(boolean reach) {
isReach = reach;
}
public boolean isBase() {
return isBase;
}
public void setBase(boolean base) {
isBase = base;
}
public List<Enemy> getEnemyList() {
return enemyList;
}
//获取未开始图片
public static BufferedImage getStart() {
return start;
}
}
马里奥类–M ario.java
用来控制马里奥的行动,并且在该类中加入碰撞检测,判断马里奥是否与障碍物或者敌人发生碰 撞。该类中的属性主要定义了马里奥所在的场景,马里奥的移动和跳跃的速度,以及马里奥在移动过程 中需要显示的图片。另外该类中还定义了玩家的生命值和所获得的分数。并且在 run()方法中还定义 了当马里奥到达最后一关的旗子时,玩家将失去对马里奥的控制,剩下的由程序控制走到城堡,完整全 部游戏。在游戏中,玛丽奥要在玩家的控制下完成移动、跳跃等动作,那么这些动作首先肯定要涉及到 坐标,那么我们在该类中首先要定义两个属性,这两个属性即为马里奥的坐标 x 和 y。并且该类还要实 现 Runnable 接口,在 run() 方法中写马里奥的移动规则。 为了玩家在游戏过程中的良好体验,那么对于马里奥的移动速度和跳跃速度就必须要定义好。所以 该类里面还应当定义马里奥的移动速度和跳跃速度,其本质就是马里奥在移动过程中坐标加减的值。当 然初始值为零,必须等到马里奥构造的时候,再将这些属性赋予相对应的值。在本类中还要定义游戏的 分数以及马里奥的生命数,这些都是必不可少的。 在马里奥这个类中,还要定义马里奥的移动和跳跃方法,以便玩家在按下方向键后调用这些方法, 来达到控制马里奥的移动。。在定义马里奥的跳跃方法的时候,不单单定义一个方法就行,而且还要判 断马里奥的状态。如果马里奥是在地面或者是在障碍物的上方,那么马里奥可以进行跳跃,如果马里奥 处于空中,那么马里奥就不可以继续跳跃. 下面是马里奥向左移动的方法,其他方法同理
然后对当前马里奥所处的场景中的所有障碍物进行遍历,获取到所有障碍物的坐标,通过障碍物的 坐标和马里奥的坐标的之间的关系的判断,来决定马里奥是否与障碍物发生了碰撞,并且通过判断的结 果来对马里奥和障碍物的状态进行相应的变化,若撞击金块则产生金币,原金块就会变成空金块。还有 对碰撞怪物以及怪物和马里奥是存活状态进行判断。部分代码如下,其他功能可查看代码
package ClassPackage;
import javazoom.jl.decoder.JavaLayerException;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
public class Mario implements Runnable {
//用于表示当前马里奥的横纵坐标
private int x;
private int y;
//用于表示当前的状态
private String status;
//用于显示当前状态对应的图像
private BufferedImage show = null;
//定义一个BackGround对象 用于获取障碍物信息
private BackGround backGround = new BackGround();
//用来实现马里奥的动作
private Thread thread = null;
//定义一个索引
private int index;
//马里奥的移动速度
private int xSpeed;
//马里奥的跳跃速度
private int ySpeed;
//表示马里奥的上升的时间
private int upTime = 0;
//判断马里奥是否走到了 城堡的门口
private boolean isOK;
//用于判断马里奥是否死亡
private boolean isDeath = false;
// 积分,金币
private int score = 0;
private int gold = 0;
public int getScore() {
return score;
}
public int getGold() {
return gold;
}
//马里奥死亡方法
public void death() {
try {
new Music(4);
} catch (FileNotFoundException | JavaLayerException | InterruptedException e) {
e.printStackTrace();
}
isDeath = true;
}
public boolean isDeath() {
return isDeath;
}
public boolean isOK() {
return isOK;
}
//马里奥跳跃
public void jump() {
// 判断马里奥是否为跳跃状态
if (status.indexOf("jump") == -1) {
try {
new Music(3);
} catch (FileNotFoundException | JavaLayerException | InterruptedException e) {
e.printStackTrace();
}
//判断马里奥的跳跃方向是否为左
if (status.indexOf("left") != -1) {
status = "jump--left";
} else {
//不为左 则为右
status = "jump--right";
}
ySpeed = -10;
upTime = 7;
}
//判断马里奥是否碰到了旗子
if (backGround.isReach()) {
ySpeed = 0;
}
}
//马里奥下落
public void fall() {
//判单马里奥的跳跃方向是否为左
if (status.indexOf("left") != -1) {
status = "jump--left";
} else {
//不为左 则为 右
status = "jump--right";
}
ySpeed = 10;
}
public Mario() {
}
public Mario(int x, int y) {
this.x = x;
this.y = y;
//初始马里奥向右站立
show = StaticValue.stand_R;
//马里奥当前状态
this.status = "status--right";
thread = new Thread(this);
thread.start();
}
//向左移动
public void leftMove() {
//改变速度
xSpeed = -5;
//判断马里奥是否碰到了旗子
if (backGround.isReach()) {
xSpeed = 0;
}
//判断马里奥是否处于空中
if (status.indexOf("jump") != -1) {
status = "jump--left";
} else {
status = "move--left";
}
}
//向右移动
public void rightMove() {
xSpeed = 5;
//判断马里奥是否碰到了旗子
if (backGround.isReach()) {
xSpeed = 0;
}
//判断马里奥是否处于空中
if (status.indexOf("jump") != -1) {
status = "jump--right";
} else {
status = "move--right";
}
}
//向左停止
public void leftStop() {
xSpeed = 0;
//判断马里奥是否处于空中
if (status.indexOf("jump") != -1) {
status = "jump--left";
} else {
status = "stop--left";
}
}
//向右停止
public void rightStop() {
xSpeed = 0;
//判断马里奥是否处于空中
if (status.indexOf("jump") != -1) {
status = "jump--right";
} else {
status = "stop--right";
}
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public BufferedImage getShow() {
return show;
}
public void setShow(BufferedImage show) {
this.show = show;
}
public void setBackGround(BackGround backGround) {
this.backGround = backGround;
}
public String getStatus() {
return status;
}
public BackGround getBackGround() {
return backGround;
}
public Thread getThread() {
return thread;
}
@Override
public void run() {
while (true) {
//判断马里奥是否在障碍物上
boolean onObstacle = false;
//判断是否可以向右走
boolean canRight = true;
//判断是否可以向左走
boolean canLeft = true;
//判断是否到达旗杆位置
if (backGround.isFlag() && this.x >= 500) {
this.backGround.setReach(true);
if (this.backGround.isBase()) {
status = "move--right";
if (x < 690) {
x += 5;
} else {
//表示马里奥已经到了城堡处
isOK = true;
}
//如果旗子没有下落完成
} else {
//判断马里奥是否在空中
if (y < 395) {
xSpeed = 0;
this.y += 5;
status = "jump--right";
}
//如果马里奥罗到了地上
if (y > 395) {
this.y = 395;
status = "stop--right";
}
}
} else {
//遍历当前场景的所有障碍物
for (int i = 0; i < backGround.getObstacleList().size(); i++) {
Obstacle ob = backGround.getObstacleList().get(i);
if (ob.getY() == this.y + 25 && (ob.getX() > this.x - 30 && ob.getX() < this.x + 25)) {
onObstacle = true;
}
//判断跳起是否顶到了砖块
if ((ob.getY() >= this.y - 30 && ob.getY() <= this.y - 20) && (ob.getX() > this.x - 30 && ob.getX() < this.x + 25)) {
if (ob.getType() == 0) {
backGround.getObstacleList().remove(ob);
backGround.createMoney(ob.getX(), ob.getY() - 40, 9); //金币
try {
new Music(2);
} catch (FileNotFoundException | JavaLayerException | InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
score += 50;
}
upTime = 0;
}
if (ob.getType() == 9) {
backGround.getObstacleList().remove(ob);
backGround.createMoney(ob.getX(), ob.getY() + 40, 10);
gold += 1;
}
//判断是否可以往右走
if (ob.getX() == this.x + 25 && (ob.getY() > this.y - 30 && ob.getY() < this.y + 25)) {
canRight = false;
}
//判断是否可以往左走
if (ob.getX() == this.x - 30 && (ob.getY() > this.y - 30 && ob.getY() < this.y + 25)) {
canLeft = false;
}
}
//判断马里奥是否碰到敌人死亡 或者踩死敌人
for (int i = 0; i < backGround.getEnemyList().size(); i++) {
Enemy e = backGround.getEnemyList().get(i); //当前敌人
if (e.getY() == this.y + 20 && (e.getX() - 25 <= this.x && e.getX() + 35 >= this.x)) {
if (e.getType() == 1) {
e.death();
upTime = 3;
ySpeed = -10;
score += 100;
} else if (e.getType() == 2) {
//马里奥死亡
death();
}
}
if ((e.getX() + 35 > this.x && e.getX() - 25 < this.x) && (e.getY() + 35 > this.y && e.getY() - 20 < this.y)) {
death();
}
}
//进行马里奥跳跃的操作
if (onObstacle && upTime == 0) {
if (status.indexOf("left") != -1) {
if (xSpeed != 0) {
status = "move--left";
} else {
status = "stop--left";
}
} else {
if (xSpeed != 0) {
status = "move--right";
} else {
status = "stop--right";
}
}
} else {
if (upTime != 0) {
upTime--;
} else {
fall();
}
y += ySpeed;
}
}
//判断马里奥是否在运动
if ((canLeft && xSpeed < 0) || (canRight && xSpeed > 0)) {
//改变马里奥坐标
x += xSpeed;
//判断马里奥是否移动到了屏幕最左边
if (x < 0) {
x = 0;
}
}
//判断当前是否在移动状态
if (status.contains("move")) {
index = index == 0 ? 1 : 0;
}
//判断是否是向左移动
if ("move--left".equals(status)) {
show = StaticValue.run_L.get(index);
}
//判断是否是向右移动
if ("move--right".equals(status)) {
show = StaticValue.run_R.get(index);
}
//判断是否向左停止
if ("stop--left".equals(status)) {
show = StaticValue.stand_L;
}
//判断是否向右停止
if ("stop--right".equals(status)) {
show = StaticValue.stand_R;
}
//判断马里奥是否向左跳跃
if ("jump--left".equals(status)) {
show = StaticValue.jump_L;
}
//判断马里奥是否向右跳跃
if ("jump--right".equals(status)) {
show = StaticValue.jump_R;
}
try {
//让线程休眠50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
障碍物类–Obstacle.java
绘制场景中所需要的障碍物,例如地面、砖块、水管等等。该类中的属性包括了障碍物的坐标,障 碍物所需要显示的图片等。游戏中的场景是由背景中的障碍物绘制而成的,不同的障碍物所在的位置肯 定也不相同,那么对于障碍物而言,就必须要有坐标属性来使绘制的时候将不同的障碍物绘制到不同的 位置,所以必须要有两个 int 属性 x 和 y 来表示障碍物的坐标。同时该类也必须要实现 Runnable 接口, 实现这个接口的作用主要是为了在最有一个场景中控制旗子的运动,当然同时还要为该类加入线程。 最后该类中的 run 方法主要是为了控制最后一个场景中的旗子的移动,并且在旗子移动完毕后要 设置一个标记,并且将该标记表示给马里奥类,这样马里奥就可以开始自主移动了。部分代码如下:
package ClassPackage;
import java.awt.image.BufferedImage;
public class Obstacle implements Runnable {
// 坐标
private int x;
private int y;
//障碍物类型
private int type;
// 用于显示图像
private BufferedImage show = null;
//当前场景对象
private BackGround bg = null;
//用于完成棋子下落的线程对象
private Thread thread = new Thread(this);
public Obstacle(int x, int y, int type, BackGround bg) {
this.x = x;
this.y = y;
this.type = type;
this.bg = bg;
show = StaticValue.obstacle.get(type);
//如果是棋子,则启动线程
if (type == 8) {
thread.start();
}
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getType() {
return type;
}
public BufferedImage getShow() {
return show;
}
@Override
public void run() {
while (true) {
if (this.bg.isReach()) {
if (this.y < 375) {
this.y += 5;
} else {
this.bg.setBase(true);
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
敌人类–Enemy.java
该类中主要设置了两种敌人,一种是蘑菇怪,可以被马里奥踩死,另一种是食人花,不能被踩死。 该类中的属性包括了敌人的坐标,需要显示的图片,以及敌人的移动方向和移动范围等。。 在该类中首先要实现 Runnable 接口,因为在游戏中的敌人是可以移动的,所以一定要通过重写 run() 方法来达到敌人可以移动的效果。当然还要在该类中定义一个 Thread 属性,用于控制线程。在 该类中就要定义两个 int 属性 x 和 y,用于控制敌人的位置以及敌人的移动。 这个类中应当有两个构造方法,对于不同的敌人,所需要的属性都是不同的。对于蘑菇则需要判断 是否移动到窗口边界,以及是否死亡。对于食人花则需要判断是否移动到上下限。
package ClassPackage;
import java.awt.image.BufferedImage;
public class Enemy implements Runnable {
//存储当前的坐标
private int x;
private int y;
//存储敌人的类型
private int type;
//判断敌人运动的方向
private boolean face_tc = true;
//用于显示当前敌人的图像
private BufferedImage show;
//定义一个背景图像
private BackGround bg;
//食人花运动的极限范围
private int max_up = 0;
private int max_down = 0;
//定义线程对象
private Thread thread = new Thread(this);
//定义当前图片的状态
private int image_type = 0;
//蘑菇敌人的构造函数
public Enemy(int x, int y, boolean face_tc, int type, BackGround bg) {
this.x = x;
this.y = y;
this.type = type;
this.face_tc = face_tc;
this.bg = bg;
this.show = StaticValue.mogu.get(0);
//启动线程实现蘑菇的移动
thread.start();
}
//食人花敌人的构造函数
public Enemy(int x, int y, boolean face_tc, int type, int max_up, int max_down, BackGround bg) {
this.x = x;
this.y = y;
this.type = type;
this.face_tc = face_tc;
this.show = StaticValue.flower.get(0);
this.bg = bg;
this.max_up = max_up;
this.max_down = max_down;
//启动线程实现食人花的移动
thread.start();
}
//乌龟敌人的构造函数
public Enemy(int x, int y, int type, BackGround bg) {
this.x = x;
this.y = y;
this.type = type;
this.show = StaticValue.flower.get(0);
this.bg = bg;
//启动线程实现乌龟的移动
thread.start();
}
// 敌人的死亡方法
public void death() {
show = StaticValue.mogu.get(2);
this.bg.getEnemyList().remove(this);
}
public int getType() {
return type;
}
public BufferedImage getShow() {
return show;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public void run() {
while (true) {
//判断是否是蘑菇敌人
if (type == 1) {
if (face_tc) {
this.x -= 2;
} else {
this.x += 2;
}
image_type = image_type == 1 ? 0 : 1;
show = StaticValue.mogu.get(image_type);
}
//定义两个布尔变量
boolean canLeft = true;
boolean canRight = true;
//遍历每一个障碍物
for (int i = 0; i < bg.getObstacleList().size(); i++) {
Obstacle ob1 = bg.getObstacleList().get(i);
//判断是否可以往右走
if (ob1.getX() == this.x + 36 && (ob1.getY() + 65 > this.y && ob1.getY() - 35 < this.y)) {
canRight = false;
}
//判断是否可以继续往左走
if (ob1.getX() == this.x - 36 && (ob1.getY() + 65 > this.y && ob1.getY() - 35 < this.y)) {
canLeft = false;
}
}
if (face_tc && !canLeft || this.x == 0) {
face_tc = false;
} else if ((!face_tc) && (!canRight) || this.x == 764) {
face_tc = true;
}
//判断是否是食人花敌人
if (type == 2) {
if (face_tc) {
this.y -= 2;
} else {
this.y += 2;
}
image_type = image_type == 1 ? 0 : 1;
//食人花是否到达了极限位置
if (face_tc && (this.y == max_up)) {
face_tc = false;
}
if ((!face_tc) && (this.y == max_down)) {
face_tc = true;
}
show = StaticValue.flower.get(image_type);
}
if (type == 3) {
this.x -= 1;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
image_type = image_type == 0 ? 1 : 0;
show = StaticValue.turtle.get(image_type);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
音乐类–Music.java
对于音乐类,则通过变量 type 来判断是播放哪种音乐,为了让播放音乐时人物不会卡住,采用单 独一个线程播放音乐,对于不同音乐则创建多个线程实现。然后只需要在其他类中,通过在不同场景中 定义 Music 对象 new Music(type),t 来实现触发该 type 对应的音乐播放。部分代码如下:
package ClassPackage;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Music {
public Music(int type) throws FileNotFoundException, JavaLayerException, InterruptedException {
if (type == 1) {
Player player;
String str = System.getProperty("user.dir") + "/src/music/music.wav";
BufferedInputStream name = new BufferedInputStream(new FileInputStream(str));
player = new Player(name);
player.play();
} else if (type == 2) {
Player playerGold;
String str = System.getProperty("user.dir") + "/src/Music/MarioTopGoldCoins.mp3";
BufferedInputStream name = new BufferedInputStream(new FileInputStream(str));
playerGold = new Player(name);
new Thread(() -> {
//调用播放方法进行播放
try {
playerGold.play();
} catch (JavaLayerException e) {
e.printStackTrace();
}
}).start();
} else if (type == 3) {
Player playerJump;
String str = System.getProperty("user.dir") + "/src/Music/jump.mp3";
BufferedInputStream name = new BufferedInputStream(new FileInputStream(str));
playerJump = new Player(name);
new Thread(() -> {
//调用播放方法进行播放
try {
playerJump.play();
} catch (JavaLayerException e) {
e.printStackTrace();
}
}).start();
} else if (type == 4) {
Player playerDeath;
String str = System.getProperty("user.dir") + "/src/Music/death.mp3";
BufferedInputStream name = new BufferedInputStream(new FileInputStream(str));
playerDeath = new Player(name);
new Thread(() -> {
//调用播放方法进行播放
try {
playerDeath.play();
} catch (JavaLayerException e) {
e.printStackTrace();
}
}).start();
} else if (type == 5) {
Player playerCity;
String str = System.getProperty("user.dir") + "/src/Music/AccelerateToTheCastle.mp3";
BufferedInputStream name = new BufferedInputStream(new FileInputStream(str));
playerCity = new Player(name);
new Thread(() -> {
//调用播放方法进行播放
try {
playerCity.play();
} catch (JavaLayerException e) {
e.printStackTrace();
}
}).start();
}
}
}
项目结构: