从零开始学AS3游戏开发【四】 敌人来袭!保护基地!

注:本系列教程每周一篇,旨在引导刚刚接触FLASH的新手通过实例进行游戏开发的学习。在过程中逐步说明涉及到的类及对应的使用方法。从一个光秃秃的方块开始,根据不同的控制方式、玩法产生不同的分支,最终完善成一个个可玩的游戏。希望对各位入门的朋友有所帮助!在教程涉及的各种处理方法,可能不够完善,也希望各位高手指正:)

转载请注名来源于天地会

第四篇 敌人来袭!保护基地!

上一篇的教程中。我们的主角可以发射子弹攻击敌人了。但是敌人还是傻傻的在屏幕内游荡。这显然没有什么乐趣。在本篇教程中。我们将让敌人可以发射子弹进行攻击。另外,将增加新的游戏单元——基地。并增加游戏规则。如果基地被敌人摧毁。则游戏结束。

还是先来计划一下要实现的功能:

1。增加基地对象,设置基地的hp为5,被敌人攻击5次后,基地会被毁灭
2。自己发射的子弹对基地无效
3。更改敌人的控制器,让它每10秒自动发射1颗子弹
4。更改敌人的控制器,让它尽量向基地移动。
5。当基地被敌人攻破时,提示游戏结束

做好计划后,开始动手吧!
1。增加基地对象。

和绘制敌人的方块和自己的方块一样,我们绘制一个一样大小的方块,里面画一个五角星。导出名设置为Skin_base
1.jpg
(本篇教程是在家里编写的。使用的是英文版的cs3,不过有了前面的几篇,大家应该可以看的懂的,位置都一样)

素材画好了,再建一个基地类,集成自FaceObject.为什么集成自FaseObject?第一,基地是有皮肤的。第二,基地是有hp的,完全符合FaceObject的要求。可能你会问,那控制器怎么办?难道还要给基地也写一个控制器?如果你只是想让基地傻傻的站再那里被攻击,当然可以从gameObject继承,但是,我们想有趣一些。再接下来的教程里,我们可能让基地自动攻击接近的敌人(塔防基础),因此,现在就暂时随便给它个basicController就可以了:)

先来看一下基地的类

  1. package D5Power.Objects
  2. {
  3.         import D5Power.Controller.basicController;
  4.         
  5.         import flash.display.Sprite;
  6.         
  7.         public class Base extends FaceObject
  8.         {
  9.                 public function Base(ctrl:basicController, face:Sprite)
  10.                 {
  11.                         super(ctrl, face);
  12.                 }
  13.         }
  14. }
复制代码

是的,我们除了继承,什么都没做。再来看一下生成基地的代码:

  1. // 生成基地
  2.                         var base:Base = new Base(new basicController(),new Skin_base());
  3.                         base.x = obj.x+obj.width;
  4.                         base.y = obj.y;
  5.                         scene.addObject(base);
复制代码

我们给了基地一个最基础的控制器(其实什么也没控制),然后给了它基地的皮肤,最后,我们把基地的坐标调整到了主角的右边。并把它添加到了游戏场景中。现在,测试一下影片,并移动主角向基地开炮吧!什么?你自己吧基地打坏了?⋯⋯

2。自己的子弹不伤害基地。

自己把自己的基地打坏实在是太不爽了(当然,有的游戏也是这样设计的,我们小时候玩的坦克大战就是这样的)。我们还是不要打坏自己基地的好。
分析一下,其实现在场景里是分两个阵营的(联盟和部落?),所以,我们可以根据阵营来判断目标是否可以被攻击。也就是说,游戏对象应该有阵营属性。我们可以考虑,如果场景里的一切对象都可以被摧毁,应该是一件很爽的事情,那么,很明显,我们把阵营这个属性付给gameObject,于是,所有对象都可以有阵营了。说做就做,给gameObject增加以下代码:

  1.                 /**
  2.                  * 所属阵营
  3.                  */ 
  4.                 public var part:uint=0;
复制代码

然后,把子弹(BulletObject)的判断攻击程序修改一下,把:

  1. if (obj.hitTestPoint(x, y, true) && obj!=_shooter && obj!=this)
复制代码

修改为

  1. if (obj.hitTestPoint(x, y, true) && obj.part!=_shooter.part && obj!=this)
复制代码

也就是说,只有阵营不一样的时候才会检测攻击。

最后,在生成各对象的时候,给他们一个阵营id,修改Main.as

  1. ...
  2.                         var obj:Player = new Player(ctrl, new Skin1());
  3.                         obj.x = 200;
  4.                         obj.y = 200;
  5.                         obj.part = 1;//增加了这一句
  6. ...
  7.                                 var monster:Monster = new Monster(ctrl2, new Skin2());
  8.                                 monster.x = int(Math.random() * 500);
  9.                                 monster.y = int(Math.random() * 300);
  10.                                 monster.part=2;// 还有这一句
  11. ...
  12.                         var base:Base = new Base(new basicController(),new Skin_base());
  13.                         base.x = obj.x+obj.width;
  14.                         base.y = obj.y;
  15.                         base.part = obj.part;//基地的阵营和主角的是一致的
复制代码

现在再测试一下程序,恩,子弹从基地穿过了,但是照样可以打到敌人。舒坦了。。

3.敌人10秒发射一颗子弹

首先,当然是为敌人实现IShoot接口,这部分和Player的射击是完全一样的,直接上代码:

  1. ...
  2. public class Monster extends FaceObject implements IShoot
  3. ...
  4.                 /**
  5.                  * 发射子弹
  6.                  */
  7.                 public function Shoot():void
  8.                 {
  9.                         var b:BulletObject = new BulletObject(this);
  10.                         b.x = x+width/2;
  11.                         b.y = y+height/2;
  12.                         Global.scene.addObject(b);
  13.                 }
