[翻译]国外的一篇AS2飞机教程(1)

作者自己的水平应该是很高的了,但从游戏的模式上而言,这个游戏并没有太多的创新,仍旧是无限循制,
不过想法上已经相当完整了,有这样一个很好的开发习惯,想做大的东东,就不会无从下手了。
作者写此文章的主要目的是在于介绍给大家如何真正用面向对象的AS2来写游戏,
所以,从这个角度而言,这篇文章还是相当优秀的,尤其是在国内这方面资料比较馈乏的时候,
这种文章可谓雪中送碳的佳举,我翻译一下,一方面是自己有提高,另一个方面,也算是给广大的
FLASHER做点贡献吧~~~~(文章已被闪吧收录,可以到这里下源代码
   http://www2.flash8.net/teach/2715.htm
)
(第一次翻国外的东东,所以有什么不地道的地方,敬请高手斧正.
对了,要是作者本人给你发邮件说想找EmilMatthew收中文版的版权费时,你们就说不认识我啊,
谢谢啦~~~:)
)

这篇文档是我第一个用OOP(面向对象的编程)方法写的AS2游戏。
这个一个标准的空间打击敌人的游戏,有三架敌机和一架游戏者的喷火镭射机。
起初只会有一关,但是设计上可以使得增加关数的可能性变得非常方便。
游戏中会有基本的分数以及生命值。
和别的正在挣扎着实行在FLASH MX2004中的新的ACTIONSCRIPT2.0语法的人一样,
这篇文档也是为了我个人学习的好处。
我会遵照一个合理的传统的OOP设计的结构。
非常感谢并尊重来自http://www.1coinplay.com的Squize让我使用他的小精灵纸(?)来做为这个例子。

1要求文档。
2使用事件。
3类的表格。
4交互的表格。
5试验最初的代码。
6迭代(Iteration)
7测试。
8发布。

*************************************************************************************
1要求:
Spaceship:
上下左右移动。
通过空格键来发射镭射。

Enemy:
在一个随机的高度从右移到左。
有三个实例。
当它从左移出屏幕时,重新回到右侧。

BackGround:
从左移到右。
前景要比背景移动的快(视差(PARALLAX))

Laser:
当空格键按下时火焰从飞船中喷出。

Game:
如果射到敌人,游戏者得到分数。
如果飞船撞到敌机,则会损失生命值。
如果游戏者得到了胜利的分数,那个他(她)就赢了,游戏重新开始。
如果损失生命数超过三,游戏结束,游戏者失败。

2使用的事件:
使用的事件是用图表来代表(设计的)要求。它们展示了在游戏中会发生的不同的事件,或者是所有会发生的不同的动作。


这就是你所有所要做的。仅仅是把它们在一张纸上完整的实现(NUT ?),
这张表格能很好的给最终将使用的类及方法一个好的提示。
当你做完这些以后,该是你搞定你会要使用什么类以及这些类需要什么什么属性及方法.


类的表格:
类的表格展示了类之间的关系。
在我们下面的这张表格里:飞船,敌人,镭射及背景类都包含在进了游戏类.
这意味着那些类的实体被在游戏类中声明了。
这被叫做取合或者是“有一层”关系而区别于被描述为“是一种”关系的继承。?
游戏类被取聚合进了我的Flash文件里。

类的描述:
宇宙飞船类
属性或方法    类型      描述
speed       Number  x变量的增量
moveShip()  method  用按键来移动飞船
setShipX(x) setter  _x变量获取值的方法
getShipX()  getter  返回_x值
setShipY(y) setter  _y变量获取值的方法
getShipY()  getter  返回_y值

敌机类
属性或方法    类型      描述
enSpeed     Number  x变量的增量
moveEn()    method  随机的移动敌机
setEnX(x)   setter  _x变量获取值的方法
getEnX()    getter  返回_x值
setEnY(y)   setter  _y变量获取值的方法
getEnY()    getter  返回_y值

镭射类
属性或方法      类型      描述
fire() method start   当空格键按下时移动
setLaserX(x)  setter  _x变量获取值的方法
getLaserX()   getter  返回_x值
setLaserY(y)  setter  _y变量获取值的方法
getLaserY()   getter  返回_y值

背景类
属性或方法      类型      描述
scroll()     method   从右移到左
setBackX(x)  setter   _x变量获取值的方法
getBackX()   getter   返回_x值
setBackY(y)  setter   _y变量获取值的方法
getBackY()   getter   返回_y值

