单位网站制作,国家企业信用信息查询平台官网,网址多少钱一个,soho网站建设前言 Google浏览器内含了一个小彩蛋当没有网络连接时#xff0c;浏览器会弹出一个小恐龙#xff0c;当我们点击它时游戏就会开始进行#xff0c;大家也可以玩一下试试#xff0c;网址#xff1a;恐龙快跑 - 霸王龙游戏. (ur1.fun) 今天我们也可以用Java来简单的实现一下这… 前言 Google浏览器内含了一个小彩蛋当没有网络连接时浏览器会弹出一个小恐龙当我们点击它时游戏就会开始进行大家也可以玩一下试试网址恐龙快跑 - 霸王龙游戏. (ur1.fun) 今天我们也可以用Java来简单的实现一下这个小游戏。 一 系统功能结构图 二 系统业务流程图 三 程序目录结构 一 游戏模型设计 游戏模型主要指游戏中出现的刚体。刚体是指不会因为受力而变形的物体。游戏中的刚体包括奔跑的恐龙石头和仙人掌。背景图片虽然会滚动但背景图片不参与任何碰撞检测所以不属于游戏模型。 1.恐龙类 奔跑的小恐龙是游戏的主角也是玩家控制的角色。项目中的model.Dinosaur就是恐龙类。
1-1 定义 Dinosaur类的成员属性绝大多数都是私有属性只有少数公有属性用于游戏面板绘图使用如主图片和横纵坐标。Dinosaur类的私有属性包含3张来回切换的跑步图片最大起跳高度落地时的坐标以及各种状态的布尔值和计时器。 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; //刷新时间
} 在构造方法中我们要设置恐龙的初始状态将恐龙横坐标固定在50像素纵坐标采用落地时的坐标120像素构造方法的代码如下 public Dinosaur() {x50;//横坐标默认是50yLOWEST_Y;//纵坐标默认起始值是120image1ImageIO.read(new File(image/恐龙1.png));image2ImageIO.read(new File(image/恐龙2.png));image3ImageIO.read(new File(image/恐龙3.png));}
1-2.踏步 游戏中恐龙的横坐标不变但是背景的运动会使恐龙呈现一中运动的状态为了使这种假象的运动状态逼真我们就需要做出恐龙奔跑的动作。step()的方法就是踏步我们只需要将图片来回切换就可以做到这种效果。 public 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;//计时器递增}
1-3.跳跃 跳跃是小恐龙躲避障碍的动作也是我们唯一可以控制恐龙的 行为。当程序调用jump()方法时该方法会更改恐龙的跳跃属性也就是让恐龙处于跳跃状态跳跃的同时也会触发音效。 /*** 跳跃*/public void jump() {if (!jumpState) {// 如果没处于跳跃状态Sound.jump();// 播放跳跃音效}jumpState true;// 处于跳跃状态} 1-4.移动 move方法是恐龙移动方法该方法将恐龙的所有动作效果封装起来然后交由游戏面板调用。每一帧画面都会执行一次恐龙的move方法。move 方法不断地调用step踏步方法因为stepTimer踏步计时器会有效控制图片的切换频率所以不用担心频繁调用的问题。 move()方法会判断恐龙是否处于跳跃状态如果处于跳跃状态并且恐龙站在地上就让jumpValue跳跃增变量值变为-4让恐龙的纵坐标不断与jumpValue 相加纵坐标值越来越小这样恐龙的图片位置就会越来越高。当恐龙纵坐标达到跳跃最大高度时再让jumpValue的值变为4纵坐标值越来越大恐龙的图片就会越来越低。当恐龙再次回到地面上时取消跳跃状态。至此恐龙就完成了一次跳跃动作。 /** 移动的方法*/public void move() {step();//不断踏步if(jumpState) {//如果正在跳跃if(yLOWEST_Y) {//如果纵坐标大于等于最低点jumpValue -4;//增变量为负值/** 这是因为我们窗体的显示是按照像素的大小和位置决定的* 从左上角开始横纵坐标均为0,然后开始增长,向下y增长,向右x增长*/ }if(yLOWEST_Y-JUMP_HIGHT) {//如果跳过最高点jumpValue 4;//增变量为正值}yjumpValue;//纵坐标发生变化if(yLOWEST_Y) {//如果再次落地jumpState false;// 停止跳跃}}}
1-5.边界对象 因为我们这里设计的有跳跃的状态那么就要设置判断是否发生碰撞我们这里将物体具体化为矩形类型方便处理和判断是否发生碰撞将恐龙的头和脚抽象具体为矩形。 /*** 足部边界区域* * return*/public Rectangle getFootBounds() {return new Rectangle(x 30, y 59, 29, 18);}/*** 头部边界区域* * return*/public Rectangle getHeadBounds() {return new Rectangle(x 66, y 25, 32, 22); 2 .障碍类 游戏中设置了两种障碍 一种是很矮的石头 一种是很高的仙人掌 不管是石头还是仙人掌每一个障碍的特点都大致相同都会随着背景一起移动都是可能碰撞的区域。 2-1.定义 Obstacle类就是障碍类该类提供了3个共有属性分别是横坐标纵坐标和图片对象其他属性均为私有属性。因为障碍都会随着背景一起移动所以障碍的移动速度采用背景图片的速度。 public class Obstacle {public int x, y;// 横纵坐标public BufferedImage image;private BufferedImage stone;// 石头图片private BufferedImage cacti;// 仙人掌图片private int speed;// 移动速度
} 使用构造方法随机生成仙人掌或石头采用随机数的方法生成0和10表示采用仙人掌的图片1表示采用石头的图片。 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中取一值若为0image cacti;// 采用仙人掌图片} else {image stone;// 采用石头图片}x 800;// 初始横坐标y 200 - image.getHeight();// 纵坐标speed BackgroundImage.SPEED;// 移动速度与背景同步}
2-2.移动 由于我们的画面中恐龙是在原地不同的而背景画面是向左走的因此我们的障碍物也要向左移动像素的位置向左移动也就是行坐标的像素减少。同样我们也设置障碍物的移动方法为move() /*** 移动*/public void move() {x - speed;// 横坐标递减}2-3.消除 当障碍移除游戏画面以后就不会在的游戏的数据产生影响。为了减除程序计算的压力我们要将移除游戏画面的障碍消除。isLive()方法用于获取障碍的有效状态该方法会根据障碍的位置判断返回true和flase当障碍还在窗体内返回true表示还在窗体内flase表示没在窗体内将障碍对象从碰撞集合中删除。 /*** 是否存活* * return*/public boolean isLive() {// 如果移出了游戏界面if (x -image.getWidth()) {return false;// 消亡}return true;// 存活}
2-4.边界对象 为将障碍具体化设置为矩形方便后面参与碰撞检测不管是仙人掌还是石头都要通过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);} 二 音效模块设计 当然一款游戏离不开音乐的支持。因为音频处理功能是JDK早期版本就有并且一直没有更新所以目前JDK支持的音乐格式很少。JDK支持的音乐格式可以参看在线文档-jdk-zh (oschina.net) 我们这里使用JDK支持的WAVE格式 1.音频播放器 MusicPlayer类是音频播放器类该类实现了Runnable接口并在线程中定义了一个线程对象该线程用于启动混音器数据行的业务。 public class MusicPlayer implements Runnable{File soundFile; //音乐文件Thread thread; //父线程boolean circulate; //是否循环播放
} 它的构造方法有两个参数。filepath表示音乐文件的完整文件名circulate表示是否重复播放构造方法抛出找不到文件异常外部类创建MusicPlayer类对象时必须要捕捉此异常。 /*** 构造方法默认不循环播放* * param filepath* 音乐文件完整名称* throws FileNotFoundException*/public MusicPlayer(String filepath) throws FileNotFoundException {this(filepath, false);}/*** 构造方法* * param filepath* 音乐文件完整名称* param circulate* 是否循环播放* throws FileNotFoundException*/public MusicPlayer(String filepath, boolean circulate) throws FileNotFoundException {this.circulate circulate;soundFile new File(filepath);if (!soundFile.exists()) {// 如果文件不存在throw new FileNotFoundException(filepath 未找到);}} 既然此类实现了Runnable接口必须实现run()方法。在run()方法中声明了一个128kb的缓冲字节数组程序以不断循环的方式将音乐以音频输入流格式读入缓冲区在把缓冲区的数据写入混音器数据行中这样就可以不断向外部音频设备发送音频信号实现播放音乐的效果。 /**重写线程执行方法*/Overridepublic 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);// 根据循环标志判断是否循环播放} 播放音乐和停止音乐的方法如下使用start方法启动线程来播放音乐使用stop方法来强制关闭线程实现关闭音乐的效果。 /*** 播放*/public void play() {thread new Thread(this);// 创建线程对象thread.start();// 开启线程}/*** 停止播放*/public void stop() {thread.stop();// 强制关闭线程}/* 2.音效工具类 我们知道游戏设计有跳的动作以及碰撞的效果这些都要添加一些音效才能够使游戏的效果更加好。所以我们可以为每一个动作设计一个单独的线程当要执行该动作时启动一次线程之后再关闭即可。 package service;import java.io.FileNotFoundException;
/*** 音效类* author JWF*/
public class Sound {static final String DIR music/;// 音乐文件夹static final String BACKGROUD background.wav;// 背景音乐static final String JUMP jump.wav;// 跳跃音效static final String HIT hit.wav;// 撞击音效/*** 播放跳跃音效*/static public void jump() {play(DIR JUMP, false);// 播放一次跳跃音效}/*** 播放撞击音效*/static public void hit() {play(DIR HIT, false);// 播放一次撞击音效}/*** 播放背景音乐*/static public void backgroud() {play(DIR BACKGROUD, true);// 循环播放背景音乐}/*** 播放* * param file* 音乐文件完整名称* param circulate* 是否循环播放*/private static void play(String file, boolean circulate) {try {// 创建播放器MusicPlayer player new MusicPlayer(file, circulate);player.play();// 播放器开始播放} catch (FileNotFoundException e) {e.printStackTrace();}}
}三 计分器模块设计 这里计分器使用一个静态的整型数组记录有史以来前三名的成绩当玩家打破记录时计分器会更新分数此类为ScoreRecorder类定义如下 public class ScoreRecorder {private static final String SCOREFILE data/soure;// 得分记录文件private static int scores[] new int[3];// 当前得分最高前三名
}
读取原始分数数据初始化 在使用ScoreRecorder类之前需要先调用该类的静态方法init。init方法可以让计分器从成绩记录文件中读取到历史前3名数据。成绩记录文件记录了3个历史成绩这3个成绩升序排列并用“”分隔。如果成绩记录文件不存在或者文件中没有记录有效成绩则会取消读取操作并让历史前3名成绩均为0。init0方法的具体代码如下 /*** 分数初始化*/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) {// 如果分割结果小于3Arrays.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();}}}写入游戏数据并保存 当游戏停止时要记录最新的前三名的成绩。saveSore()方法可以将当前成绩数组中的值写入成绩记录文件中。 /*** 保存分数*/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();}}} addNewScore()方法用于向成绩数组中添加新成绩该方法的score参数就是要添加的新成绩数值。在addNewScoreO方法中如果添加的新成绩小于历史前3名则会舍弃如果新成绩大于历史前3名中的某个成绩则会重新排列前3名成绩。这个逻辑是通过Arrays 类提供的 sort排序方法和copyOfRange()复制数组元素方法实现的. /*** 添加分数。如果新添加的分数比排行榜分数高则会将新分数记入排行榜。* * param score* 新分数*/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);// 将后三个元素赋值给得分数组}
获取分数的方法 /*** 获取分数* * return*/static public int[] getScores() {return scores;} 四 视图模块设计
一 主窗体 主窗体是整个游戏最外层的容器。主窗体的本身没有任何内容仅是一个宽820像素,高260像素的窗体。项目中 view.MainFrame类表示游戏的主窗体类该类继承于JFrame类。MainFrame类没有成员属性。MainFrame 类的构造方法中定义了窗体的宽、高、标题等特性同时也具有游戏启动时的初始化功能。例如第一次载入游戏面板时初始化计分器播放背景音乐等。MainFrame类的构造方法的具体代码如下 public MainFrame() {restart();// 开始setBounds(340, 150, 821, 260);// 设置横纵坐标和宽高setTitle(奔跑吧小恐龙);// 标题Sound.backgroud();// 播放背景音乐ScoreRecorder.init();// 读取得分记录addListener();// 添加监听setDefaultCloseOperation(EXIT_ON_CLOSE);// 关闭窗体则停止程序}构造方法中调用的 restart方法就是让游戏重新开始的方法也可以用于第一次启动游戏在restart)方法中首先获取了窗体的主容器对象然后删除容器中的所有组件最后创建一个新的游戏面板对象并添加到容器中同时添加主窗体的键盘事件。方法中最后一行代码尤为关键如果在删除原组件并添加新的游戏面板之后不做重新验证操作将会导致新面板无法正确显示。restart()方法的具体代码如下 /*** 重新开始*/public void restart() {Container c getContentPane();// 获取主容器对象c.removeAll();// 删除容器中所有组件GamePanel panel new GamePanel();// 创建新的游戏面板c.add(panel);addKeyListener(panel);// 添加键盘事件c.validate();// 容器重新验证所有组件} 构造方法中调用了addListener()方法用于让窗体添加键盘以外的监听事件游戏中主要用于在关闭窗口之前保存最新的得分记录。在窗体关闭之前会触发windowClosing()方法在此方法中调用ScoreRecord计分器的saveScore()方法保存成绩。 /*** 添加监听*/private void addListener() {addWindowListener(new WindowAdapter() {// 添加窗体监听public void windowClosing(WindowEvent e) {// 窗体关闭前ScoreRecorder.saveScore();// 保存得分记录}});}
二 游戏面板 游戏面板是整个程序的核心几乎所有的算法都是以游戏面板为基础实现的。游戏面板的主要作用是绘制游戏界面将所有的游戏元素都展现出来。游戏界面会按照(默认)20毫秒一次的刷新频率实现游戏帧数的刷新这样不仅可以让界面中的元素运动起来也可以让各个元素在运动的过程中进行逻辑的运算。 项目中的 GamePanel 类表示游戏面板类该类继承了JPanel面板类同时实现了KeyListener 键盘事件监听接口。GamePanel类有很多成员属性其中恐龙对象、背景图片对象、障碍集合和得分都是游戏界面中可以看到的元素。此外还有很多后台使用的属性如游戏结束标志、障碍计时器等。 游戏采用双缓冲机制防止界面闪烁image对象就是缓冲图片对象也可以成为主图片对象所有的游戏画面都绘制在image对象中然后再将image对象绘制到游戏面板中。GamePanel类的定义如下 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 ListObstacle list new ArrayListObstacle();// 障碍集合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();// 启动线程}
} 在paintlmage)方法中会让每一个游戏元素都执行各自的运动如背景图片的滚动、恐龙的移动和障碍的移动等。在绘制障碍之前会先判断障碍集合中的障碍对象是否是有效的如是无效障碍则会删除。paintImage0方法的具体代码如下 /*** 绘制主图片*/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);}/*** 游戏是否结束* * return*/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();// 恐龙跳跃}}Overridepublic void keyReleased(KeyEvent e) {}Overridepublic void keyTyped(KeyEvent e) {} 三 成绩对话框 成绩对话框会在游戏结束时弹出对话框中会显示目前为止记录的前3名成绩单击对话框底部的按钮会重新开始游戏。项目中的 view.ScoreDialog就是成绩对话框类该类继承JDialog对话框类。 ScoreDialog类中有一个构造方法构造方法参数为对话框的父窗体。构造方法第一行调用了父类的构造方法通过父类构造方法阻塞父窗体这样可以保证弹出成绩对话框之后主窗体内会停止全部功能且不可选中。这样可以保证玩家单击“重新开始”按钮后主窗体才会执行restart()方法。 public class ScoreDialog extends JDialog {/*** 构造方法* * param frame* 父窗体*/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() {// 按钮添加事件监听Overridepublic 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);// 显示对话框}
}五 游戏核心功能设计 一 刷新帧 帧是一个量词一幅静态画面就是一帧。无数不同的静态画面交替放映就形成了动画。帧的刷新频率决定着画面中的动作是否流畅列如电影在正常情况下是24帧也就是影片一秒钟会闪过24幅静态画面。想让游戏中的物体运动起来就需要让游戏画面不断地刷新像播放电影一样这就是刷新帧的概念。 项目中的service.FreshThead类就是游戏中的刷新帧线程类该类继承于Thread线程类并在线程的主方法中无限地循环每过20毫秒就执行游戏面板的repaint)方法每次执行 repaint0方法前都会先执行用户输入的指令这样每次绘制的画面就会都不一样极短时间内切换画面就形成了动画效果。游戏面板的isFinish)方法返回 false就代表游戏结束当前线程才会停止。 当刷新帧的业务停止后程序会获取加载游戏面板的主窗体对象然后弹出成绩对话框最后让主窗体对象重新开始新游戏。FreshThead类的具体代码如下 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();// 主窗体重载开始游戏}
}
二 滚动背景 前面我们提到了小恐龙的实际运动是在原地踏步要想实现移动效果实际上是背景图片在向后移动我们设计的背景图片一共有两张通过这两张的不断循环无缝衔接来实现背景滚动的效果。 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;// 第一幅图片初始坐标为0x2 800;// 第二幅图片初始横坐标为800g.drawImage(image1, x1, 0, null);} roll方法让图片实现不断的滚动当有任意一张图片移动出画面时就立刻回到右侧是初始位置准备下一轮的滚动。 /*** 滚动*/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);}三 碰撞检测 java awt.Rectangle类提供了intersects(Rectangle r)方法来判断两个边界是否发生了交汇。当两个边界对象发生交汇时intersects()方法的返回结果为true当两个边界对象没有交汇时intersects()方法的返回结果为false。 因为我们前面为恐龙和石头以及仙人掌做了边界处理因此我们可以用这个方法来检测是否发生碰撞。 在GamePanel游戏面板类的paintImage方法中绘制完每一个障碍后会判断刚刚绘制的障碍对象是否碰到了恐龙。利用上述的方法进行判断只要存在true结果就让游戏结束。 if (o.getBounds().intersects(golden.getFootBounds())|| o.getBounds().intersects(golden.getHeadBounds())) {Sound.hit();// 播放撞击声音gameOver();// 游戏结束}
四 键盘监听 前面GamePanel类实现了KeyListener的接口,该接口实现了三种方法(键盘监听的方法)keyPressedkeyReleasedkeyTyped.这里我们就用到了按下的监听事件实现的方法如果点击空格键就让恐龙实现跳跃的方法。 public void keyPressed(KeyEvent e) {int code e.getKeyCode();// 获取按下的按键值if (code KeyEvent.VK_SPACE) {// 如果是空格golden.jump();// 恐龙跳跃}}
【 游戏运行效果】