java实现-现奔跑吧小恐龙-小游戏

现奔跑吧!小恐龙

项目结构
在这里插入图片描述

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码

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); // 显示对话框
    }

}

源码、图片和音效下载-下载地址

猜你喜欢

转载自blog.csdn.net/weixin_46085748/article/details/124978455
今日推荐