复制代码

10秒发射一次子弹?这个的实现思路可以参考我们第一篇教程所讲的定时渲染。修改MonsterController代码,增加以下两个属性:

  1.                 /**
  2.                  * 最后一次射击时间
  3.                  */ 
  4.                 protected var lastShoot:Date;
  5.                 /**
  6.                  * 射击间隔,单位秒
  7.                  */ 
  8.                 protected var shootSpeed:uint = 10;
复制代码

在构造函数中,把射击时间进行初始化:

  1.                 public function MonsterControler() 
  2.                 {
  3.                         lastShoot = new Date();
  4.                         super();
  5.                 }
复制代码

在控制器的自动运行函数AutoRun中,增加自动射击部分的代码:

  1.                         // 自动射击
  2.                         var now:Date = new Date();
  3.                         if(now.time-lastShoot.time>shootSpeed*1000)        // 当距离上一次射击的时间间隔超过规定时间时运行
  4.                         {
  5.                                 me.Shoot();        // 射击!
  6.                                 lastShoot = now;// 更新最后一次射击时间
  7.                         }
复制代码

这个实现的原理其实和定期渲染是一样的。编译测试,可以发现敌人的坦克在行进的过程中会时不时的发射子弹了!我们继续进行下一步吧!


4。聪明的敌人,向基地开炮!

想我们现在这样,敌人基本上永远都不会击中基地的。我们现在让敌人聪明一些,如果在巡航的过程中,与基地在同一直线上,那么有60%的几率向基地开炮,什么?你想问为什么不是100%。恩。。你觉得如果这样,玩家还有的玩么。。。

修改AutoRun函数,增加一下代码(在“if (!me.nextCanMove) changeDir()”一句以后增加)

  1. if(Global.base!=null && (Math.abs(me.x-Global.base.x)<10 || Math.abs(me.y-Global.base.y)<10))
  2.                         {
  3.                                 if(Math.random()>0.6) return;
  4.                                 if(now.time - lastShoot.time<2000) return;
  5.                                 var tempDir:uint = me.direction; // 临时记录方向
  6.                                 // 转向基地
  7.                                 var r:Number = Math.atan2(me.y-Global.base.y,me.x-Global.base.x);
  8.                                 var a:int = r/Math.PI*180;
  9.                                 
  10.                                 if(a>80 && a<100)
  11.                                 {
  12.                                         me.direction = ActionObject.UP;
  13.                                 }else if(a>170 && a<190){
  14.                                         me.direction = ActionObject.RIGHT;
  15.                                 }else if(a>260 || a<-80){
  16.                                         me.direction = ActionObject.DOWN;
  17.                                 }else{
  18.                                         me.direction = ActionObject.LEFT;
  19.                                 }
  20.                                 me.Shoot();
  21.                                 lastShoot = now;
  22.                                 me.direction = tempDir;
  23.                         }
复制代码

来分析一下代码。首先,触发的条件是基地被设置了,而且敌人的坐标与基地的坐标相差不超过10。满足这个条件后,才会向基地射击。之后,取随机数,如果随机数大于0.6,则终止程序运行。其次,如果射击间隔小于2秒,也终止程序运行(Q1).接下来,把敌人当前的方向保存到tempDir中。之后计算基地与敌人的夹角。使用atan2方法返回由X 轴到(y,x) 点的角度,一定要注意,第一参数是y,第二参数是x。这个地方连flash builder 4的提示都是错的!。现在我们以基地为原点计算夹角,自然就是在敌人坐标的基础上减去基地坐标。获得弧度值后,再转换为角度值(这段如果看不懂,可能需要取翻一下高中数学的课本了,呵呵)。之后,根据角度值来判断基地所在的方位,并把敌人的方向转向基地。射击后,更新射击时间,并恢复敌人的方向(Q2)

关于Q1:
为什么要设置2秒的间隔。首先,敌人射击是10秒自动的,当到了10秒的时候,就会射击,然后自动更新射击时间。如果向基地射击的时间也沿用这个判断,那可能永远也不会向基地射击。而如果不加入时间判断,那可能在敌人经过基地附近的一瞬间会发射n颗子弹。

关于Q2:
为什么要改回敌人的方向?如果不改回,敌人将向基地方向运行,并定期发射子弹。这样难度大了些。当然,如果你喜欢,可以不改回来:)

现在运行程序,敌人会在与基地在同一直线的时候转身给基地一炮。不用多久,基地就被干掉了。

5。覆盖实现基地的Hurt代码:

  1.                 override public function Hurt(val:uint):void
  2.                 {
  3.                         super.Hurt(val);
  4.                         if(_hp<=0)
  5.                         {
  6.                                 Global.base=null;
  7.                                 trace('基地被摧毁!');
  8.                         }
  9.                 }
复制代码

当基地的hp小于0的时候,把Global.base设置为null,这样敌人就不会再对基地射击了(第一条件不满足),最后,输出信息“基地被摧毁”

现在,我们这个小程序已经有一点意思了。至少你放再那里不管,你的基地一会就会被摧毁了。各位兄弟可以发挥自己的想象,如果除了基地外,敌人也会自动攻击我们的主角呢?我想,看完这篇也不难实现了吧!

不过,现在的程序实在是太难看了,也不知道角色的方向到底朝向哪里。另外,好像角色间可以穿过!在下一篇教程里,我们将修改现在使用的皮肤,并引入碰撞检测。

以下是本篇教程的最终效果和源码:

 main.swf (3.9 KB) 
 Teach.zip (22.09 KB)

原文地址:https://www.cnblogs.com/keng333/p/2304961.html