1、4251 10011 0010 1010 1101 0001 0100 1011第13章 游戏设计案例详解4251 10011 0010 1010 1101 0001 0100 1011模拟钢琴游戏太空射击游戏点灯游戏4251 10011 0010 1010 1101 0001 0100 1011案例一:模拟钢琴游戏4251 10011 0010 1010 1101 0001 0100 1011钢琴游戏的类结构 接下来要做的是:在木纹图像上画出一个个的琴键; 然后给每个琴键编写一个演奏的play()方法。4251 10011 0010 1010 1101 0001 0100 1011实例化一个
2、琴键对象 在Piano类的构造方法Piano()中添加: Key key_g=new Key(); addObject(key_g ,300,180);为什么?4251 10011 0010 1010 1101 0001 0100 1011实现琴键的动画效果实现琴键的动画效果 打开Key类的act()方法,在其中添加相应的琴键动画效果代码。4251 10011 0010 1010 1101 0001 0100 1011给琴键添加声音效果 按下G键,发出“哆”的声音准备声音文件“3a.wav”在key类的act()方法中添加如下代码:public void act() if( Greenfoot
3、.isKeyDown(g)/如果按下g键 Greenfoot.playSound(3a.wav); /演奏声音文件“3a.wav” setImage(white-key-down.png);/显示键被按下的效果图片 else setImage(white-key.png);/显示键被松开的效果图片 4251 10011 0010 1010 1101 0001 0100 1011完善琴键的声音效果 如果一直按住G键,琴键对应的声音文件连续播放现实中的钢琴演奏是按下一个琴键,钢琴奏出一个音符,这个音符的长度不受琴键被按下的时间长短所控制。解决办法是先声明一个变量,用这个变量记录下某个琴键是否被按下
4、的状态,然后在if条件判断时考虑这个状态变量的情况。 通常人们将这种记录某个对象的状态值的变量称为标记变量,大多数情况下,标记变量是个boolean类型的变量,它只有true或false两种状态。4251 10011 0010 1010 1101 0001 0100 1011编写多个琴键 每个琴键对象对应一个电脑键盘按键和一个声音文件。钢琴有几十个键,需要在Key类的act()方法里面用几十组if语句来为每个琴键对象指定其电脑按键和声音文件,势必act()方法的代码会很长很复杂,有很多的代码重复。 能不能在实例化生成每个琴键对象时,就指定这个琴键对象的电脑按键电脑按键和声音文件呢?即编写一个通
5、用的Key()构造器,使它能够将电脑按键和声音文件作为参数传入到构造器中,如Key(String keyName,String soundFile),这样在创建每个琴键对象时就为其指定了电脑按键和声音文件。4251 10011 0010 1010 1101 0001 0100 1011用循环添加多个琴键 实现了通用的琴键Key构造器,就可实现多个琴键了int i = 0;while (i 12) Key key = new Key( “g”, “3a.wav); addObject(key, 54 + (i*63), 140); /每隔63个像素绘制一个琴键 i = i + 1;4251 10
6、011 0010 1010 1101 0001 0100 1011用数组来完善钢琴游戏 目前为止,所有的琴键都是目前为止,所有的琴键都是G G键键 如何生成每个都不一样的琴键呢?如何生成每个都不一样的琴键呢?即前面的循环语句中,如何向Key构造器中传入不同的键名和声音文件 Key key = new Key( “键名”, “声音文件); 最好的办法是数组循环中需要每次循环传入不同的值,最好的办法是数组因为把数组的下标作为循环变量,就使得数组和循环二者结合起来了4251 10011 0010 1010 1101 0001 0100 1011绘制黑色琴键 黑色琴键是对应白色琴键的升半调音符设计一个
7、黑色键名数组和一个黑色键音符文件名数组 与白色键一样,黑色键也有两张图像在生成琴键对象的时候,就将琴键所对应的效果图片传入到其构造器中不用担心要在Key类的act()方法中使用麻烦的if语句来区分是黑色还是白色琴键 使得Key类的act()方法更具通用性,使得程序更加易于扩展,如容易继续添加其它类型的按键 4251 10011 0010 1010 1101 0001 0100 1011案例二:太空射击游戏4251 10011 0010 1010 1101 0001 0100 1011太空射击游戏中的类4251 10011 0010 1010 1101 0001 0100 1011太空射击游戏对
8、象关系图 4251 10011 0010 1010 1101 0001 0100 1011太空射击游戏类关系图 4251 10011 0010 1010 1101 0001 0100 1011源代码比较Ship、Enemy和Laser三个类三者的代码重复部分 都有用setLocation(int x,int y)实现从一个位置坐标到另一个位置坐标的匀速直线运动 Ship类和Enemy类之间,以及Enemy类与Laser之间的碰撞检测 碰撞发生后有爆炸效果和战果记分的相关代码大部分是相同的,只有少许差异 由此可能带来的问题setLocation(int x,int y)中的坐标从int型改成do
9、uble型(这样就可以处理比较精确的小数了),这样的修改在三个类中都得修改 程序扩展比较困难 ,如增加一个飞碟类Saucer 飞碟Saucer类的代码与Ship类和Enemy类的基本相同 哪个类中的代码有点问题,其它所有类都得同步修改 4251 10011 0010 1010 1101 0001 0100 1011代码重复问题解决最好办法:继承继承 继承是一种解决重复问题的机制首先,将Ship、Enemy和Laser三个类的共同部分抽取出来,单独地做成一个Sprite类 然后,分别声明Ship、Enemy和Laser是这个Sprite类的子类最后再分别在Ship类里加上Ship自己独有的部分,
10、在Enemy类里加上Enemy独有的部分,Laser也是如此 Ship、Enemy和Laser三个类都继承Sprite类,它们都是Sprite类的子类 三个类中共同的属性只需描述一次就够了 子类的设计变得简单了,只需考虑自己的特有部分就行了,再也不用操心如何与其它类的代码保持一致的难题4251 10011 0010 1010 1101 0001 0100 1011继承程序结构的类图 4251 10011 0010 1010 1101 0001 0100 1011运用继承后的游戏类图 4251 10011 0010 1010 1101 0001 0100 1011用抽象类来优化程序结构 程序结构
11、的进一步分析 Ship、Enemy类的act()方法,发现两个类中都有关于碰撞检测、对象重新生成和游戏记分的功能代码 与下面的伪代码是一致的,只是实现代码不同。4251 10011 0010 1010 1101 0001 0100 1011如何优化?能否将它们抽象出来,归入Sprite类呢?Ship类和Enemy类中相同功能的实现代码不一样不能简单地将它们移植到超类中应该在更高的层次上将二者的雷同部分抽象出来然后归入到超类Sprite中 解决办法抽象类为两个类中的功能相同部分的代码设计一个名为处理碰撞的handleShotCollision()方法但是Ship类和Enemy类对于handleS
12、hotCollision()方法的具体实现代码可以不同。解决这个问题,需要用到Java中抽象类的技术 4251 10011 0010 1010 1101 0001 0100 1011案例三:点灯游戏4251 10011 0010 1010 1101 0001 0100 1011需要考虑的问题牌桌上有两组扣起来的牌鼠标点任一张牌牌正面翻过来鼠标连续翻开两张牌灯泡阵列如何生成灯泡阵列如何生成 翻开的牌不同牌自动扣起来牌桌上有两组扣起来的牌鼠标点任一张牌牌正面翻过来鼠标连续翻开两张牌灯泡的明暗转换灯泡的明暗转换 过关场景转换过关场景转换的问题的问题 关卡的设计问题关卡的设计问题 灯泡对象的创建灯泡对
13、象的创建 怎样改变周围灯的怎样改变周围灯的明暗状态明暗状态4251 10011 0010 1010 1101 0001 0100 1011关卡的设计问题 关卡设计游戏各个关卡的场景是一组灯泡阵列,因此将每一关的场景用一个三维整数数组bulb来表示。数组最外面一维(第一维)用整数04分别表示第一关第五关,当然如果关卡数多,就用更多的数字。 4251 10011 0010 1010 1101 0001 0100 1011过关场景转换 文字信息提示是过关场景转换的标志从最开始到进入第一关、从前一关进入后一关,以及游戏全部结束等几个状态的转换。几个状态转换时都应该给游戏者一些文字提示信息,表明现在是游
14、戏开始,还是中间第几关,或者是游戏全部结束。 游戏根据游戏者的鼠标动作来决定游戏进程的流转 if ( 游戏界面上显示了文字 ) if ( 游戏者用鼠标点击游戏面板 ) 进入下一关/游戏者在玩游戏else if ( 面板上的等全部是亮的 ) 在面板上显示“过关”或“游戏全部结束”的文字信息4251 10011 0010 1010 1101 0001 0100 1011灯泡对象的创建 游戏面板上的每一盏灯都是一个Light对象 初始时,根据所处的关卡,每个Light对象预置其初始的明暗状态值。即Light类应该有一个boolean型的字段isON用来记录当前此Light对象的明暗状态。另外,准备两
15、张表示灯泡明暗的图片,保存到项目文件夹中的images子文件夹。同时在Light类中设计两个GreenfootImage图片对象指向那两张图片。Light构造器需要传入创建这个Light灯泡对象时它应该的明暗状态值。public Light(boolean isOn) 4251 10011 0010 1010 1101 0001 0100 1011灯泡的明暗转换运行过程中,Light对象时刻捕捉当前游戏者的鼠标是否点击了此Light对象这个可以使用Greenfoot的静态方法mouseClicked(Object o)来实现。当捕捉到鼠标对此Light对象的点击后,用代码将此Light对象的明
16、暗状态进行切换。public void switchOnOff()/切换灯泡的明暗状态的方法 if (isOn) isOn = false; setImage(offImage); else isOn = true; setImage(onImage); 4251 10011 0010 1010 1101 0001 0100 1011灯泡阵列生成 灯泡阵列的生成是:将三维数组bulb中的灯泡二维(4x4)阵列元素位置上的“1”或“-1”转换成灯泡对应的明暗状态图片,并显示在场景面板上。 一个重要的问题: bulb数组中灯泡二维阵列元素数值的坐标系该如何转换成游戏场景面板上的灯泡图片坐标系 42
17、51 10011 0010 1010 1101 0001 0100 1011改变周围灯的明暗状态 鼠标点击了面板上的一盏灯这盏灯周围的灯(包括它自己)的状态都要被切换也就是要找出这盏灯周围的所有灯 Greenfoot中Actor有一个getNeighbours(1,false,Light.class)方法 可以帮助编程者获取这盏灯周围距离为一个单元格范围内的所有Light对象的集合。 需要设计一个List集合来保存这些找到的Light对象 4251 10011 0010 1010 1101 0001 0100 1011点灯游戏的对象关系图4251 10011 0010 1010 1101 00
18、01 0100 1011灯泡Light类的设计 Light类的成员字段:保存灯泡明暗状态的boolean型的isOn属性字段 两个GreenfootImage型的灯泡明暗图片对象onImage与offImage 面板Table对象中要检查面板上的所有灯泡是否全部被点亮 所以Table对象要修改Light对象的isOn属性 那么Light类中的isOn属性应该设计成public 否则就设计专门的成员方法来实现从Light对象的外部来对Light成员属性的修改 4251 10011 0010 1010 1101 0001 0100 1011面板Table类的设计 Tabel类的作用游戏各关卡场景的
19、生成 关卡状态的转换 各关卡场景的生成 每一关场景中的灯泡阵列由一个整型的三维数组决定 三维数组是游戏设计者按照难易程度递增的原则制订出来的 4251 10011 0010 1010 1101 0001 0100 1011Table类Table的属性字段level 表示游戏关卡的第几关,游戏开始之前它的初值为0 isEnded用来判断游戏是否全部结束,其初值应该是falseTable的成员方法 4251 10011 0010 1010 1101 0001 0100 1011游戏关卡状态 判断点灯游戏各关卡之间的状态:检查游戏面板界面上是显示说明信息还是显示灯泡阵列。当显示说明文字时 用鼠标点击
20、面板界面,游戏进入灯泡阵列的界面,即进入下一关;当面板上的灯泡全部被点亮 游戏又进入游戏的等待状态,即显示说明信息阶段。 4251 10011 0010 1010 1101 0001 0100 1011Table面板上灯泡全点亮如何判定Table上的灯泡全部被点亮遍历一堆的灯泡对象,显然需要用到本章学习的List集合。Greenfoot的World类提供了一个getObjects(java.lang.Class cls)方法可以获得场景World中一个类的所有对象。Table类是继承World类的,所以Table对象可以调用这个getObjects()方法。在Table类的act()方法中完成
21、灯泡全亮的检查先创建一个包括面板上所有Light对象的集合lights,然后用for循环遍历集合lights,检查每一盏灯泡是否全部被点亮。 List lights = getObjects(Light.class);for (Light light : lights) isAllOn = isAllOn & light.isOn; / 遍历检查每一盏灯泡,看是否完成关卡 4251 10011 0010 1010 1101 0001 0100 1011游戏文字信息类Text的设计 Text负责向游戏面板上显示该显示的文字说明信息Greenfoot中的处理办法是把要显示的文字做成图像对象即GreenfootImage图像对象 建议查看GreenfootIamge的文档public class Text extends Actor public Text( String text, int size, Color color) setImage(new GreenfootImage(text, size, color, new Color(255, 255, 255, 191); 4251 10011 0010 1010 1101 0001 0100 1011作业 对本章介绍的游戏案例进行扩展,加入更多功能效果