在我们开始这部分之前,有件事得先解释清楚。Flash很慢。Flash非常慢。卷屏意味着以每秒20-30次的速度移动屏幕上数以百计的方块。而太多移动的方块意味着Flash不能及时地完成重绘,于是速度下降。你的游戏将慢得像条睡着的蜗牛在爬。
“什么?”你可能会问,“难道不用卷屏吗?怎么会慢得像睡着的蜗牛在爬呢?”
你可以用移动的方块,但是你得小心,别让卷屏的区域太大,而且移动的方块别太多。多大算“太大”,多少又算“太多”,这就得你自己体会了。记住,Flash游戏大多数时候是在浏览器上玩的,游戏者可能同时打开了多个窗口,后台可能有多个程序在同时运行,而且不是所有游戏者都有那么好的电脑配置。可以用低配置的电脑来测试你的游戏,如果觉得速度慢,就把它改小点。
来看看我们接下来要做的:
原理
左边的图中是非卷屏游戏。主角向右移动,而其它的东西原地不动。现在看右边的图,这是个卷屏游戏。主角仍然是要向右移动,但是我们实际上并没有移动主角,为了让他看起来是在向仍然右移动,我们将其它东西向左移了。
所以,原理很简单:
当需要变换主角的位置时,向反方向移动其它部分就行了。但是,由于我们习惯于将主角与其它背景区块一起放入MC中,主角会跟他们一起往反方向移动。为了固定主角的位置,我们仍然将所有物体向反方向移,同时,将主角向正确的方向移动相同的距离。
来看个卷屏游戏的例子。假设主角要向右移10px。我们先将所有方块(包括主角)向左移10px,并将主角向右移10px。最后看起来主角并没有动,而其它方块向左移了。
卷屏的最简单的方法是将所有方块都放到屏幕上,但是只显示其中一小部分,然后整个一起移动。这可能会让你的游戏速度非常慢,因为没显示出来的那上千个方块也在移动。另一个办法是将移出可见区域的方块删除,并在另一头添加新的方块。这会好一点儿,但是Flash花费在删除/复制MC上的时间又太多了。
最后一个办法是:只把可见部分的方块放到舞台上,当它们移出可见区域时,我们将相同的方块移动到另一端,重命名,并再次使用相同的MC。这被称作“gotoAndStop”卷屏引擎:
如图所示,当方块从右边消失,我们将它移动到左边。同时我们要重命名MC,因为所有的MC都是以“t_3_4”(表示它位于y=3,x=4)这样的名字命名的。而在新位置的方块可能要显示不同的帧(图片)。这就是为什么我们要将它发送到含有正确图片的帧,也是这种方法叫做“gotoAndStop”的原因。
准备卷屏
在大多数卷屏游戏中,主角的位置是始终在屏幕中央的。
你可以看见,主角左边的方块数和右边的方块数是相等的。这就是说,总的列数是3、5、7、9、11……而绝不会是2、4、6、8。行数也是一样。
来声明一个游戏对象:
game = {tileW:30, tileH:30, currentMap:1, visx:7, visy:5, centerx:120, centery:90};
属性 visx 代表可见的列数,visy 代表可见的行数。我们还需要属性centerx/centery来标记影片的中心点。
当我们移动方块时,可能需要显示一些在地图数组中没有声明的方块。例如,当主角高高兴兴地走到地图数组中已声明的最左边的方块上以后,就需要显示更左边的方块。对于这类方块,我们会建立新的不含图片的方块类型(这样对Flash来说负担比较小)。在方块MC的第20帧创建关键帧。以下是声明这类方块的代码:
game.Tile4 = function () { };
game.Tile4.prototype.walkable = false;
game.Tile4.prototype.frame = 20;
你可能还想部分地遮住一些方块,这就需要新建含有名为“frame”的关联的新MC,该MC的中间挖空,只显示经过挖空处的方块。
建立卷屏的世界
让我们从 buildMap 函数开始。
function buildMap (map) {
_root.attachMovie("empty", "tiles", 1);
game.halfvisx=int(game.visx/2);
game.halfvisy=int(game.visy/2);
我们将为game对象计算两个新的属性。halfvisx和halfvisy被分别用来存放主角与可见区域边缘之间的列数和行数。当总列数visx=5时,halfvisx=2,就是说主角右边有2列、左边有2列,中间就是主角所在的那一列。
game.clip = _root.tiles;
game.clip._x = game.centerx-(char.xtile*game.tileW)-game.tileW/2;
game.clip._y = game.centery-(char.ytile*game.tileH)-game.tileH/2;
我们得根据以下两个变量的值来决定包含主角和所有方块的MC的位置:游戏的中心点和主角的位置。游戏的中心点在game对象中由centerx/centery声明;主角的位置在char对象中由xtile/ytile声明。当主角被放在x坐标的(char.xtile*game.tileW)+game.tileW/2处时,方块MC必须被放在相反的坐标上,所以我们要用centerx减去这个值。
for (var y = char.ytile-game.halfvisy; y<=char.ytile+game.halfvisy+1; ++y) {
for (var x = char.xtile-game.halfvisx;
x<=char.xtile+game.halfvisx+1; ++x) {
var name = "t_"+y+"_"+x;
if(y>=0 and x>=0 and y<=map.length-1 and x<=map[0].length-1){
game[name] = new game["Tile"+map[y][x]]();
}else{
game[name] = new game.Tile4();
}
game.clip.attachMovie("tile", name, 1+y*100+x*2);
game.clip[name]._x = (x*game.tileW);
game.clip[name]._y = (y*game.tileH);
game.clip[name].gotoAndStop(game[name].frame);
}
}
这个循环创建了所有的可见方块对象,并且复制该方块对象相应的MC到舞台上。如你所见,这个循环并不是从0开始,而是从ytile-halfvisy开始。同理它也不是在map数组的末尾结束的,而是在ytile+halfvisy+1结束。然后,if条件句检查将被创建的方块是否已经在map数组中。如果不在,我们就使用tile4模版中的空白方块。
为了使卷屏的过程平滑,我们得在舞台右边和底部分别增加一行和一列方块。这些方块可以保证,哪怕我们把半个方块移到舞台的另一边,也不会出现任何空白区域。
_root.attachMovie("frame", "frame", 100);
最后一行复制帧来覆盖影片中你不想显示的区域。
char.clip = game.clip.char;
char.x = (char.xtile*game.tileW)+game.tileW/2;
char.y = (char.ytile*game.tileW)+game.tileW/2;
char.width = char.clip._width/2;
char.height = char.clip._height/2;
char.clip._x = char.x;
char.clip._y = char.y;
char.clip.gotoAndStop(char.frame);
char.xstep=char.x;
char.ystep=char.y;
char的创建方法与前面一样。仅有的不同是增加了两个新的属性:xstep和ystep。我们将在后面用它们来检查当前是否是移动方块行或列到另一端的合适时间。
卷屏!卷屏!
既然卷屏的世界已经建好了,下面就该让它真正“卷”动起来了。在为主角计算出ob.xtile/ob.ytile的值以后,在moveChar函数的末尾加上如下代码来进行卷屏:
game.clip._x = game.centerx-ob.x;
game.clip._y = game.centery-ob.y;
if(ob.xstep<ob.x-game.tileW){
var xtile = Math.floor(ob.xstep/game.tileW) + 1;
var xnew=xtile+game.halfvisx+1;
var xold=xtile-game.halfvisx-1;
for (var i = ob.ytile-game.halfvisy-1; i<=ob.ytile+game.halfvisy+1; ++i) {
changeTile (xold, i, xnew, i, _root["myMap"+game.currentMap]);
}
ob.xstep=ob.xstep+game.tileW;
} else if(ob.xstep>ob.x){
var xtile = Math.floor(ob.xstep/game.tileW);
var xnew=xtile+game.halfvisx+1;
var xold=xtile-game.halfvisx-1;
for (var i = ob.ytile-game.halfvisy-1; i<=ob.ytile+game.halfvisy+1; ++i) {
changeTile (xold, i, xnew, i, _root["myMap"+game.currentMap]);
}
ob.xstep=ob.xstep-game.tileW;
}
if(ob.ystep<ob.y-game.tileH){
var ytile = Math.floor(ob.ystep/game.tileH)+1;
var ynew=ytile+game.halfvisy+1;
var yold=ytile-game.halfvisy-1;
for (var i = ob.xtile-game.halfvisx-1; i<=ob.xtile+game.halfvisx+1; ++i) {
changeTile (i, yold, i, ynew, _root["myMap"+game.currentMap]);
}
ob.ystep=ob.ystep+game.tileH;
} else if(ob.ystep>ob.y){
var ytile = Math.floor(ob.ystep/game.tileH);
var yold=ytile+game.halfvisy+1;
var ynew=ytile-game.halfvisy-1;
for (var i = ob.xtile-game.halfvisx-1; i<=ob.xtile+game.halfvisx+1; ++i) {
changeTile (i, yold, i, ynew, _root["myMap"+game.currentMap]);
}
ob.ystep=ob.ystep-game.tileH;
}
return (true);
首先,我们根据中心点的位置和主角的坐标值将包含主角和所有方块的game.clip放到相应的位置。然后我们写了4段相似的代码,每一段定义一个方向。当主角的移动距离大于方块的大小时,循环会调用含有相应变量的changeTile函数。循环结束,方块被移动/重命名/修改以后,就该更新属性ystep/xstep的值了。
现在,让我们来编写changeTile函数:
function changeTile (xold, yold, xnew, ynew, map) {
var nameold = "t_"+yold+"_"+xold;
var namenew = "t_"+ynew+"_"+xnew;
if(ynew>=0 and xnew>=0 and ynew<=map.length-1 and xnew<=map[0].length-1){
game[namenew] = new game["Tile"+map[ynew][xnew]]();
game.clip[nameold]._name = namenew;
game.clip[namenew].gotoAndStop(game[namenew].frame);
game.clip[namenew]._x = (xnew*game.tileW);
game.clip[namenew]._y = (ynew*game.tileH);
}else{
game[namenew] = new game.Tile4();
game.clip[nameold]._name = namenew;
game.clip[namenew].gotoAndStop(game[namenew].frame);
}
}
这样,我们会分别得到方块的2个旧的和2个新的坐标值。为了检查方块是否还在map数组之内,我们还增加了map参数。 “nameold”是这些方块的旧名字,而“namenew”是它们的新名字。在我们用新名字创建了新的tile对象以后,语句:
game.clip[nameold]._name = namenew;
将方块MC重命名。新的MC是空的,所以我们不必将它放到新的_x/_y,让它留在原处就行了。