摘 要 本文介绍如何利用ActionScript在Flash中编程,并通过一个“甲虫推球”的游戏讲解如何显示游戏地图,如何响应键盘,并分析了甲虫推球的算法,实现Flash游戏的开发。
关键词 ActionScript,Flash,甲虫推球
一、游戏介绍
Flash在二维动画设计中独领风骚,用Flash开发的小游戏也多了起来。在Flash中开发游戏或者进行其它项目的开发都要用到ActionScript,通过使用ActionScript能使使用者在Flash动画设计中如虎添翼,下面通过在Flash中开发一个“甲虫推球”的游戏让读者领略一下ActionScript的风采,希望对读者在Flash中进行动画设计有所启发。
“甲虫推球”游戏是根据一个传统的智力游戏“推箱子”改编的,游戏界面如图1所示:
游戏区由一定数目的方格组成,这里用了14×16的方格。游戏分成多关,在每一关中由形状不同的围墙构成一个活动区域,在活动区域中分布着一只甲虫和若干个球,还有与球的数量一致的球洞。玩家通过光标键移动甲虫来推动球(注意只能推不能拉),把每个球推入球洞,即完成了一关,可以自动进入下一关。
二、游戏分析
根据本游戏所要实现的功能,要考虑的核心问题有三个:
1.显示游戏地图,本游戏的地图明显是由一个个方格图片组成,共14×16个方格,自然想到用14×16的二维数组来存放每格的地图数据。那么分别用什么数值来表示每种地图数据呢?本游戏的地图元素共有五种,分别是围墙、围墙内地板、球洞、球、甲虫。其中围墙、地板、球洞的位置是固定的、静止的地图元素;而球和甲虫是可移动的,是移动的地图元素,而且是显示在静止地图元素之上的。为了运算方便,考虑用四位二进制数来表示地图数据。其中低二位来表示静止地图元素,高二位来表示移动地图元素。具体见表1:
|
二进制数 |
十进制数 |
所代表地图元素 |
静止地图元素
用低二位来表示 |
0000 |
0 |
无静止地图元素 |
0001 |
1 |
围墙 |
0010 |
2 |
围墙内地板 |
0011 |
3 |
球洞 |
移动地图元素
用高二位来表示 |
0000 |
0 |
无移动地图元素 |
0100 |
4 |
球 |
1000 |
8 |
甲虫 |
低二位用到全部四种情况,高二位用到其中三种情况,组合起来有十二种情况,但在本游戏中有些情况是不会存在的,因为球和甲虫不可能到围墙上或围墙外去。实际出现的地图数据有表2八种情况:
二进制数 |
十进制数 |
所代表地图元素 |
0000 |
0 |
围墙外底格 |
0001 |
1 |
围墙 |
0010 |
2 |
围墙内地板 |
0011 |
3 |
球洞 |
0110 |
6 |
地板上有球 |
0111 |
7 |
球洞上有球 |
1010 |
10 |
地板上有甲虫 |
1011 |
11 |
球洞上有甲虫 |
由于在Flash中无法直接表示二进制形式的常量,所以在代码中都用对应的十进制数表示。例如MapData[5][8]=6,表示在第6行第9列的位置是地板,上面还有球。由于Flash舞台的坐标原点在左上角,所以在设定游戏地图的行列号时,规定行号从上到下递增,列号从左到右递增。
2.推球是由光标键控制甲虫的移动进行的,所以要根据甲虫的位置进行判断,如果甲虫前面没有围墙也没有球,则甲虫向前移动一步;如果甲虫前面是球,则要判断该球前面是否有围墙或球,如果两者都没有,则甲虫可以推球;即甲虫和其前面的球都向前移动一步。
3.如何判断球已全部入洞,以便能自动跳到下一关。为此在显示每关的游戏地图时先要得到本关的球洞数量及已入洞的球数,在每次推球时如果有球出洞或入洞,都要更新已入洞的球数,最后判断已入洞的球数是否与球洞数量相等,如果相等则说明球已全部入洞,跳到下一关。
三、游戏实现
1.显示游戏地图
为了在游戏软件中灵活显示地图元素,先要将这些地图元素(围墙、围墙内地板、球洞、球、甲虫)绘制好,并以一个个图形元件的形式存在本游戏fla文件的库中,其中球洞、围墙、地板的尺寸应与地图方格尺寸一致,甲虫、球的尺寸应小于地图方格尺寸。在创建图形元件时有两点必须注意:(1)创建每个图形元件后要设置该元件链接属性中的标识符,以便后面编程中创建元件实例时使用。(2)“甲虫”这个图形元件的注册点必须在图形的中点,这样“甲虫”在转动方向时不会造成位置变化。
下面开始编程,逐行分析预先放在MapData数组中的地图数据,取其低二位获取该位置的静止地图元素值,代码如下:
for (var i = 0; i < TotalRow; i++) // TotalRow为地图总行数
{ for (var j = 0; j < TotalCol; j++) // TotalCol为地图总列数
{ var num = MapData[i][j]&3;
再根据静止地图元素值加载相应的图形元件实例,所用到的函数为:attachMovie("元件链接标识符","元件实例名",元件实例深度); 注意在给静止地图元件实例取名时应该统一编号,便于换关时清除各元件实例。另外用attachMovie函数创建元件实例时,第三个参数元件实例深度应各不相同,如果有几个元件实例的深度相同,那么只会显示最后创建的那个元件实例。代码如下:
switch (num)
{ case 1 ://显示围墙
var static_mc;
static_mc=attachMovie("wall","static_mc"+StaticMc_num,StaticMcDepth);
StaticMc_num++;
StaticMcDepth++;
在上面的代码中,attachMovie函数的第一个参数 "wall"是图形元件“围墙”的链接标识符;第二个参数:"static_mc"+StaticMc_num,其中StaticMc_num是初始值为0的数值型数据,和字符串"static_mc"之间用“+”运算时,不是作加法运算,而是作字符串连接运算,编译系统会先将StaticMc_num自动转换成字符串型,进行字符串连接运算,从而使静止地图元件实例取名从static_mc0开始,然后是static_mc1、static_mc2…往下递增;第三个参数StaticMcDepth也是初始值为0的数值型数据,所以各元件实例深度值也是逐一递增的,以保证不同。创建实例后,还要设置其在舞台的坐标位置,Flash舞台的坐标原点在左上角,设置静止地图元件的注册点也在左上角,代码如下。
static_mc._x=j*block_width;// block_width为方格宽度
static_mc._y=i*block_height;// block_height为方格高度
显示地板、球洞的代码与上面显示围墙的类似,只是在显示球洞时要有一个球洞的计数器(hole_num++;),以便后面判断球是否已全部入洞。
接下来要取地图数据的第3和第4位,获取该位置的移动地图元素值,代码如下:
num = MapData[i][j]&12;
同样根据移动地图元素值加载相应的图形元件实例,代码与上面显示静止地图元素类似,但是考虑后面移动球和甲虫编程方便,球和甲虫的实例名都是独立命名的,球的实例名依次是ball_mc0、ball_mc1…,甲虫的实例名是bug_mc。另外球和甲虫是显示在静止地图元素之上的,在Flash中,当两个或多个元件实例显示在同一位置时,深度值大的显示在上面,所以必须保证球和甲虫的深度值大于所有静止地图元件实例的深度值,在下面代码中用到的深度值DynMcDepth是从TotalCol*TotalRow(地图总列数*地图总行数)开始递增的。甲虫和球两个元件的注册点设置在图形的中点,所以其在舞台的坐标位置与行列号的换算要加上半个方格的尺寸。显示球时要判断一下该球是否在球洞中:if(MapData[i][j]==7),在球洞中则要将已入洞球数计数器加一(oknum++;)。由于甲虫推球是围绕甲虫的位置进行的,所以在显示甲虫时,别忘了记下甲虫所在行号列号(bug_row=i;bug_col=j;),具体代码如下。
switch (num)
{ case 4://显示球
var ball_mc=attachMovie("ball","ball_mc"+ball_num,DynMcDepth);
ball_num++;
DynMcDepth++;
ball_mc._x=j*block_width+block_width/2;
ball_mc._y=i*block_height+block_height/2;
if(MapData[i][j]==(3+4))oknum++;
break;
case 8 ://显示甲虫
var bug_mc=attachMovie("bug","bug_mc",DynMcDepth);
DynMcDepth++;
bug_mc._x=j*block_width+block_width/2;
bug_mc._y=i*block_height+block_height/2;
bug_row=i;
bug_col=j;
break;
2.推球
甲虫推球是由用户操作光标键进行的,所以要编写响应键盘操作的代码。在Flash中要响应键盘操作通过以下三个步骤:(1)注册一个键盘侦听对象以接收键盘通知,采用的方法是:Key.addListener(侦听对象名);其中的侦听对象可以是当前影片中的某个对象,一般直接用主场景时间轴对象_root,例如:Key.addListener(_root)。(2)响应键盘通知,键盘通知分两种:onKeyDown和onKeyUp,一看就知道前者是在键盘按下时通知,后者是在键盘按下后释放时通知,那么到底用哪个呢?也许有人觉得随便用哪个都行,的确在一般情况下两者是一样的效果,但是在连续按键(即按住某个键不放)时就不一样了,此时onKeyDown会响应多次,onKeyUp只会响应一次。如果选择后者,那就响应onKeyDown键盘通知,采用的方法是定义onKeyDown函数:侦听对象名.onKeyDown = function(){…},侦听对象应与前面注册的键盘侦听对象一致。例如:_root.onKeyDown = function(){…}(3)区分所按键,通过前面两步已经能响应键盘操作,但还必须区分所按键,以便作出不同的响应。这通过Key.getCode()方法进行,Key.getCode()方法用于获取最近一次所按键的键控代码。本游戏只需要响应上移、下移、左移、右移四个光标键,具体代码如下。
Key.addListener(_root);
_root.onKeyDown = function()
{ switch(Key.getCode())
{ case Key.UP:
…
case Key.DOWN:
…
case Key.LEFT:
…
case Key.RIGHT:
……
当玩家按了上移光标键后,如何实现甲虫推球呢?首先要做的是调整甲虫的朝向,由于在创建甲虫的图形元件时,其朝向是向上的,所以只要设置:bug_mc._rotation=0。
接下来要根据甲虫所在行列位置进行分析,如果甲虫上一格(即bug_row-1行、bug_col列所对应位置)没有围墙也没有球,则甲虫向上移动一步,代码如下。
if(MapData[bug_row-1][bug_col]!=1/*甲虫上一格没有墙*/
&&(MapData[bug_row-1][bug_col]&12)!=4)/*甲虫上一格没有球*/
甲虫向上移动一步前别忘了从甲虫原位置的地图数据中去除甲虫数据,甲虫向上移动一步后别忘了将甲虫新位置的地图数据加上甲虫数据,向上移动甲虫时除了修改行号外别忘了更改甲虫Y坐标位置,移动实际是由坐标位置的更改产生的,代码如下。
{ MapData[bug_row][bug_col]-=8;//从甲虫原位置的地图数据中去除甲虫
bug_row--;//修改行号
bug_mc._y=bug_row*block_height+block_height/2;//更改甲虫Y坐标位置
MapData[bug_row][bug_col]+=8;}//将甲虫新位置的地图数据加上甲虫
如果甲虫上面有球,则要判断该球的上面是否有围墙或球,如果两者都没有,则甲虫可以推球,即甲虫和其上面的球都向上移动一步。同样的在移动球时别忘了对球旧新位置的地图数据进行修改,另外在移动球时如果有球出洞或入洞,都要更新已入洞的球数。和甲虫的移动相比,球的移动要麻烦一些。因为甲虫只有一个,而球有多个,所以要先知道球的编号才能移动相应的球,通过自定义函数GetBallIndex获取相应行列位置球的编号。
else if((MapData[bug_row-1][bug_col]&12)==4)//甲虫上一格有球
{ if(MapData[bug_row-2][bug_col]!=1/*甲虫上一格没有墙*/
&&(MapData[bug_row-2][bug_col]&12)!=4) )/*甲虫上一格没有球*/
{ MapData[bug_row-1][bug_col]-=4;// 从球原位置的地图数据中去除球
if(MapData[bug_row-1][bug_col]==3)oknum--;//本球出洞
var Ball_Index=GetBallIndex(bug_row-1,bug_col);// 通过自定义函数GetBallIndex获取球的编号
var ball_mc=_root["ball_mc"+Ball_Index];// 得到相应球的实例
ball_mc._y=(bug_row-2)*block_height+block_height/2; //更改球Y坐标位置
if(MapData[bug_row-2][bug_col]==3)oknum++;//本球入洞
MapData[bug_row-2][bug_col]+=4; //将球新位置的地图数据加上球
MapData[bug_row][bug_col]-=8;
bug_row--;
bug_mc._y=bug_row*block_height+block_height/2;
MapData[bug_row][bug_col]+=8;
}
}
GetBallIndex函数的实现比较简单,只要循环获取每个编号的球的坐标,判断是否与参数所对应行列位置中心坐标是否一致,一致则返回该编号。
function GetBallIndex(row,col)
{ for(var i=0;i<ball_num;i++)
{ var ball_mc=_root["ball_mc"+i];
if(ball_mc._x==col*block_width+block_width/2
&&ball_mc._y==row*block_height+block_height/2)
break;
}
return i;
}
下移、左移、右移的处理方法与上移类似,不再重复。
3.自动跳到下一关
在显示每关的游戏地图时已获取本关的球洞数量及已入洞的球数,在每次推球时也已根据是否有球出洞或入洞对已入洞的球数作出更新,在每次推球后判断球是否已全部入洞就水到渠成了,只要判断已入洞的球数是否与球洞数量相等,如果相等则说明球已全部入洞,即可跳到下一关。如何换关呢?先用removeMovieClip函数逐一删除前面用attachMovie创建的地图元素实例,初始化各计数器变量,然后读取下一关的地图数据放在MapData数组中,再重复调用第一步中的代码创建地图元素实例,显示新的游戏地图。另外别忘了增加两个按钮实现换关。代码类似,不再列举,只是提醒读者把一些重复型的代码定义成函数,以便调用,使代码条理清晰。
参考文献
《ActionScript 3.0 编程精髓》 作 者:Colin Moock 机械工业出版社 2008年5月
|