韩国梯子游戏戏左边和右边怎么正确判断?????

Flash 区块游戏开发_清风网络
Flash 区块游戏开发
& Flash 区块游戏开发你似乎来到了没有知识存在的荒原...
来源链接是否正确?用户、话题或问题是否存在?文章 - 55&
trackbacks - 0
2526272829301234567891011121314151617181920212223242526272829303112345
朋友的Blog
阅读排行榜
评论排行榜
为什么用方块?
在开始埋头编写代码之前,让我们稍微谈谈区块/方块游戏(tile based games)。为什么要使用方块?是区块游戏更容易制作吗?或许还是他们比基于艺术的游戏(art based games)更复杂?Flash适合区块游戏吗?
在很久很久以前,方块技术已经被应用到游戏制作中。那时候,电脑还没有上GHz的cpu,没有上百MB的内存。缓慢的速度、有限的内存意味着,游戏制作者不得不使用他们的脑子来发明聪明的办法,让游戏看起来更棒,而且更快。
比如,你想在你的游戏中加入漂亮的背景,但是图片太大了,而且使得你的游戏变得很慢。怎么办?把图片切成方块!&
在上图中,你可以看到图片的某些部分是完全一样的。1和2是一模一样的,3和4是一样的,5到7都是完全一样的。如果你把图片切割开来,重复使用相同的部分,你就已经在应用方块了。这个大图片比方块的文件大小大多了。实际上,你用4块不同的方块就可以画出了这个图片。
方块还有其他一些不错的特性,当你想要替换部分背景,那么你不需要重新绘制所有的东西,你只要改变1个方块就行了。你还可以重复使用方块,创建不同的对象。比如,你可能有草地的方块,还有花的方块,当你需要在草地的背景上放几朵花时,只需要把原来地方的草换成花就行了。
Flash 和方块
我们都知道,Flash是基于矢量的,所以Flash生成的文件体积更小,而且可以无限缩放。因此,我们一点都不需要方块来制作游戏吗?好吧,用Flash你可以很容易地做一个基于艺术的游戏(art based games),但是当你的游戏区域增大时,或者你想要更多的特性时,你可能会遇到麻烦。许多东西用区块游戏来做是如此简单(立体视角,寻找路径和深度排序)。不要忘记,区块游戏已经存在了很长一段时间,许多理论对于Flash来说依然适用。
用Flash做区块游戏也有不太舒服的地方,我们用不上许多绘图功能和时间线的部分,我们的游戏是通过actionscripot制作的,基本上,我们要写大量的代码来创建、移动、修改舞台上的图片。
用位图作为区块也是一个好主意。是的,我们可以在Flash中绘制所有的东西,用矢量图也可以,但是当游戏运行的时候,播放器需要计算屏幕上的矢量数据,我们可不希望有什么东西弄慢了我们的游戏。位图在播放以前是预先渲染的,而且通常情况下他们更好看。如果你想在Flash中导入位图作为方块,通常最好的做法是把图像存为带透明背景的GIF文件(用于各种对象,比如花等)枯燥的讲话到此结束,让我们做点东西吧& :-)首先,我们来看看怎样存储我们的地图。
地图的格式我们将用Flash提供给我们的一个美妙的格式表示地图:数组。如果你不知道什么是数组,打开Flash的帮助,先看看。二维数组我们需要一个二维数组表示地图,不,他不是什么空间、时间的维数,它是说一个数组的每一个元素还是数组。迷惑了?让我们来看看。通常,这是大家经常看到的简单的数组:myArray=[&a&, &b&, &c&, &d&];
这很简单。你可以用myArray[0]得到第一个元素,就是&a&,用myArray[1]得到第二个元素&b&,等等。现在换个聪明的法子! 如果我们不用&a&,&b&和&c&放在数组中,但是我们把另外的数组放进去呢?是的,我们可以这么做的。看这里,让我们做个这样的数组:
a=[&a1&, &a2&, &a3&]; b=[&b1&, &b2&, &b3&]; c=[&c1&, &c2&, &c3&]; myArray=[a, b, c];现在我们已经定义了一个数组,而且他的每一个元素都是数组。那么,myArray[0]的值现在就是一个数组 [&a1&,&a2&,&a3&],第二个元素值就是 [&b1&,&b2&,&b3&],等等。如果你这样写: myVar=myArray[2];那么myVar得到的值是 [&c1&, &c2&, &c3&]. OK,那又怎么样?现在你也许会问。我们不会停止在这里的。如果你这样写myVar=myArray[2][0];那么他得到的值就是myArray第三个元素的第一个元素的值&c1&。让我们试试更多的。myVar=myArray[0][1]取得myArray的第一个元素(a)的第二个元素(&a2&)。 myVar=myArray[1][0] 得到值&b1& 你想得到整个图片? 继续看&&创建地图首先我们写出这个地图的数组,这个数组包含了每个方块的信息 myMap = [ [1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1] ];正如你所看到的,我们的地图有6行8列。如果我们的英雄(主角)从左上角开始,他可以往右移动8格,往下移动6格,超出这个范围,他就会走出这个地图,走入未知的空间。但是一些聪明的人已经想到了一个重要的问题:&数组中的这些数字是做什么用的呢?&好吧,我们会使用一些OOP(那是面向对象,不过不要逃跑,他们并不是像他们听起来那样可怕)来创建方块,并且管理我们的游戏(可以参阅Flash的OOP教程的链接部分)。在开始的时候,我们会定义多种方块,他们就像模板一样放到游戏中。然后我们遍历整个地图数组,检测每个数字。例如,如果我们得到数字1,那么我们就从Tile1模板中创建一个新的方块,这个方块的特点我们都事先在模板中定义好了。在游戏中,我们会检查那个方块对象的属性。他可以有许多属性,最基本的方块只有2个属性:walkable(通行性)和frame(帧)。Walkable是表示一个方块是不是允许角色从他上面走过去,如果可以,就是true(真);如果不是,就是false(假)。我们不使用hitTest,因为hitTest很慢,而且在区块游戏中使用hitTest并不是很好。Frame是表示显示方块的第几帧。当放置方块到舞台时会用到这个参数。因为我们使用同一个方块movie clip(影片夹子,检查mc)来存放不同的方块。使用时复制这个mc。他们默认是显示第一帧。在&创建方块&部分会有更多这方面的内容。所以,如果我们声明下面的方块://wall tile Tile1= function () {}; Tile1.prototype.walkable= Tile1.prototype.frame=2;那么我们每次在地图数组中遇到1的时候,就会创建一个类似的对象(Tile1),我们还定义了这个方块不能被通行(walkable=false),而且在那个点上的方块mc显示第二帧。
关于地图的东西
你或许会考虑,为什么我要选择这种方式呢?我可以告诉你这是最好的方法。我可以说这个地图格式可以最快地创建地图,可以产生最小的文件。我只能说在和区块游戏大了多年交道之后,我发现这个格式最适合我的需要。但是我们也可以看看其他可能的方法,保存地图数据的方法。
JailBitch的方法
这是原始的OutsideOfSociety的教程所采用的格式,非常简单。他以同样的方式把某个点的帧数字保存到二维数组中。每次你都需要检测下一个方块是不是墙(或者是可以捡起来的东西,或者是门,或者任何东西),你可以从地图数组中查找数字。
(这里显示的数组并不是全部的,下面还有没有显示出来)
当检测碰撞时,你能够可以让某部分的帧作为墙(或者可拾取的东西,或者门)。例如,你可以让所有的帧数在0到100的方块都作为可通行的方块,所有的从101到200的是墙,大于200的是特殊的方块。
当你只有很少的方块类型,而且方块不会变化很多时,这是一个很好的很简单的方式。
OutsideOfSociety的文章:
沙漠中的树
一些地图具有许多不同的方块,一些只有很少的几种。例如,想象在沙漠中,方圆几百公里都是沙子,如果你很幸运,你可以看到一片绿洲。或者在海上,除了水还是水,然后出现一个海岛。
如果你的地图大部分是相同的方块(沙子),而且只有少量的变化(树),那么二维数组并不是很好的选择。他会产生许多&死信息&,许多行的0,直到一些其他的frame数字出现。在这种情况下,你可以单独声明非沙子的方块,然后让剩下的方块都是沙子。
让我们假设你有一个100&100的地图,有3个树。你可以这样写:
当创建地图的时候,你遍历这个trees数组,放置trees方块,让其他的方块显示沙子。那样比写100&100的二维数组要简单多了。当然,当你有更多的对象(树、灌木、草、石头、水&&),这个方法的速度不是很快,而且你也很难记住什么地方放了什么方块。
如果你有Flash MX或更新版本,估计你已经听到过XML。他的格式和HTML很像,他允许声明许多东西。你也可以用XML来保存你的地图数据。下面的XML地图基于Jobe Makar的《Macromedia Flash MX Game Design Demystified》。看看这个XML的地图:
&map&&&row&&&&cell type=&1&&&&&cell type=&1&&&&&cell type=&1&&&&/row&&&row&&&&cell type=&1&&&&&cell type=&4&&&&&cell type=&1&&&&/row&&&row&&&&cell type=&1&&&&&cell type=&1&&&&&cell type=&1&&&&/row&&/map&
这里我们设定了3&3的地图。首先是头部&map&,然后设置了3个&row& 结点。每个row结点有3个cell结点。
如果从外部文件中载入地图,XML可能是很好的方案,因为大部分的XML解析可以有Flash MX内建的函数完成。从外部文本文件中载入二维数组可没有那么简单,你经常要靠loadVariables得到字符串,然后又不得不把字符串分割成数组,这个过程是很慢的。
XML也有缺点:他会导致更大的文件大小(不过对于现在的网络,这种大小可以忽略),而且你需要Flash Player 6以上。下面的所有例子都使用二维数组来存储地图数据,而且使用对象的方法来创建方块,就像在&地图的格式&中介绍的那样。
现在我们将会让方块在屏幕上显示出来、定位到合适的地方,然后显示正确的帧。就像这个:
首先我们先定义一些对象和值:
myMap = [[1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1]];game={tileW:30, tileH:30};//可通行的方块game.Tile0= function () {};game.Tile0.prototype.walkable= game.Tile0.prototype.frame=1;//墙game.Tile1= function () {};game.Tile1.prototype.walkable= game.Tile1.prototype.frame=2;
你可以看到,我们把地图保存在myMap数组中。地图定义的下一行,定义了一个叫game的对象。我们会把所有用到的其他对象(方块、敌人&&)都作为game的子对象,我们也可以不这样做,直接把所有的对象都放在主场景_root或者其他任何地方,但是这样做(把对象都放在一个固定的地方)更加清晰一些。
注意我们给了game对象2个属性:tileW和tileH,两个属性的值都是30。那表示我们的方块的宽度(tileW)和高度(tileH)。方块不一定是个正方形,你也可以使用长宽不相等的矩形。一旦我们想要知道,方块的宽度和高度,我们可以这样写:
game.tileWgame.tileH
而且如果你想要改变方块的大小,只要在一行代码中改就行了。
下面的几行代码在game对象里面构造了Tile0对象,然后用prototype构造了他的2个属性。 game.Tile0= function () {};game.Tile0.prototype.walkable= game.Tile0.prototype.frame=1;
第一行的 game.Tile0=function(){} 声明了一个新的对象类型。当我们从地图的二维数组中得到0时,我们就会使用Tile0作为相应方块的类型。
下面2行告诉我们Tile0对象和所有用Tile0创建的对象都具有的一些属性。我们会设置他们的walkable为true(意味着无法通行)还有frame为1(复制方块mc后,显示在第一帧)。
你准备好做整个地图了吗?我们将要写个函数来布置所有的方块了,函数名取做buildMap。如果你要显示别的地图,你也可以使用这个函数。buildMap将要做的是:
+复制一个空mc作为容器(放置各种对象)+遍历地图数组+为每个小格子创建相应的方块对象+复制所有的方块mc+定位所有的方块mc+让所有的方块mc显示在正确的帧
这是代码:
function buildMap (map) {&_root.attachMovie(&empty&, &tiles&, ++d);&game.clip=_root.&var mapWidth = map[0].&var mapHeight = map.&for (var i = 0; i & mapH ++i) {&for (var j = 0; j & mapW ++j) {&&var name = &t_&+i+&_&+j;&&game[name]= new game[&Tile&+map[i][j]];&&game.clip.attachMovie(&tile&, name, i*100+j*2);&&game.clip[name]._x = (j*game.tileW);&&game.clip[name]._y = (i*game.tileH);&&game.clip[name].gotoAndStop(game[name].frame);&}&}}
第一行声明了buildMap作为一个函数,并且参数是map。当我们调用这个函数的时候,我们会同时传递地图数组给他。
下面的一行复制了一个空mc到舞台上:_root.attachMovie(&empty&, &tiles&, ++d);
你需要一个空mc(里面没有任何东西)在库中。在库面板中右键单击这个mc,选择&Linkage&&(链接),选择&Export this symbol&(导出这个符号),在ID栏填上&empty&。现在,attachMovie命令会在库中查找链接名称是empty的mc,找到之后他会在舞台上复制一个这样的mc,并给他一个新的名字tiles。这个mc将会收容舞台上所有的方块,就相当于一个容器。使用这样的容器有个很美妙的事情,就是每当我们想要删除所有的方块时(比如游戏结束),我们只需要删除tiles这个mc就行了,然后所有的方块都消失了。如果你不用容器,直接把方块都复制到_root(主场景)中,那么当你进入下一场景的时候(比如游戏结束),这些复制的方块不会消失,你不得不使用更多的actionscript来删除他们。
复制了这个tiles之后,我们还要把他连接到我们的game对象中:game.clip = _root.tiles现在,当我们需要访问tiles时,我们只需要使用game.clip,这很便利。如果我们需要把tiles放到别的地方,我们只需要改一下这行就行了,不需要改动整个代码。
然后我们创建了两个新的变量:mapWidth和mapHeight。我们通过这两个变量遍历整个地图数组。mapWidth的值是地图数组的第一个元素的长度。如果你忘了地图数组什么样子,回头看看。地图数组的第一个元素是一个数组[1,1,1,1,1,1,1,1],mapWidth就是他的长度值(数组的长度就是数组的元素个数),在这里就是8。现在我们从地图数组中知道了地图的宽度。
同理,mapHeight的值就是地图数组的长度值,他是数组的行数,也是地图的行数。
我们这样遍历地图数组:
for (var i = 0; i & mapH ++i) {for (var j = 0; j & mapW ++j) {
我们让变量i从0开始,每次自加1,直到他比mapHeight大。变量j从0循环到mapWidth。var name = &t_&+i+&_&+j变量name的到的值是和i、j的值有关的。假设i=0,j=1,那么name=&t_0_1&;如果i=34,j=78,那么name=&t_34_78&。
现在我们创建新的方块game[name]= new game[&Tile&+map[i][j]]
左边的game[name]表示新的方块对象将会放置在game对象里面,就像其他对象一样。map[i][j]的值告诉我们这个点(i,j)的方块类型,如果是0,就创建一个Tile0对象;如果是1,就创建一个Tile1对象。这个点的方块具有的属性在相应的Tile对象中都事先定义好了。当i=0,j=0时,相当于这样的形式:game[&t_0_0&]=new game[&Tile0&]
记住:所有的方块都作为game对象的子对象。
在下一行中,我们复制了一个新的mc到舞台上,并使用game.clip[name]来访问他。mc的坐标可以通过i,j值乘以方块宽度和方块高度得到。我们通过gotoAndStop命令让他跳到正确的帧,借助他继承得到的frame属性。
当我们需要创建地图时,我们这样调用buildMap函数就行了:buildMap(myMap);
再谈谈区块原型的定义
既然我们把区块作为对象处理,我们可以利用对象的许多优点。对象有个美丽的特性是他们可以继承属性。如果你认真阅读了上一章,你会记得我们这样写区块的原型:
game.Tile0= function () {};game.Tile0.prototype.walkable=game.Tile0.prototype.frame=1;
这些让我们写一次原型就可以在其他地方应用,创建新的方块时候就使用这个模板。我们还可以深入研究一下逻辑,再减少一些工作量。
让我们声明一个通用的区块类:
game.TileClass = function () {};game.TileClass.prototype.walkable=game.TileClass.prototype.frame=20;
这里我们用了一个假设。假设每个区块都是不可通行的,而且都显示在第20帧。当然了,实际的区块不全是不可通行的,否则我们不能移动。而且他们也不会都显示在第20帧。问题看起来很严重,实际上不然。我们只是定义了这两个通用属性而已,我们会让他完美工作的。
现在我们创建新的区块类型:
game.Tile0 = function () {};game.Tile0.prototype.__proto__ = game.TileClass.game.Tile0.prototype.walkable=game.Tile0.prototype.frame=1;game.Tile1 = function () {};game.Tile1.prototype.__proto__ = game.TileClass.game.Tile1.prototype.frame=2;game.Tile2 = function () {};game.Tile2.prototype.__proto__ = game.TileClass.
通过使用聪明的__proto__,我们不需要重复写同样的属性了。我们的区块从TileClass类中获得了所有必要的材料。当我们这样创建新的区块类型后:
game.Tile2 = function () {};game.Tile2.prototype.__proto__ = game.TileClass.
所有后来创建的Tile2区块都继承了属性walkable=false、frame=20。这是不是很美妙呢?但是还没有结束,我们可以改变这两个属性。看:
game.Tile0 = function () {};game.Tile0.prototype.__proto__ = game.TileClass.game.Tile0.prototype.walkable=game.Tile0.prototype.frame=1;
我们在继承了TileClass类的属性之后,又改写了walkable、frame的值。最后的结果是Tile0区块的walkable为true,frame为1。
所有这些可能太复杂了些,毕竟我们只有少量的区块类型和属性。但是你如果要做一个复杂的区块游戏,每个区块都有很多属性,那么单单定义这些重复的属性就已经够繁的了
每个游戏都有英雄。英雄要打败坏蛋、拯救公主,还要拯救全世界。我们也要加一个英雄,不过他暂时还不会拯救世界,他什么也干不会,但是他已经来了:
看到了吗,就是那个是红色的方块:)什么,看起来不够帅?你当然可以自己画一个呀。他就是库中那个名字是&char&的那个mc,而且他已经被导出为&char&连接。注意不要让英雄mc比方块大!
另外还要注意,英雄mc的注册点是在中心,而方块的注册点则在左上角:
要来些代码?好吧,加上这句:char={xtile:2, ytile:1};
这句代码定义了一个char对象。这个char对象将会被赋予所有的关于英雄的信息:他如何移动、他感觉怎么样、他吃什么&&等等。
不过这一次我们只给他两个属性:xtile和ytile。他们记录英雄所处的方块。当他四处走动的时候,我们将会更新xtile/ytile属性,这样我们总能知道他站在那个方块上面。例如当xtile=2,ytile=1时,他脚下的方块就是&t_1_2&。实际上,他是站在左数第3块、上数第2块方块上,记得坐标是从0开始数的。
我们以后会给他增加更多属性。
为了让英雄站到舞台上,在buildMap函数中,在for循环外(下面),添加这几行代码:game.clip.attachMovie(&char&, &char&, 10000);char.clip = game.clip.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;
第一行又复制了一个mc到game.clip这个mc中(你还记得我们用game.clip代表_root.tiles吧?),然后给他实例名&char&。
然后我们把char的路径保存到char对象中,这样当我们需要访问char这个mc时,不用再敲入mc的完整路径_root.tile.char了。这样做的好处是,如果我们要把char这个mc放到另外的地方,改动代码就会方便许多。
接下来我们要计算char对象的两个属性:x和y。你也许会纳闷,为什么还要两个属性,我们不是有xtile和ytile这两个属性了吗?记住,xtile和ytile只是脚底下方块的位置,不是我们需要的确切的象素值。英雄当然可以在同一块方块上面走动,x和y属性才可以给出正确的坐标。
还有,当x和y的值计算正确后再赋给_x和_y,这样做是有好处的,尤其是碰撞检测的时候。
我们通过英雄所在的方块计算出英雄的实际位置(象素值)。首先,char.xtile*game.tileW得到所在方块的实际坐标,在加上方块大小的一半,这样英雄就站到了方块的中间。如果你有些迷糊的话,可以对照他们的注册点自己画一下。
接着我们把英雄mc的宽度(宽度)的一半记为char对象的width(height)属性。这样做是很有用的,尤其是计算英雄的边界的时候。你也可以自己定义char的这两个属性的值。有些英雄可能有长长的头发,而且允许头发碰到墙上,身体却不行,这样你就应该按需要自己定义。
最后两行把英雄放到我们计算好的位置上去。char.clip._x = char.x;char.clip._y = char.y;
按键和移动
在这一章中我们将用四个方向键控制英雄的移动。在移动过程中,他会面朝移动的方向,并且会显示走动的动画。一旦他停止移动,动画也会停止。试试这个:
因为没有碰撞检测,所以英雄可以走出舞台外面,不过不要担心这个,我们以后会解决这个问题。
首先,让我们完善英雄角色。建立3个新的mc。我们需要一个mc表示角色向左走(或者向右,我选择了左),一个表示向上走,最后一个朝下走。在这些mc中,做角色走动的动画。
这些mc里不需要写代码。
现在,编辑char影片夹子(mc),在时间线上创建5个关键帧:
在关键帧1放置char_up影片夹子,关键帧2放置char_left影片夹子,关键帧4放char_right,关键帧5放char_down。许多时候向左移动和向右移动只是简单的水平翻转关系,所以你可以用一个mc表示这两个方向的动画。现在确认一下,这几帧动画mc的实例名称都是char,检查每一帧。他们都叫char?是的,不用担心。如果你不理解为什么是这样的排列方式,我们将会在代码的部分讲解这个问题。
ok,该写点代码了。
首先,移动需要有个速度,所以先给英雄添加一个速度属性:char={xtile:2, ytile:1, speed:4};
速度表示英雄每一步移动的象素值,更大的值意味着更快的移动,很小的值将会使英雄像个蜗牛。在这里使用整数是个好习惯,否则你会得到怪异的结果,实际上10象素和10.056873象素之间也看不出什么区别。
你还记得吧,我们创建了_root.char这个对象来保存英雄的信息(如果忘记了,请回头看看)?并且我们把char影片夹子放在tiles影片夹子里面了。为了让我们的英雄醒来并开始移动,我们需要添加两个函数来检查按键和控制mc。拖一个空的影片夹子empty到舞台上。你可以把它放到可视区域外,他只是用来放些代码,所以在哪里都无所谓。在这个mc上面写这些代码(选中mc,然后打开代码面板):onClipEvent (enterFrame) {&&& _root.detectKeys();}
你可以看到我们在每一帧调用detectKeys这个函数。现在写这个函数: function detectKeys() {&var ob = _root.&var keyPressed =&if (Key.isDown(Key.RIGHT)) {&&keyPressed=_root.moveChar(ob, 1, 0);&} else if (Key.isDown(Key.LEFT)) {&& keyPressed=_root.moveChar(ob, -1, 0);&} else if (Key.isDown(Key.UP)) {&&keyPressed=_root.moveChar(ob, 0, -1);&} else if (Key.isDown(Key.DOWN)) {&&keyPressed=_root.moveChar(ob, 0, 1);&}&&& if (!keyPressed) {&&ob.clip.char.gotoAndStop(1);&&& } else {&&ob.clip.char.play();&&& }}
首先我们定义了两个变量:ob 和 keyPressed。设置ob变量指向_root.char (记住,那是我们保存英雄所有信息的对象),设置变量keyPressed为false。keyPressed变量用来表示是否有四个方向键之一被按下去。
下面有4个相似的 if 判断,每个 if 都检测相应的键是不是被按下了。如果键被按下,他们就这样调用另外的一个函数moveCha:keyPressed=_root.moveChar(ob,1,0);
这一行调用moveChar函数的时候带了3个参数。第一个参数就是ob变量,就是我们的英雄对象。后两个的取值我们总是让他们为-1,1或者0。这些数字决定对象移动的方向,第二个参数表示水平移动的方向(-1:左;1:右),第三个参数代表垂直移动的方向(-1:上;1:下)。最后我们把moveChar的返回值交给变量keyPressed。你在后面就可以看到moveChar函数总是返回true,所以任何方向键被按下后,变量keyPressed值都是true。
现在来看看第二个函数moveChar:function moveChar(ob, dirx, diry) {&ob.x += dirx*ob.&ob.y += diry*ob.&ob.clip.gotoAndStop(dirx+diry*2+3);&ob.clip._x = ob.x;&ob.clip._y = ob.y;&return (true);}
看第一行,moveChar函数接收了3个参数,变量ob表示要移动的对象,dirx、diry分别表示x、y方向的移动。这是一个很通用的函数,我们可以用它移动游戏中所有东西。例如我们要让子弹飞行,我们就可以调用moveChar函数,同样,我们也可以用这个函数移动敌人。
接下来的两行我们给对象的x和y加上相应的值。同样,如果使用不同的对象(子弹、敌人),这些对象可以有不同的speed属性。所以,当我们检测到右箭头键时,我们调用moveChar函数时的参数是1,0 ,此时dirx=1,diry=0。所以x值会在原来的基础上增加(speed),而y则保持不变。如果我们调用moveChar函数的参数是0,-1(意味着上箭头键),那么y值就会在原来的基础上减小(speed),而x保持不变。
注意,如果我们还有其他的动作,比如碰撞或者跳跃,我们应该将这些动作单独计算。这样比简单的mc.hitTest方法要好不少。
这一句: ob.clip.gotoAndStop(dirx+diry*2+3);
他使得角色mc跳到正确的帧上,让角色面对正确的方向。你可以算出所有的dirx/diry组合(这里只有4种情况),如果你的角色mc时间线是和我们以前所说的设置一样的话,这里就不会出问题。你可以拿计算器算算看:)
没有计算器?那我们还是看看吧:假设按了方向键右,那么 dirx=1,diry=0,结果 dirx+diry*2=4。那么角色mc会跳到第4帧,那里正好是我们角色朝右走的动画。
接下来的两行,设置角色mc的_x/_y属性的值等于x/y的值。最后,我们返回一个true值,这样keyPressed就得到了正确的值。下一章我们将会介绍碰撞检测,很有趣的哦:)
像上面这个,英雄可以穿墙而过,那就没什么意思了。我们要想办法让英雄感受到障碍物的存在。
在第一章中,我们给每个方块都设置了一个&walkable&属性,当某个位置方块的walkable属性是false的时候,英雄就无法穿过它。当值为true的时候,英雄就可以从上面走过(这个东西叫做&逻辑&:)。
为了让这个逻辑起作用,我们将会这样做:当方向键被按下以后,我们首先检查下一个方块是不是可通行的。如果是,我们就移动英雄。如果不是,那么就忽略掉按键事件。
这是完美的墙的碰撞:&
英雄贴着墙站着,而且下一步他就会进到墙里面。我们不会让它发生的。
但是这个世界总是不够完美,要是英雄只和墙接触一部分呢?
这就要求我们检测英雄的全部四个角是否和墙接触了。只要任意一个角和墙接触(上图中是左下角),移动就是不合理的。
或者,英雄没有贴着墙站,但是下一步就要跑到墙里去了,虽然只是一部分:&
我们不得不让他这样贴着墙站着:
&这么难?!&,你也许会喊,&不太可能办到吧?&不用担心,实际上很简单的~
检查四个角
我们不希望英雄的任何一部分能进到墙里面去,只要四个角没有进去就行了,这是假设英雄的大体形状是个长方形(他们确实是的)。
为了实现这个功能,让我们写个函数:getMyCornersfunction getMyCorners (x, y, ob) {&ob.downY = Math.floor((y+ob.height-1)/game.tileH);&ob.upY = Math.floor((y-ob.height)/game.tileH);&ob.leftX = Math.floor((x-ob.width)/game.tileW);&ob.rightX = Math.floor((x+ob.width-1)/game.tileW);&//检测他们是否是障碍物&ob.upleft = game[&t_&+ob.upY+&_&+ob.leftX].&ob.downleft = game[&t_&+ob.downY+&_&+ob.leftX].&ob.upright = game[&t_&+ob.upY+&_&+ob.rightX].&ob.downright = game[&t_&+ob.downY+&_&+ob.rightX].}
这个函数接收了3个参数:对象中心的x/y位置(象素值)、对象的名称。
&等一下&,你也许会迷惑,&我们不是已经在英雄对象中保存了他的当前位置了吗?&是的,但是我们当时存的是当前的位置,这里处理的是将要达到位置(先假定英雄可以移动)。
首先,我们根据这个x/y坐标计算出英雄所处的方块。可能英雄的中心在一个方块上面,但是左上角在另外一个方块上面,左下角又在第三个方块中,这是有可能的。(y+英雄的高度)/方块高度=英雄下面的两个角所在区块的行值。
最后的四行使用了我们计算出的方块的可通行性。例如,左上角使用upY行leftX列的方块的walkable属性。你可以看到,得到的四个结果(upleft、downleft、upright、downright)被保存到ob对象中了,所以我们以后还可以用到它。
我要再一次指出的是,getMyCorners函数不仅可以用在英雄上面,这里的ob也可以是任何可移动的对象。做区块游戏要多考虑函数的通用性,在后面的章节中你会体会到这种思想的正确性。
当我们检查了四个角以后,现在就可以很简单地移动了:
如果4个角都是可以通行的,那么就移动,否则不移动。但是要让最后英雄贴着墙站着,还得多写几个字。修改后的moveChar函数处理4个可能的方向的移动,它看起来可能有些长,实际上仅仅是4段类似的代码。让我们看看:
function moveChar(ob, dirx, diry) {&getMyCorners (ob.x, ob.y+ob.speed*diry, ob);&if (diry == -1) {&&if (ob.upleft and ob.upright) {&&&ob.y += ob.speed*&&} else {&&&ob.y = ob.ytile*game.tileH+ob.&&}&}&if (diry == 1) {&&if (ob.downleft and ob.downright) {&&&ob.y += ob.speed*&&} else {&&&ob.y = (ob.ytile+1)*game.tileH-ob.&&}&}&getMyCorners (ob.x+ob.speed*dirx, ob.y, ob);&if (dirx == -1) {&&if (ob.downleft and ob.upleft) {&&&ob.x += ob.speed*&&} else {&&&ob.x = ob.xtile*game.tileW+ob.&&}&}&if (dirx == 1) {&&if (ob.upright and ob.downright) {&&&ob.x += ob.speed*&&} else {&&& ob.x = (ob.xtile+1)*game.tileW-ob.&&}&}&ob.clip._x = ob.x;&ob.clip._y = ob.y;&ob.clip.gotoAndStop(dirx+diry*2+3);&ob.xtile = Math.floor(ob.clip._x/game.tileW);&ob.ytile = Math.floor(ob.clip._y/game.tileH);&//---------下面两行由qhwa添加--------&ob.height = ob.clip._height/2;&ob.width = ob.clip._width/2;&//---------------------------------&return (true);}
像以前一样,moveChar函数通过键盘检测函数传递过来的值得到对象和方向。这一行:getMyCorners (ob.x, ob.y+ob.speed*diry, ob);计算垂直移动(当diry不等于0时)后的四个角的可行性,随后,通过四个角walkable的值检查是不是合法的移动:if (diry == -1) {&&if (ob.upleft and ob.upright) {&&&ob.y += ob.speed*&&} else {&&&ob.y = ob.ytile*game.tileH+ob.&&}&}
这块代码是用来检测向上的移动的。当上箭头键被按下去后,diry的值等于-1。我们使用了getMyCorners函数得到的ob.upleft和ob.upright值,如果他们都是true,那就意味着上面两个角所在方块都是可通行的,我们就给角色的y坐标加上ob.speed*diry,让角色朝上移动。
但是如果这两个角任何一个碰巧是不可通行的,即ob.upleft或者ob.upright是false,我们就要把角色放到墙边上。为了让角色贴着它上面的墙,他的中心点必须距离当前方块的上边缘char.height象素,如图:
ob.ytile&game.tileH得到的是当前方块的y坐标,也就是上边缘的y坐标,再加上角色的height值,就是正确的位置了。同样的道理,另外三个方向的部分也可以这样分析出来。
最后一行的把实际的mc放到计算出来的坐标处,让角色显示正确的动画帧,并且更新角色的属性。同以前一样,函数返回true值。
Qhwa注:我在这里加了两行ob.height = ob.clip._height/2;ob.width = ob.clip._width/2;
这是因为当clip这个影片夹子跳转相应的帧后,原来的_width和_height可能会发生变化,如果还用初始化时的值,可能就会出错。如果英雄的高度和宽度是一样的,就没有必要这么做了。Tony推荐使用确定的而且相同的高度和宽度。
芝麻开门-地图切换
你能在一个房子里面呆多久?一张图片能看多久?是的,我们需要提供更多的空间给英雄。那意味着要改变地图、创建新的房间、把英雄放置到合适的位置。
为了创建两个房间,我们声明了两个地图数组: myMap1 = [[1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 0, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1, 0, 1], [1, 0, 0, 0, 0, 0, 0, 2], [1, 1, 1, 1, 1, 1, 1, 1]];
myMap2 = [[1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0, 1],[1, 0, 1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1, 0, 1], [2, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1, 1]];
在game对象中设置当前的地图序号:game={tileW:30, tileH:30, currentMap:1}
然后,我们开始扫描地图myMap1,通过buildMap函数把它实现到屏幕上。我们可以把myMap1作为一个参数传递给buildMap函数,这样buildMap函数就具备更好的通用性。
buildMap(_root[&myMap&+game.currentMap]);
接下来,我们还需要一个描绘&门&的对象:
game.Doors = function (newmap, newcharx, newchary){&this.newmap =&this.newcharx =&this.newchary =};
game.Doors.prototype.walkable =game.Doors.prototype.frame = 3;game.Doors.prototype.door =game.Tile2 = function () { };game.Tile2.prototype = new game.Doors(2, 1, 4);game.Tile3 = function () { };game.Tile3.prototype = new game.Doors(1, 6, 4);
你可能已经猜到了,Doors对象是可以通行的,它是mc的第3帧,它还有一个属性door,而且值是true.我们后面将会利用这个属性来判断英雄是不是走到了门口.
这里用到一个东西,叫做&继承&,听起来有些恐怖?呵呵,其实很简单也很有用.所有的门对象创建的时候都用了Doors模板,Doors对象拥有的所有的所有属性都传递给了他们.比如他们都是可通行的,而且都是第3帧.
我们创建一个门的时候,必须指定几个必要的信息:它是通往哪个房间(map)的,还有,英雄的新坐标是多少?
你可能要问,为什么要给英雄指定新坐标呢?因为创建新的房间以后,如果英雄还在原来的坐标,就有可能看起来不正确.还有,要避免英雄的新坐标所在的方块是不可通行的障碍物,或者是另外一个门.最糟糕的结果是英雄不停地被传送于两个房间,天哪~ 最合理的应该是把英雄放在新地图中门的旁边。
在创建门方块的时候,我们传递了3个参数:newMap、newCharX、newCharY。他们分别是新的地图序号,新的X坐标和新的Y坐标。当地图数组中出现数字2的时候,buildMap函数会在相应的地方创建一个Tile2方块。英雄穿过Tile2方块后,他将到达myMap2,他的新坐标是(1,4)。你可以在多个地图中都用2,他们都可以让英雄回到myMap2。
更多的代码
在moveChar函数中插入下面这段代码,放在return语句之前:if (game[&t_& + ob.ytile + &_& + ob.xtile].door and ob == _root.char){&changeMap(ob);}
他的作用是,我们在移动了对象之后判断是不是站到了门口,而且对象要是英雄才行,如果都满足要求,那么就换个地图。我们利用changMap函数实现更换地图的功能:
function changeMap(ob){&var name = &t_& + ob.ytile + &_& + ob.&game.currentMap = game[name].newM&ob.ytile = game[name].&ob.xtile = game[name].&ob.frame = ob.clip._&buildMap(_root[&myMap& + game.currentMap]);}
这个函数很好理解,我们从门对象(game[name])中得到newMap、newCharX、newCharY参数,然后调用buildMap函数创建新地图。
这里用到了一个新的属性:frame,它是用来记录英雄当前的方向的,如果没有记录,在新地图里面,英雄总是停在第一帧。我们同时还要加这句as到buildMap函数中(设置了英雄的坐标以后):char.clip.gotoAndStop(char.frame);
这就是关于门的全部了,好了,多串串门吧~
开始之前,我们先把视角从俯视改成侧视,这样英雄才可以跳跃。就像下面的这个,按左右键英雄移动,按空格键跳跃:
跳跃意味着上升,上升在Flash中意味着_y属性的减少。所以我们需要计算这样的式子:新的坐标=现在的坐标-上升速度
如果只计算一次,坐标只改变一次,英雄很快就停止了。因此我们需要持续不断的计算新坐标。而且,我们还应该改变速度的值,否则英雄就在空中下不来了。下落和上升一样,只不过速度值前面加个负号而已。
为了改变速度,我们定义一个新的变量:重力加速度。重力把英雄拉向地面,学过物理吧?重力加速度并不直接改变坐标,他改变的是速度:速度=速度+加速度
这就是Flash中的表示方法,=可不是表示左右相等,是把右边计算的结果赋给&速度&变量。这个式子也是需要不停计算的,以便保持连贯的运动。你可以改变重力的值,较小的值意味着在空中的时间更长,较大的值很快就会把英雄&拉&下来。从另外的角度看,重力也意味着跳跃能力,我们可以给不同的对象赋以不同的&重力&属性,这样他们跳得就不至于一样高。
让我们来看一个例子。比如刚开始的速度是-10,重力是2。那么开始的时候,英雄将会上移10象素,然后速度降低到8;接着英雄上移8象素,然后速度又变成了6&&如此往复,直到速度等于0的时候,英雄不再上升了。接着速度成了2,英雄开始下降2象素;下一步又下降4象素、6象素、8象素&&直到落地。
落地后,跳跃自然也应该结束了。但是要是英雄在跳跃过程中顶到障碍物怎么办?很简单,我们把速度强行改成0就行了,然后英雄就会落下来。
【注意】在区块游戏开发中,不要让速度值超过方块的高度。过大的速度会导致碰撞检测跳过某个方块,导致英雄&穿过&障碍物。或许有些魔法师可以穿过障碍物,但是在普通的区块游戏中,这是个bug。
跳跃并不影响水平方向的运动,在空中我们还可以用方向键控制英雄的水平运动。我们需要做的是,在左移或右移后,判断英雄脚下是不是还有东西,如果是空的,跳跃就开始了(这时候初始速度是0,英雄直接下落)。
会跳的英雄
我们给英雄再添加一些属性:char = {xtile:2, ytile:1, speed:4, jumpstart:-18, gravity:2, jump:false};speed属性是水平移动速度,jumpstart是跳跃的初始速度,granvity是重力值,jump属性用来表示英雄是不是在跳跃过程中。
下面加一句as到buildMap函数中:char.y = ((char.ytile + 1) * game.tileW) - char.
因为我们的视图是侧视的,英雄刚开始的位置可能是&漂&在空中的,我们应该让他站到地面上来。
changeMap函数和getMyCorners函数不需要任何变动。
腾空的感觉
我们先来改造detectKey函数,删除上下键检测,添加空格键检测:
function detectKeys(){&var ob = _root.&var keyPressed =&if (Key.isDown(Key.SPACE) and !ob.jump)&{&&ob.jump =&&ob.jumpspeed = ob.&}&if (Key.isDown(Key.RIGHT))&{&&keyPressed = _root.moveChar(ob, 1, 0);&}&else if (Key.isDown(Key.LEFT))&{&&keyPressed = _root.moveChar(ob, -1, 0);&}&if (ob.jump)&{&&keyPressed = _root.jump(ob);&}&if (!keyPressed)&{&&ob.clip.char.gotoAndStop(1);&}&else&{&&ob.clip.char.play();&}}
注意看我们怎么避免跳跃过程中空格键触发新的跳跃(听起来很拗口,哈哈),实际上就是当处于跳跃中时,忽略空格键。如果按了空格键,而且英雄没有处于跳跃过程,那就开始跳吧,把jump属性改成true,把jumpspeed改成speedstart属性值。
看一下,在左右方向键的检测语句后面,我们添加了几句。if (ob.jump)&{&&keyPressed = _root.jump(ob);&}
如果jump属性为true(正在跳),那么执行jump函数。只要jump属性为true,jump函数就会不断地被执行,直到jump属性变成了false。这个函数可以在空格键松开后仍然执行,只要jump属性为true。
Jump函数所做的只是改变jumpspeed的值,给他加上重力值。同时做一些处理防止速度值过大,让jumpspeed不会超过方块高度。最后调用moveChar函数移动角色。
function jump (ob){&ob.jumpspeed = ob.jumpspeed + ob.&if (ob.jumpspeed & game.tileH)&{&&ob.jumpspeed = game.tileH;&}&if (ob.jumpspeed & 0)&{&&moveChar(ob, 0, -1, -1);&}&else if (ob.jumpspeed & 0)&{&&moveChar(ob, 0, 1, 1);&}&return (true);}
我们还需要改一下moveChar函数,因为加入了跳跃过程,跳跃时的速度jumpspeed和水平移动的速度speed是不同的:
function moveChar(ob, dirx, diry, jump){&if (Math.abs(jump) == 1)&{&&speed = ob.jumpspeed *&}&else&{&&speed = ob.&}&...
jump参数是从上面的jump函数中传递过来的,它的取值不是1就是-1。如果jump的绝对值(Math.abs)是1,移动的距离就是jumpspeed*jump,否则就是speed属性。
如果这一步移动结束后,角色的顶到了障碍物,就要把jumpspeed值改成0:ob.y = ob.ytile * game.tileH + ob.ob.jumpspeed = 0;
如果这一步移动结束后,角色站到了地面上,跳跃就该结束了:ob.y = (ob.ytile + 1) * game.tileH - ob.ob.jump =
在左右移动后,我们还要看看角色是不是还站在平台上,如果不是,就应该落下来:ob.x += speed *fall (ob);
所以,我们还需要一个fall函数:function fall (ob){&if (!ob.jump)&{&&getMyCorners (ob.x, ob.y + 1, ob);&&if (ob.downleft and ob.downright)&&{&&&ob.jumpspeed = 0;&&&ob.jump =&&}&}}
如果角色已经处于跳跃过程中,这个函数就没有必要运行了,它是用来检测&踩空&的情况的,只有当角色站着(!ob.jump)的时候才有用。这时候我们调用getMycorners函数,如果角色下方的两个方块都是可通行的,那就应该落下来了,起始速度是0,然后把jump属性改成true。
到目前为止,我们已经做出了阻止英雄通过墙的效果。很有趣,不是吗?许多游戏还有一类叫做&云&的方块,角色门可以左右穿行他们,甚至可以从下面跳上去,但是当下落的时候,他们确是不可通行的,英雄会停在上面。看这个例子:
你看到区别了吧?让我们来看看图。这个是普通的砖墙方块,英雄不能从任何角度穿过它:
再来看云。除了上面,英雄可以从任何方向穿过。如果英雄从上面掉下来,我们让他停在上面。
首先我们要做一些带有&cloud&属性的方块,如果cloud属性是true,这个方块就是一块&云&。定义:
game.Tile4 = function () {};game.Tile4.prototype.walkable =game.Tile4.prototype.cloud =game.Tile4.prototype.frame = 4;
它的walkable属性是true,意味着英雄可以穿行过去。为了让英雄能站到上面,我们需要创建新的函数。
function checkIfOnCloud (ob){&var leftcloud = game[&t_& + ob.downY + &_& + ob.leftX].&var rightcloud = game[&t_& + ob.downY + &_& + ob.rightX].&if ((leftcloud or rightcloud) and ob.ytile != ob.downY)&{&&return(true);&}&else&{&&return(false);&}}
我们检测英雄的左下角和右下角的方块是不是云,只要有一块是,就返回true。否则返回false。
现在我们需要在两个地方调用这个函数:moveChar函数中往下运动的时候,还有fall函数中检测英雄是不是继续下落的时候。
在moveChar函数中if (diry == 1)的后面原来有这句:
if (ob.downleft and ob.downright){&...
改成这样,加上云的检测:
if (ob.downleft and ob.downright and !checkIfOnCloud (ob)){&...
在fall函数中也一样,把这个:
if (ob.downleft and ob.downright){&...
if (ob.downleft and ob.downright and !checkIfOnCloud (ob)){&...
只有左下方和右下方都可通行,而且下面的不是云,英雄才能往下掉。
在区块游戏中梯子是很常见的一种东西。英雄可以在梯子上爬上爬下(我打赌你不知道:)。当在梯子上按上下方向键的时候,我们会让英雄上下攀爬。
看起来梯子很简单,实际上又很多东西需要考虑。首先,有多少种梯子?
在图中,有4种不同种类的梯子。梯子A处于一个不可通行的障碍物中。英雄在上面能做什么呢?他可以上下爬,但是不能左右运动,否则就会卡在墙里,那可不好受。
qhwa注: 有些游戏中在这种梯子上是可以左右移下来的,这个功能可以由你自己添加。
梯子B所在的方块是可通行的,而且它的上面还有梯子,所以英雄可以上下爬,也可以左右移动。但是当他左右移动的时候,就该掉下来了。
梯子C下面没有梯子了,英雄只能在它上面向上爬,或者左右移动。
梯子D并不是在所有的游戏中都会出现。有些人认为这种梯子是设计的失误,因为他们不导向任何地方,在空中就断掉了。英雄可以爬上去然后站到梯子上面吗?如果梯子顶部的左右有方块,英雄可以走过去吗?这些都是容易出现分歧的。
这些都是梯子的一些例子,当然还有其他形式的梯子,但是我希望你能看到在开始写代码之前理一下思绪是多么重要。游戏各不相同,可能这里的东西在有的时候很适用,但是可能在别的地方就未必了,只要你每次写代码之前思考一下,不要硬套,就会事半功倍。
让我们列一下关于梯子的规则:
1. 通过上下方向键,英雄可以在梯子上上下移动2. 当英雄和梯子接触时,他可以爬上去3. 当英雄和梯子接触,且下方也有梯子时,他可以爬下来4. 当英雄在梯子上,且左右没有墙时,他可以左右移动5. 英雄不能在梯子上跳跃
这些应该够了。
请给我一把梯子
梯子是显示在方块的上面的,所以我们要给他做一个独立的影片夹子。这样我们就不用为上面说的不同类型的梯子创建不同的图像了。确定你的梯子mc被导出到as(&Export for as&),并且检查链接名是否为&ladder&。
在ladder影片夹子中,画出如上形状的梯子,梯子水平方向在方块的中间。
和其他方块一样,我们也要定义梯子的原型:
game.Tile4 = function () {};game.Tile4.prototype.walkable =game.Tile4.prototype.frame = 2;game.Tile4.prototype.ladder =game.Tile4.prototype.item = &ladder&;
game.Tile5 = function () {};game.Tile5.prototype.walkable =game.Tile5.prototype.frame = 1;game.Tile5.prototype.ladder =game.Tile5.prototype.item = &ladder&;
这两个不同的方块(Tile4和Tile5)都具有frame属性,这是用来表示梯子后面(在屏幕上是下面层)的方块类型。他们还有值为true的ladder属性(用来表示这里有把梯子),值为&ladder&的item属性(用来attachMovie用的,复制ladder影片夹子)
在buildMap函数中复制ladder影片夹到方块中:
game.clip[name].gotoAndStop(game[name].frame);if (game[name].item != undefined){&game.clip[name].attachMovie(game[name].item, &item&, 1);}
这段代码首先让方块显示正常的帧(由frame属性决定),然后判断item属性是否为空,如果不是(有值)就复制item表示的mc。你可以把item属性设定成别的值,这样就可以复制别的mc,在别的地方也可以用到,只是要注意别在一个方块中复制太多不同的mc。
为了不重复输入代码,我们把moveChar函数的结束部分修改一下,调用一个新函数updateChar:
updateChar (ob, dirx, diry);return (true);
这是updateChar函数:
function updateChar (ob, dirx, diry){&ob.clip._x = ob.x;&ob.clip._y = ob.y;&ob.clip.gotoAndStop(dirx + diry * 2 + 3);&ob.xtile = Math.floor(ob.clip._x / game.tileW);&ob.ytile = Math.floor(ob.clip._y / game.tileH);&if (game[&t_& + ob.ytile + &_& + ob.xtile].door and ob == _root.char)&{&&changeMap (ob);&}}
在fall函数中添加:
ob.climb =
修改detectKeys函数,添加上下键的监测:
if (Key.isDown(Key.RIGHT)){&getMyCorners (ob.x - ob.speed, ob.y, ob);&if (!ob.climb or ob.downleft and ob.upleft and ob.upright and ob.downright)&{&&keyPressed = _root.moveChar(ob, 1, 0);&}}else if (Key.isDown(Key.LEFT)){&getMyCorners (ob.x - ob.speed, ob.y, ob);&if (!ob.climb or ob.downleft and ob.upleft and ob.upright and ob.downright)&{&&keyPressed = _root.moveChar(ob, -1, 0);&}}else if (Key.isDown(Key.UP)){&if (!ob.jump and checkUpLadder (ob))&{&&keyPressed = _root.climb(ob, -1);&}}else if (Key.isDown(Key.DOWN)){&if (!ob.jump and checkDownLadder (ob))&{&&keyPressed = _root.climb(ob, 1);&}}
当我们检测了左右键之后,我们判断英雄是不是不在跳跃过程中(!ob.jump),而且利用checkUpLadder函数和checkDownLadder函数判断附近是不是有梯子,如果一切正常,调用climb函数来移动英雄。
攀爬动作的函数
我们将要创建3个新的函数,1个为了检测是否能往上爬,1个为了检测是否能往下爬,还有一个是实现攀爬动作的函数。
function checkUpLadder (ob){&var downY = Math.floor((ob.y + ob.height - 1) / game.tileH);&var upY = Math.floor((ob.y - ob.height) / game.tileH);&var upLadder = game[&t_& + upY + &_& + ob.xtile].&var downLadder = game[&t_& + downY + &_& + ob.xtile].&if (upLadder or downLadder)&{&&return (true);&}&else&{&&fall (ob);&}}
这段代码首先计算英雄的上下两个y坐标(头和脚),根据所在的区块的ladder属性就可以判断是否可以往上爬。如果上下都没有梯子,我们检测英雄是否应该掉下来。
function checkDownLadder (ob){&var downY = Math.floor((ob.speed + ob.y + ob.height) / game.tileH);&var downLadder = game[&t_& + downY + &_& + ob.xtile].&if (downLadder)&{&&return (true);&}&else&{&&fall (ob);&}}
为了检测往下的攀爬动作,我们需要英雄脚底下方块的ladder属性。和往上爬不同,我们还要考虑到英雄接下来(移动结束)所在的方块的ladder属性(ob.speed+ob.y+ob.height)。
function climb (ob, diry){&ob.climb =&ob.jump =&ob.y += ob.speed *&ob.x = (ob.xtile * game.tileW) + game.tileW / 2;&updateChar (ob, 0, diry);&return (true);}
在climb函数中,我们首先设置climb标记为true,jump标记为false。然后计算新的y坐标,把英雄放在梯子方块的中间,ob.x = (ob.xtile * game.tileW) + game.tileW / 2;
英雄可以在梯子左侧或右侧抓着梯子爬,但是这样不太雅观:)
最后我们利用updateChar函数移动英雄到正确的位置。
愚蠢的敌人
我们的英雄已经很完美了,但是他很无聊,唯一能做的只是来回走。我们需要别的东西。不是食物,不是饮料,也不是美女,我们需要的是&&一些敌人。敌人就像加在汤里的盐,缺了它,一切都索然无味。好的游戏中会有聪明的敌人,但是我们从一些最笨的敌人开始做起。他们所做的仅仅是来回走,顺便检测是不是碰上英雄了。
到目前为止,我们已经有了两类对象:英雄和方块。英雄由玩家操纵,方块不会运动。敌人应该类似英雄,唯一不同的是,我们不能操作他们移动,我们将会赋予他们一定的智能。我们将要做两种不同的敌人,第一种上下走,第二种会左右走。他们都会在撞到墙上后回头继续走。(真够笨的:)
在你开始构造你的超级复杂的敌人之前,再想想一些东西。许多游戏实际上没有用到敌人,有的虽然用到了,也不是很聪明。Flash并不是非常强大,如果你的游戏中有100个敌人,他们都聪明地使用A*算法跟踪英雄,我真的怀疑是否有这么强大的机器能运行。如果可以的话,最好让一些敌人愚蠢些,一些聪明些,结果玩家可能就会忽略了他们的差别。另外,我们都想要比别人聪明,所以就让玩家感觉这种乐趣好了 :)
同创建英雄一样,创建一个剪辑放置敌人(如果忘了怎么做,在回头看看)。他们也有4帧,分别是左、上、下、右的动画。同样的,他们也要导出为&enemy1&和&enemy2&(在库面板中设置linkage)。现在我们添加一个enemies数组:
myEnemies = [[0],[[1, 6, 1]],[[2, 1, 3]]];
看得出来,我们在map1放了1个敌人。[1,6,1]:第一个1代表敌人的类型(hoho~我们有好多中敌人得)6,1是他开始的位置。创建地图的时候,我们会把他放到x=6,y=1的方块上。同理在map2中也有一个敌人,但是类型是2,位置是1,3。你可在一个地图中放置多个敌人,但是千万记住,不要把他们嵌到墙里面!记得要放在一个可通行的方块上面。
让我们声明一些敌人的模板:
game.Enemyp1= function () {};game.Enemyp1.prototype.xMove=0;game.Enemyp1.prototype.yMove=1;game.Enemyp1.prototype.speed=2;
game.Enemyp2= function () {};game.Enemyp2.prototype.xMove=1;game.Enemyp2.prototype.yMove=0;game.Enemyp2.prototype.speed=2;
他们的代码看起来很相似,但是他们动作就不一样了。Enemyp1会上下走,因为他的yMove属性是1;但是Enemyp2只会水平移动。你可以设置xMove/yMove属性为1或-1或0。不过请不要同时把这两个属性都设置成非零值,除非你希望敌人能走对角线。
你也可以把xMove和yMove都设置成0,这样的敌人没有移动的能力,也许你会用到。
speed属性声明了敌人移动的速度。不同的敌人可以有不同的速度。
在buildMap函数中,介于创建门和创建英雄的代码之间,加入如下代码:
var enemies = myEnemies[game.currentMap];game.currentEnemies = enemies.for (var i = 0; i&game.currentE ++i) {& var name = &enemy&+i;& game[name]= new game[&Enemyp&+enemies[i][0]];& game.clip.attachMovie(&enemy&+enemies[i][0], name, 10001+i);& game[name].clip=game.clip[name];& game[name].xtile = enemies[i][1];& game[name].ytile = enemies[i][2];& game[name].width = game.clip[name]._width/2;& game[name].height = game.clip[name]._height/2;& game[name].x = (game[name].xtile *game.tileW)+game.tileW/2;& game[name].y = (game[name].ytile *game.tileH)+game.tileH/2;& game[name].clip._x = game[name].x;& game[name].clip._y = game[name].y;}
这段代码什么意思?首先我们得到当前地图的敌人数组,转存为enemies数组,然后把敌人的个数传给currentEnemies,接着遍历敌人数组,把他们都安置好。(记住,虽然这里只用到了一个敌人,但是可以有更多的)
变量name的值是新创建的敌人的名字,&enemy0&,&enemy1&,&enemy2&&&依次类推。然后我们从相应的模板(刚刚声明过)创建新的对象:
game[name]= new game[&Enemy&+enemies[i][0]];从敌人数组(enemies[i])的第一个元素(enemies[i][0])得到敌人的类型,比如是1,然后通过enemyp1模板创建一个敌人。
下面的几行是取得坐标,然后把敌人放置到该处。ok,这就是buildMap函数该变动的地方。
但是,你也许会大声喊出来,但是他还不会动!好吧,我们就让他动起来
同人一样,敌人也需要脑子,我们就写个enemyBrain函数好了:
function enemyBrain () {for (var i = 0; i&game.currentE ++i) {& var name = &enemy&+i;& var ob = game[name];& getMyCorners (ob.x+ob.speed*ob.xMove, ob.y+ob.speed*ob.yMove, ob);& if (ob.downleft and ob.upleft and ob.downright and ob.upright) {&&& moveChar(ob, ob.xMove, ob.yMove);& } else {&&& ob.xMove = -ob.xM&&& ob.yMove = -ob.yM& }& var xdist = ob.x - char.x;& var ydist = ob.y - char.y;& if (Math.sqrt(xdist*xdist+ydist*ydist) & ob.width+char.width) {&&& removeMovieClip(_root.tiles);&&& _root.gotoAndPlay(1);& }}
正如你所看到的,我们又要遍历数组了。我们把enemies数组的每个元素存到ob,当i等于0的时候,ob就是enemy0。
然后我们调用getCorners函数,检查敌人是不是碰到了障碍物,如果upleft、downleft、upright、downright都是true,则说明可以行走。我们就可以放心的调用moveChar函数,同时传递xMove、yMove给moveChar函数。我们以前用moveChar都是移动英雄的,现在移动敌人也没有任何区别,我说过,我们可以重复使用许多函数的。
如果英雄碰到了障碍物,我们就让xMove和yMove都取相反数,比如原来xMove是1,就取-1,这样敌人就会掉头移动。如果yMove原来是0,那么相反数还是0,没有影响。
最后一部分检测英雄和敌人的距离,看是不是碰到了一起,如果是,oh,game over。当然,游戏不可能这么简单的结束掉,你可以减少英雄的生命值或者做别的处理,这些都由你自己完成。我们使用的计算距离的公式用的是&勾股定理&,如果你需要精确到象素级别的接触,你应该用hitTest,但是这里没有必要这么精确。别离敌人太近,否则你会死掉的。:-)
我们需要不停的调用这个函数,所以在detectKeys函数中加入:
_root.enemyBrain();
这就是一个敌人,很愚蠢的敌人。接下来我们要做更聪明的敌人,他们象人一样四处巡逻,碰到障碍物后会改变方向继续巡逻。hoho,继续吧~
平台上的敌人
如果你需要在平台上面放一个巡逻的敌人,就像这样:
只需要写几行就行了。敌人可以来回巡逻,碰到边缘的时候会自动折回。这个需要敌人检测下一个落脚点方块的通行性:
getMyCorners (ob.x+ob.speed*ob.xMove, ob.y+ob.speed*ob.yMove+1, ob);if (!ob.downleft and !ob.downright) {
注意一下这里的一个很重要的数字:1。 (ob.y+ob.speed*ob.yMove+1)加1是为了检查英雄脚下面的方块。还有要注意的是只有当downleft和downright都是true的时候,才能继续向前走,否则就掉头走。
教给敌人一些窍门
碰到障碍物可以改变方向,而不是简单的回头走。
让我们修改一下enemyBrain函数。以前是简单的将ob.xMove和ob.yMove取相反数,现在我们给他一个随机的新方向:
ob.xMove = random(3)-1;if (ob.xMove) {&ob.yMove = 0;} else {&ob.yMove = random(2)*2-1;}
一旦敌人接触障碍物,xMove就会得到一个随机的数值(-1,0或1,random(3)会返回0,1或2)。如果生成的xMove值是0,我们就设置yMove为1或-1。random(2) : 0 或 1random(2)*2 : 0 或 2random(2)*2-1 : -1 或 1如果生成的xMove值是1,那么就把yMove值设置成0,然后重新生成xMove值。
这样就好多了,但是如果我们还可以优化一下,我们应该避免与原来相同或相反的方向。
} else {& if (ob.xMove == 0) {&&& ob.xMove = random(2)*2-1;&&& ob.yMove = 0;&&& getMyCorners (ob.x+ob.speed*ob.xMove, ob.y+ob.speed*ob.yMove, ob);&&& if (!ob.downleft or !ob.upleft or !ob.downright or !ob.upright) {&&&&& ob.xMove = -ob.xM&&& }& } else {&&& ob.xMove = 0;&&& ob.yMove = random(2)*2-1;&&& getMyCorners (ob.x+ob.speed*ob.xMove, ob.y+ob.speed*ob.yMove, ob);&&& if (!ob.downleft or !ob.upleft or !ob.downright or !ob.upright) {&&&&& ob.yMove = -ob.yM&&& }& }}
这回我们先检查当前的方向。例如,如果原先是垂直移动(xMove==0)那么我们让xMove为1或-1,然后让yMove为0。但是如果敌人在地图的拐角处,他的新方向可能会让他再一次撞上墙。这就是为什么我们要调用getCorners函数来检测障碍物,如果有障碍物,掉头走。
Ok,这回敌人更加聪明了一些,因为玩家不能预知他遇到障碍物后的行走方向。但是你也注意到了,这个傻小子只会去撞墙然后改变方向。如果你的地图中有大范围的空白区域,玩家很难保证敌人能经过那里。最好的例子是一个大房子,英雄站在中间,那么敌人永远也抓不到他。
我们赋予敌人自动转向的能力,即便他没有遇到障碍物。
我们在他的原型中添加一个turning属性来表征转向的能力。
turning表示的是敌人每行走一步时转向的可能性。0代表永远不会转向,100表示每步都要转向(你可别千万这么做)。
更改一下if判断
if (ob.downleft and ob.upleft and ob.downright&and ob.upright and random(100)&ob.turning) {
如果random(100)的值小于ob.turning,就换一个新的方向,就算本来能够继续往前走。
你可以用很多方法杀死敌人。你可以通过刀剑、枪或者其他的东西。让我们来看看我们怎样向敌人射击(使用shift键射击)。
当我说到&子弹&的时候,我的意思是那些从英雄处飞出来的、看起来要去杀死敌人的东西。它可以是球、箭头、冰激凌、企鹅等等。
首先,我们还是应该考虑一下,射击应该是怎样的过程,子弹是怎样运动。一旦shift键被按下,首先,子弹对象、子弹剪辑被创建,子弹剪辑的位置应该是英雄所在的位置。而且子弹应该朝英雄面对的方向运动。如果子弹遇到墙或者敌人,它应该自动消失。如果它碰到的是敌人,那么敌人也要消失。
子弹的速度应当比英雄的速度大,除非你准备让英雄可以用某种方式阻挡飞行的子弹。通常那些傻乎乎的敌人不会看到飞行的子弹,但是你也可以创建会躲避子弹的敌人。你也可以做一些能朝英雄射击的敌人。
画一个子弹的mc,然后做一个linkage,ID是bullet,以便用AS复制到舞台中。子弹MC中的图像应该居中对齐。
让我们声明一下子弹对象:
game.Bullet= function () {};game.Bullet.prototype.speed=5;game.Bullet.prototype.dirx=0;game.Bullet.prototype.diry=-1;game.Bullet.prototype.width=2;game.Bullet.prototype.height=2;
子弹每步移动的距离是5,高度和宽度都是2象素,这已经足够杀死敌人了。
dirx/diry属性确定了子弹移动的方向。实际上我们还是通过moveChar函数移动子弹。如果dirx=1,子弹向右移动,diry=-1则向上移动。我可以从英雄(或敌人)对象中得到方向参数,设定dirx/diry的值,但是在游戏刚开始的时候,英雄还没有移动,这时候玩家如果要射击,就用上面这个默认的方向(dirx=0,diry=-1,朝上移动)。
给game对象添加两个新的属性:
game={tileW:30, tileH:30, currentMap:1, bulletcounter:0};game.bullets= new Array();
bulletcounter属性用来记录子弹的数目,以便我们给新产生的子弹取不重复的名称。游戏里第一发子弹名称是bullet0,然后是bullet1、bullet2&&最多是bullet100,接下来返回到bullet0。我们可以让数目无限增长,但是谁也不能保证到最后会发生什么。
game.bullets数组用来放置舞台上的子弹对象。一开始的时候它是一个空的数组。
然后给角色对象加上一个shootspeed属性,设置每次发弹的最小时间间隔:
char={xtile:2, ytile:1, speed:4, shootspeed:1000};
更高的shootspeed值代表更长的间隔、更慢的射击速度,单位是毫秒。
敌人死亡后,我们还要把他们从game对象中删除。改变buildMap函数的创建敌人的部分:
game.currentEnemies = [];for (var i = 0; i&enemies. ++i) {& var name = &enemy&+i;& game[name]= new game[&Enemy&+enemies[i][0]];& game[name].id=i;& game.currentEnemies.push(game[name]);
然后在enemyBrain函数中,把这句:
var name = &enemy&+i;换成 :var name = &enemy&+game.currentEnemies[i].
我通过currentEnemies数组来放置舞台上活动的敌人。一旦敌人被杀死,我们就会把它从currentEnemies数组中删除。新的属性&id&帮助我们找到敌人在enemies数组中的位置。
在detectKeys函数检查完按键后添加代码:
if (Key.isDown(Key.SHIFT) and getTimer()&ob.lastshot+ob.shootspeed) {& _root.shoot(ob);}
如果SHIFT键被按下,而且时间间隔足够长,调用shoot函数。
在moveChar函数的开头添加两行代码,用来保存当前对象的方向:
ob.dirx=ob.diry=
我们可以用这个来指定子弹运行的方向。
为了成功的让子弹按照我们预定的想法飞行,我们要写一个新的函数shoot:
function shoot (ob) {& ob.lastshot=getTimer();& game.bulletcounter++;& if (game.bulletcounter&100) {&&& game.bulletcounter=0;& }& var name = &bullet&+game.& game[name]= new game.B& game[name].id=game.& game.bullets.push(game[name]);& if (ob.dirx or ob.diry) {&&& game[name].dirx= ob.&&& game[name].diry= ob.& }& game[name].xtile= ob.& game[name].ytile= ob.& game.clip.attachMovie(&bullet&, name, 10100+game.bulletcounter);& game[name].clip=game.clip[name];& game[name].x = (ob.x+game[name].dirx*ob.width);& game[name].y = (ob.y+game[name].diry*ob.height);& game.clip[name]._x = game[name].x;& game.clip[name]._y = game[name].y;}
首先我们传递了一个obj对象给函数。如果开枪的是英雄,这个obj就是英雄;如果是敌人射击,obj就是敌人对象。
我们通过getTimer()函数把当前的时间保存到lastshot属性中。
接着给game.bulletcounter属性增加1,如果它大于100,我们就让他回到0。
现在用bulletcounter产生一个新的子弹名字,创建一个子弹对象,然后把这个数字存到该对象中,把这个对象的地址放入game.bullets数组中。
下面的if判断用来检测角色对象是否移动过(dirx/diry有值),如果是,则把这两个方向属性传递给新建的子弹对象。否则子弹会有默认的运行方向。
为了让子弹从角色处出现,我们把角色的xtile和ytile属性复制给子弹对象。
代码的最后部分创建了新的子弹剪辑,计算坐标,最后显示到该位置。有趣的地方在于如何计算子弹的象素坐标:
game[name].x = (ob.x+game[name].dirx*ob.width);
首先我们得到角色的位置(ob.x),这是角色的中心坐标。通常子弹并不是从角色的正中心发出,我们给他加上了角色的宽度。这个地方的巧妙之处在于乘了子弹的dirx属性,dirx取-1,0,1都能适合。当dirx为0的时候,子弹的x坐标就是角色的中心,不过这时候子弹是竖直运动的,出现在角色的正上方或正下方。
杀死敌人!
在detectKeys函数的结尾添加一行,调用一个新的函数来移动子弹,并且检查是不是打到了什么东西。
_root.moveBullets();
moveBullets函数:
function moveBullets () {& for (var i = 0; i&game.bullets. ++i) {&&& var ob=game.bullets[i];&&& getMyCorners (ob.x+ob.speed*ob.dirx, ob.y+ob.speed*ob.diry, ob);&&& if (ob.downleft and ob.upleft and ob.downright and ob.upright) {&&&&& moveChar(ob, ob.dirx, ob.diry);&&& } else {&&&&& ob.clip.removeMovieClip();&&&&& delete game[&bullet&+game.bullets[i].id];&&&&& game.bullets.splice(i,1);&&& }&&& for (var j = 0; j&game.currentEnemies. ++j) {&&&&& var name = &enemy&+game.currentEnemies[j].&&&&& var obenemy = game[name];&&&&& var xdist = ob.x - obenemy.x;&&&&& var ydist = ob.y - obenemy.y;&&&&& if (Math.sqrt(xdist*xdist+ydist*ydist) & ob.width+obenemy.width) {&&&&&&& obenemy.clip.removeMovieClip();&&&&&&& delete game[&enemy&+game.currentEnemies[j].id];&&&&&&& game.currentEnemies.splice(j,1);&&&&&&& ob.clip.removeMovieClip();&&&&&&& delete game[&bullet&+game.bullets[i].id];&&&&&&& game.bullets.splice(i,1);&&&&& }&&& }& }}
这个函数通过循环来操作放在bullets数组中的每一个子弹对象。
使用getMyCorners函数我们得知子弹下一步是不是打在了障碍物上。如果不是,我们就调用moveChar函数移动子弹。
如果子弹确实击中了障碍物,我们就要删除它了。有3样事情要做:+删除子弹mc (使用removeMovieClip)+删除子弹对象 (使用delete函数)+从子弹数组中删除当前的子弹
尽管我们可以只删除子弹mc而留下子弹对象,这样做不会有大问题。因为子弹mc已经不存在,对它的所有的操作不能被执行。但是这样做会降低效率,子弹数组越来越大,而且里面全是垃圾数据。
当移动了子弹而且子弹没有撞上障碍物,我们开始检查它是不是击中了敌人。循环currentEnemies数组,计算出子弹和敌人的距离。如果他们离得太近,我们就删除他们&&子弹和敌人都消失。
如果你要永久清楚这个被杀的敌人(当你再一次进入这个房间的时候,它不会再出现),在这里再添加一行:myEnemies[game.currentMap][obenemy.id]=0;
你也可以让射击变得更加丰富多彩:
+限制子弹的总数。你可以设置一个变量,每次创建一个子弹,变量就减1,只有这个变量值大于0才能创建对象。+限制舞台上只有1颗子弹。当子弹数组长度&0时不创建子弹。+让敌人也能发弹。让敌人射击也很容易做到,方法和他们改变移动方向一样。+创建不同的武器。你可以声明多种子弹模板,规定不同的伤害值,你可以得到更强的武器,更快的杀死敌人。
玩的开心 :-)
在侧视图中射击:
本章我们将会让英雄从地上拾取各种物品,例如:水晶、金币、死的蜘蛛、生命药水、弹药&&
物品可以有很多种,有的用来增加生命值,有的用来增加弹药。这个例子里面的物品只有一个功能,增加你的point值(显示在下面的那个东西)。创建其他种类的物品就由你自己来完成了。
我们从&芝麻开门&这一章的影片开始,这样我们可以去掉许多复杂的代码,或许这样更容易看明白。
首先要画一个物品的影片剪辑。把各种物品的图片都放在一个剪辑的不同帧。和英雄、敌人一样,这个剪辑的注册点也在中心。在第一帧添加stop()动作。导出as链接,ID是items。和梯子类似,把物品单独做成一个剪辑,放置物品到舞台时,不用重新绘制背景方块。
你捡了多少?
为了显示捡起了多少物品,在舞台上添加一个动态文本。把它放到区块的外面,然后把它的&变量(variale)&设成points:
变量point用来记录拾取的物品数。我们可以先把它添加到game对象中。改一下定义game对象的语句,添加一个points属性,值为0(大部分都是从0开始的):
game = {tileW:30, tileH:30, currentMap:1, points:0};
和其他东西一样,首先我们要声明一个物品(items)对象,然后创建数组,用来放置位置信息。
myItems = [[0],[[1,1,1],[1,1,2],[2,1,3]],[[2,1,3],[2,6,3],[1,5,4]]];game.Item1= function () {};game.Item1.prototype.points=1;game.Item2= function () {};game.Item2.prototype.points=10;
myItems数组的结构和enemies数组一样(见《愚蠢的敌人》)。它包含了每个地图的物品数组,我们没有用到map0,所以第一个数组为空。在map1中我们设置了3个物品:[1,1,1],[1,1,2],[2,1,3]。每个物品都有3个数字,第一个是物品类型(这里的1或2),也就是物品影片剪辑显示的帧数。第二、第三个数是物品的坐标,我们已经很多次用这种形式了。以[2,1,3]为例,它是第二种类型的物品,坐标是x=1,y=3。
最后部分的代码声明了两种物品类型。目前他们只有一个point属性,用来表示玩家得到后增加的point值。Item1只有1点,Item2却有10点。
现在修改buildMap函数,增加一些创建物品的动作,把下面的代码放在创建英雄部分的前面:
game.items = myItems[game.currentMap];for (var i = 0; i&game.items. ++i) {&var name = &item&+game.items[i][2]+&_&+game.items[i][1];&game[name]= new game[&Item&+game.items[i][0]];&game[name].position=&game.clip.attachMovie(&items&, name, 10001+i);&game[name].clip=game.clip[name];&game[name].clip._x = (game.items[i][1]*game.tileW)+game.tileW/2;&game[name].clip._y = (game.items[i][2]*game.tileH)+game.tileH/2;&game[name].clip.gotoAndStop(game.items[i][0]);}_root.points=game.
首先我们复制当前地图用到的物品到game.items对象中,然后循环game.items对象(数组)。
从这行开始:
var name = &item&+game.items[i][2]+&_&+game.items[i][1];
我们给新的物品命名。名字由物品的坐标决定,比如一个物品所在的区块坐标是x=1,y=3,那它的名称是item3_1。
创建了物品对象以后,我们给他添加了一个position属性。这个属性就是物品在game.items数组中的索引值。通过这个属性可以很方便地在game.items数组中找到当前的物品,这样就可以在捡到物品后在数组中删除掉它。
放置物品到正确的位置之后,下一步就是让他显示正确的帧。
最后,我们更新一下points变量,在场景的相应地方显示出来。游戏开始的时候点数可能只有0,但是改变地图的时候,可能英雄已经捡到了几个物品。
我们已经有了英雄、物品,下一步要做的就是让英雄碰到物品的时候捡起它。把这段代码添加到moveChar函数的末尾:
var itemname=game[&item&+ob.ytile+&_&+ob.xtile];if (itemname and ob == _root.char) {&game.points=game.points+itemname.&_root.points=game.&removeMovieClip(itemname.clip);&game.items[itemname.position]=0;&delete game[&item&+ob.ytile+&_&+ob.xtile];}
变量itemname的值由英雄当前的位置决定。比如英雄的坐标是x=4,y=9,那么itemname的值就是&item9_4&。如果这个item9_4确实是存在的,itemname就是物品对象;如果不巧不存在这样的item9_4对象,itemname就是undefined(未定义值)。
如果英雄得到了物品,我们首先把物品的points值加到game.points变量,然后更新_root.points以显示到文本框中。
现在要做的就是把物品从舞台上删除。每一个物品在三个地方都要删除:物品剪辑、物品数组中相应的物品,以及物品对象本身。目前我们不必在数组中删除掉他,仅仅将物品用0替换就行了。另外不要忘了,物品数组仅仅是myItems数组的一部分,如果我们离开地图然后又返回,原先已经拾取的物品又会出现。为了阻止这种现象,我们要更新changeMap函数。
添加下面的代码到changeMap函数的开始:
var tempitems=[];for (var i = 0; i&game.items. ++i) {& if(game.items[i]) {&&& var name = &item&+game.items[i][2]+&_&+game.items[i][1];&&& delete game[name];&&& tempitems.push(game.items[i]);& }}myItems[game.currentMap]=
这里我们用到了一个临时的数组:tempitem,用来复制未被拾取的物品,即myItems数组中的非零元素。删除game[name]对象是我们上面提到的三个删除任务中的一个。
这里我做了侧视图例子:
首先来讲一个小故事,关于浮动的方块的故事。或许你已经听说过了&移动平台&这个名字,不要迷惑,他们是一样的东西,一样有趣。
很久很久以前,在区块游戏的世界里,住者一个年轻的方块。他是一个快乐的方块。有一天,他遇到了一个英雄。英雄问他:&年轻人,为什么你不会浮动呢?&
&我不知道怎么移动&&& 小方块说道。
&那真遗憾,&英雄说道,&我想要站在你上面,去以前够不到的地方。&
那一天之后,小方块就不再像以前一样开心了。
其实我们可以帮助他浮动的,看:
在我们开工写代码之前,我们还是先来考虑一下规则。
我们应该做什么?如何做?
* 浮动方块应该是云(cloud)类型的方块 (云)* 方块可以横向或纵向移动* 英雄可以从上方落到上面* 当英雄站到上面以后,他会随着方块运动* 方块上的英雄不能穿过障碍物
踏上浮动的方块
英雄怎么站到运动方块上?第一个也是最简单的一个方法就是跳上去。
在上图中,英雄正处于跌落的过程中,下一步他将会碰到运动方块。我们将会把他放置到方块上去。注意,英雄必须是在运动方块的上方,而且必须是往下运动,否则他就无法站到上面。
但是,这并不是英雄上去的唯一途径。下图中,英雄站在障碍物上,没有移动。
但是方块是移动的,而且接下来就会接触到英雄,若不做任何处理,英雄就会&陷&到方块里面。所以,我们要做的就是让方块带者英雄一起往上移动。
离开运动的方块
一旦我们的英雄可以站到方块上,我们还要保证他能够以某种方式离开。首先,他可以跳开。它可以走到方块的边缘。下图画出了许多可能的情形:
党英雄站在竖直移动的方块上,而且上方有个障碍物时,他应该掉下来,否则就会被压扁。党方块水平移动,党英雄碰到障碍物,他应该被贴着障碍物放置;如果方块移开的话,英雄就会掉下去。
上图中,英雄随着方块往下移动,党他碰到障碍物的时候,他不再随着方块移动,而是停在障碍物上。而方块则继续往下移动。
画出移动方块的影片剪辑。你可以做很多种类的移动方块,把他们放在 movingtiles 影片剪辑的不同帧,然后将剪辑连接为movingtiles
定义 movingtiles 对象:
game.MovingTilep1= function () {};game.MovingTilep1.prototype.speed=2;game.MovingTilep1.prototype.dirx=0;game.MovingTilep1.prototype.diry=1;game.MovingTilep1.prototype.miny= 0;game.MovingTilep1.prototype.maxy=2;game.MovingTilep1.prototype.width=game.tileW/2;game.MovingTilep1.prototype.height=game.tileH/2;game.MovingTilep2= function () {};game.MovingTilep2.prototype.speed=2;game.MovingTilep2.prototype.dirx=1;game.MovingTilep2.prototype.diry=0;game.MovingTilep2.prototype.minx= -2;game.MovingTilep2.prototype.maxx=2;game.MovingTilep2.prototype.width=game.tileW/2;game.MovingTilep2.prototype.height=game.tileH/2;
我们有两种类型的可移动方块: MovingTilep1可以竖直移动(它的diry属性为非0数值),MovingTilep2可以水平移动(它的dirx值非0)。speed属性,你一定猜到了,代表方块一次移动的像素距离。
miny/maxy/minx/maxx 属性设置了方块运动的边界。我们当然可以把边界坐标设置成绝对的数值范围,但是如果需要把方块放到别的地方的话,就要改动边界的范围了。而在这里,我们用的是相对于方块起始位置的数值。这样我们可以把方块放在任意的位置,而不用修改边界范围。需要注意的是,移动的方块不会检测碰撞,所以你应该确保他们运动时不撞到障碍物。或者你也可以允许他们穿过障碍物。你在做游戏,你就是上帝。
来看一个例子。方块起始的位置是(x=2,y=5),竖直运动,miny=-1,maxy=4。它会怎么运动呢?起始位置-miny=5+(-1)=4,所以最小可以到达的位置是(x=2,y=4),最大可以到达的位置是(x=2,y=9)。
方块起始位置的数组和敌人起始位置的数组类似:
//浮动方块数组 [方块类型, x位置, y位置]myMovingTiles = [[0],[[1, 4, 2]],[[2, 4, 4]]];
在地图1中,我们定义了一个浮动方块,它的类型编号是1(从MovingTile1模版创建),起始位置是(x=4,y=2)。地图2中同样有1个浮动方块。你也可以在一个地图中放置多个浮动方块。
接下来是要在buildMap函数中添加浮动方块的生成代码。在创建敌人部分后面加入:
game.movingtiles = myMovingTiles[game.currentMap];for (var i = 0; i&game.movingtiles. ++i) {&var name = &movingtile&+i;&game[name]= new game[&MovingTilep&+game.movingtiles[i][0]];&game.clip.attachMovie(&movingtiles&, name, 12001+i);&game[name].clip=game.clip[name];&game[name].clip.gotoAndStop(game.movingtiles[i][0]);&game[name].xtile = game.movingtiles[i][1];&game[name].ytile = game.movingtiles[i][2];&game[name].x = game[name].xtile *game.tileW+game.tileW/2;&game[name].y = game[name].ytile *game.tileH+game.tileH/2;&game[name].clip._x = game[name].x;&game[name].clip._y = game[name].y;&game[name].minx=game[name].minx+game[name].&game[name].maxx=game[name].maxx+game[name].&game[name].miny=game[name].miny+game[name].&game[name].maxy=game[name].maxy+game[name].}
首先还是取得当前地图的浮动方块数组(第一句)。变量game.movingtiles保存了当前地图的浮动方块数据,包括数目和方位。然后创建新的对象,放置mc到舞台正确的位置,跳转到相应的帧。类型1的方块是第一帧,类型2的方块则是第二帧。代码的最后一部分是计算浮动方块的运动范围。虽然变量名称还是miny/maxy/minx/maxx,但是这些属性变成了确定的数字,和原先的含义(相对起始位置的坐标)已经不同了(或者说他们是绝对的坐标,使用的时候不需要再参考起始位置)。
在moveChar函数中,需要添加一行代码,用来保存y坐标:
ob.lasty=ob.y;
在moveChar函数中还需要改写移动功能的代码:
if (diry == 1) {& if (ob.downleft and ob.downright and !checkMovingTiles(speed*diry)) {&&& ob.y += speed*& } else {&&& ob.jump =&&& if(ob.onMovingTile){&&&&& ob.y=ob.onMovingTile.y-ob.onMovingTile.height-ob.&&& }else{&&&&& ob.y = (ob.ytile+1)*game.tileH-ob.&&& }& }}
我们使用了checkMovingTiles函数,如果英雄将会降落在浮动方块上,这个函数会返回true。如果英雄马上就要落在浮动方块上,我们设置他的y坐标为刚好在方块上面。
英雄在浮动方块上面吗?
或许你已经从moveChar函数中增加的部分看出来了,没错,我们需要创建一个新函数,用来检测角色是不是站在浮动方块上面。checkMovingTiles函数不仅仅返回答案(是或者不是),而且还把英雄所在浮动方块的名字保存到char对象中。
function checkMovingTiles (y) {& if(char.diry&&-1){&&& var heroymax=char.y+char.height+y;&&& var heroxmax=char.x+char.&&& var heroxmin=char.x-char.&&& foundit=&&& for (var i = 0; i&game.movingtiles. i++) {&&&&& var ob=game[&movingtile&+i];&&&&& var tileymax=ob.y+ob.&&&&& var tileymin=ob.y-ob.&&&&& var tilexmax=ob.x+ob.&&&&& var tilexmin=ob.x-ob.&&&&& if(char.lasty+char.height&=tileymin){&&&&&&& if (heroymax&=tileymax and heroymax&=tileymin) {&&&&&&&&& if (heroxmax&tilexmin and heroxmax&tilexmax) {&&&&&&&&&&& char.onMovingTile=&&&&&&&&&&& foundit=&&&&&&&&&&&&&&&&&&&& } else if (heroxmin&tilexmin and heroxmin&tilexmax) {&&&&&&&&&&& char.onMovingTile=&&&&&&&&&&& foundit=&&&&&&&&&&&&&&&&&& }&&&&& }&&& }& }& return(foundit);& }}
让我们看看发生了什么。如果角色不是往上运动(diry值不是-1),我们就计算出角色的边界。然后遍历浮动方块数组,看角色是否和当前的浮动方块接触:
带有&lasty&属性的if语句是用来确定角色的上一个位置是在浮动方块的上方,下面的两个if语句则判断角色是不是和方块有接触。如果有碰撞,那就意味着我们找到了正确的移动方块,于是onMovingTile属性就会纪录下找到的方块对象。
让他也动起来
请准备好看史上最丑陋最冗长最小气的函数!它很长,因为它要很多东西。首先,它移动所有的浮动方块,然后检查这些方块是不是需要反过来运动了,这些还不够,它还要处理英雄在浮动方块上面的动作,检查是不是应该掉下来。
function moveTiles () {& for (var i = 0; i&game.movingtiles. i++) {&&& var ob=game[&movingtile&+i];&&& getMyCorners (ob.x + ob.speed*ob.dirx, ob.y + ob.speed*ob.diry, ob)&&& if (ob.miny&ob.upY or ob.maxy&ob.downY) {&&&&& ob.diry=-ob.&&& }&&& if (ob.minx&ob.leftX or ob.maxx&ob.rightX) {&&&&& ob.dirx=-ob.&&& }&&& ob.x = ob.x + ob.speed*ob.&&& ob.y = ob.y + ob.speed*ob.&&& ob.xtile = Math.floor(ob.x/game.tileW);&&& ob.ytile = Math.floor(ob.y/game.tileH);&&& ob.clip._x = ob.x;&&& ob.clip._y = ob.y;&&& if(ob.diry==-1){&&&&& checkMovingTiles(0);&&& }& }& //check if hero is on moving tile& if(char.onMovingTile){&&& getMyCorners (char.x, char.y+char.onMovingTile.speed*char.onMovingTile.diry, char);&&& if (char.onMovingTile.diry == -1) {&&&&& if (char.upleft and char.upright) {&&&&&&& char.y=char.onMovingTile.y-char.onMovingTile.height-char.&&&&& } else {&&&&&&& char.y = char.ytile*game.tileH+char.&&}

我要回帖

更多关于 梯子游戏规律 有哪些 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信