游戏类
属性或方法              类型      描述
score                Number   游戏者的得分
lives                Number   游戏者的生命数
checkCollisions()    method   碰撞检测
CheckLives(lives)    method   如果生命值为0就停止游戏。
checkScore()         method   检测并展示分数

交互的表格
交互表格述了通过方法传递的信息来符合我们一开始所提出的要求。
我现在对这项计划是否有效没有任何概念,因为我不知道Flash是否能处理组合。
游戏类包含着其它类并且是游戏的管理者。
但是我并不清楚是否能在一个类中控制一个继承于MovieClip类的子类(的实例).
那好,这里就是这计划,让我们看一下它是否能起作用。
计划不是被放在混凝土里的。如果它们无效,你可以把它们扔掉,或者是修改,甚至从草稿重新开始。
最重要的事情是过程。
通过过程来明确问题并呈现给我们处理问题的方法。这就像给电影的情节串连图版.
你草拟出它,呈现出脚本的想法同时也瞄准了观众的需求。
一旦你把这个都搞定了,你就把它扔在了一边并用本能进行着导演。
你知道故事的背景因为你浏览了情节串连图版的过程。这里也是如此:

上面是我画的一个份交互的表格。
我甚至做了一些改变并意识到一些在类表格中里所遗漏的内容。
就像那个moveship方法需要一个方向(左,右,上,下)并且我能过一个变量dir来传递按了哪个键。
dir可以是string类(“left”)或者是一个字母(1,2,3,4).这并不重要。

测试优先:
我新建了一个新的Flash 文件并创建了一个spaceship_mc(mc),把它连接到库里的ship_mc并把“Spaceship”放进连接(link)AS2 CLASS选项中。
然后在交互面板中我写下了下面的代码:
//连接spaceship_mc
attachMovie("ship_mc", "myShip",  getNextHighestDepth());
//声明变量
var enArray:Array;  // 现在是空的
var bulletArray:Array; // 空的
var myGame = new Game(myShip , enArray , bulletArray);
//游戏循环
_root.onEnterFrame = function(){
 myGame.moveShip("right");
}
Then I wrote minimal code so that the classes would just compile
然后我写下最少量的代码以致能让它刚好编译
class Game{
 //游戏类,控制整个游戏并包含其它类的实例。
 // 声明变量

 var ship:Spaceship;
 var enemyArray:Array;
 var laserArray:Array;
 (译者注:上面最好用Private加以标明,否则就没有封装的意义了)
 // 构造函数
 function Game(_ship:Spaceship, _enArray:Array, _bullArray:Array){
 ship       = _ship;
 enemyArray = _enArray;
 laserArray = _bullArray;
 }
 (译者注:这个函数最好在头前加Public在尾部加:void,这样子就比较正规了。)
}
然后在Spaceship.as文件里我这样写:
class Spaceship extends MovieClip{

}
然后编译,但是没有错误。
但是那里应该有些错误因为moveShip方法还没有出现?嗯??(译者注:我倒)
在调试中什么也没有出现。
相当讨厌的出错检查。。。噢,好吧。。。
现在我必须写Game.moveShip方法了。。。我会写下它并测试。。。
我写下了moveShip方法并意识到我必须再写一个可以检测哪个键被按下了并传回一个Sring类的dir变量的
chekcKey 方法用以检测按键。
// Game类
// 检测按键的方法:
 function checkKey():String{
 if(Key.isDown(Key.RIGHT)){
 dir = "right";
 }
 if(Key.isDown(Key.LEFT)){
 dir = "left";
 }
 if(Key.isDown(Key.UP)){
 dir = "up";
 }
 if(Key.isDown(Key.DOWN)){
 dir = "down";
 }
 return dir;
 } 
 // moveship方法
 // 1. 检查按键
 // 2. 分配变量dir就是在每按下键一次,把dir传给spaceship.move方法
 function moveShip(dir:String){
 ship.move(dir);
 }
编译后并得到了错误。。。
" 行 38: 没有'move'方法."
太棒了,开始得到错误了,(译者注,我再倒),那正是所期待着的要发生的事。
现在写下ship.move方法来清除错误。。。
那就是“测试优先”的想法(作者这样写:主要是这里做的是个小东东,而且主要目的
在于尝试AS2编程,至于诸位自己做项目的时候,用什么模式,还请自己拿捏准哟.)
把方法在一系列的测试中实施直到它们出了问题,然后编写方法直到它符合要求接着移向下一个方法。
我使得飞船移动,这里就是我如何做的。。。

