此次咕咕为大家准备了雷霆战机游戏开发的全过程,我将整个开发都写在了文档中,有图有真相,步步到位,供大家进行细节功能实现上的参考,除此之外,游戏所需的图片与音乐素材、逻辑脑图都一 一放在了文末的彩蛋中。由于这篇文章是对此次游戏开发的大方面的总结剖析,可能在细节方面不是讲解得特别透彻,所以提前告诉了大家文末的详情文档,也可以结合开发详情文档和文章一起参考此次雷霆战机游戏的开发吼
抢先体验我们先来看看,一个雷霆战机游戏的最终效果:
雷霆战机
观看完视频之后,相信大家对这个游戏所需要的元素已经有基本的了解了,雷霆战机游戏基本由图片素材、音乐素材和逻辑代码构成,是一个2D平面游戏;说白了,雷霆战机游戏就是由逻辑代码将图片素材和音乐素材糅合整理起来的一个东西,所以其中最重要的还是要搞清楚游戏内部的逻辑分析。
需求分析在进行开发之前,我们需要对游戏内部所具备的基本功能要了如指掌,为此,我将总体功能分为以下4种类型:
英雄战机 1.显示英雄战机 2.显示英雄战机的子弹 3.英雄战机能够移动 4.英雄战机能够连续开火,并且子弹之间需要有空隙,不能过于密集 5.英雄战机的子弹能够击杀敌机
敌方战机 1.显示敌机 2.显示敌机的子弹 3.敌机能够随机移动和变换方向(PS:未知的敌人才是最可怕的,嘿嘿嘿),并且敌机是需要随机产生的,产生之后均飞向游戏界面下方直至消失 4.敌机能够连续开火,并且子弹之间需要有空隙,不能过于密集 5.敌机的子弹能够击杀英雄战机的子弹
页面显示和音乐 1.显示游戏的基本界面,能够自定义宽高与位置 2.显示游戏的背景,兵且能够让背景图无限滚动,直至游戏结束 3.游戏需要有背景音乐,开火的声音,英雄战机击杀敌机的爆炸声 4.需要在游戏界面右上方显示英雄战机的血量值
爆炸及其其余细节处理 1.英雄战机需要具备生命值,当被敌机的子弹击中时,血量应该降低,当血量将至零时,英雄战机死亡 2.在英雄战机的子弹接触到敌机时,需要产生爆炸效果,并且敌机消失 3.在敌机的子弹接触到英雄战机时,需要产生爆炸效果;当英雄战机的血量降至为零时,敌机消失,并且升起游戏结束的文字,页面停止滚动,敌机不再产生 4.游戏需要实现超级火力模式,也就是通过按住某个键,英雄战机可连续发出满屏子弹
以上就是雷霆战机游戏的基本功能需求,有了实际的功能需求,在开发过程中就已经明确了方向,接下来的事情就是将功能需求转化为具体逻辑代码的实现。
模块化处理为了更好管理我们的代码,我们需要将游戏整体进行模块拆分的处理,每个文件负责各自的游戏功能;因此,可将整个游戏拆分成以下7个类来共同实现游戏的整体功能
主界面类:显示游戏的窗口(大小、位置、可见性)面板类:显示游戏的内容(游戏背景、战机、敌机、子弹、爆炸)英雄战机类:定义英雄战机相关的信息(大小、位置、移动、开火)英雄战机子弹类:定义英雄战机的子弹的相关信息(大小、位置、移动)敌机类:定义敌机相关的信息(大小、位置、移动、开火)敌机子弹类:定义敌机子弹的基本信息(大小、位置、移动)爆炸类:定义爆炸的基本信息(大小、位置、爆炸) 主界面类:RaidenGameMain主界面类需要实现一个window下的窗口,所以在创建主界面类的时候需要继承java的JFrame这个父类,这样才能正常的实现一个窗口。在此类我们需要在构造函数里设置窗口的宽高、位置、标题、可见性等等基本信息,然后在主函数入口实例化此类对象即可创建一个自定义窗口。 主界面类需要实现以下功能:
设置窗口在显示屏中显示的位置设置窗口的大小(宽高)设置窗口的标题设置窗口关闭程序设置窗口不允许调整大小设置窗口内的十字光标设置窗口的可见性(此设置必须放在构造函数的最末尾,否则会引发一些bug)将面板类放在主界面上,通过new一个面板类对象实现为游戏窗口添加鼠标移动监听器,处理鼠标移动的操作:主要是需要实现战机跟随鼠标移动的功能为游戏窗口添加鼠标状态监听器,处理鼠标点击与释放的操作:主要是实现英雄战机可以通过鼠标的点击与释放进行开火的功能为游戏窗口添加添加鼠标监听器,监测鼠标的按下与松开状况:主要是实现通过按下S键就可以调用超级火力的功能(这里的S键是开发者自定义的按键,如果你喜欢A键,那么也可以设置为A键)下面插入主界面类的源代码:
package com.fw.raiden;import java.awt.Cursor;import java.awt.event.KeyAdapter;import java.awt.event.KeyEvent;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.awt.event.MouseMotionListener;import javax.swing.JFrame;public class RaidenGameMain extends JFrame {/** * 我们游戏的主界面 * @author DELL */private static final long serialVersionUID = 1L;//构造方法,当创建类的对象的时候,也就是new的时候自动调用public RaidenGameMain() {// 设置显示的位置,设置窗口的坐标: x ythis.setLocation(550,10);// 设置窗口的大小: 宽 高this.setSize(800,1000);// 设置窗口关闭程序this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 设置窗口的标题this.setTitle("咕咕的压箱战机");// 设置游戏窗口不允许调整大小this.setResizable(false);// 设置游戏内部为十字光标this.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));// 将Panel放在主界面中this.setContentPane(new RaidenGamePanel());// 为游戏窗口添加鼠标移动监听器,处理鼠标移动的操作this.addMouseMotionListener(new MouseMotionListener() {// 创建鼠标移动事件public void mouseMoved(MouseEvent e) {RaidenGamePanel.myHero.mouseMoved(e);}// 创建鼠标拖拽事件public void mouseDragged(MouseEvent e) {RaidenGamePanel.myHero.mouseMoved(e);} });// 为游戏窗口添加添加鼠标监听器,监测鼠标的按下与松开状况// this指向本类实例对象this.addMouseListener(new MouseAdapter() {// 关于mousePressed方法的具体实现可以参考word文档@Override// 绑定鼠标按下的事件public void mousePressed(MouseEvent e) {// myHero是静态变量 可以全局访问// 将开火标志置为真Hero.fireFlag = true;}@Override// 绑定鼠标释放的事件public void mouseReleased(MouseEvent e) {// 将开火标志置为假Hero.fireFlag = false;}});// 设置键盘监听器this.addKeyListener(new KeyAdapter() {@Override// 绑定键盘按下事件 public void keyPressed(KeyEvent e) {// 当S键被按下时触发超级火力模式if(e.getKeyCode() == KeyEvent.VK_S) {// 调用超级火力RaidenGamePanel.myHero.superFire();}}});// 设置窗口的可见性,默认为不可见的(一定要在主界面类的构造函数的末尾再设置!!!!!!!!)this.setVisible(true);}//输入main,使用快捷键Alt + / ,选择main methodpublic static void main(String[] args) {new RaidenGameMain();}}关于鼠标事件和键盘事件监听的设置,是使用了由java提供的方法来编写的,所以这里只需要记住这些方法即可,不必硬钻其具体代码的逻辑实现,这些方法是人家已经封装好了的,我们只需要懂得用即可,这也大大提高了我们的开发效率!
面板类:RaidenGamePanel面板的作用是在主界面窗口的基础下再添加一层窗口,类似于PS中的图层叠加,具体的空间位置就是面板类始终在主界面窗口的上方
面板类需要将游戏的所有画面都使用java提供的paint方法画出来,其中包括背景图、英雄战机、敌机、子弹、爆炸等等,其中最重要的就是将背景图片滚动起来,那么这里我们需要用到线程的知识,在线程是无限循环调用paint方法让背景图片实现无限滚动的效果
以下是面板类需要实现的功能:
加载背景图片与背景音乐文件画出背景图片游戏开始时无限循环播放背景音乐,背景图片无限滚动画出英雄战机画出英雄战机的子弹画出敌机画出敌机的子弹画出爆炸使用线程类将paint方法无限循环地被调用,使界面具备动态效果下面插入面板类的源代码:
package com.fw.raiden;import java.applet.Applet;import java.applet.AudioClip;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Image;import java.awt.Toolkit;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.Random;import javax.swing.JPanel;/** *游戏内容面板 *@author DELL */@SuppressWarnings("deprecation")public class RaidenGamePanel extends JPanel {private static final long serialVersionUID = 1L;// 构造方法,用于调用线程public RaidenGamePanel() {// 启动线程MyGameThread my = new MyGameThread();my.start();}// 定义一个变量,表示的是背景图片的 y 坐标, y代表面板顶端到图片顶端的距离int y= -2400; // 说明面板顶端到图片顶端的距离是-2400,也就是说图片顶端在面板顶端的上面,所以面板里的内容实际上是图片中间部分的内容// 游戏結束,文字显示的起始位置int gameOverStrY = 1000;// 游戏結束的变量boolean gameFlag = false;// 定义和加载游戏背景图片static Image bjImg;// 通过系统的工具包类,来完成图片的加载和创建static Toolkit tk = Toolkit.getDefaultToolkit();// 加载并播放背景音乐static AudioClip ac;// 静态块static {// 加载音乐ac = Applet.newAudioClip(RaidenGamePanel.class.getClassLoader().getResource("Every Breath You Take.mid"));// 加载背景图片bjImg = tk.createImage(RaidenGamePanel.class.getClassLoader().getResource("bj002.jpg"));}// 创建战机对象static Hero myHero = new Hero(300,700);// 创建战机的子弹集合static List herMissileList = new ArrayList();// 创建敌机集合static List enemyList = new ArrayList();// 创建爆炸集合static List explodeList = new ArrayList();// 创建敌机子弹集合static List enemyMissileList = new ArrayList();@Overridepublic void paint(Graphics g) {// 给敌机增加数量if(enemyList.isEmpty()) {// 创建一个随机数 代表随机产生的战机的数量int n = new Random().nextInt(20)+10;// 遍历循环 逐个将敌机加入到集合中for(int i =0;i100 || oldMissile.live == false){//解决战机与敌机重合而无法开火的bug// 播放开火声音ac.play();// 创建新的英雄战机子弹HeroMissile missile = new HeroMissile(x+17,y - 60);// 将新的英雄战机子弹添加至集合中RaidenGamePanel.herMissileList.add(missile);// 恒赋值为上一颗英雄战机子弹oldMissile = missile;}}// 获取英雄战机区域public Rectangle getRect() {return new Rectangle(x,y,HERO_WIDTH,HERO_HEIGHT);}// 超级火力方法public void superFire(){// 一次性产生十倍的子弹 并且子弹的横坐标是依次增加的 这就造成了全屏扫射的奇观for(int i = 0;i= 1030 ) {live = false;}}// 获取敌机子弹区域public Rectangle getRect() {return new Rectangle(x,y,w,h);}// 打英雄战机的方法public void hitHero(Hero hero) {// 获取敌机子弹的区域Rectangle enemyMissileRect = this.getRect();// 获取英雄战机的区域Rectangle heroRect = hero.getRect();// 判断敌机子弹区域和英雄战机的区域是否相交if(enemyMissileRect.intersects(heroRect)) {// 如果相交 敌机子弹消亡 英雄战机的生命值减10this.live = false;int heroLife = hero.life - 10;// 当英雄战机的生命值小于且等于零时 将英雄战机的存活状态只为假if(heroLife