注:本系列教程每周一篇,旨在引导刚刚接触FLASH的新手通过实例进行游戏开发的学习。在过程中逐步说明涉及到的类及对应的使用方法。从一个光秃秃的方块开始,根据不同的控制方式、玩法产生不同的分支,最终完善成一个个可玩的游戏。希望对各位入门的朋友有所帮助!在教程涉及的各种处理方法,可能不够完善,也希望各位高手指正:)
转载请注名来源于天地会。
前篇勘误:gameScene.as的public function removeObject中,(48行)if (id != -1) return;应为if (id == -1) return;特此更正。请各位谅解。本篇源码已修正本错误。
第三篇 向敌人开火!
在上一篇教程中,我们向游戏场景中增加了随机移动的3个敌人(当然,你也可以改成更多),同时,我们的角色也可以在场景中进行移动。但是,并没有产生攻击。在这次的教程中,我们将让自己的角色可以发射子弹,并攻击敌人。
为了实现这个功能,我们必须逐一实现以下内容:
1.构建子弹类
2.修改控制器,让自己可以射击
3.在子弹类中控制子弹的飞行
4.检测子弹是否击中敌人或飞出屏幕
5.新增敌人和自己的HP属性,当被子弹击中后,HP减少
6.敌人死亡
下面,我们来逐一进行实现。
0.一些必要的结构调整。
可以预见的,子弹的飞行方向应该与角色的移动方向相符。但在目前我们的程序中,ActionObject具备方向这个属性,但是它除了控制方向外,还肩负了是否产生运动的判断。(当walkDirection为0的时候,不执行move方法)。这样,当角色处于停止状态(walkDirection为0),我们就不好判断子弹的发射方向了。因此,必须进行一点小的修改。
在ActionObject中,把walkDirection的默认值改为1(向上),增加一个属性_isWalk
- /**
- * 移动方向
- */
- protected var walkDirection:uint = 1;
- /**
- * 是否移动
- */
- protected var _isWalk:Boolean = false;
- ...
- public function set isWalk(val:Boolean):void
- {
- _isWalk = val;
- }
修改Do方法为根据_isWalk属性来判断是否移动
- /**
- * 覆盖父类的Do方法
- */
- override public function Do():void
- {
- if (_isWalk != 0) move();
- super.Do();
- }
最后,修改KeyController的按键侦听函数:
- /**
- * 当按键按下时触发
- * @param e
- */
- protected function onKeyDown(e:KeyboardEvent):void
- {
- var me:Player = _target as Player; // 将me修改为Player类,方便后面的射击实现
- switch(e.keyCode)
- {
- case 38:
- me.direction = ActionObject.UP;
- me.isWalk = true;// 增加了这一句,将isWalk设置为true,进入行走状态
- break;
- case 40:
- me.direction = ActionObject.DOWN;
- me.isWalk = true;// 增加了这一句,将isWalk设置为true,进入行走状态
- break;
- case 37:
- me.direction = ActionObject.LEFT;
- me.isWalk = true;// 增加了这一句,将isWalk设置为true,进入行走状态
- break;
- case 39:
- me.direction = ActionObject.RIGHT;
- me.isWalk = true;// 增加了这一句,将isWalk设置为true,进入行走状态
- break;
- default:trace(e.keyCode); break;
- }
- }
- /**
- * 当按键弹起时触发
- * @param e
- */
- protected function onKeyUp(e:KeyboardEvent):void
- {
- var me:ActionObject = _target as ActionObject;
- var active:Array = new Array(37,38,39,40);
- if (active.indexOf(e.keyCode) != -1)
- {
- me.isWalk = false; // 增加了这一句,恢复isWalk为false
- }
- }
同理,修改MonsterController的changeDir方法:
- /**
- * 随机修改方向
- */
- private function changeDir():void
- {
- var me:ActionObject = _target as ActionObject;
- me.direction = 1 + int(Math.random() * 4);
- me.isWalk = true; // 增加了这一句
- }
1.构建子弹类。
首先简单分析一下子弹:它的计算比较简单,可以不需要控制器(当然,如果你喜欢,你可以给它加一个控制器),因此,我们直接从GameObject继承即可。子弹可以自己飞行,并会击中目标。我们需要在Do方法里增加飞行控制。子弹有外观,我们现在可以暂时用绘图函数来自己绘制。同时,我们不难想到,子弹的飞行是有方向的。而这个方向和角色面对的方向有关。在我们目前的代码中,ActionObject是具备方向属性的。最后,子弹必须可以准确击中敌人,而不是自己。因此,子弹有必要记录发射者是谁,方便识别敌友。
我们逐一来实现功能。首先,当然是构造函数,我们需要在子弹被声明的时候就确认它是由谁发射出来的,因此,子弹首先需要有发射人这个属性,并在构造函数中予以声明。同时,子弹在发出的瞬间,应该获得发射人的方向,之后,不管发射人如何运动,子弹的运动轨迹不受影响。最后,通过绘图函数绘制出子弹:
- /**
- * 子弹发射人
- */
- protected var _shooter:gameObject;
- /**
- * 子弹的飞行方向
- */
- protected var _direction:uint;
- public function BulletObject(shooter:gameObject)
- {
- _shooter = shooter;
- _direction = _shooter.direction;
- graphics.beginFill(0x000000);// 用黑色开始填充
- graphics.drawCircle( -1, -1, 1);// 绘制一个半径为1的圆,为了保证圆心在正中,我们把圆从-1,-1这个坐标开始绘制
- graphics.endFill();// 结束绘制
- }
graphics是一个Graphics类的实例。是Sprite的一个属性。我们可以通过他来绘制矢量图形。详细的方法可以参阅Graphics类的API文档
子弹的飞行需要速度,因此,子弹必须有速度这个属性,然后,通过发射人的方向来判断子弹的飞行方向,并做出处理:
- /**
- * 子弹飞行速度
- */
- protected var _speed:Number = 10.2;
- override public function Do():void
- {
- switch(_direction)
- {
- case ActionObject.UP:
- y -= _speed;
- break;
- case ActionObject.DOWN:
- y += _speed;
- break;
- case ActionObject.LEFT:
- x -= _speed;
- break;
- case ActionObject.RIGHT:
- x += _speed;
- break;
- default:break;
- }
- }
最后,子弹击中目标后,应该消失。因为我们通过graphics绘制了图形,所以,一个好的习惯就是在它消失前,把graphics清理掉
- override public function die():void
- {
- graphics.clear();
- }
到这里,子弹类的结构就搭建好了。我们将在后面再根据实际的需求逐步对子弹类进行完善。
2.修改控制器,让自己可以射击
首先,我们要让角色可以射击。我们可以预知,除了自己可以射击以外,敌人也应该可以射击(虽然在本次的教程中,我们暂时只实现了自己的角色进行射击)。所以,“射击”这个动作应该是一个统一的标准。因此,我们通过接口来实现它。
下面,我们来定义射击接口。
新建一个包MyInterface,在文件夹上点右键,选择New Interface选项
属性文件名IShoot.as,使用I作为接口的命名,是一个很好的习惯。以区分它和其他文件。
接口不实现任何方法,也不能包含任何属性。他只不过是一个标准的定制。射击接口的代码如下:
- package D5Power.MyInterface
- {
- /**
- * 射击接口
- * @author D5Power
- */
- public interface IShoot
- {
- /**
- * 射击方法
- */
- function Shoot():void;
- }
- }
在这个接口中,我们只规定了一个Shoot方法。随着游戏的扩展,可以丰富这个接口,让它去实现更多的功能。
标准定义好了,我们需要在实际操作中来让相关人等遵守这个标准。修改Player类:
- ...
- import D5Power.MyInterface.IShoot;
- ...
- public class Player extends FaceObject implements IShoot
我们通过implement关键字,让Player类实现了IShoot接口。之后,必须实现接口所规定的全部标准(方法)。在IShoot接口中,我们只写了Shoot一个方法,因此,Player既然已经声明支持IShoot接口,它就必须要有Shoot方法,否则FLASH将提出严正抗议(编译报错。。。)
- ...
- public function Shoot():void
- {
- ...
- }
- ...
只有在实际实现接口的类的对应方法中,才能有函数结构({}里的内容),并声明函数的作用域(public,protected,或其他)。而在接口的声明(IShoot.as)里,是不允许这样做的。关于接口的详细介绍,可以参考《Flash ActionScript 3 殿堂之路》的相关章节:)这里不再过多描述。
考虑一下Shoot方法的内容。无非就是声明一个新的子弹类,然后把它加到场景里面去,也就是说,需要使用gameScene.addObject方法,但是。我们目前没有任何位置可以访问到gameScene,因此,我们需要修改Global.as:
- /**
- * 直接调用游戏场景
- */
- public static var scene:gameScene;
而把gameScene的构造函数,修改为以下内容:
- /**
- * 创建游戏基本场景需要传递基本舞台这个参数
- * @param _stage 舞台
- */
- public function gameScene(_stage:Stage)
- {
- Global.stage = _stage;
- Global.scene = this; // 增加了这句
- objectList = new Array();
- Global.stage.addEventListener(Event.ENTER_FRAME, render);
- }
做了这两项准备后,我们可以编写Player的Shoot方法了:
- /**
- * 发射子弹
- */
- public function Shoot():void
- {
- var b:BulletObject = new BulletObject(this);
- b.x = x+width/2;
- b.y = y+height/2;
- Global.scene.addObject(b);
- }
从代码中我们可以看到,我们声明了一个新的子弹,并把子弹移动到当前自己角色的坐标的位置(我们根据角色的宽度和高度调整了子弹的位置,你可以考虑一下,这是为什么),并向游戏场景添加了这个对象。
最后,我们只要修改控制器,当按下合适的按键时,触发这个射击方法就OK了。我们现在选择了空格作为发射键,ASCII码为32,因此,修改KeyController的onKeyDown代码:
- /**
- * 当按键按下时触发
- * @param e
- */
- protected function onKeyDown(e:KeyboardEvent):void
- {
- var me:Player = _target as Player;
- switch(e.keyCode)
- {
- case 38:
- me.direction = ActionObject.UP;
- me.isWalk = true;
- break;
- case 40:
- me.direction = ActionObject.DOWN;
- me.isWalk = true;
- break;
- case 37:
- me.direction = ActionObject.LEFT;
- me.isWalk = true;
- break;
- case 39:
- me.direction = ActionObject.RIGHT;
- me.isWalk = true;
- break;
- case 32:
- // 空格射击
- me.Shoot();
- break;
- default:trace(e.keyCode); break;
- }
- }
发布测试一下,我们已经可以看到,在按下空格后,我们的角色在发射子弹了。不过,子弹不会击中敌人,超出屏幕范围后,也没有做对应的删除操作。OK,我们继续来实现其他功能。
3-4 子弹的飞行及碰撞检测 我们知道,在场景中的所有游戏对象都记录在对象列表gameScene.objectList中,因此,只要逐个判断是否与列表中的对象发生碰撞,就知道子弹击中哪个对象了。当然,不能击中自己,呵呵。在此之前,我们没有从外部调用过objectList,而它是一个protected的变量,从外部是不能访问的,因此,我们来写一个get方法来让外部可以对他进行读取。
继续来实现碰撞检测的代码,可想而知,我们应该把检测代码写到子弹类的onDo函数里。这样在子弹飞行的时候可以随时判断他是不是发生了碰撞:
另外,子弹飞出屏幕范围后应该被清除。因此,我们有必要判断子弹的目前坐标是否符合要求,代码比较简单:
我们的需求写到了,给敌人和自己增加HP属性。或许你会想到,那就直接给gameObject增加HP属性就行了。然后它的子子孙孙就都有HP属性了。这样显然是不合理的。很简单的一个例子:子弹也是gameObject,但是子弹显然不应该有HP。我们选择了FaceObject来增加HP属性,你也可以选择ActionObject来增加。根据自己的实际情况来判断:)
最后,当然是把这些补充到我们刚才的“// 这里编写伤害敌人的代码”的位置。这段代码现在看起来应该是这样的:
这里,我们的伤害值是固定的,如果你想通过道具或其他途径,改变伤害值。可以把伤害值写成一个属性,具体的实现方法各位可以自己去思考:) 现在,我们黑方块已经可以发射子弹攻击红色方块了。在下一篇教程中,我们将使敌方角色也发射子弹,并增加基地对象,并使敌人优先攻击基地。 本篇最终效果如下: main.swf (3.3 KB) 本篇的最终源码如下: |
-
Teach.rar (16.73 KB)