class Game{
 //声明变量
 var ship:Spaceship;
 var enemyArray:Array;
 var laserArray:Array;
 var dir:String;
 
 // ====   构造函数  ==============
 function Game(_ship:Spaceship, _enArray:Array,
 _bullArray:Array){
 ship       = _ship;
 enemyArray = _enArray;
 laserArray = _bullArray;
 }
 
 // ====  检查按键的方法 =======
 function checkKey():String{
 if(Key.isDown(Key.RIGHT)){
 dir = "right";
 }
 else if(Key.isDown(Key.LEFT)){
 dir = "left";
 }
 else if(Key.isDown(Key.UP)){
 dir = "up";
 }
 else if(Key.isDown(Key.DOWN)){
 dir = "down";
 }
 else dir="stop";
 //下面这个起作用了
 //trace(dir + " - in game.checkey");

 return dir;
 }
 
 // ===  移动飞船的方法 =====
 // 1.检查按键
 // 2.分配给变量dir相应的方向
 // 3 把变量dir的值传进spaceship.move方法中
 function moveShip(dir:String){
 ship.move(checkKey());

 }
} //game类结束
在宇宙飞船类中:

class Spaceship extends MovieClip{
 var dir:String;
function move(_dir:String){
 dir = _dir;
 if(dir=="left") _x -= 20;
 if(dir=="right")_x += 20;
 if(dir=="up")   _y -= 20;
 if(dir=="down") _y += 20;
 if(dir=="stop"){
  _x +=0;
  _y +=0;
  }
 }
} //Spaceship类结束
我在把变量dir传进ship.move方法时没有遇到任何麻烦,然后我尝试试着直接使用checkKey方法,因为它总是返回dir,当然,这个起效了。
然后我不得不加一个stop在其中...无论何时当键按起的时候,飞船的运动有一些可笑,这样的状态一直持继着,直到我在后来能把运动变得平缓为止...
但是我知道我的设计起效了。
我仅仅要作是只是把宇宙飞船(SPACESHIP)的实例传给游戏类的构造函数中。
然后,哈哈,presto(类例于芝麻开门之类的话)...接下来是什么呢?嗯,或许是发射镭射子弹吧。。。好的,让我们走。

 

发射雷射子弹
这个花了我两天的时间,我几乎不能相信。我持续着错误的子弹发射。。。这里是我发射镭射子弹方法的第一次尝试。
// 在game类中:
//  ====== 发射镭射子弹 ======== 
 function fireLaser_old(){
 if(Key.isDown(Key.SPACE)){
     // set start pt of laser
  laserArray[bulletNum]._y = ship.getY();
  laserArray[bulletNum]._x = ship.getX();
  // if space bar pressed set flag
  hasFired = true;
  bulletNum++;
  if(bulletNum >=5){
   bulletNum = 0;
   }
  }
 }
上面的代码已相当接近我最后结束时的状态,最实际的问题是在于movelaser方法。
噢哈哈,我把镭射子弹发射放在了我的FLA中的EnterFrame循环中了...大大的错误...
这必须放在FLA文件的onKeyDown事件中。这就是目前为止的状况:

//在fla文件的actions面板中
attachMovie("ship_mc", "myShip",  getNextHighestDepth());
var enArray     = new Array(3);
var bulletArray = new Array(5);
//连接bullet数组
for(var i =0;i < 5;i++){
 attachMovie("laser_mc", "laser"+i, 100 + i);
 bulletArray[i] = _root["laser"+i];
}
// 声明并初始化游戏类的实体
// 把飞船, 敌机数组和子弹数组传进去
var myGame = new Game(myShip , enArray , bulletArray);

//  ======= 获取键值============ //
someListener = new Object();
someListener.onKeyDown = function () {
 myGame.fireLaser();
 
 };
Key.addListener(someListener);

