1.游戏功能
随机出现障碍物,人物可以通过向上跳进行避免,游戏结束后出现分数,对难度进行一定的控制:当分数>1000时难度升级,当分数>4000时,难度再进行升级,并存在音乐播放功能。
2.具体实现
2.1 model
(1)Dinosaur类
游戏人物。主要有构造方法,移动方法、跳跃方法,获取边界方法用于碰撞检测。
构造方法:加载图片、定义人物的x,y坐标
step():主要是通过变换恐龙的图片来达到走路的动作
jump():跳跃
move():移动方法通过是否处于跳跃状态分成两种
剩下的一些为getset方法
package com.study.dinosaur.model; import com.study.dinosaur.service.FreshThread; import com.study.dinosaur.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; //主图片 public 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=120; //跳起的最大高度 private final int LOWEST_Y=415; //落地的最低坐标 private final int FREASH= FreshThread.FRESH; //刷新时间 /** * 构造函数 * @throws IOException */ public Dinosaur() throws IOException { x=50; y=LOWEST_Y; image1= ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\dinosaur1.png")); image2= ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\dinosaur2.png")); image3= ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\dinosaur3.png")); } /*** * 踏步向前移动 */ public void step(){ //每过250毫秒就更换一张图片 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; } /**8 * 移动 * 1.向前走 * 2.跳跃 */ 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 + 25, y + 100, 45, 44); } public Rectangle getHeadBounds() { return new Rectangle(x + 55, y + 50, 50, 25); } public BufferedImage getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } public BufferedImage getImage1() { return image1; } public void setImage1(BufferedImage image1) { this.image1 = image1; } public BufferedImage getImage2() { return image2; } public void setImage2(BufferedImage image2) { this.image2 = image2; } public BufferedImage getImage3() { return image3; } public void setImage3(BufferedImage image3) { this.image3 = image3; } 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 int getJumpValue() { return jumpValue; } public void setJumpValue(int jumpValue) { this.jumpValue = jumpValue; } public boolean isJumpState() { return jumpState; } public void setJumpState(boolean jumpState) { this.jumpState = jumpState; } public int getStepTimer() { return stepTimer; } public void setStepTimer(int stepTimer) { this.stepTimer = stepTimer; } public int getJUMP_HIGHT() { return JUMP_HIGHT; } public int getLOWEST_Y() { return LOWEST_Y; } public int getFREASH() { return FREASH; } }
(2)Obstacle类
障碍主要有仙人掌和石头两类,在构造方法中利用random进行随机生成;
isLive():主要是判断此时障碍是否还在当前游戏面板内,方便碰撞检测
package com.study.dinosaur.model; import com.study.dinosaur.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; BufferedImage image; BufferedImage stone; BufferedImage cacti; private int speed; public Obstacle() throws IOException { stone= ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\rock.png")); cacti=ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\cacti.png")); Random random=new Random(); if(random.nextInt(2)==0){ image=cacti; }else{ image=stone; } x=1024; y=560-image.getHeight(); speed= BackgroundImage.SPEED; } public void move(){ x-=speed; } public boolean isLive(){ if(x<=-image.getWidth()){ return false; } return true; } public Rectangle getBounds(){ if (image==cacti){ return new Rectangle(x,y,24,image.getHeight()-1); } return new Rectangle(x+5,y,32,28); } 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 getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } public BufferedImage getStone() { return stone; } public void setStone(BufferedImage stone) { this.stone = stone; } public BufferedImage getCacti() { return cacti; } public void setCacti(BufferedImage cacti) { this.cacti = cacti; } public int getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } @Override public String toString() { return "Obstacle{" + "x=" + x + ", y=" + y + ", image=" + image + ", stone=" + stone + ", cacti=" + cacti + ", speed=" + speed + '}'; } }
2.2 Service
(1)MusicPlayer
主要是新开一个线程进行音乐的播放。
读取音乐文件后写入混频器源数据行:
1.声明一个128k的缓冲区
2.不断循坏将音乐文件读入缓冲区
3.将缓冲区数据写入混频器源数据行package com.study.dinosaur.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.soundFile = new File(filePath); this.circulate = circulate; if(!soundFile.exists()){ throw new FileNotFoundException(filePath+"文件未找到"); } } /*** * run()方法播放 * 1.声明一个128k的缓冲区 * 2.不断循坏将音乐文件读入缓冲区 * 3.将缓冲区数据写入混频器源数据行 */ @Override public void run() { byte[] auBuffer=new byte[1024*128]; 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); auline= (SourceDataLine) AudioSystem.getLine(info); auline.open(format); auline.start(); int byteCount=0; while(byteCount!=-1){ //读取缓冲区文件 byteCount=audioInputStream.read(auBuffer,0,auBuffer.length); //存在文件就写入 if(byteCount>=0){ auline.write(auBuffer,0,byteCount); } } } catch (UnsupportedAudioFileException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (LineUnavailableException e) { e.printStackTrace(); }finally { //一定要执行的操作 auline.drain(); auline.close(); } }while(circulate); } //开始播放 public void play(){ thread=new Thread(); thread.start(); } //停止播放 public void stop(){ thread.stop(); } }
(2)Sound类
音乐播放器类主要是启动musicPlayer,同时供游戏面板调用
package com.study.dinosaur.service; import java.io.FileNotFoundException; /*** * 音乐播放器类 */ public class Sound { public static final String DIR = "D:\\JavaProject\\JavaGames\\dinosaur\\music\\"; public static final String BACKGROUND = "background.wav"; public static final String JUMP = "jump.wav"; public static final String HIT = "hit.wav"; private static void play(String file, boolean circulate) { try { MusicPlayer 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); } }
(3)计分模块
package com.study.dinosaur.service; import java.io.*; import java.util.Arrays; /**8 * 计分 */ public class ScoreRecorder { private static final String SCOREFILE = "D:\\JavaProject\\JavaGames\\dinosaur\\data\\source"; private static int[] scores = new int[3]; public static void init(){ //新建文件 File file = new File(SCOREFILE); if (!file.exists()){ try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } return; } FileInputStream fileInputStream = null; InputStreamReader reader = null; BufferedReader bufferedReader = null; try { fileInputStream = new FileInputStream(file); reader = new InputStreamReader(fileInputStream); bufferedReader = new BufferedReader(reader); String value = bufferedReader.readLine(); if (!(value==null||"".equals(value))){ String[] values = value.split(","); if (values.length<3){ Arrays.fill(scores,0); }else { for (int i = 0; i < 3; i++) { scores[i] = Integer.parseInt(values[i]); } } } } catch (IOException e) { e.printStackTrace(); }finally { try{ if (bufferedReader != null) { bufferedReader.close(); reader.close(); fileInputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void saveScore(){ String value = scores[0]+","+scores[1]+","+scores[2]; FileOutputStream fileOutputStream = null; OutputStreamWriter writer = null; BufferedWriter bufferedWriter = null; try{ fileOutputStream = new FileOutputStream(SCOREFILE); writer = new OutputStreamWriter(fileOutputStream); bufferedWriter = new BufferedWriter(writer); bufferedWriter.write(value); bufferedWriter.flush(); } catch (IOException e) { e.printStackTrace(); }finally { try{ if (bufferedWriter != null) { bufferedWriter.close(); writer.close(); fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void addNewScore(int score){ int[] temp = Arrays.copyOf(scores,4); temp[3] = score; Arrays.sort(temp); scores = Arrays.copyOfRange(temp,1,4); } public static int[] getScores(){ return scores; } }
(4)刷新页面模块
package com.study.dinosaur.service; import com.study.dinosaur.view.GamePanel; import com.study.dinosaur.view.MainFrame; import com.study.dinosaur.view.ScoreDialog; import java.awt.*; import java.io.IOException; public class FreshThread extends Thread { public static final int FRESH = 20; public GamePanel gamePanel; public FreshThread(GamePanel gamePanel) { this.gamePanel = gamePanel; } @Override public void run() { while (!gamePanel.isFinish()) { gamePanel.repaint(); try { Thread.sleep(FRESH); } catch (InterruptedException e) { e.printStackTrace(); } } Container c = gamePanel.getParent(); while (!(c instanceof MainFrame)) { c = c.getParent(); } MainFrame frame = (MainFrame) c; new ScoreDialog(frame); try { frame.restart(); } catch (IOException e) { e.printStackTrace(); } } }
2.3 View层
(1)主面板
package com.study.dinosaur.view; import com.study.dinosaur.service.ScoreRecorder; import com.study.dinosaur.service.Sound; import javax.swing.*; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; public class MainFrame extends JFrame { public MainFrame() throws IOException { restart(); setBounds(340,150,1024,720); setTitle("奔跑吧!小恐龙!"); Sound.background(); ScoreRecorder.init(); addListener(); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setVisible(true); } public void restart() throws IOException { Container c = getContentPane(); c.removeAll(); GamePanel gamePanel = new GamePanel(); c.add(gamePanel); addKeyListener(gamePanel); c.validate(); } private void addListener(){ addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { // ScoreRecorder.saveScore(); } }); } }
(2)游戏面板
package com.study.dinosaur.view; import com.study.dinosaur.model.Dinosaur; import com.study.dinosaur.model.Obstacle; import com.study.dinosaur.service.FreshThread; import com.study.dinosaur.service.ScoreRecorder; import com.study.dinosaur.service.Sound; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * 主要的游戏面板类 */ public class GamePanel extends JPanel implements KeyListener { private BufferedImage image; private BackgroundImage background; private Dinosaur golden; private Graphics2D graphics2D; private int addObstacleTimer = 0; private boolean finish = false; //障碍的list private List<Obstacle> list =new ArrayList<Obstacle>(); private final int FRESH = FreshThread.FRESH; private int score = 0; private int scoreTimer = 0; public GamePanel() throws IOException { image = new BufferedImage(1024, 720, BufferedImage.TYPE_INT_RGB); graphics2D = image.createGraphics(); background = new BackgroundImage(); golden = new Dinosaur(); list.add(new Obstacle()); FreshThread t = new FreshThread(this); t.start(); } private void paintImage() throws IOException { //控制移动速度 if (score > 1000) { BackgroundImage.SPEED = 5; } if (score > 4000) { BackgroundImage.SPEED = 7; } background.roll(); golden.move(); graphics2D.drawImage(background.image, 0, 0, 1024, 720, this); graphics2D.drawImage(golden.getImage(), golden.getX(), golden.getY(), this); if (addObstacleTimer >= 130 + Math.random() * 100 + 1000) { if (Math.random() * 100 > 60) { list.add(new Obstacle()); } addObstacleTimer = 0; } for (int i = 0; i < list.size(); i++) { Obstacle obstacle = list.get(i); if (obstacle.isLive()) { obstacle.move(); graphics2D.drawImage(obstacle.getImage(), obstacle.getX(), obstacle.getY(), this); if (obstacle.getBounds().intersects(golden.getFootBounds()) || obstacle.getBounds().intersects(golden.getHeadBounds())) { Sound.hit(); gameOver(); } } else { list.remove(i); i--; } } graphics2D.setColor(Color.BLACK); graphics2D.setFont(new Font("MS Song", Font.BOLD, 24)); graphics2D.drawString(String.format("%6d", score), 900, 30); addObstacleTimer += FRESH; scoreTimer += FRESH; score += 1; } @Override public void paint(Graphics graphics) { try { paintImage(); } catch (IOException e) { e.printStackTrace(); } graphics.drawImage(image, 0, 0, this); } public void gameOver() { ScoreRecorder.addNewScore(score); finish = true; } public boolean isFinish() { return finish; } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { int code = e.getKeyCode(); if (code == KeyEvent.VK_SPACE) { golden.jump(); } } @Override public void keyReleased(KeyEvent e) { } }
(3)计分器
package com.study.dinosaur.view; import com.study.dinosaur.service.ScoreRecorder; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * Created by IntelliJ IDEA. * * @author : JarvisHsu * @create 2020/08/20 16:45 */ public class ScoreDialog extends JDialog { public ScoreDialog(JFrame frame){ super(frame,true); int[] scores = ScoreRecorder.getScores(); JPanel scoreP = new JPanel(new GridLayout(4, 1)); scoreP.setBackground(Color.white); JLabel title = new JLabel("得分排行榜", JLabel.CENTER); title.setFont(new Font("MS Song",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; int x = frame.getX()+(frame.getWidth()-width)/2; int y = frame.getY()+(frame.getHeight()-height)/2; setBounds(x,y,width,height); setVisible(true); } }
(4)背景
package com.study.dinosaur.view; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * Created by IntelliJ IDEA. * * @author : JarvisHsu * @create 2020/08/20 16:45 */ public class BackgroundImage { public BufferedImage image; private BufferedImage image1,image2; private Graphics2D graphics2D; public int x1,x2; public static int SPEED = 4; public BackgroundImage(){ try { image1 = ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\background.png")); image2 = ImageIO.read(new File("D:\\JavaProject\\JavaGames\\dinosaur\\image\\background.png")); } catch (IOException e) { e.printStackTrace(); } image = new BufferedImage(1024,720,BufferedImage.TYPE_INT_RGB); graphics2D = image.createGraphics(); x1 = 0; x2 = 1024; graphics2D.drawImage(image1,x1,0,null); } public void roll(){ x1 -=SPEED; x2 -=SPEED; if (x1<=-800){ x1=800; } if (x2<=-800){ x2 = 800; } graphics2D.drawImage(image1,x1,0,null); graphics2D.drawImage(image2,x2,0,null); } }
2.5 程序启动类
package com.study.dinosaur;
import com.study.dinosaur.view.MainFrame;
import java.io.IOException;
public class GameStart {
public static void main(String[] args) throws IOException {
new MainFrame();
}
}