一、项目需求分析
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<BackGround> allBG = new ArrayList<BackGround>();
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 <= 3; i++) {
this.allBG.add(new BackGround(i, i == 3 ? true : false));
}
// 将第一个场景设置为当前场景
this.nowBG = this.allBG.get(0);
// Mario的初始化
this.mario = new Mario(0, 480);
// 将场景放入Mario对象的属性中
this.mario.setBg(nowBG);
//重绘
this.repaint();
this.addKeyListener(this);
//开始线程
t1.start();
setVisible(true);
}
//画笔
public void paint(Graphics g) {
// 建立临时缓冲图片 (红绿蓝三原色类型)
BufferedImage image = new BufferedImage(900, 600, BufferedImage.TYPE_3BYTE_BGR);
Graphics g2 = image.getGraphics();
if (this.isStart) {
// 绘制背景
g2.drawImage(this.nowBG.getBgImage(), 0, 0, this);
g2.setFont(new Font("微软雅黑", Font.BOLD, 15));
g2.drawString("生命 : " + this.mario.getLife(), 60, 60);
g2.drawString("分数 : " + this.mario.getScore(), 500, 60);
// 添加敌人
Iterator<Enemy> iterEnemy = this.nowBG.getAllEnemy().iterator();
//遍历画出敌人,如果有下一个
while (iterEnemy.hasNext()) {
Enemy e = iterEnemy.next();
g2.drawImage(e.getShowImage(), e.getX(), e.getY(), this);
}
// 添加障碍物
Iterator<Obstrution> iterator = this.nowBG.getAllObstruction().iterator();
while (iterator.hasNext()) {
Obstrution obstrution = iterator.next();
g2.drawImage(obstrution.getShowImage(), obstrution.getX(), obstrution.getY(), this);
}
// 添加Mario
g2.drawImage(this.mario.getShowImage(), this.mario.getX(), this.mario.getY(), this);
} else {
g2.drawImage(StaticValue.startImage, 0, 0, this);
}
// 把缓冲图片绘制到窗体中
g.drawImage(image, 0, 0, this);
}
// 上:38 下:40 左:37 右:39 空格(跳跃):32
// 按下按键则持续移动,释放停止移动
@Override
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() == 32) {
this.mario.jump();
}
} else {
//按下空格才可以开始游戏
if (e.getKeyCode() == 32) {
this.isStart = true;
this.nowBG.enemyStartMove();
}
}
}
//释放按键
public void keyReleased(KeyEvent e) {
if (this.isStart) {
if (e.getKeyCode() == 39) {
this.mario.rightStop();
}
if (e.getKeyCode() == 37) {
this.mario.leftStop();
}
}
}
// 人物移动一下,刷新一次地图
public void run() {
while (true) {
//刷新地图
this.repaint();
try {
//Mario线程速度
Thread.sleep(30);
if (this.mario.getX() >= 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<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> allTurtleImage = new ArrayList<BufferedImage>();
//全部的障碍物(各种砖块)
public static List<BufferedImage> allObstructionImage = new ArrayList<BufferedImage>();
//Mario死亡图片
public static BufferedImage marioDeadImage = null;
// System.getProperty("user.dir")返回当前项目的路径
// 图片初始化
public static void init() {
// 导入Mario的图片
for (int i = 1; i <= 10; i++) {
try {
allMarioImage.add(ImageIO.read(new File("images/"+ i + ".gif")));
} catch (IOException e) {
e.printStackTrace();
}
}
// 导入背景图片
try {
startImage = ImageIO.read(new File("images/"+"start.gif"));
bgImage = ImageIO.read(new File("images/"+ "firststage.gif"));
endImage = ImageIO.read(new File("images/"+ "firststageend.gif"));
} catch (IOException e) {
e.printStackTrace();
}
// 导入所有敌人的图片
for (int i = 1; i <= 5; i++) {
try {
if (i <= 2) {
allFlowerImage.add(ImageIO.read(new File("images/"+ "flower" + i + ".gif")));
}
if (i <= 3) {
allTriangleImage.add(ImageIO.read(new File( "images/"+"triangle" + i + ".gif")));
}
allTurtleImage.add(ImageIO.read(new File("images/"+ "Turtle" + i + ".gif")));
} catch (IOException e) {
e.printStackTrace();
}
}
// 导入所有障碍物的图片
for(int i=1;i<=14;i++) {
try {
allObstructionImage.add(ImageIO.read(new File("images/"+ "ob" + i + ".gif")));
} catch (IOException e) {
e.printStackTrace();
}
}
try {
//导入Mario死亡的图片
marioDeadImage = ImageIO.read(new File("images/over.gif"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、背景以及在背景调用的方法
public class BackGround {
// 当前场景的显示图片
private BufferedImage bgImage = null;
// 场景的循序
private int sort;
// 当前是否为最后一个场景
private boolean flag;
// 定义游戏结束的标记
private boolean isOver = false;
// 定义降旗结束的标记
private boolean isDown = false;
//是否发射子弹的标记
public boolean isShoot = false;
// 全部的敌人
private List<Enemy> allEnemy = new ArrayList<Enemy>();
// 全部的障碍物
private List<Obstrution> allObstruction = new ArrayList<Obstrution>();
// 被消灭的敌人
private List<Enemy> removeEnemy = new ArrayList<Enemy>();
// 被消灭障碍物
private List<Obstrution> removeObstrution = new ArrayList<Obstrution>();
// 构造方法
public BackGround(int sort, boolean flag) {
this.sort = sort;
this.flag = flag;
if (flag) {
bgImage = StaticValue.endImage;
} else {
bgImage = StaticValue.bgImage;
}
// 第一个场景的地图配置
if (sort == 1) {
for (int i = 0; i < 15; i++) {
// 绘制地面
this.allObstruction.add(new Obstrution(i * 60, 540, 9, this));
}
// 绘制蘑菇
// this.allEnemy.add(new Enemy(535, 360, true, 2, 485, 535, this));
// 绘制砖块
this.allObstruction.add(new Obstrution(120, 360, 4, this));
this.allObstruction.add(new Obstrution(300, 360, 0, this));
this.allObstruction.add(new Obstrution(360, 360, 4, this));
this.allObstruction.add(new Obstrution(420, 360, 0, this));
this.allObstruction.add(new Obstrution(480, 360, 4, this));
this.allObstruction.add(new Obstrution(540, 360, 0, this));
this.allObstruction.add(new Obstrution(420, 180, 4, this));
// 绘制水管
this.allObstruction.add(new Obstrution(660, 540, 6, this));
this.allObstruction.add(new Obstrution(720, 540, 5, this));
this.allObstruction.add(new Obstrution(660, 480, 8, this));
this.allObstruction.add(new Obstrution(720, 480, 7, this));
// 绘制隐的砖块
this.allObstruction.add(new Obstrution(660, 300, 3, this));
// 绘制栗子怪
this.allEnemy.add(new Enemy(600, 480, true, 1, this));
// 绘制食人花
this.allEnemy.add(new Enemy(690, 480, true, 2, 420, 540, this));
}
// 第二个场景的配置
if (sort == 2) {
for (int i = 0; i < 15; i++) {
if (i != 10 && i != 11) {
this.allObstruction.add(new Obstrution(i * 60, 540, 9, this));
}
}
// 绘制第一根水管
this.allObstruction.add(new Obstrution(60, 540, 6, this));
this.allObstruction.add(new Obstrution(120, 540, 5, this));
this.allObstruction.add(new Obstrution(60, 480, 6, this));
this.allObstruction.add(new Obstrution(120, 480, 5, this));
this.allObstruction.add(new Obstrution(60, 420, 8, this));
this.allObstruction.add(new Obstrution(120, 420, 7, this));
// 绘制第二根水管
this.allObstruction.add(new Obstrution(240, 540, 6, this));
this.allObstruction.add(new Obstrution(300, 540, 5, this));
this.allObstruction.add(new Obstrution(240, 480, 6, this));
this.allObstruction.add(new Obstrution(300, 480, 5, this));
this.allObstruction.add(new Obstrution(240, 420, 6, this));
this.allObstruction.add(new Obstrution(300, 420, 5, this));
this.allObstruction.add(new Obstrution(240, 360, 8, this));
this.allObstruction.add(new Obstrution(300, 360, 7, this));
// 绘制栗子怪
this.allEnemy.add(new Enemy(600, 480, true, 1, this));
}
//第三个场景
if (sort == 3) {
for (int i = 0; i < 15; i++) {
this.allObstruction.add(new Obstrution(i * 60, 540, 9, this));
}
// 绘制旗帜
this.allObstruction.add(new Obstrution(550, 190, 11, this));
this.allObstruction.add(new Obstrution(535, 505, 12, this));
//绘制障碍物
this.allObstruction.add(new Obstrution(360, 480, 1, this));
this.allObstruction.add(new Obstrution(360, 420, 1, this));
this.allObstruction.add(new Obstrution(360, 360, 1, this));
this.allObstruction.add(new Obstrution(300, 480, 1, this));
this.allObstruction.add(new Obstrution(300, 420, 1, this));
this.allObstruction.add(new Obstrution(240, 480, 1, this));
}
}
// 重置方法,Mario复活后将所有的障碍物和敌人放回到原有坐标,并将其状态也修改
public void reset() {
// 放回障碍物和敌人
this.allEnemy.addAll(this.removeEnemy);
this.allObstruction.addAll(this.removeObstrution);
// 重置敌人和障碍物
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();
}
}
// 使此场景的每个敌人都开始移动
public void enemyStartMove() {
for (int i = 0; i < this.allEnemy.size(); i++) {
this.allEnemy.get(i).startMove();
}
}
}
4、Mario类
public class Mario implements Runnable {
// Mario坐标
private int x;
private int y;
// 用字符串描述Mario状态
private String status;
// 状态图片
private BufferedImage showImage;
// 生命数和分数
private int score;
private int life;
// 移动速度属性
private int xmove = 0;
// 跳跃速度属性
private int ymove = 0;
// 跳跃上升时间
private int uptime = 0;
// Mario的死亡标识
private boolean isDead = false;
//定义游戏完成的标记
private boolean isWin = false;
// 加入线程
private Thread t = null;
// 当前移动中显示的图片索引
private int moving = 0;
// 定义一个场景对象,保存当前Mario所在的场景(障碍物的位置)
private BackGround bg;
// 构造方法
public Mario(int x, int y) {
this.x = x;
this.y = y;
// 初始化Mario
this.showImage = StaticValue.allMarioImage.get(0);
this.score = 0;
this.life = 3;
//创建线程
t = new Thread(this);
t.start();
//Mario初始状态为右站立
this.status = "right--standing";
}
//向左移动的方法
public void leftMove() {
// 改变速度
xmove = -5;
// 改变状态
// 如果当前已经是跳跃状态,应该保留原有状态,而不能改变为移动状态
if (this.status.indexOf("jumping") != -1) {
this.status = "left--jumping";
} else {
this.status = "left--moving";
}
}
//向右移动的方法
public void rightMove() {
xmove = 5;
if (this.status.indexOf("jumping") != -1) {
this.status = "right--jumping";
} else {
this.status = "right--moving";
}
}
//左停止
public void leftStop() {
xmove = 0;
if (this.status.indexOf("jumping") != -1) {
this.status = "left--jumping";
} else {
this.status = "left--standing";
}
}
//右停止
public void rightStop() {
xmove = 0;
if (this.status.indexOf("jumping") != -1) {
this.status = "right--jumping";
} else {
this.status = "right--standing";
}
}
// 跳跃的实现
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;
}
}
// 下落的方法
public void down() {
if (this.status.indexOf("left") != -1) {
// 往左跳
this.status = "left--jumping";
} else {
// 往右跳
this.status = "right--jumping";
}
//下落速度
ymove = 5;
}
// Mario死亡的方法
public void dead() {
//死亡一次,生命数减1
this.life--;
//生命为0
if (this.life == 0) {
this.isDead = true;
this.status = "right--standing";
} else {
//复活厚刷新背景和Mario位置
this.bg.reset();
this.x = 0;
this.y = 420;
}
}
// 为了控制持续移动(坐标一直改变),使用线程
public void run() {
while (true) {
// 如果Mario移动到旗杆处,则由程序自动控制移动,并且游戏结束。
if (this.bg.isFlag() && this.x >= 510) {
this.bg.setOver(true);
if (this.bg.isDown()) {
this.status = "right--moving";
//降旗后,Mario开始向城堡移动
if(this.x < 570 ) {
this.x += 5;
}else {
if(this.y < 480) {
this.y += 5;
}
this.x += 5;
//Mario到达城堡,游戏结束
if(x >= 780) {
this.isWin = true;
}
}
} else {
// mario自动落到地面,旗帜也落下来
if (this.y < 480) {
// 移动太快是因为没有调用线程的sleep方法,放到else外面就好了
this.y += 3;
}
if (this.y > 444) {
this.y = 444;
// 改变Mario落地的状态
this.status = "right--standing";
}
}
} else {
// 定义允许移动的标记
boolean canLeft = true;
boolean canRight = true;
// 定义Mario是否处于障碍物上的标记(默认可跳跃)
boolean onLand = false;
// 判断当前Mario是否与障碍物碰撞
for (int i = 0; i < this.bg.getAllObstruction().size(); i++) {
Obstrution ob = this.bg.getAllObstruction().get(i);
// 直接用60(砖块的尺寸)判断,有时候很难在两块砖块之间跳上去,且下落会与砖块重合,因此设成50
// 碰撞不允许向右移动
if (ob.getX() == this.x + 60 && (ob.getY() + 50 > this.y && ob.getY() - 50 < this.y)) {
// 往右碰到隐形砖块直接穿过
if (ob.getType() != 3 && ob.getType() != 11) {
canRight = false;
}
}
// 不允许向左移动
if (ob.getX() == this.x - 60 && (ob.getY() + 50 > 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<Enemy> 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(苦笑不得);由此可见,做项目,写代码一定要思路清晰,知道自己在干什么,下一步又该干什么,一步一步地写,道长且艰,写代码一定要偶尔停一下,回头看看自己写了什么,又缺什么,才不容易出错。