// 游戏循环
_root.onEnterFrame = function(){
 myGame.checkKey();
 myGame.moveShip(dir);
 myGame.moveLaser();
}
这样好了一些,但是仍旧没有得到正确的子弹发射。。。它要比空格键延迟两个键后才发射。
嗯,好吧,这是我这次使用的moveLaser方法:
// ========= move laser  ==========//
 function moveLaser_old(){
  if(hasFired==true){
  laserArray[bulletNum]._x += 30;
  }
  if(laserArray[bulletNum]._x > Stage.width)
  {
   laserArray[bulletNum]._x = -10;
   hasFired = false;
  }
 } // ============== //

下面是最终(也就是现在)使用的镭射子弹发射方法。
它的运作很简单,得到飞机的位置并传递给子弹。
然后子弹数目开始增加,当数字大于5的时候,它被重新初始化到0。
// ========= FIRE LASER ====== //
 function fireLaser(){
  if(Key.isDown(Key.SPACE)){
   // set start point
   laserArray[bulletNum]._y = ship.getY();
   laserArray[bulletNum]._x = ship.getX();
   // increment the bullet number
   ++bulletNum;
   // if more than 5 bullets , start again at 0
   if (bulletNum>5) {
    bulletNum = 0;
    }
  }
 }
下面是最终移动子弹的方法,同样是非常的单简。
这个过程遍历所有的子弹,如果有子弹可以提供,就移动它。它仍旧不是很完美,但现在还能凑和着用。
// ======= MOVE LASER ======= //
 function moveLaser(){
  var bulleti = 0;
  while (bulleti<6) {
  laserArray[bulleti]._x += 30;
  bulleti++;
  }
 } // ============= //

下面是到目前为止所创建的游戏类:

class Game{
 //  ========= 声明变量
 var ship:Spaceship;
 var enemyArray:Array;
 var laserArray:Array;
 var dir:String;
 var bulletNum:Number = 0;
 
 // ====   构造函数==============
 function Game(_ship:Spaceship, _enArray:Array,
  _bullArray:Array){
  ship       = _ship;
  enemyArray = _enArray;
  laserArray = _bullArray;
 }
 
 // ====  检测键被按下的方法 =======
 function checkKey():String{
 if(Key.isDown(Key.RIGHT)){
 dir = "right";
 }
 else if(Key.isDown(Key.LEFT)){
 dir = "left";
 }
 else if(Key.isDown(Key.UP)){
 dir = "up";
 }
 else if(Key.isDown(Key.DOWN)){
 dir = "down";
 }
 else dir="stop";
 
 return dir;
 }

 // ===  移动飞船的方法 =====
 // 1.检查按键
 // 2.分配给变量dir相应的方向
 // 3 把变量dir的值传进spaceship.move方法中
 function moveShip(dir:String){
 ship.move(checkKey());

 }
 
 // ========= 发射子弹 ====== //
 function fireLaser(){
  if(Key.isDown(Key.SPACE)){
   // 设置起始位置
   laserArray[bulletNum]._y = ship.getY();
   laserArray[bulletNum]._x = ship.getX();
   // 增加子弹数目
   ++bulletNum;
   // 如果大于5,重新将数目开始于 0
   if (bulletNum>5) {
    bulletNum = 0;
    }
  }
 }
 
 // ======= 移动子弹 ======= //
 function moveLaser(){
  var bulleti = 0;
  while (bulleti<6) {
  laserArray[bulleti]._x += 30;
  bulleti++;
  }
 } // ============= //
 
  
} //  ------ 游戏类结束 ------- //
 
下面是Spaceship类和它所使用的 getX 和getY方法:

class Spaceship extends MovieClip{
 var dir:String;
 // ===== 移动 =========
 function move(_dir:String){
 dir = _dir;
 if(dir=="left") _x -= 20;
 if(dir=="right")_x += 20;
 if(dir=="up")   _y -= 20;
 if(dir=="down") _y += 20;
 if(dir=="stop"){
  _x +=0;
  _y +=0;
  }
 }
 // ====== 得到X值 ====
 function getX():Number{
 return _x;
 }
 //  ======= 得到Y值 =====
 function getY():Number{
 return _y;
 }
} //  ====Spaceship类结束 

这里是Laser类:

class Laser extends MovieClip{
 // ======= 构造函数 ========= //
 function Laser(x:Number, y:Number){
 _x = x;
 _y = y;
 }
}

下载到目前为止的源代码:


下面让我们进入这篇文章的第二部分.

 

 

 

 

 

 

 

 

 

 

 


 

你可能感兴趣的:([翻译]国外的一篇AS2飞机教程(1))