java 奔跑吧小恐龙 奔跑吧恐龙宝贝游戏_构造方法


奔跑吧小恐龙是一款简单的跑酷游戏(代码简单,适合初学者学习)。玩家控制小恐龙向前狂奔,躲避沿途出现的石头和仙人掌,跑的越远,分数越高。游戏内还增加了背景音乐、跳跃音乐和碰撞音乐。

本文的代码虽然长,但不难理解,希望大家能够耐心看完。

文中代码均可直接运行,完整代码请见github

文章目录

  • 系统结构设计
  • 游戏功能架构图
  • 项目目录结构预览
  • 游戏模型设计
  • 恐龙类
  • 障碍类
  • 音效模块设计
  • 音频播放器

系统结构设计

游戏功能架构图

项目目录结构预览

游戏模型设计

恐龙类

1.定义

Dinosaur类的成员属性绝大多数都是私有属性,只有少数公有属性用于游戏面板绘图使用。Dinosaur类的私有属性包含3张来回切换的跑步照片:

java 奔跑吧小恐龙 奔跑吧恐龙宝贝游戏_java 奔跑吧小恐龙_02


Dinosaur类的定义如下:

public class Dinosaur {
    public BufferedImage image;// 主图片
    private BufferedImage image1, image2, image3;// 跑步图片
    public int x, y;// 坐标
    private int jumpValue = 0;// 跳跃的增变量
    private boolean jumpState = false;// 跳跃状态
    private int stepTimer = 0;// 踏步计时器
    private final int JUMP_HIGHT = 100;// 跳起最大高度
    private final int LOWEST_Y = 120;// 落地最低坐标
    private final int FREASH = FreshThread.FREASH;// 刷新时间--刷新帧线程
    }

在Dinosaur类的构造方法中将恐龙的横坐标固定在50像素的位置,纵坐标采用落地时的坐标,具体代码如下:

