现奔跑吧!小恐龙
项目结构
效果
源码
main包
package main;
import view.MainFrame;
// 游戏开始类
public class Start {
public static void main(String[] args) {
MainFrame frame = new MainFrame();// 创建主窗体
frame.setVisible(true);// 显示主窗体
}
}
modle包
package modle;
import service.FreshThread;
import service.Sound;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
// 恐龙类
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; // 刷新时间--刷新帧线程
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();
}
}
// 踏步
private void step() {
// 每过250毫秒,更换一张图片。因为共有3图片,所以除以3取余,轮流展示这三张
int tmp = stepTimer / 250 % 3;
switch (tmp) {
case 1 :
image = image1;
break;
case 2 :
image = image2;
break;
default :
image = image3;
}
stepTimer += FREASH; // 计时器递增
}
// 跳跃
public void jump() {
if (!jumpState) {
// 如果没处于跳跃状态
Sound.jump(); // 播放跳跃音效
}
jumpState = true; // 处于跳跃状态
}
// 移动
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; // 停止跳跃
}
}
}
public Rectangle getFootBounds() {
// 获取恐龙的脚部边界对象
return new Rectangle(x + 30, y + 59, 29, 18); // 用于后续做碰撞检测
}
public Rectangle getHeadBounds() {
// 获取恐龙的头部边界对象
return new Rectangle(x + 66, y + 25, 32, 22);
}
}
package modle;
import view.BackGroundImage;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
// 障碍类
public class Obstacle {
public int x, y; // 横纵坐标
public BufferedImage image;
private BufferedImage stone; // 石头图片---(32,26)
private BufferedImage cacti; // 仙人掌图片---(32,59)
private int speed; // 移动速度--图片跟着背景走
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(); // 纵坐标--使图片处在地平线上
speed = BackGroundImage.SPEED; // 移动速度与背景同步--BackgroundImage=背景
}
// 移动
public void move() {
x -= speed; // 横坐标递减--障碍物的速度与背景的速度一致
}
public boolean isLive() {
// 如果移出了游戏界面
if (x <= -image.getWidth()) {
return false; // 消亡
}
return true; // 存活
}
// 通过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);
}
}
service包
package service;
import view.GamePanel;
import view.MainFrame;
import view.ScoreDialog;
import java.awt.*;
// 刷新帧线程类
public class FreshThread extends Thread{
public static final int FREASH = 20;
GamePanel p; // 游戏面板
public FreshThread(GamePanel p) {
this.p = p;
}
public void run() {
while (!p.isFinish()) {
// 如果游戏未结束
p.repaint(); // 重绘游戏面板
try {
Thread.sleep(FREASH); // 按照刷新时间休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Container c = p.getParent(); // 获取面板父容器
while (!(c instanceof MainFrame)) {
// 如果父容器不是主窗体类
c = c.getParent(); // 继续获取父容器的父容器
}
MainFrame frame = (MainFrame) c; // 将容器强制转换为主窗体类
new ScoreDialog(frame); // 弹出得分记录对话框
frame.restart(); // 主窗体重载开始游戏
}
}
package service;
import javax.sound.sampled.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
// 音乐播放器类
public class MusicPlayer implements Runnable {
// 音乐文件
File soundFile;
// 父线程
Thread thread;
// 是否循环播放
boolean circulate;
public MusicPlayer(String filepath,boolean circulate) throws FileNotFoundException {
this.circulate = circulate;
soundFile = new File(filepath);
if(!soundFile.exists()){
// 如果文件不存在
throw new FileNotFoundException(filepath + "未找到");
}
}
@Override
public void run() {
byte[] auBuffer = new byte[1024 * 128]; // 创建128k缓冲区
do{
AudioInputStream audioInputStream = null; // 创建音频输入流对象
SourceDataLine auline = null; // 混频器源数据行
try {
// 从音乐文件中获取音频输入流
audioInputStream = AudioSystem.getAudioInputStream(soundFile);
AudioFormat format = 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的数据
byteCount = audioInputStream.read(auBuffer, 0,
auBuffer.length);
if (byteCount >= 0) {
// 如果读出有效数据
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); // 根据循环标志判断是否循环播放
}
public void play(){
// 创建线程对象
thread = new Thread(this);
// 开启线程
thread.start();
}
public void stop(){
// 强制关闭线程
thread.stop();
}
}
package service;
import java.io.*;
import java.util.Arrays;
// 分数记录器类
public class ScoreRecorder {
private static final String SCOREFILE = "data/soure"; // 得分记录文件
private static int scores[] = new int[3]; // 当前得分最高前三名
// 分数初始化
public static void init() {
File f = new File(SCOREFILE); // 创建记录文件
if (!f.exists()) {
// 如果文件不存在
try {
f.createNewFile(); // 创建新文件
} catch (IOException e) {
e.printStackTrace();
}
return; // 停止方法
}
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream(f); // 文件字节输入流
isr = new InputStreamReader(fis); // 字节流转字符流
br = new BufferedReader(isr); // 缓冲字符流
String value = br.readLine(); // 读取一行
if (!(value == null || "".equals(value))) {
// 如果不为空值
String vs[] = value.split(","); // 分割字符串
if (vs.length < 3) {
// 如果分割结果小于3
Arrays.fill(scores, 0); // 数组填充0
} else {
for (int i = 0; i < 3; i++) {
// 将记录文件中的值赋给当前分数数组
scores[i] = Integer.parseInt(vs[i]);
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 依次关闭流
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 保存分数
public static void saveScore() {
// 拼接得分数组
String value = scores[0] + "," + scores[1] + "," + scores[2];
FileOutputStream fos = null;
OutputStreamWriter osw = null;
BufferedWriter bw = null;
try {
fos = new FileOutputStream(SCOREFILE); // 文件字节输出流
osw = new OutputStreamWriter(fos); // 字节流转字符流
bw = new BufferedWriter(osw); // 缓冲字符流
bw.write(value); // 写入拼接后的字符串
bw.flush(); // 字符流刷新
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 依次关闭流
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 添加分数。如果新添加的分数比排行榜分数高,则会将新分数记入排行榜。
static public void addNewScore(int score) {
// 在得分组数基础上创建一个长度为4的临时数组
int tmp[] = Arrays.copyOf(scores, 4);
tmp[3] = score; // 将新分数赋值给第四个元素
Arrays.sort(tmp); // 临时数组降序排列
scores = Arrays.copyOfRange(tmp, 1, 4); // 将后三个元素赋值给得分数组
}
// 获取分数
static public int[] getScores() {
return scores;
}
}
package service;
import java.io.FileNotFoundException;
// 音效类
public class Sound {
// 音乐文件夹
public static final String DIR = "music\\";
// 背景音乐
public static final String BACKGROUND = "background.wav";
// 跳跃音效
public static final String JUMP = "jump.wav";
// 撞击音效
public static final String HIT = "hit.wav";
// flag是否播放mp3格式音频
public static void play(String file,boolean circulate){
MusicPlayer player = null;
try {
// 创建播放器
player = new MusicPlayer(file, circulate);
// 播放器开始播放
player.play();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static void jump(){
// 播放跳跃音效(不循环播放)
play(DIR + JUMP,false);
}
public static void hit(){
// 播放第撞击音效(不循环播放)
play(DIR + HIT, false);
}
public static void background(){
// 播放背景音效(循环播放)
play(DIR + BACKGROUND,true);
}
}
view包
package view;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
// 滚动背景
public class BackGroundImage {
public BufferedImage image; // 背景图片
private BufferedImage image1, image2; // 滚动的两个图片
private Graphics2D g; // 背景图片的绘图对象
public int x1, x2; // 两个滚动图片的坐标
public static final int SPEED = 4; // 滚动速度
public BackGroundImage() {
try {
image1 = ImageIO.read(new File("image/背景.png"));
image2 = ImageIO.read(new File("image/背景.png"));
} catch (IOException e) {
e.printStackTrace();
}
// 主图片采用宽800高300的彩色图片
image = new BufferedImage(800, 300, BufferedImage.TYPE_INT_RGB);
g = image.createGraphics(); // 获取主图片绘图对象
x1 = 0; // 第一幅图片初始坐标为0
x2 = 800; // 第二幅图片初始横坐标为800
g.drawImage(image1, x1, 0, null);
}
// 滚动
public void roll() {
x1 -= SPEED; // 第一幅图片左移
x2 -= SPEED; // 第二幅图片左移
if (x1 <= -800) {
// 如果第一幅图片移出屏幕
x1 = 800; // 回到屏幕右侧
}
if (x2 <= -800) {
// 如果第二幅图片移出屏幕
x2 = 800; // 回到屏幕右侧
}
g.drawImage(image1, x1, 0, null); // 在主图片中绘制两幅图片
g.drawImage(image2, x2, 0, null);
}
}
package view;
import modle.Dinosaur;
import modle.Obstacle;
import service.FreshThread;
import service.ScoreRecorder;
import service.Sound;
import java.awt.event.KeyListener;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.JPanel;
// 游戏主面板
public class GamePanel extends JPanel implements KeyListener {
private BufferedImage image; // 主图片
private BackGroundImage background; // 背景图片
private Dinosaur golden; // 恐龙
private Graphics2D g2; // 主图片绘图对象
private int addObstacleTimer = 0; // 添加障碍计时器
private boolean finish = false; // 游戏结束标志
private ArrayList<Obstacle> list = new ArrayList<Obstacle>(); // 障碍集合
private final int FREASH = FreshThread.FREASH; // 刷新时间
int score = 0; // 得分
int scoreTimer = 0; // 分数计时器
public GamePanel() {
// 主图片采用宽800高300的彩色图片
image = new BufferedImage(800, 300, BufferedImage.TYPE_INT_BGR);
g2 = image.createGraphics(); // 获取主图片绘图对象
background = new BackGroundImage(); // 初始化滚动背景
golden = new Dinosaur(); // 初始化小恐龙
list.add(new Obstacle()); // 添加第一个障碍
FreshThread t = new FreshThread(this); // 刷新帧线程
t.start(); // 启动线程
}
// 绘制主图片
private void paintImage() {
background.roll(); // 背景图片开始滚动
golden.move(); // 恐龙开始移动
g2.drawImage(background.image, 0, 0, this); // 绘制滚动背景
if (addObstacleTimer == 1300) {
// 每过1300毫秒
if (Math.random() * 100 > 40) {
// 60%概率出现障碍
list.add(new Obstacle());
}
addObstacleTimer = 0; // 重新计时
}
for (int i = 0; i < list.size(); i++) {
// 遍历障碍集合
Obstacle o = list.get(i); // 获取障碍对象
if (o.isLive()) {
// 如果是有效障碍
o.move(); // 障碍移动
g2.drawImage(o.image, o.x, o.y, this); // 绘制障碍
// 如果恐龙头脚碰到障碍
if (o.getBounds().intersects(golden.getFootBounds())
|| o.getBounds().intersects(golden.getHeadBounds())) {
Sound.hit(); // 播放撞击声音
gameOver(); // 游戏结束
}
} else {
// 如果不是有效障碍
list.remove(i); // 删除此障碍
i--; // 循环变量前移
}
}
g2.drawImage(golden.image, golden.x, golden.y, this); // 绘制恐龙
if (scoreTimer >= 500) {
// 每过500毫秒
score += 10; // 加十分
scoreTimer = 0; // 重新计时
}
g2.setColor(Color.BLACK); // 使用黑色
g2.setFont(new Font("黑体", Font.BOLD, 24)); // 设置字体
g2.drawString(String.format("%06d", score), 700, 30); // 绘制分数
addObstacleTimer += FREASH; // 障碍计时器递增
scoreTimer += FREASH; // 分数计时器递增
}
// 重写绘制组件方法
public void paint(Graphics g) {
paintImage(); // 绘制主图片内容
g.drawImage(image, 0, 0, this);
}
// 游戏是否结束
public boolean isFinish() {
return finish;
}
// 使游戏结束
public void gameOver() {
ScoreRecorder.addNewScore(score); // 记录当前分数
finish = true;
}
// 实现按下键盘按键方法
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode(); // 获取按下的按键值
if (code == KeyEvent.VK_SPACE) {
// 如果是空格
golden.jump(); // 恐龙跳跃
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
}
package view;
import service.ScoreRecorder;
import service.Sound;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
// 主窗体
public class MainFrame extends JFrame {
public MainFrame() {
restart(); // 开始
setBounds(340, 150, 821, 260); // 设置横纵坐标和宽高
setTitle("奔跑吧!小恐龙!"); // 标题
Sound.background(); // 播放背景音乐
ScoreRecorder.init(); // 读取得分记录
addListener(); // 添加监听
setDefaultCloseOperation(EXIT_ON_CLOSE); // 关闭窗体则停止程序
}
// 重新开始
public void restart() {
Container c = getContentPane(); // 获取主容器对象
c.removeAll(); // 删除容器中所有组件
GamePanel panel = new GamePanel(); // 创建新的游戏面板
c.add(panel);
addKeyListener(panel); // 添加键盘事件
c.validate(); // 容器重新验证所有组件
}
// 添加监听
private void addListener() {
addWindowListener(new WindowAdapter() {
// 添加窗体监听
public void windowClosing(WindowEvent e) {
// 窗体关闭前
ScoreRecorder.saveScore(); // 保存比分
}
});
}
}
package view;
import service.ScoreRecorder;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
// 成绩对话窗
public class ScoreDialog extends JDialog {
public ScoreDialog(JFrame frame) {
super(frame, true); // 调用父类构造方法,阻塞父窗体
int scores[] = ScoreRecorder.getScores(); // 获取当前前三名成绩
JPanel scoreP = new JPanel(new GridLayout(4, 1)); // 成绩面板,4行1列
scoreP.setBackground(Color.WHITE); // 白色背景
JLabel title = new JLabel("得分排行榜", JLabel.CENTER); // 标题标签,居中
title.setFont(new Font("黑体", Font.BOLD, 20)); // 设置字体
title.setForeground(Color.RED); // 红色体字
JLabel first = new JLabel("第一名:" + scores[2], JLabel.CENTER); // 第一名标签
JLabel second = new JLabel("第二名:" + scores[1], JLabel.CENTER); // 第二名标签
JLabel third = new JLabel("第三名:" + scores[0], JLabel.CENTER); // 第三名标签
JButton restart = new JButton("重新开始"); // 重新开始按钮
restart.addActionListener(new ActionListener() {
// 按钮添加事件监听
@Override
public void actionPerformed(ActionEvent e) {
// 当点击时
dispose(); // 销毁对话框
}
});
scoreP.add(title); // 成绩面板添加标签
scoreP.add(first);
scoreP.add(second);
scoreP.add(third);
Container c = getContentPane(); // 获取主容器
c.setLayout(new BorderLayout()); // 使用边界布局
c.add(scoreP, BorderLayout.CENTER); // 成绩面板放中间
c.add(restart, BorderLayout.SOUTH); // 按钮放底部
setTitle("游戏结束"); // 对话框标题
int width, height; // 对话框宽高
width = height = 200; // 对话框宽高均为200
// 获得主窗体中居中位置的横坐标
int x = frame.getX() + (frame.getWidth() - width) / 2;
// 获得主窗体中居中位置的纵坐标
int y = frame.getY() + (frame.getHeight() - height) / 2;
setBounds(x, y, width, height); // 设置坐标和宽高
setVisible(true); // 显示对话框
}
}
源码、图片和音效下载-下载地址