public Dinosaur() {
        x = 50;         // 恐龙的横坐标
        y = LOWEST_Y;   // 恐龙的纵坐标
        try {
            image1 = ImageIO.read(new File("image/恐龙1.png"));    // 读取恐龙的图片
            image2 = ImageIO.read(new File("image/恐龙2.png"));
            image3 = ImageIO.read(new File("image/恐龙3.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.踏步
游戏中恐龙的横坐标虽然一直没变,但背景的运动会造成恐龙向前移动的假象。Step()方法就是踏步动作的方法,具体代码如下:

/**
     * 踏步
     */
    private void step() {
        // 每过250毫秒,更换一张图片。因为共有3图片,所以除以3取余,轮流展示这三张
        int tmp = stepTimer / 250 % 3;     // 0、1、2
        switch (tmp) {
            case 1 :
                image = image1;
                break;
            case 2 :
                image = image2;
                break;
            default :
                image = image3;
        }
        stepTimer += FREASH;// 计时器递增
    }

3.跳跃
跳跃是小恐龙躲避障碍的动作,也是玩家可以控制恐龙做出的唯一动作。
具体代码如下:

/**
     * 跳跃
     */
    public void jump() {
        if (!jumpState) {// 如果没处于跳跃状态
            Sound.jump();// 播放跳跃音效
        }
        jumpState = true;// 处于跳跃状态
    }

4.移动
move()是恐龙移动方法,move()方法不断地调用step()踏步方法
stepTimer踏步计时器会有效控制图片地切换频率
move()方法地具体代码如下:

/**
	 * 移动
	 */
    public void move() {
        step();// 不断踏步
        if (jumpState) {// 如果正在跳跃
            if (y == LOWEST_Y) {// 如果纵坐标大于等于最低点---(越往上坐标越小)
                jumpValue = -4;// 增变量为负值--向上跳
            }
            if (y <= LOWEST_Y - JUMP_HIGHT) {// 如果跳过最高点
                jumpValue = 4;// 增变量为正值--向下跳
            }
            y += jumpValue;// 纵坐标发生变化
            if (y == LOWEST_Y) {// 如果再次落地
                jumpState = false;// 停止跳跃
            }
        }
    }

5.边界对象
java.awt.Rectangle类是举行边界类。为恐龙的头部和足部创建矩形边界对象,用于做碰撞测试
getFootBounds()方法用于获取恐龙的足部边界对象,具体代码如下:

/**
     * 足部边界区域
     * 
     * @return
     */
    public Rectangle getFootBounds() {   // 获取恐龙的脚部边界对象
        return new Rectangle(x + 30, y + 59, 29, 18);   // 用于后续做碰撞检测
    }

getHeadBounds()方法用于获取恐龙的头部边界对象,具体代码如下:

/**
     * 头部边界区域
     * 
     * @return
     */
    public Rectangle getHeadBounds() {	// 获取恐龙的头部边界对象
        return new Rectangle(x + 66, y + 25, 32, 22);   // new Rectangle(x, y, width, height)
    }

障碍类

游戏中的障碍有两种,一种是很矮的石头,另一种是很高的仙人掌,如下图所示。恐龙碰到任何一种障碍,都会导致游戏的结束:

java 奔跑吧小恐龙 奔跑吧恐龙宝贝游戏_游戏_03


1.定义

项目中的com.mr.modle.Obstacle类是障碍类,

因为所有的障碍都会随着背景一起移动,所以障碍的移动速度采用背景图片的速度。Obstacle类的定义如下:

public class Obstacle {
	public int x, y;// 横纵坐标
	public BufferedImage image;
	private BufferedImage stone;// 石头图片---(32,26)
	private BufferedImage cacti;// 仙人掌图片---(32,59)
	private int speed;// 移动速度--图片跟着背景走
	}

在Obstacle类的构造方法中,Obstacle对象会随机成为石头或者仙人掌。具体代码如下:

public Obstacle() {
		try {
			stone = ImageIO.read(new File("image/石头.png")); // 石头图片
			cacti = ImageIO.read(new File("image/仙人掌.png")); // 仙人掌图片
		} catch (IOException e) {
			e.printStackTrace();
		}
		Random r = new Random();// 创建随机对象
		if (r.nextInt(2) == 0) {// 从0和1中取一值,若为0
			image = cacti;// 采用仙人掌图片
		} else {
			image = stone;// 采用石头图片
		}
		x = 800;// 初始横坐标
		y = 200 - image.getHeight();// 纵坐标--使图片处在地平线上
//		System.out.println(image.getWidth());
		speed = BackgroundImage.SPEED;// 移动速度与背景同步--BackgroundImage=背景
	}

2.移动
Obstacle类中的move()方法可以让障碍在游戏画面中移动,且移动速度与背景图片的移动速度相同。move()方法的具体代码如下:

/**
	 * 移动
	 */
	public void move() {
		x -= speed;// 横坐标递减--障碍物的速度与背景的速度一致
	}

3.消除
当障碍移动出游戏画面之后,就不会再对游戏产生任何影响。
要及时将移出画面之外的障碍删除掉,isLive()方法用于获取障碍的有效状态。isLive()的具体代码如下:

/**
	 * 是否存活
	 * 
	 * @return
	 */
	public boolean isLive() {
		// 如果移出了游戏界面
		if (x <= -image.getWidth()) {
			return false;// 消亡
		}
		return true;// 存活
	}

4.边界对象
障碍的边界对象用于做碰撞检测计算。
用getBounds()方法返回边界对象,该方法的具体代码如下:

/**
	 * 获取边界
	 * 
	 * @return
	 */
	// 通过getBounds()方法返回边界对象
	public Rectangle getBounds() {
		if (image == cacti) {// 如果使用仙人掌图片
			// 返回仙人掌的边界
			return new Rectangle(x + 7, y, 15, image.getHeight());
		}
		// 返回石头的边界
		return new Rectangle(x + 5, y + 4, 23, 21);
	}

音效模块设计

本节介绍如何利用java代码播放音乐文件。
JDK支持播放的音乐格式非常少,本章使用的所有音乐文件均为WAVE格式。JDK支持的具体音乐格式可以参考javax.sound.sampled.AudioFileFormat.Type类

音频播放器

项目中的com.mr.service.MusicPlayer类是音频播放器类,该类使用javax.sound.sampled包提供的混音器工具实现音频播放功能。
MusicPlayer类实现了Runnerable接口,并在成员属性中定义了一个线程对象,该线程对象用于启动混音器数据行的读写业务。MusicPlayer类的定义如下:

```java
/**
 * 音乐播放器
 */
public class MusicPlayer implements Runnable {
	File soundFile; // 音乐文件
	Thread thread;// 父线程 -- 执行run方法
	boolean circulate;// 是否循环播放

MusicPlayer类的构造方法有两个参数,filepath表示音乐文件的完整文件名,circulate表示是否重复播放。MusicPlayer类构造方法的具体代码如下:

/**
	 * 构造方法
	 * 
	 * @param filepath  音乐文件完整名称
	 * @param circulate 是否循环播放
	 * @throws FileNotFoundException
	 */

	// 构造方法--2
	public MusicPlayer(String filepath, boolean circulate) throws FileNotFoundException {
		this.circulate = circulate;
		soundFile = new File(filepath);   // File soundFile
		if (!soundFile.exists()) {// 如果文件不存在
			throw new FileNotFoundException(filepath + "未找到");  // 抛出错误,文件未找到
		}
	}

MusicPlayer类要实现Runnable接口,必须先实现run()方法。
在run()方法中声明了一个128K的缓冲区字节数组,程序以不断循环的方式将音乐文件以音频输入流格式读入缓冲区,再把缓冲区的数据写入混音器源数据行中,实现播放音乐的效果。run()方法的具体代码如下:

```java
/**
 * 音乐播放器
 */
public class MusicPlayer implements Runnable {
	File soundFile; // 音乐文件
	Thread thread;// 父线程 -- 执行run方法
	boolean circulate;// 是否循环播放

MusicPlayer类的构造方法有两个参数,filepath表示音乐文件的完整文件名,circulate表示是否重复播放。MusicPlayer类构造方法的具体代码如下:

/**
	 * 构造方法
	 * 
	 * @param filepath  音乐文件完整名称
	 * @param circulate 是否循环播放
	 * @throws FileNotFoundException
	 */

	// 构造方法--2
	public MusicPlayer(String filepath, boolean circulate) throws FileNotFoundException {
		this.circulate = circulate;
		soundFile = new File(filepath);   // File soundFile
		if (!soundFile.exists()) {// 如果文件不存在
			throw new FileNotFoundException(filepath + "未找到");  // 抛出错误,文件未找到
		}
	}

MusicPlayer类要实现Runnable接口,必须先实现run()方法。
在run()方法中声明了一个128K的缓冲区字节数组,程序以不断循环的方式将音乐文件以音频输入流格式读入缓冲区,再把缓冲区的数据写入混音器源数据行中,实现播放音乐的效果。run()方法的具体代码如下:

/*
	 * 在run()方法中声明一个128K的缓冲区字节数组
	 * 程序以不断循环的方式将音乐文件以音频输入流格式读入缓冲区
	 * 把缓冲区的数据写入混音器源数据行中
	 * */
	public void run() {
		byte[] auBuffer = new byte[1024 * 128];// 创建128k缓冲区
		do {
			AudioInputStream audioInputStream = null; // 创建音频输入流对象
			SourceDataLine auline = null; // 混频器源数据行
			try {
				// 从音乐文件中获取音频输入流
				audioInputStream = AudioSystem.getAudioInputStream(soundFile);// soundFile--音乐文件
				AudioFormat format = audioInputStream.getFormat(); // 获取音频格式
//				System.out.println(audioInputStream.getFormat());
				// 按照源数据行类型和指定音频格式创建数据行对象
				DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); // 获取音乐格式
				// 利用音频系统类获得与指定 Line.Info 对象中的描述匹配的行,并转换为源数据行对象
				auline = (SourceDataLine) AudioSystem.getLine(info);
				auline.open(format);// 按照指定格式打开源数据行
				auline.start();// 源数据行开启读写活动
				int byteCount = 0;// 记录音频输入流读出的字节数
				while (byteCount != -1) {// 如果音频输入流中读取的字节数不为-1
					// 从音频数据流中读出128K的数据--auBuffer=128k缓冲区
					byteCount = audioInputStream.read(auBuffer, 0, auBuffer.length);
					if (byteCount >= 0) {// 如果读出有效数据--auline=混频器源数据行
						auline.write(auBuffer, 0, byteCount);// 将有效数据写入数据行中
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			} catch (UnsupportedAudioFileException e) {
				e.printStackTrace();
			} catch (LineUnavailableException e) {
				e.printStackTrace();
			} finally {
				auline.drain();// 清空数据行
				auline.close();// 关闭数据行
			}
		} while (circulate);// 根据循环标志判断是否循环播放
	}

现在只要将MusicPlayer类放入线程中,就可以播放音乐。创建play()方法,传入参数,然后开启线程。play()方法的具体代码如下:

/**
	 * 播放
	 */
	public void play() {
		thread = new Thread(this);// 创建线程对象
		thread.start();// 开启线程
	}

创建stop()方法,在该方法中强制停止线程。
stop()方法的具体代码如下:

/**
	 * 停止播放
	 */
	public void stop() {
		thread.stop();// 强制关闭线程
	}