<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 8  文章 - 55  trackbacks - 0
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    常用鏈接

    留言簿(6)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    朋友的Blog

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    為什么用方塊?

    在開始埋頭編寫代碼之前,讓我們稍微談?wù)剠^(qū)塊/方塊游戲(tile based games)。為什么要使用方塊?是區(qū)塊游戲更容易制作嗎?或許還是他們比基于藝術(shù)的游戲(art based games)更復(fù)雜?Flash適合區(qū)塊游戲嗎?

    在很久很久以前,方塊技術(shù)已經(jīng)被應(yīng)用到游戲制作中。那時(shí)候,電腦還沒有上GHz的cpu,沒有上百M(fèi)B的內(nèi)存。緩慢的速度、有限的內(nèi)存意味著,游戲制作者不得不使用他們的腦子來發(fā)明聰明的辦法,讓游戲看起來更棒,而且更快。

    比如,你想在你的游戲中加入漂亮的背景,但是圖片太大了,而且使得你的游戲變得很慢。怎么辦?把圖片切成方塊! 

    在上圖中,你可以看到圖片的某些部分是完全一樣的。1和2是一模一樣的,3和4是一樣的,5到7都是完全一樣的。如果你把圖片切割開來,重復(fù)使用相同的部分,你就已經(jīng)在應(yīng)用方塊了。這個(gè)大圖片比方塊的文件大小大多了。實(shí)際上,你用4塊不同的方塊就可以畫出了這個(gè)圖片。

    方塊還有其他一些不錯(cuò)的特性,當(dāng)你想要替換部分背景,那么你不需要重新繪制所有的東西,你只要改變1個(gè)方塊就行了。你還可以重復(fù)使用方塊,創(chuàng)建不同的對(duì)象。比如,你可能有草地的方塊,還有花的方塊,當(dāng)你需要在草地的背景上放幾朵花時(shí),只需要把原來地方的草換成花就行了。

    Flash 和方塊

    我們都知道,F(xiàn)lash是基于矢量的,所以Flash生成的文件體積更小,而且可以無限縮放。因此,我們一點(diǎn)都不需要方塊來制作游戲嗎?好吧,用Flash你可以很容易地做一個(gè)基于藝術(shù)的游戲(art based games),但是當(dāng)你的游戲區(qū)域增大時(shí),或者你想要更多的特性時(shí),你可能會(huì)遇到麻煩。許多東西用區(qū)塊游戲來做是如此簡單(立體視角,尋找路徑和深度排序)。不要忘記,區(qū)塊游戲已經(jīng)存在了很長一段時(shí)間,許多理論對(duì)于Flash來說依然適用。

    用Flash做區(qū)塊游戲也有不太舒服的地方,我們用不上許多繪圖功能和時(shí)間線的部分,我們的游戲是通過actionscripot制作的,基本上,我們要寫大量的代碼來創(chuàng)建、移動(dòng)、修改舞臺(tái)上的圖片。

    用位圖作為區(qū)塊也是一個(gè)好主意。是的,我們可以在Flash中繪制所有的東西,用矢量圖也可以,但是當(dāng)游戲運(yùn)行的時(shí)候,播放器需要計(jì)算屏幕上的矢量數(shù)據(jù),我們可不希望有什么東西弄慢了我們的游戲。位圖在播放以前是預(yù)先渲染的,而且通常情況下他們更好看。如果你想在Flash中導(dǎo)入位圖作為方塊,通常最好的做法是把圖像存為帶透明背景的GIF文件(用于各種對(duì)象,比如花等)
    枯燥的講話到此結(jié)束,讓我們做點(diǎn)東西吧  :-)
    首先,我們來看看怎樣存儲(chǔ)我們的地圖。

    地圖的格式

    我們將用Flash提供給我們的一個(gè)美妙的格式表示地圖:數(shù)組。如果你不知道什么是數(shù)組,打開Flash的幫助,先看看。

    二維數(shù)組

    我們需要一個(gè)二維數(shù)組表示地圖,不,他不是什么空間、時(shí)間的維數(shù),它是說一個(gè)數(shù)組的每一個(gè)元素還是數(shù)組。迷惑了?讓我們來看看。

    通常,這是大家經(jīng)常看到的簡單的數(shù)組:
    myArray=["a", "b", "c", "d"];

    這很簡單。你可以用myArray[0]得到第一個(gè)元素,就是”a”,用myArray[1]得到第二個(gè)元素”b”,等等。
    現(xiàn)在換個(gè)聰明的法子! 如果我們不用”a”,”b”和”c”放在數(shù)組中,但是我們把另外的數(shù)組放進(jìn)去呢?是的,我們可以這么做的。看這里,讓我們做個(gè)這樣的數(shù)組:

    a=["a1", "a2", "a3"];
    b=["b1", "b2", "b3"];
    c=["c1", "c2", "c3"];
    myArray=[a, b, c];

    現(xiàn)在我們已經(jīng)定義了一個(gè)數(shù)組,而且他的每一個(gè)元素都是數(shù)組。那么,myArray[0]的值現(xiàn)在就是一個(gè)數(shù)組 [“a1”,”a2”,”a3”],第二個(gè)元素值就是 [“b1”,”b2”,”b3”],等等。如果你這樣寫:
    myVar=myArray[2];
    那么myVar得到的值是 ["c1", "c2", "c3"].
    OK,那又怎么樣?現(xiàn)在你也許會(huì)問。我們不會(huì)停止在這里的。如果你這樣寫
    myVar=myArray[2][0];
    那么他得到的值就是myArray第三個(gè)元素的第一個(gè)元素的值”c1”。
    讓我們?cè)囋嚫嗟摹?br />myVar=myArray[0][1]
    取得myArray的第一個(gè)元素(a)的第二個(gè)元素(”a2”)。
    myVar=myArray[1][0] 得到值”b1”
    你想得到整個(gè)圖片? 繼續(xù)看……

    創(chuàng)建地圖

    首先我們寫出這個(gè)地圖的數(shù)組,這個(gè)數(shù)組包含了每個(gè)方塊的信息
    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列。如果我們的英雄(主角)從左上角開始,他可以往右移動(dòng)8格,往下移動(dòng)6格,超出這個(gè)范圍,他就會(huì)走出這個(gè)地圖,走入未知的空間。

    但是一些聰明的人已經(jīng)想到了一個(gè)重要的問題:“數(shù)組中的這些數(shù)字是做什么用的呢?”好吧,我們會(huì)使用一些OOP(那是面向?qū)ο螅贿^不要逃跑,他們并不是像他們聽起來那樣可怕)來創(chuàng)建方塊,并且管理我們的游戲(可以參閱Flash的OOP教程的鏈接部分)。在開始的時(shí)候,我們會(huì)定義多種方塊,他們就像模板一樣放到游戲中。然后我們遍歷整個(gè)地圖數(shù)組,檢測每個(gè)數(shù)字。

    例如,如果我們得到數(shù)字1,那么我們就從Tile1模板中創(chuàng)建一個(gè)新的方塊,這個(gè)方塊的特點(diǎn)我們都事先在模板中定義好了。在游戲中,我們會(huì)檢查那個(gè)方塊對(duì)象的屬性。他可以有許多屬性,最基本的方塊只有2個(gè)屬性:walkable(通行性)和frame(幀)。

    Walkable是表示一個(gè)方塊是不是允許角色從他上面走過去,如果可以,就是true(真);如果不是,就是false(假)。我們不使用hitTest,因?yàn)閔itTest很慢,而且在區(qū)塊游戲中使用hitTest并不是很好。

    Frame是表示顯示方塊的第幾幀。當(dāng)放置方塊到舞臺(tái)時(shí)會(huì)用到這個(gè)參數(shù)。因?yàn)槲覀兪褂猛粋€(gè)方塊movie clip(影片夾子,檢查mc)來存放不同的方塊。使用時(shí)復(fù)制這個(gè)mc。他們默認(rèn)是顯示第一幀。在“創(chuàng)建方塊”部分會(huì)有更多這方面的內(nèi)容。

    所以,如果我們聲明下面的方塊:
    //wall tile Tile1= function () {}; Tile1.prototype.walkable=false; Tile1.prototype.frame=2;
    那么我們每次在地圖數(shù)組中遇到1的時(shí)候,就會(huì)創(chuàng)建一個(gè)類似的對(duì)象(Tile1),我們還定義了這個(gè)方塊不能被通行(walkable=false),而且在那個(gè)點(diǎn)上的方塊mc顯示第二幀。

    關(guān)于地圖的東西

    你或許會(huì)考慮,為什么我要選擇這種方式呢?我可以告訴你這是最好的方法。我可以說這個(gè)地圖格式可以最快地創(chuàng)建地圖,可以產(chǎn)生最小的文件。我只能說在和區(qū)塊游戲大了多年交道之后,我發(fā)現(xiàn)這個(gè)格式最適合我的需要。但是我們也可以看看其他可能的方法,保存地圖數(shù)據(jù)的方法。

    JailBitch的方法

    這是原始的OutsideOfSociety的教程所采用的格式,非常簡單。他以同樣的方式把某個(gè)點(diǎn)的幀數(shù)字保存到二維數(shù)組中。每次你都需要檢測下一個(gè)方塊是不是墻(或者是可以撿起來的東西,或者是門,或者任何東西),你可以從地圖數(shù)組中查找數(shù)字。


    (這里顯示的數(shù)組并不是全部的,下面還有沒有顯示出來)

    當(dāng)檢測碰撞時(shí),你能夠可以讓某部分的幀作為墻(或者可拾取的東西,或者門)。例如,你可以讓所有的幀數(shù)在0到100的方塊都作為可通行的方塊,所有的從101到200的是墻,大于200的是特殊的方塊。

    當(dāng)你只有很少的方塊類型,而且方塊不會(huì)變化很多時(shí),這是一個(gè)很好的很簡單的方式。

    OutsideOfSociety的文章: http://oos.moxiecode.com/tut_01/index.html

    沙漠中的樹

    一些地圖具有許多不同的方塊,一些只有很少的幾種。例如,想象在沙漠中,方圓幾百公里都是沙子,如果你很幸運(yùn),你可以看到一片綠洲。或者在海上,除了水還是水,然后出現(xiàn)一個(gè)海島。

    如果你的地圖大部分是相同的方塊(沙子),而且只有少量的變化(樹),那么二維數(shù)組并不是很好的選擇。他會(huì)產(chǎn)生許多“死信息”,許多行的0,直到一些其他的frame數(shù)字出現(xiàn)。在這種情況下,你可以單獨(dú)聲明非沙子的方塊,然后讓剩下的方塊都是沙子。

    讓我們假設(shè)你有一個(gè)100×100的地圖,有3個(gè)樹。你可以這樣寫:

    當(dāng)創(chuàng)建地圖的時(shí)候,你遍歷這個(gè)trees數(shù)組,放置trees方塊,讓其他的方塊顯示沙子。那樣比寫100×100的二維數(shù)組要簡單多了。
    當(dāng)然,當(dāng)你有更多的對(duì)象(樹、灌木、草、石頭、水……),這個(gè)方法的速度不是很快,而且你也很難記住什么地方放了什么方塊。

    S,M,XXXL

    如果你有Flash MX或更新版本,估計(jì)你已經(jīng)聽到過XML。他的格式和HTML很像,他允許聲明許多東西。你也可以用XML來保存你的地圖數(shù)據(jù)。
    下面的XML地圖基于Jobe Makar的《Macromedia Flash MX Game Design Demystified》。看看這個(gè)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>

     

    這里我們?cè)O(shè)定了3×3的地圖。首先是頭部”map”,然后設(shè)置了3個(gè)”row” 結(jié)點(diǎn)。每個(gè)row結(jié)點(diǎn)有3個(gè)cell結(jié)點(diǎn)。

    如果從外部文件中載入地圖,XML可能是很好的方案,因?yàn)榇蟛糠值腦ML解析可以有Flash MX內(nèi)建的函數(shù)完成。從外部文本文件中載入二維數(shù)組可沒有那么簡單,你經(jīng)常要靠loadVariables得到字符串,然后又不得不把字符串分割成數(shù)組,這個(gè)過程是很慢的。

    XML也有缺點(diǎn):他會(huì)導(dǎo)致更大的文件大小(不過對(duì)于現(xiàn)在的網(wǎng)絡(luò),這種大小可以忽略),而且你需要Flash Player 6以上。
    下面的所有例子都使用二維數(shù)組來存儲(chǔ)地圖數(shù)據(jù),而且使用對(duì)象的方法來創(chuàng)建方塊,就像在“地圖的格式”中介紹的那樣。

    創(chuàng)建方塊

    現(xiàn)在我們將會(huì)讓方塊在屏幕上顯示出來、定位到合適的地方,然后顯示正確的幀。就像這個(gè):

    首先我們先定義一些對(duì)象和值:

    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=true;
    game.Tile0.prototype.frame=1;
    //墻
    game.Tile1= function () {};
    game.Tile1.prototype.walkable=false;
    game.Tile1.prototype.frame=2;

    你可以看到,我們把地圖保存在myMap數(shù)組中。
    地圖定義的下一行,定義了一個(gè)叫g(shù)ame的對(duì)象。
    我們會(huì)把所有用到的其他對(duì)象(方塊、敵人……)都作為game的子對(duì)象,我們也可以不這樣做,直接把所有的對(duì)象都放在主場景_root或者其他任何地方,
    但是這樣做(把對(duì)象都放在一個(gè)固定的地方)更加清晰一些。

    注意我們給了game對(duì)象2個(gè)屬性:tileW和tileH,
    兩個(gè)屬性的值都是30。那表示我們的方塊的寬度(tileW)和高度(tileH)。方塊不一定是個(gè)正方形,你也可以使用長寬不相等的矩形。
    一旦我們想要知道,方塊的寬度和高度,我們可以這樣寫:

    game.tileW
    game.tileH

    而且如果你想要改變方塊的大小,只要在一行代碼中改就行了。

    下面的幾行代碼在game對(duì)象里面構(gòu)造了Tile0對(duì)象,然后用prototype構(gòu)造了他的2個(gè)屬性。
    game.Tile0= function () {};
    game.Tile0.prototype.walkable=true;
    game.Tile0.prototype.frame=1;

    第一行的 game.Tile0=function(){} 聲明了一個(gè)新的對(duì)象類型。
    當(dāng)我們從地圖的二維數(shù)組中得到0時(shí),我們就會(huì)使用Tile0作為相應(yīng)方塊的類型。

    下面2行告訴我們Tile0對(duì)象和所有用Tile0創(chuàng)建的對(duì)象都具有的一些屬性。我們會(huì)設(shè)置他們的walkable為true(意味著無法通行)還有frame為1(復(fù)制方塊mc后,顯示在第一幀)。

    顯示地圖

    你準(zhǔn)備好做整個(gè)地圖了嗎?我們將要寫個(gè)函數(shù)來布置所有的方塊了,函數(shù)名取做buildMap。如果你要顯示別的地圖,你也可以使用這個(gè)函數(shù)。buildMap將要做的是:

    +復(fù)制一個(gè)空mc作為容器(放置各種對(duì)象)
    +遍歷地圖數(shù)組
    +為每個(gè)小格子創(chuàng)建相應(yīng)的方塊對(duì)象
    +復(fù)制所有的方塊mc
    +定位所有的方塊mc
    +讓所有的方塊mc顯示在正確的幀

    這是代碼:

    function buildMap (map) {
     _root.attachMovie("empty", "tiles", ++d);
     game.clip=_root.tiles;
     var mapWidth = map[0].length;
     var mapHeight = map.length;
     for (var i = 0; i < mapHeight; ++i) {
     for (var j = 0; j < mapWidth; ++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作為一個(gè)函數(shù),并且參數(shù)是map。當(dāng)我們調(diào)用這個(gè)函數(shù)的時(shí)候,我們會(huì)同時(shí)傳遞地圖數(shù)組給他。

    下面的一行復(fù)制了一個(gè)空mc到舞臺(tái)上:
    _root.attachMovie("empty", "tiles", ++d);

    你需要一個(gè)空mc(里面沒有任何東西)在庫中。
    在庫面板中右鍵單擊這個(gè)mc,選擇”Linkage…”(鏈接),選擇”Export this symbol”(導(dǎo)出這個(gè)符號(hào)),在ID欄填上”empty”。
    現(xiàn)在,attachMovie命令會(huì)在庫中查找鏈接名稱是empty的mc,
    找到之后他會(huì)在舞臺(tái)上復(fù)制一個(gè)這樣的mc,并給他一個(gè)新的名字tiles。
    這個(gè)mc將會(huì)收容舞臺(tái)上所有的方塊,就相當(dāng)于一個(gè)容器。
    使用這樣的容器有個(gè)很美妙的事情,就是每當(dāng)我們想要?jiǎng)h除所有的方塊時(shí)(比如游戲結(jié)束),
    我們只需要?jiǎng)h除tiles這個(gè)mc就行了,然后所有的方塊都消失了。
    如果你不用容器,直接把方塊都復(fù)制到_root(主場景)中,
    那么當(dāng)你進(jìn)入下一場景的時(shí)候(比如游戲結(jié)束),這些復(fù)制的方塊不會(huì)消失,你不得不使用更多的actionscript來刪除他們。

    復(fù)制了這個(gè)tiles之后,我們還要把他連接到我們的game對(duì)象中:
    game.clip = _root.tiles
    現(xiàn)在,當(dāng)我們需要訪問tiles時(shí),我們只需要使用game.clip,這很便利。
    如果我們需要把tiles放到別的地方,我們只需要改一下這行就行了,不需要改動(dòng)整個(gè)代碼。

    然后我們創(chuàng)建了兩個(gè)新的變量:mapWidth和mapHeight。
    我們通過這兩個(gè)變量遍歷整個(gè)地圖數(shù)組。
    mapWidth的值是地圖數(shù)組的第一個(gè)元素的長度。
    如果你忘了地圖數(shù)組什么樣子,回頭看看。
    地圖數(shù)組的第一個(gè)元素是一個(gè)數(shù)組[1,1,1,1,1,1,1,1],mapWidth就是他的長度值(數(shù)組的長度就是數(shù)組的元素個(gè)數(shù)),在這里就是8。
    現(xiàn)在我們從地圖數(shù)組中知道了地圖的寬度。

    同理,mapHeight的值就是地圖數(shù)組的長度值,他是數(shù)組的行數(shù),也是地圖的行數(shù)。

    我們這樣遍歷地圖數(shù)組:

    for (var i = 0; i < mapHeight; ++i) {
    for (var j = 0; j < mapWidth; ++j) {

    我們讓變量i從0開始,每次自加1,直到他比mapHeight大。
    變量j從0循環(huán)到mapWidth。
    var name = "t_"+i+"_"+j
    變量name的到的值是和i、j的值有關(guān)的。
    假設(shè)i=0,j=1,那么name=”t_0_1”;
    如果i=34,j=78,那么name=”t_34_78”。

    現(xiàn)在我們創(chuàng)建新的方塊
    game[name]= new game["Tile"+map[i][j]]

    左邊的game[name]表示新的方塊對(duì)象將會(huì)放置在game對(duì)象里面,就像其他對(duì)象一樣。
    map[i][j]的值告訴我們這個(gè)點(diǎn)(i,j)的方塊類型,
    如果是0,就創(chuàng)建一個(gè)Tile0對(duì)象;如果是1,就創(chuàng)建一個(gè)Tile1對(duì)象。
    這個(gè)點(diǎn)的方塊具有的屬性在相應(yīng)的Tile對(duì)象中都事先定義好了。
    當(dāng)i=0,j=0時(shí),相當(dāng)于這樣的形式:
    game[“t_0_0”]=new game[“Tile0“]

    記住:所有的方塊都作為game對(duì)象的子對(duì)象。

    在下一行中,我們復(fù)制了一個(gè)新的mc到舞臺(tái)上,并使用game.clip[name]來訪問他。
    mc的坐標(biāo)可以通過i,j值乘以方塊寬度和方塊高度得到。
    我們通過gotoAndStop命令讓他跳到正確的幀,借助他繼承得到的frame屬性。

    當(dāng)我們需要?jiǎng)?chuàng)建地圖時(shí),我們這樣調(diào)用buildMap函數(shù)就行了:
    buildMap(myMap);

    再談?wù)剠^(qū)塊原型的定義

    既然我們把區(qū)塊作為對(duì)象處理,我們可以利用對(duì)象的許多優(yōu)點(diǎn)。對(duì)象有個(gè)美麗的特性是他們可以繼承屬性。如果你認(rèn)真閱讀了上一章,你會(huì)記得我們這樣寫區(qū)塊的原型:

    game.Tile0= function () {};
    game.Tile0.prototype.walkable=true;
    game.Tile0.prototype.frame=1;

    這些讓我們寫一次原型就可以在其他地方應(yīng)用,創(chuàng)建新的方塊時(shí)候就使用這個(gè)模板。我們還可以深入研究一下邏輯,再減少一些工作量。

    讓我們聲明一個(gè)通用的區(qū)塊類:

    game.TileClass = function () {};
    game.TileClass.prototype.walkable=false;
    game.TileClass.prototype.frame=20;

    這里我們用了一個(gè)假設(shè)。假設(shè)每個(gè)區(qū)塊都是不可通行的,而且都顯示在第20幀。當(dāng)然了,實(shí)際的區(qū)塊不全是不可通行的,否則我們不能移動(dòng)。而且他們也不會(huì)都顯示在第20幀。問題看起來很嚴(yán)重,實(shí)際上不然。我們只是定義了這兩個(gè)通用屬性而已,我們會(huì)讓他完美工作的。

    現(xiàn)在我們創(chuàng)建新的區(qū)塊類型:

    game.Tile0 = function () {};
    game.Tile0.prototype.__proto__ = game.TileClass.prototype;
    game.Tile0.prototype.walkable=true;
    game.Tile0.prototype.frame=1;
    game.Tile1 = function () {};
    game.Tile1.prototype.__proto__ = game.TileClass.prototype;
    game.Tile1.prototype.frame=2;
    game.Tile2 = function () {};
    game.Tile2.prototype.__proto__ = game.TileClass.prototype;

    通過使用聰明的__proto__,我們不需要重復(fù)寫同樣的屬性了。我們的區(qū)塊從TileClass類中獲得了所有必要的材料。當(dāng)我們這樣創(chuàng)建新的區(qū)塊類型后:

    game.Tile2 = function () {};
    game.Tile2.prototype.__proto__ = game.TileClass.prototype;

    所有后來創(chuàng)建的Tile2區(qū)塊都繼承了屬性walkable=false、frame=20。這是不是很美妙呢?但是還沒有結(jié)束,我們可以改變這兩個(gè)屬性。看:

    game.Tile0 = function () {};
    game.Tile0.prototype.__proto__ = game.TileClass.prototype;
    game.Tile0.prototype.walkable=true;
    game.Tile0.prototype.frame=1;

    我們?cè)诶^承了TileClass類的屬性之后,又改寫了walkable、frame的值。最后的結(jié)果是Tile0區(qū)塊的walkable為true,frame為1。

    所有這些可能太復(fù)雜了些,畢竟我們只有少量的區(qū)塊類型和屬性。但是你如果要做一個(gè)復(fù)雜的區(qū)塊游戲,每個(gè)區(qū)塊都有很多屬性,那么單單定義這些重復(fù)的屬性就已經(jīng)夠繁的了


    英雄

    每個(gè)游戲都有英雄。英雄要打敗壞蛋、拯救公主,還要拯救全世界。我們也要加一個(gè)英雄,不過他暫時(shí)還不會(huì)拯救世界,他什么也干不會(huì),但是他已經(jīng)來了:

    看到了嗎,就是那個(gè)是紅色的方塊:)
    什么,看起來不夠帥?你當(dāng)然可以自己畫一個(gè)呀。他就是庫中那個(gè)名字是“char”的那個(gè)mc,而且他已經(jīng)被導(dǎo)出為“char”連接。注意不要讓英雄mc比方塊大!

    另外還要注意,英雄mc的注冊(cè)點(diǎn)是在中心,而方塊的注冊(cè)點(diǎn)則在左上角:

    要來些代碼?好吧,加上這句:
    char={xtile:2, ytile:1};

    這句代碼定義了一個(gè)char對(duì)象。這個(gè)char對(duì)象將會(huì)被賦予所有的關(guān)于英雄的信息:
    他如何移動(dòng)、他感覺怎么樣、他吃什么……等等。

    不過這一次我們只給他兩個(gè)屬性:xtile和ytile。他們記錄英雄所處的方塊。當(dāng)他四處走動(dòng)的時(shí)候,我們將會(huì)更新xtile/ytile屬性,這樣我們總能知道他站在那個(gè)方塊上面。
    例如當(dāng)xtile=2,ytile=1時(shí),他腳下的方塊就是“t_1_2”。實(shí)際上,他是站在左數(shù)第3塊、上數(shù)第2塊方塊上,記得坐標(biāo)是從0開始數(shù)的。

    我們以后會(huì)給他增加更多屬性。

    為了讓英雄站到舞臺(tái)上,在buildMap函數(shù)中,在for循環(huán)外(下面),添加這幾行代碼:
    game.clip.attachMovie("char", "char", 10000);
    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;

    第一行又復(fù)制了一個(gè)mc到game.clip這個(gè)mc中(你還記得我們用game.clip代表_root.tiles吧?),然后給他實(shí)例名“char”。

    然后我們把char的路徑保存到char對(duì)象中,這樣當(dāng)我們需要訪問char這個(gè)mc時(shí),
    不用再敲入mc的完整路徑_root.tile.char了。這樣做的好處是,如果我們要把char這個(gè)mc放到另外的地方,改動(dòng)代碼就會(huì)方便許多。

    接下來我們要計(jì)算char對(duì)象的兩個(gè)屬性:x和y。你也許會(huì)納悶,為什么還要兩個(gè)屬性,我們不是有xtile和ytile這兩個(gè)屬性了嗎?記住,xtile和ytile只是腳底下方塊的位置,不是我們需要的確切的象素值。英雄當(dāng)然可以在同一塊方塊上面走動(dòng),x和y屬性才可以給出正確的坐標(biāo)。

    還有,當(dāng)x和y的值計(jì)算正確后再賦給_x和_y,這樣做是有好處的,尤其是碰撞檢測的時(shí)候。

    我們通過英雄所在的方塊計(jì)算出英雄的實(shí)際位置(象素值)。首先,char.xtile*game.tileW得到所在方塊的實(shí)際坐標(biāo),在加上方塊大小的一半,這樣英雄就站到了方塊的中間。如果你有些迷糊的話,可以對(duì)照他們的注冊(cè)點(diǎn)自己畫一下。

    接著我們把英雄mc的寬度(寬度)的一半記為char對(duì)象的width(height)屬性。這樣做是很有用的,尤其是計(jì)算英雄的邊界的時(shí)候。你也可以自己定義char的這兩個(gè)屬性的值。有些英雄可能有長長的頭發(fā),而且允許頭發(fā)碰到墻上,身體卻不行,這樣你就應(yīng)該按需要自己定義。

    最后兩行把英雄放到我們計(jì)算好的位置上去。
    char.clip._x = char.x;
    char.clip._y = char.y;


    按鍵和移動(dòng)

    在這一章中我們將用四個(gè)方向鍵控制英雄的移動(dòng)。在移動(dòng)過程中,他會(huì)面朝移動(dòng)的方向,并且會(huì)顯示走動(dòng)的動(dòng)畫。一旦他停止移動(dòng),動(dòng)畫也會(huì)停止。試試這個(gè):

    因?yàn)闆]有碰撞檢測,所以英雄可以走出舞臺(tái)外面,不過不要擔(dān)心這個(gè),我們以后會(huì)解決這個(gè)問題。

    首先,讓我們完善英雄角色。建立3個(gè)新的mc。我們需要一個(gè)mc表示角色向左走(或者向右,我選擇了左),一個(gè)表示向上走,最后一個(gè)朝下走。在這些mc中,做角色走動(dòng)的動(dòng)畫。

    這些mc里不需要寫代碼。

    現(xiàn)在,編輯char影片夾子(mc),在時(shí)間線上創(chuàng)建5個(gè)關(guān)鍵幀:

    在關(guān)鍵幀1放置char_up影片夾子,關(guān)鍵幀2放置char_left影片夾子,關(guān)鍵幀4放char_right,關(guān)鍵幀5放char_down。許多時(shí)候向左移動(dòng)和向右移動(dòng)只是簡單的水平翻轉(zhuǎn)關(guān)系,所以你可以用一個(gè)mc表示這兩個(gè)方向的動(dòng)畫。現(xiàn)在確認(rèn)一下,這幾幀動(dòng)畫mc的實(shí)例名稱都是char,檢查每一幀。他們都叫char?是的,不用擔(dān)心。如果你不理解為什么是這樣的排列方式,我們將會(huì)在代碼的部分講解這個(gè)問題。

    ok,該寫點(diǎn)代碼了。

    代碼

    首先,移動(dòng)需要有個(gè)速度,所以先給英雄添加一個(gè)速度屬性:
    char={xtile:2, ytile:1, speed:4};

    速度表示英雄每一步移動(dòng)的象素值,更大的值意味著更快的移動(dòng),很小的值將會(huì)使英雄像個(gè)蝸牛。在這里使用整數(shù)是個(gè)好習(xí)慣,否則你會(huì)得到怪異的結(jié)果,實(shí)際上10象素和10.056873象素之間也看不出什么區(qū)別。

    你還記得吧,我們創(chuàng)建了_root.char這個(gè)對(duì)象來保存英雄的信息(如果忘記了,請(qǐng)回頭看看)?并且我們把char影片夾子放在tiles影片夾子里面了。為了讓我們的英雄醒來并開始移動(dòng),我們需要添加兩個(gè)函數(shù)來檢查按鍵和控制mc。拖一個(gè)空的影片夾子empty到舞臺(tái)上。你可以把它放到可視區(qū)域外,他只是用來放些代碼,所以在哪里都無所謂。在這個(gè)mc上面寫這些代碼(選中mc,然后打開代碼面板):
    onClipEvent (enterFrame) {
        _root.detectKeys();
    }

    你可以看到我們?cè)诿恳粠{(diào)用detectKeys這個(gè)函數(shù)。現(xiàn)在寫這個(gè)函數(shù):
    function detectKeys() {
     var ob = _root.char;
     var keyPressed = false;
     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();
        }
    }

    首先我們定義了兩個(gè)變量:ob 和 keyPressed。設(shè)置ob變量指向_root.char (記住,那是我們保存英雄所有信息的對(duì)象),設(shè)置變量keyPressed為false。keyPressed變量用來表示是否有四個(gè)方向鍵之一被按下去。

    下面有4個(gè)相似的 if 判斷,每個(gè) if 都檢測相應(yīng)的鍵是不是被按下了。如果鍵被按下,他們就這樣調(diào)用另外的一個(gè)函數(shù)moveCha:
    keyPressed=_root.moveChar(ob,1,0);

    這一行調(diào)用moveChar函數(shù)的時(shí)候帶了3個(gè)參數(shù)。第一個(gè)參數(shù)就是ob變量,就是我們的英雄對(duì)象。后兩個(gè)的取值我們總是讓他們?yōu)?1,1或者0。這些數(shù)字決定對(duì)象移動(dòng)的方向,第二個(gè)參數(shù)表示水平移動(dòng)的方向(-1:左;1:右),第三個(gè)參數(shù)代表垂直移動(dòng)的方向(-1:上;1:下)。最后我們把moveChar的返回值交給變量keyPressed。
    你在后面就可以看到moveChar函數(shù)總是返回true,
    所以任何方向鍵被按下后,變量keyPressed值都是true。

    現(xiàn)在來看看第二個(gè)函數(shù)moveChar:
    function moveChar(ob, dirx, diry) {
     ob.x += dirx*ob.speed;
     ob.y += diry*ob.speed;
     ob.clip.gotoAndStop(dirx+diry*2+3);
     ob.clip._x = ob.x;
     ob.clip._y = ob.y;
     return (true);
    }

    看第一行,moveChar函數(shù)接收了3個(gè)參數(shù),變量ob表示要移動(dòng)的對(duì)象,dirx、diry分別表示x、y方向的移動(dòng)。這是一個(gè)很通用的函數(shù),我們可以用它移動(dòng)游戲中所有東西。例如我們要讓子彈飛行,我們就可以調(diào)用moveChar函數(shù),同樣,我們也可以用這個(gè)函數(shù)移動(dòng)敵人。

    接下來的兩行我們給對(duì)象的x和y加上相應(yīng)的值。同樣,如果使用不同的對(duì)象(子彈、敵人),這些對(duì)象可以有不同的speed屬性。所以,當(dāng)我們檢測到右箭頭鍵時(shí),我們調(diào)用moveChar函數(shù)時(shí)的參數(shù)是1,0 ,此時(shí)dirx=1,diry=0。所以x值會(huì)在原來的基礎(chǔ)上增加(speed),而y則保持不變。如果我們調(diào)用moveChar函數(shù)的參數(shù)是0,-1(意味著上箭頭鍵),那么y值就會(huì)在原來的基礎(chǔ)上減小(speed),而x保持不變。

    注意,如果我們還有其他的動(dòng)作,比如碰撞或者跳躍,我們應(yīng)該將這些動(dòng)作單獨(dú)計(jì)算。這樣比簡單的mc.hitTest方法要好不少。

    這一句:
    ob.clip.gotoAndStop(dirx+diry*2+3);

    他使得角色mc跳到正確的幀上,讓角色面對(duì)正確的方向。你可以算出所有的dirx/diry組合(這里只有4種情況),如果你的角色mc時(shí)間線是和我們以前所說的設(shè)置一樣的話,這里就不會(huì)出問題。你可以拿計(jì)算器算算看:)

    沒有計(jì)算器?那我們還是看看吧:假設(shè)按了方向鍵右,那么 dirx=1,diry=0,
    結(jié)果 dirx+diry*2=4。那么角色mc會(huì)跳到第4幀,那里正好是我們角色朝右走的動(dòng)畫。

    接下來的兩行,設(shè)置角色mc的_x/_y屬性的值等于x/y的值。最后,我們返回一個(gè)true值,這樣keyPressed就得到了正確的值。下一章我們將會(huì)介紹碰撞檢測,很有趣的哦:)


    碰撞檢測

    像上面這個(gè),英雄可以穿墻而過,那就沒什么意思了。我們要想辦法讓英雄感受到障礙物的存在。

    在第一章中,我們給每個(gè)方塊都設(shè)置了一個(gè)“walkable”屬性,當(dāng)某個(gè)位置方塊的walkable屬性是false的時(shí)候,英雄就無法穿過它。當(dāng)值為true的時(shí)候,英雄就可以從上面走過(這個(gè)東西叫做“邏輯”:)。

    為了讓這個(gè)邏輯起作用,我們將會(huì)這樣做:
    當(dāng)方向鍵被按下以后,我們首先檢查下一個(gè)方塊是不是可通行的。
    如果是,我們就移動(dòng)英雄。如果不是,那么就忽略掉按鍵事件。

    這是完美的墻的碰撞: 

    英雄貼著墻站著,而且下一步他就會(huì)進(jìn)到墻里面。我們不會(huì)讓它發(fā)生的。

    但是這個(gè)世界總是不夠完美,要是英雄只和墻接觸一部分呢?

    這就要求我們檢測英雄的全部四個(gè)角是否和墻接觸了。只要任意一個(gè)角和墻接觸(上圖中是左下角),移動(dòng)就是不合理的。

    或者,英雄沒有貼著墻站,但是下一步就要跑到墻里去了,雖然只是一部分: 

     

    我們不得不讓他這樣貼著墻站著:

    “這么難?!”,你也許會(huì)喊,“不太可能辦到吧?”不用擔(dān)心,實(shí)際上很簡單的~


    檢查四個(gè)角

    我們不希望英雄的任何一部分能進(jìn)到墻里面去,只要四個(gè)角沒有進(jìn)去就行了,這是假設(shè)英雄的大體形狀是個(gè)長方形(他們確實(shí)是的)。

    為了實(shí)現(xiàn)這個(gè)功能,讓我們寫個(gè)函數(shù):getMyCorners
    function 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].walkable;
     ob.downleft = game["t_"+ob.downY+"_"+ob.leftX].walkable;
     ob.upright = game["t_"+ob.upY+"_"+ob.rightX].walkable;
     ob.downright = game["t_"+ob.downY+"_"+ob.rightX].walkable;
    }

    這個(gè)函數(shù)接收了3個(gè)參數(shù):對(duì)象中心的x/y位置(象素值)、對(duì)象的名稱。

    “等一下”,你也許會(huì)迷惑,“我們不是已經(jīng)在英雄對(duì)象中保存了他的當(dāng)前位置了嗎?”是的,但是我們當(dāng)時(shí)存的是當(dāng)前的位置,這里處理的是將要達(dá)到位置(先假定英雄可以移動(dòng))。

    首先,我們根據(jù)這個(gè)x/y坐標(biāo)計(jì)算出英雄所處的方塊。可能英雄的中心在一個(gè)方塊上面,但是左上角在另外一個(gè)方塊上面,左下角又在第三個(gè)方塊中,這是有可能的。
    (y+英雄的高度)/方塊高度=英雄下面的兩個(gè)角所在區(qū)塊的行值。

    最后的四行使用了我們計(jì)算出的方塊的可通行性。例如,左上角使用upY行l(wèi)eftX列的方塊的walkable屬性。你可以看到,得到的四個(gè)結(jié)果(upleft、downleft、upright、downright)被保存到ob對(duì)象中了,所以我們以后還可以用到它。

    我要再一次指出的是,getMyCorners函數(shù)不僅可以用在英雄上面,這里的ob也可以是任何可移動(dòng)的對(duì)象。做區(qū)塊游戲要多考慮函數(shù)的通用性,在后面的章節(jié)中你會(huì)體會(huì)到這種思想的正確性。

    移動(dòng)

    當(dāng)我們檢查了四個(gè)角以后,現(xiàn)在就可以很簡單地移動(dòng)了:

    如果4個(gè)角都是可以通行的,那么就移動(dòng),否則不移動(dòng)。但是要讓最后英雄貼著墻站著,還得多寫幾個(gè)字。修改后的moveChar函數(shù)處理4個(gè)可能的方向的移動(dòng),它看起來可能有些長,實(shí)際上僅僅是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*diry;
      } else {
       ob.y = ob.ytile*game.tileH+ob.height;
      }
     }
     if (diry == 1) {
      if (ob.downleft and ob.downright) {
       ob.y += ob.speed*diry;
      } else {
       ob.y = (ob.ytile+1)*game.tileH-ob.height;
      }
     }
     getMyCorners (ob.x+ob.speed*dirx, ob.y, ob);
     if (dirx == -1) {
      if (ob.downleft and ob.upleft) {
       ob.x += ob.speed*dirx;
      } else {
       ob.x = ob.xtile*game.tileW+ob.width;
      }
     }
     if (dirx == 1) {
      if (ob.upright and ob.downright) {
       ob.x += ob.speed*dirx;
      } else {
        ob.x = (ob.xtile+1)*game.tileW-ob.width;
      }
     }
     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函數(shù)通過鍵盤檢測函數(shù)傳遞過來的值得到對(duì)象和方向。
    這一行:
    getMyCorners (ob.x, ob.y+ob.speed*diry, ob);
    計(jì)算垂直移動(dòng)(當(dāng)diry不等于0時(shí))后的四個(gè)角的可行性,
    隨后,通過四個(gè)角walkable的值檢查是不是合法的移動(dòng):
    if (diry == -1) {
      if (ob.upleft and ob.upright) {
       ob.y += ob.speed*diry;
      } else {
       ob.y = ob.ytile*game.tileH+ob.height;
      }
     }

    這塊代碼是用來檢測向上的移動(dòng)的。當(dāng)上箭頭鍵被按下去后,diry的值等于-1。
    我們使用了getMyCorners函數(shù)得到的ob.upleft和ob.upright值,如果他們都是true,那就意味著上面兩個(gè)角所在方塊都是可通行的,我們就給角色的y坐標(biāo)加上ob.speed*diry,讓角色朝上移動(dòng)。

    但是如果這兩個(gè)角任何一個(gè)碰巧是不可通行的,即ob.upleft或者ob.upright是false,
    我們就要把角色放到墻邊上。為了讓角色貼著它上面的墻,他的中心點(diǎn)必須距離當(dāng)前方塊的上邊緣char.height象素,如圖:

    ob.ytile×game.tileH得到的是當(dāng)前方塊的y坐標(biāo),也就是上邊緣的y坐標(biāo),再加上角色的height值,就是正確的位置了。同樣的道理,另外三個(gè)方向的部分也可以這樣分析出來。

    最后一行的把實(shí)際的mc放到計(jì)算出來的坐標(biāo)處,讓角色顯示正確的動(dòng)畫幀,并且更新角色的屬性。同以前一樣,函數(shù)返回true值。

    Qhwa注:我在這里加了兩行
    ob.height = ob.clip._height/2;
    ob.width = ob.clip._width/2;

    這是因?yàn)楫?dāng)clip這個(gè)影片夾子跳轉(zhuǎn)相應(yīng)的幀后,原來的_width和_height可能會(huì)發(fā)生變化,如果還用初始化時(shí)的值,可能就會(huì)出錯(cuò)。如果英雄的高度和寬度是一樣的,就沒有必要這么做了。Tony推薦使用確定的而且相同的高度和寬度。

    芝麻開門-地圖切換

    你能在一個(gè)房子里面呆多久?一張圖片能看多久?是的,我們需要提供更多的空間給英雄。那意味著要改變地圖、創(chuàng)建新的房間、把英雄放置到合適的位置。

    為了創(chuàng)建兩個(gè)房間,我們聲明了兩個(gè)地圖數(shù)組:
    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對(duì)象中設(shè)置當(dāng)前的地圖序號(hào):
    game={tileW:30, tileH:30, currentMap:1}

    然后,我們開始掃描地圖myMap1,通過buildMap函數(shù)把它實(shí)現(xiàn)到屏幕上。我們可以把myMap1作為一個(gè)參數(shù)傳遞給buildMap函數(shù),這樣buildMap函數(shù)就具備更好的通用性。

    buildMap(_root[“myMap”+game.currentMap]);

    接下來,我們還需要一個(gè)描繪”門”的對(duì)象:

    game.Doors = function (newmap, newcharx, newchary)
    {
     this.newmap = newmap;
     this.newcharx = newcharx;
     this.newchary = newchary;
    };

    game.Doors.prototype.walkable = true;
    game.Doors.prototype.frame = 3;
    game.Doors.prototype.door = true;
    game.Tile2 = function () { };
    game.Tile2.prototype = new game.Doors(2, 1, 4);
    game.Tile3 = function () { };
    game.Tile3.prototype = new game.Doors(1, 6, 4);

    你可能已經(jīng)猜到了,Doors對(duì)象是可以通行的,它是mc的第3幀,它還有一個(gè)屬性door,而且值是true.我們后面將會(huì)利用這個(gè)屬性來判斷英雄是不是走到了門口.

    這里用到一個(gè)東西,叫做”繼承”,聽起來有些恐怖?呵呵,其實(shí)很簡單也很有用.所有的門對(duì)象創(chuàng)建的時(shí)候都用了Doors模板,Doors對(duì)象擁有的所有的所有屬性都傳遞給了他們.比如他們都是可通行的,而且都是第3幀.

    我們創(chuàng)建一個(gè)門的時(shí)候,必須指定幾個(gè)必要的信息:
    它是通往哪個(gè)房間(map)的,還有,英雄的新坐標(biāo)是多少?

    你可能要問,為什么要給英雄指定新坐標(biāo)呢?因?yàn)閯?chuàng)建新的房間以后,如果英雄還在原來的坐標(biāo),就有可能看起來不正確.還有,要避免英雄的新坐標(biāo)所在的方塊是不可通行的障礙物,或者是另外一個(gè)門.最糟糕的結(jié)果是英雄不停地被傳送于兩個(gè)房間,天哪~
    最合理的應(yīng)該是把英雄放在新地圖中門的旁邊。

    在創(chuàng)建門方塊的時(shí)候,我們傳遞了3個(gè)參數(shù):newMap、newCharX、newCharY。
    他們分別是新的地圖序號(hào),新的X坐標(biāo)和新的Y坐標(biāo)。當(dāng)?shù)貓D數(shù)組中出現(xiàn)數(shù)字2的時(shí)候,buildMap函數(shù)會(huì)在相應(yīng)的地方創(chuàng)建一個(gè)Tile2方塊。英雄穿過Tile2方塊后,他將到達(dá)myMap2,他的新坐標(biāo)是(1,4)。你可以在多個(gè)地圖中都用2,他們都可以讓英雄回到myMap2。

    更多的代碼

    在moveChar函數(shù)中插入下面這段代碼,放在return語句之前:
    if (game["t_" + ob.ytile + "_" + ob.xtile].door and ob == _root.char)
    {
     changeMap(ob);
    }

    他的作用是,我們?cè)谝苿?dòng)了對(duì)象之后判斷是不是站到了門口,而且對(duì)象要是英雄才行,如果都滿足要求,那么就換個(gè)地圖。我們利用changMap函數(shù)實(shí)現(xiàn)更換地圖的功能:

    function changeMap(ob)
    {
     var name = "t_" + ob.ytile + "_" + ob.xtile;
     game.currentMap = game[name].newMap;
     ob.ytile = game[name].newchary;
     ob.xtile = game[name].newcharx;
     ob.frame = ob.clip._currentframe;
     buildMap(_root["myMap" + game.currentMap]);
    }

    這個(gè)函數(shù)很好理解,我們從門對(duì)象(game[name])中得到newMap、newCharX、newCharY參數(shù),然后調(diào)用buildMap函數(shù)創(chuàng)建新地圖。

    這里用到了一個(gè)新的屬性:frame,它是用來記錄英雄當(dāng)前的方向的,如果沒有記錄,在新地圖里面,英雄總是停在第一幀。我們同時(shí)還要加這句as到buildMap函數(shù)中(設(shè)置了英雄的坐標(biāo)以后):
    char.clip.gotoAndStop(char.frame);

    這就是關(guān)于門的全部了,好了,多串串門吧~


    跳躍

    開始之前,我們先把視角從俯視改成側(cè)視,這樣英雄才可以跳躍。就像下面的這個(gè),按左右鍵英雄移動(dòng),按空格鍵跳躍:

    跳躍基礎(chǔ)

    跳躍意味著上升,上升在Flash中意味著_y屬性的減少。所以我們需要計(jì)算這樣的式子:
    新的坐標(biāo)=現(xiàn)在的坐標(biāo)-上升速度

    如果只計(jì)算一次,坐標(biāo)只改變一次,英雄很快就停止了。因此我們需要持續(xù)不斷的計(jì)算新坐標(biāo)。而且,我們還應(yīng)該改變速度的值,否則英雄就在空中下不來了。
    下落和上升一樣,只不過速度值前面加個(gè)負(fù)號(hào)而已。

    為了改變速度,我們定義一個(gè)新的變量:重力加速度。重力把英雄拉向地面,學(xué)過物理吧?重力加速度并不直接改變坐標(biāo),他改變的是速度:
    速度=速度+加速度

    這就是Flash中的表示方法,=可不是表示左右相等,是把右邊計(jì)算的結(jié)果賦給”速度”變量。這個(gè)式子也是需要不停計(jì)算的,以便保持連貫的運(yùn)動(dòng)。你可以改變重力的值,較小的值意味著在空中的時(shí)間更長,較大的值很快就會(huì)把英雄“拉”下來。
    從另外的角度看,重力也意味著跳躍能力,我們可以給不同的對(duì)象賦以不同的“重力”屬性,這樣他們跳得就不至于一樣高。

    讓我們來看一個(gè)例子。比如剛開始的速度是-10,重力是2。
    那么開始的時(shí)候,英雄將會(huì)上移10象素,然后速度降低到8;
    接著英雄上移8象素,然后速度又變成了6……
    如此往復(fù),直到速度等于0的時(shí)候,英雄不再上升了。
    接著速度成了2,英雄開始下降2象素;
    下一步又下降4象素、6象素、8象素……直到落地。

    落地后,跳躍自然也應(yīng)該結(jié)束了。但是要是英雄在跳躍過程中頂?shù)秸系K物怎么辦?
    很簡單,我們把速度強(qiáng)行改成0就行了,然后英雄就會(huì)落下來。

    【注意】在區(qū)塊游戲開發(fā)中,不要讓速度值超過方塊的高度。過大的速度會(huì)導(dǎo)致碰撞檢測跳過某個(gè)方塊,導(dǎo)致英雄“穿過”障礙物。或許有些魔法師可以穿過障礙物,但是在普通的區(qū)塊游戲中,這是個(gè)bug。

    跳躍并不影響水平方向的運(yùn)動(dòng),在空中我們還可以用方向鍵控制英雄的水平運(yùn)動(dòng)。我們需要做的是,在左移或右移后,判斷英雄腳下是不是還有東西,如果是空的,跳躍就開始了(這時(shí)候初始速度是0,英雄直接下落)。

    會(huì)跳的英雄

    我們給英雄再添加一些屬性:
    char = {xtile:2, ytile:1, speed:4, jumpstart:-18, gravity:2, jump:false};
    speed屬性是水平移動(dòng)速度,jumpstart是跳躍的初始速度,
    granvity是重力值,jump屬性用來表示英雄是不是在跳躍過程中。

    下面加一句as到buildMap函數(shù)中:
    char.y = ((char.ytile + 1) * game.tileW) - char.height;

    因?yàn)槲覀兊囊晥D是側(cè)視的,英雄剛開始的位置可能是“漂”在空中的,我們應(yīng)該讓他站到地面上來。

    changeMap函數(shù)和getMyCorners函數(shù)不需要任何變動(dòng)。

    騰空的感覺

    我們先來改造detectKey函數(shù),刪除上下鍵檢測,添加空格鍵檢測:

    function detectKeys()
    {
     var ob = _root.char;
     var keyPressed = false;
     if (Key.isDown(Key.SPACE) and !ob.jump)
     {
      ob.jump = true;
      ob.jumpspeed = ob.jumpstart;
     }
     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();
     }
    }

    注意看我們?cè)趺幢苊馓S過程中空格鍵觸發(fā)新的跳躍(聽起來很拗口,哈哈),實(shí)際上就是當(dāng)處于跳躍中時(shí),忽略空格鍵。如果按了空格鍵,而且英雄沒有處于跳躍過程,那就開始跳吧,把jump屬性改成true,把jumpspeed改成speedstart屬性值。

    看一下,在左右方向鍵的檢測語句后面,我們添加了幾句。
    if (ob.jump)
     {
      keyPressed = _root.jump(ob);
     }

    如果jump屬性為true(正在跳),那么執(zhí)行jump函數(shù)。只要jump屬性為true,jump函數(shù)就會(huì)不斷地被執(zhí)行,直到j(luò)ump屬性變成了false。這個(gè)函數(shù)可以在空格鍵松開后仍然執(zhí)行,只要jump屬性為true。

    Jump函數(shù)所做的只是改變jumpspeed的值,給他加上重力值。同時(shí)做一些處理防止速度值過大,讓jumpspeed不會(huì)超過方塊高度。最后調(diào)用moveChar函數(shù)移動(dòng)角色。

    function jump (ob)
    {
     ob.jumpspeed = ob.jumpspeed + ob.gravity;
     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函數(shù),因?yàn)榧尤肓颂S過程,跳躍時(shí)的速度jumpspeed和水平移動(dòng)的速度speed是不同的:

    function moveChar(ob, dirx, diry, jump)
    {
     if (Math.abs(jump) == 1)
     {
      speed = ob.jumpspeed * jump;
     }
     else
     {
      speed = ob.speed;
     }
     ...

     

    jump參數(shù)是從上面的jump函數(shù)中傳遞過來的,它的取值不是1就是-1。如果jump的絕對(duì)值(Math.abs)是1,移動(dòng)的距離就是jumpspeed*jump,否則就是speed屬性。

    如果這一步移動(dòng)結(jié)束后,角色的頂?shù)搅苏系K物,就要把jumpspeed值改成0:
    ob.y = ob.ytile * game.tileH + ob.height;
    ob.jumpspeed = 0;

    如果這一步移動(dòng)結(jié)束后,角色站到了地面上,跳躍就該結(jié)束了:
    ob.y = (ob.ytile + 1) * game.tileH - ob.height;
    ob.jump = false;

    在左右移動(dòng)后,我們還要看看角色是不是還站在平臺(tái)上,如果不是,就應(yīng)該落下來:
    ob.x += speed * dirx;
    fall (ob);

    所以,我們還需要一個(gè)fall函數(shù):
    function fall (ob)
    {
     if (!ob.jump)
     {
      getMyCorners (ob.x, ob.y + 1, ob);
      if (ob.downleft and ob.downright)
      {
       ob.jumpspeed = 0;
       ob.jump = true;
      }
     }
    }

    如果角色已經(jīng)處于跳躍過程中,這個(gè)函數(shù)就沒有必要運(yùn)行了,它是用來檢測“踩空”的情況的,只有當(dāng)角色站著(!ob.jump)的時(shí)候才有用。這時(shí)候我們調(diào)用getMycorners函數(shù),如果角色下方的兩個(gè)方塊都是可通行的,那就應(yīng)該落下來了,
    起始速度是0,然后把jump屬性改成true。

    騰云駕霧

    到目前為止,我們已經(jīng)做出了阻止英雄通過墻的效果。很有趣,不是嗎?許多游戲還有一類叫做“云”的方塊,角色門可以左右穿行他們,甚至可以從下面跳上去,
    但是當(dāng)下落的時(shí)候,他們確是不可通行的,英雄會(huì)停在上面。看這個(gè)例子:

    你看到區(qū)別了吧?讓我們來看看圖。
    這個(gè)是普通的磚墻方塊,英雄不能從任何角度穿過它:

    再來看云。除了上面,英雄可以從任何方向穿過。
    如果英雄從上面掉下來,我們讓他停在上面。

    首先我們要做一些帶有“cloud”屬性的方塊,
    如果cloud屬性是true,這個(gè)方塊就是一塊“云”。定義:

    game.Tile4 = function () {};
    game.Tile4.prototype.walkable = true;
    game.Tile4.prototype.cloud = true;
    game.Tile4.prototype.frame = 4;

    它的walkable屬性是true,意味著英雄可以穿行過去。
    為了讓英雄能站到上面,我們需要?jiǎng)?chuàng)建新的函數(shù)。

    function checkIfOnCloud (ob)
    {
     var leftcloud = game["t_" + ob.downY + "_" + ob.leftX].cloud;
     var rightcloud = game["t_" + ob.downY + "_" + ob.rightX].cloud;
     if ((leftcloud or rightcloud) and ob.ytile != ob.downY)
     {
      return(true);
     }
     else
     {
      return(false);
     }
    }

    我們檢測英雄的左下角和右下角的方塊是不是云,只要有一塊是,就返回true。否則返回false。

    現(xiàn)在我們需要在兩個(gè)地方調(diào)用這個(gè)函數(shù):
    moveChar函數(shù)中往下運(yùn)動(dòng)的時(shí)候,還有fall函數(shù)中檢測英雄是不是繼續(xù)下落的時(shí)候。

    在moveChar函數(shù)中if (diry == 1)的后面原來有這句:

    if (ob.downleft and ob.downright)
    {
     ..
    .

    改成這樣,加上云的檢測:

    if (ob.downleft and ob.downright and !checkIfOnCloud (ob))
    {
     ...

    在fall函數(shù)中也一樣,把這個(gè):

    if (ob.downleft and ob.downright)
    {
     ...

    換成:

    if (ob.downleft and ob.downright and !checkIfOnCloud (ob))
    {
     ...

    只有左下方和右下方都可通行,而且下面的不是云,英雄才能往下掉。

    enjoy :)


    梯子

    在區(qū)塊游戲中梯子是很常見的一種東西。英雄可以在梯子上爬上爬下(我打賭你不知道:)。當(dāng)在梯子上按上下方向鍵的時(shí)候,我們會(huì)讓英雄上下攀爬。

    看起來梯子很簡單,實(shí)際上又很多東西需要考慮。首先,有多少種梯子?

    在圖中,有4種不同種類的梯子。
    梯子A處于一個(gè)不可通行的障礙物中。英雄在上面能做什么呢?他可以上下爬,但是不能左右運(yùn)動(dòng),否則就會(huì)卡在墻里,那可不好受。

    qhwa注: 有些游戲中在這種梯子上是可以左右移下來的,這個(gè)功能可以由你自己添加。

    梯子B所在的方塊是可通行的,而且它的上面還有梯子,所以英雄可以上下爬,也可以左右移動(dòng)。但是當(dāng)他左右移動(dòng)的時(shí)候,就該掉下來了。

    梯子C下面沒有梯子了,英雄只能在它上面向上爬,或者左右移動(dòng)。

    梯子D并不是在所有的游戲中都會(huì)出現(xiàn)。有些人認(rèn)為這種梯子是設(shè)計(jì)的失誤,因?yàn)樗麄儾粚?dǎo)向任何地方,在空中就斷掉了。英雄可以爬上去然后站到梯子上面嗎?如果梯子頂部的左右有方塊,英雄可以走過去嗎?這些都是容易出現(xiàn)分歧的。

    這些都是梯子的一些例子,當(dāng)然還有其他形式的梯子,但是我希望你能看到在開始寫代碼之前理一下思緒是多么重要。游戲各不相同,可能這里的東西在有的時(shí)候很適用,但是可能在別的地方就未必了,只要你每次寫代碼之前思考一下,不要硬套,就會(huì)事半功倍。

    規(guī)則

    讓我們列一下關(guān)于梯子的規(guī)則:

    1. 通過上下方向鍵,英雄可以在梯子上上下移動(dòng)
    2. 當(dāng)英雄和梯子接觸時(shí),他可以爬上去
    3. 當(dāng)英雄和梯子接觸,且下方也有梯子時(shí),他可以爬下來
    4. 當(dāng)英雄在梯子上,且左右沒有墻時(shí),他可以左右移動(dòng)
    5. 英雄不能在梯子上跳躍

    這些應(yīng)該夠了。

    請(qǐng)給我一把梯子

    梯子是顯示在方塊的上面的,所以我們要給他做一個(gè)獨(dú)立的影片夾子。這樣我們就不用為上面說的不同類型的梯子創(chuàng)建不同的圖像了。確定你的梯子mc被導(dǎo)出到as("Export for as"),并且檢查鏈接名是否為"ladder"。

    在ladder影片夾子中,畫出如上形狀的梯子,梯子水平方向在方塊的中間。

    和其他方塊一樣,我們也要定義梯子的原型:

    game.Tile4 = function () {};
    game.Tile4.prototype.walkable = false;
    game.Tile4.prototype.frame = 2;
    game.Tile4.prototype.ladder = true;
    game.Tile4.prototype.item = "ladder";

    game.Tile5 = function () {};
    game.Tile5.prototype.walkable = true;
    game.Tile5.prototype.frame = 1;
    game.Tile5.prototype.ladder = true;
    game.Tile5.prototype.item = "ladder";

    這兩個(gè)不同的方塊(Tile4和Tile5)都具有frame屬性,這是用來表示梯子后面(在屏幕上是下面層)的方塊類型。他們還有值為true的ladder屬性(用來表示這里有把梯子),值為"ladder"的item屬性(用來attachMovie用的,復(fù)制ladder影片夾子)

    在buildMap函數(shù)中復(fù)制ladder影片夾到方塊中:

    game.clip[name].gotoAndStop(game[name].frame);
    if (game[name].item != undefined)
    {
     game.clip[name].attachMovie(game[name].item, "item", 1);
    }

    這段代碼首先讓方塊顯示正常的幀(由frame屬性決定),然后判斷item屬性是否為空,如果不是(有值)就復(fù)制item表示的mc。你可以把item屬性設(shè)定成別的值,這樣就可以復(fù)制別的mc,在別的地方也可以用到,只是要注意別在一個(gè)方塊中復(fù)制太多不同的mc。

    為了不重復(fù)輸入代碼,我們把moveChar函數(shù)的結(jié)束部分修改一下,調(diào)用一個(gè)新函數(shù)updateChar:

    updateChar (ob, dirx, diry);
    return (true);


    這是updateChar函數(shù):

    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函數(shù)中添加:

    ob.climb = false;


    修改detectKeys函數(shù),添加上下鍵的監(jiān)測:

    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);
     }
    }

    當(dāng)我們檢測了左右鍵之后,我們判斷英雄是不是不在跳躍過程中(!ob.jump),而且利用checkUpLadder函數(shù)和checkDownLadder函數(shù)判斷附近是不是有梯子,
    如果一切正常,調(diào)用climb函數(shù)來移動(dòng)英雄。

    攀爬動(dòng)作的函數(shù)

    我們將要?jiǎng)?chuàng)建3個(gè)新的函數(shù),1個(gè)為了檢測是否能往上爬,
    1個(gè)為了檢測是否能往下爬,還有一個(gè)是實(shí)現(xiàn)攀爬動(dòng)作的函數(shù)。

    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].ladder;
     var downLadder = game["t_" + downY + "_" + ob.xtile].ladder;
     if (upLadder or downLadder)
     {
      return (true);
     }
     else
     {
      fall (ob);
     }
    }

     

    這段代碼首先計(jì)算英雄的上下兩個(gè)y坐標(biāo)(頭和腳),根據(jù)所在的區(qū)塊的ladder屬性就可以判斷是否可以往上爬。如果上下都沒有梯子,我們檢測英雄是否應(yīng)該掉下來。

    function checkDownLadder (ob)
    {
     var downY = Math.floor((ob.speed + ob.y + ob.height) / game.tileH);
     var downLadder = game["t_" + downY + "_" + ob.xtile].ladder;
     if (downLadder)
     {
      return (true);
     }
     else
     {
      fall (ob);
     }
    }


    為了檢測往下的攀爬動(dòng)作,我們需要英雄腳底下方塊的ladder屬性。
    和往上爬不同,我們還要考慮到英雄接下來(移動(dòng)結(jié)束)所在的方塊的ladder屬性(ob.speed+ob.y+ob.height)。

    function climb (ob, diry)
    {
     ob.climb = true;
     ob.jump = false;
     ob.y += ob.speed * diry;
     ob.x = (ob.xtile * game.tileW) + game.tileW / 2;
     updateChar (ob, 0, diry);
     return (true);
    }

    在climb函數(shù)中,我們首先設(shè)置climb標(biāo)記為true,jump標(biāo)記為false。然后計(jì)算新的y坐標(biāo),把英雄放在梯子方塊的中間,
    ob.x = (ob.xtile * game.tileW) + game.tileW / 2;

    英雄可以在梯子左側(cè)或右側(cè)抓著梯子爬,但是這樣不太雅觀:)

    最后我們利用updateChar函數(shù)移動(dòng)英雄到正確的位置。


    愚蠢的敵人

    我們的英雄已經(jīng)很完美了,但是他很無聊,唯一能做的只是來回走。我們需要?jiǎng)e的東西。不是食物,不是飲料,也不是美女,我們需要的是——一些敵人。敵人就像加在湯里的鹽,缺了它,一切都索然無味。好的游戲中會(huì)有聰明的敵人,但是我們從一些最笨的敵人開始做起。他們所做的僅僅是來回走,順便檢測是不是碰上英雄了。

    到目前為止,我們已經(jīng)有了兩類對(duì)象:英雄和方塊。英雄由玩家操縱,方塊不會(huì)運(yùn)動(dòng)。敵人應(yīng)該類似英雄,唯一不同的是,我們不能操作他們移動(dòng),我們將會(huì)賦予他們一定的智能。我們將要做兩種不同的敵人,第一種上下走,第二種會(huì)左右走。他們都會(huì)在撞到墻上后回頭繼續(xù)走。(真夠笨的:)

    在你開始構(gòu)造你的超級(jí)復(fù)雜的敵人之前,再想想一些東西。許多游戲?qū)嶋H上沒有用到敵人,有的雖然用到了,也不是很聰明。Flash并不是非常強(qiáng)大,如果你的游戲中有100個(gè)敵人,他們都聰明地使用A*算法跟蹤英雄,我真的懷疑是否有這么強(qiáng)大的機(jī)器能運(yùn)行。如果可以的話,最好讓一些敵人愚蠢些,一些聰明些,結(jié)果玩家可能就會(huì)忽略了他們的差別。另外,我們都想要比別人聰明,所以就讓玩家感覺這種樂趣好了 :)

    準(zhǔn)備敵人

    同創(chuàng)建英雄一樣,創(chuàng)建一個(gè)剪輯放置敵人(如果忘了怎么做,在回頭看看)。他們也有4幀,分別是左、上、下、右的動(dòng)畫。同樣的,他們也要導(dǎo)出為“enemy1”和“enemy2”(在庫面板中設(shè)置linkage)。現(xiàn)在我們添加一個(gè)enemies數(shù)組:

    myEnemies = [
    [0],
    [[1, 6, 1]],
    [[2, 1, 3]]
    ];

     

    看得出來,我們?cè)趍ap1放了1個(gè)敵人。
    [1,6,1]:第一個(gè)1代表敵人的類型(hoho~我們有好多中敵人得)
    6,1是他開始的位置。創(chuàng)建地圖的時(shí)候,我們會(huì)把他放到x=6,y=1的方塊上。同理在map2中也有一個(gè)敵人,但是類型是2,位置是1,3。你可在一個(gè)地圖中放置多個(gè)敵人,但是千萬記住,不要把他們嵌到墻里面!記得要放在一個(gè)可通行的方塊上面。

    讓我們聲明一些敵人的模板:

    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;

    他們的代碼看起來很相似,但是他們動(dòng)作就不一樣了。Enemyp1會(huì)上下走,因?yàn)樗膟Move屬性是1;但是Enemyp2只會(huì)水平移動(dòng)。你可以設(shè)置xMove/yMove屬性為1或-1或0。不過請(qǐng)不要同時(shí)把這兩個(gè)屬性都設(shè)置成非零值,除非你希望敵人能走對(duì)角線。

    你也可以把xMove和yMove都設(shè)置成0,這樣的敵人沒有移動(dòng)的能力,也許你會(huì)用到。

    speed屬性聲明了敵人移動(dòng)的速度。不同的敵人可以有不同的速度。


    擺放敵人

    在buildMap函數(shù)中,介于創(chuàng)建門和創(chuàng)建英雄的代碼之間,加入如下代碼:

    var enemies = myEnemies[game.currentMap];
    game.currentEnemies = enemies.length;
    for (var i = 0; i<game.currentEnemies; ++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;
    }

    這段代碼什么意思?首先我們得到當(dāng)前地圖的敵人數(shù)組,轉(zhuǎn)存為enemies數(shù)組,然后把敵人的個(gè)數(shù)傳給currentEnemies,接著遍歷敵人數(shù)組,把他們都安置好。
    (記住,雖然這里只用到了一個(gè)敵人,但是可以有更多的)

    變量name的值是新創(chuàng)建的敵人的名字,“enemy0”,“enemy1”,“enemy2”……依次類推。然后我們從相應(yīng)的模板(剛剛聲明過)創(chuàng)建新的對(duì)象:

    game[name]= new game["Enemy"+enemies[i][0]];
    從敵人數(shù)組(enemies[i])的第一個(gè)元素(enemies[i][0])得到敵人的類型,比如是1,然后通過enemyp1模板創(chuàng)建一個(gè)敵人。

    下面的幾行是取得坐標(biāo),然后把敵人放置到該處。ok,這就是buildMap函數(shù)該變動(dòng)的地方。

    但是,你也許會(huì)大聲喊出來,但是他還不會(huì)動(dòng)!好吧,我們就讓他動(dòng)起來


    移動(dòng)敵人

    同人一樣,敵人也需要腦子,我們就寫個(gè)enemyBrain函數(shù)好了:

    function enemyBrain () {
    for (var i = 0; i<game.currentEnemies; ++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.xMove;
        ob.yMove = -ob.yMove;
      }
      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);
      }
    }

    }

     

    正如你所看到的,我們又要遍歷數(shù)組了。我們把enemies數(shù)組的每個(gè)元素存到ob,當(dāng)i等于0的時(shí)候,ob就是enemy0。

    然后我們調(diào)用getCorners函數(shù),檢查敵人是不是碰到了障礙物,如果upleft、downleft、upright、downright都是true,則說明可以行走。我們就可以放心的調(diào)用moveChar函數(shù),同時(shí)傳遞xMove、yMove給moveChar函數(shù)。我們以前用moveChar都是移動(dòng)英雄的,現(xiàn)在移動(dòng)敵人也沒有任何區(qū)別,我說過,我們可以重復(fù)使用許多函數(shù)的。

    如果英雄碰到了障礙物,我們就讓xMove和yMove都取相反數(shù),比如原來xMove是1,就取-1,這樣敵人就會(huì)掉頭移動(dòng)。如果yMove原來是0,那么相反數(shù)還是0,沒有影響。

    最后一部分檢測英雄和敵人的距離,看是不是碰到了一起,如果是,oh,game over。當(dāng)然,游戲不可能這么簡單的結(jié)束掉,你可以減少英雄的生命值或者做別的處理,這些都由你自己完成。我們使用的計(jì)算距離的公式用的是“勾股定理”,如果你需要精確到象素級(jí)別的接觸,你應(yīng)該用hitTest,但是這里沒有必要這么精確。別離敵人太近,否則你會(huì)死掉的。:-)

    我們需要不停的調(diào)用這個(gè)函數(shù),所以在detectKeys函數(shù)中加入:

    _root.enemyBrain();

    這就是一個(gè)敵人,很愚蠢的敵人。接下來我們要做更聰明的敵人,他們象人一樣四處巡邏,碰到障礙物后會(huì)改變方向繼續(xù)巡邏。hoho,繼續(xù)吧~

    下載本例的源文件

    平臺(tái)上的敵人

    如果你需要在平臺(tái)上面放一個(gè)巡邏的敵人,就像這樣:

    只需要寫幾行就行了。敵人可以來回巡邏,碰到邊緣的時(shí)候會(huì)自動(dòng)折回。這個(gè)需要敵人檢測下一個(gè)落腳點(diǎn)方塊的通行性:

    getMyCorners (ob.x+ob.speed*ob.xMove, ob.y+ob.speed*ob.yMove+1, ob);
    if (!ob.downleft and !ob.downright) {

    注意一下這里的一個(gè)很重要的數(shù)字:1。 (ob.y+ob.speed*ob.yMove+1)加1是為了檢查英雄腳下面的方塊。還有要注意的是只有當(dāng)downleft和downright都是true的時(shí)候,才能繼續(xù)向前走,否則就掉頭走。

    下載本例的源文件


    教給敵人一些竅門

    碰到障礙物可以改變方向,而不是簡單的回頭走。

    讓我們修改一下enemyBrain函數(shù)。以前是簡單的將ob.xMove和ob.yMove取相反數(shù),現(xiàn)在我們給他一個(gè)隨機(jī)的新方向:

    ob.xMove = random(3)-1;
    if (ob.xMove) {
     ob.yMove = 0;
    } else {
     ob.yMove = random(2)*2-1;
    }

    一旦敵人接觸障礙物,xMove就會(huì)得到一個(gè)隨機(jī)的數(shù)值(-1,0或1,random(3)會(huì)返回0,1或2)。如果生成的xMove值是0,我們就設(shè)置yMove為1或-1。
    random(2) : 0 或 1
    random(2)*2 : 0 或 2
    random(2)*2-1 : -1 或 1
    如果生成的xMove值是1,那么就把yMove值設(shè)置成0,然后重新生成xMove值。

    下載本例的源文件

    這樣就好多了,但是如果我們還可以優(yōu)化一下,我們應(yīng)該避免與原來相同或相反的方向。

    代碼:

    } 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.xMove;
        }
      } 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.yMove;
        }
      }
    }

    這回我們先檢查當(dāng)前的方向。例如,如果原先是垂直移動(dòng)(xMove==0)那么我們讓xMove為1或-1,然后讓yMove為0。但是如果敵人在地圖的拐角處,他的新方向可能會(huì)讓他再一次撞上墻。這就是為什么我們要調(diào)用getCorners函數(shù)來檢測障礙物,如果有障礙物,掉頭走。

    下載本例的源文件

    Ok,這回?cái)橙烁勇斆髁艘恍驗(yàn)橥婕也荒茴A(yù)知他遇到障礙物后的行走方向。但是你也注意到了,這個(gè)傻小子只會(huì)去撞墻然后改變方向。如果你的地圖中有大范圍的空白區(qū)域,玩家很難保證敵人能經(jīng)過那里。最好的例子是一個(gè)大房子,英雄站在中間,那么敵人永遠(yuǎn)也抓不到他。

    我們賦予敵人自動(dòng)轉(zhuǎn)向的能力,即便他沒有遇到障礙物。

    我們?cè)谒脑椭刑砑右粋€(gè)turning屬性來表征轉(zhuǎn)向的能力。

    turning表示的是敵人每行走一步時(shí)轉(zhuǎn)向的可能性。0代表永遠(yuǎn)不會(huì)轉(zhuǎn)向,100表示每步都要轉(zhuǎn)向(你可別千萬這么做)。

    更改一下if判斷

    if (ob.downleft and ob.upleft and ob.downright
     and ob.upright and random(100)>ob.turning) {

    如果random(100)的值小于ob.turning,就換一個(gè)新的方向,就算本來能夠繼續(xù)往前走。

    下載本例的源文件

    射擊

    你可以用很多方法殺死敵人。你可以通過刀劍、槍或者其他的東西。讓我們來看看我們?cè)鯓酉驍橙松鋼簦ㄊ褂胹hift鍵射擊)。

    當(dāng)我說到“子彈”的時(shí)候,我的意思是那些從英雄處飛出來的、看起來要去殺死敵人的東西。它可以是球、箭頭、冰激凌、企鵝等等。

    首先,我們還是應(yīng)該考慮一下,射擊應(yīng)該是怎樣的過程,子彈是怎樣運(yùn)動(dòng)。一旦shift鍵被按下,首先,子彈對(duì)象、子彈剪輯被創(chuàng)建,子彈剪輯的位置應(yīng)該是英雄所在的位置。而且子彈應(yīng)該朝英雄面對(duì)的方向運(yùn)動(dòng)。如果子彈遇到墻或者敵人,它應(yīng)該自動(dòng)消失。如果它碰到的是敵人,那么敵人也要消失。

    子彈的速度應(yīng)當(dāng)比英雄的速度大,除非你準(zhǔn)備讓英雄可以用某種方式阻擋飛行的子彈。通常那些傻乎乎的敵人不會(huì)看到飛行的子彈,但是你也可以創(chuàng)建會(huì)躲避子彈的敵人。你也可以做一些能朝英雄射擊的敵人。

    準(zhǔn)備射擊

    畫一個(gè)子彈的mc,然后做一個(gè)linkage,ID是bullet,以便用AS復(fù)制到舞臺(tái)中。子彈MC中的圖像應(yīng)該居中對(duì)齊。

    讓我們聲明一下子彈對(duì)象:

    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;

    子彈每步移動(dòng)的距離是5,高度和寬度都是2象素,這已經(jīng)足夠殺死敵人了。

    dirx/diry屬性確定了子彈移動(dòng)的方向。實(shí)際上我們還是通過moveChar函數(shù)移動(dòng)子彈。如果dirx=1,子彈向右移動(dòng),diry=-1則向上移動(dòng)。我可以從英雄(或敵人)對(duì)象中得到方向參數(shù),設(shè)定dirx/diry的值,但是在游戲剛開始的時(shí)候,英雄還沒有移動(dòng),這時(shí)候玩家如果要射擊,就用上面這個(gè)默認(rèn)的方向(dirx=0,diry=-1,朝上移動(dòng))。

    給game對(duì)象添加兩個(gè)新的屬性:

    game={tileW:30, tileH:30, currentMap:1, bulletcounter:0};
    game.bullets= new Array();

    bulletcounter屬性用來記錄子彈的數(shù)目,以便我們給新產(chǎn)生的子彈取不重復(fù)的名稱。游戲里第一發(fā)子彈名稱是bullet0,然后是bullet1、bullet2……最多是bullet100,接下來返回到bullet0。我們可以讓數(shù)目無限增長,但是誰也不能保證到最后會(huì)發(fā)生什么。

    game.bullets數(shù)組用來放置舞臺(tái)上的子彈對(duì)象。一開始的時(shí)候它是一個(gè)空的數(shù)組。

    然后給角色對(duì)象加上一個(gè)shootspeed屬性,設(shè)置每次發(fā)彈的最小時(shí)間間隔:

    char={xtile:2, ytile:1, speed:4, shootspeed:1000};

    更高的shootspeed值代表更長的間隔、更慢的射擊速度,單位是毫秒。

    敵人死亡后,我們還要把他們從game對(duì)象中刪除。改變buildMap函數(shù)的創(chuàng)建敵人的部分:

    game.currentEnemies = [];
    for (var i = 0; i<enemies.length; ++i) {
      var name = "enemy"+i;
      game[name]= new game["Enemy"+enemies[i][0]];
      game[name].id=i;
      game.currentEnemies.push(game[name]);

    然后在enemyBrain函數(shù)中,把這句:

    var name = "enemy"+i;
    換成 :var name = "enemy"+game.currentEnemies[i].id;

    我通過currentEnemies數(shù)組來放置舞臺(tái)上活動(dòng)的敵人。一旦敵人被殺死,我們就會(huì)把它從currentEnemies數(shù)組中刪除。新的屬性“id”幫助我們找到敵人在enemies數(shù)組中的位置。

    在detectKeys函數(shù)檢查完按鍵后添加代碼:

    if (Key.isDown(Key.SHIFT) and getTimer()>ob.lastshot+ob.shootspeed) {
      _root.shoot(ob);
    }

    如果SHIFT鍵被按下,而且時(shí)間間隔足夠長,調(diào)用shoot函數(shù)。

    在moveChar函數(shù)的開頭添加兩行代碼,用來保存當(dāng)前對(duì)象的方向:

    ob.dirx=dirx;
    ob.diry=diry;

    我們可以用這個(gè)來指定子彈運(yùn)行的方向。


    開槍

    為了成功的讓子彈按照我們預(yù)定的想法飛行,我們要寫一個(gè)新的函數(shù)shoot:

    function shoot (ob) {
      ob.lastshot=getTimer();
      game.bulletcounter++;
      if (game.bulletcounter>100) {
        game.bulletcounter=0;
      }
      var name = "bullet"+game.bulletcounter;
      game[name]= new game.Bullet;
      game[name].id=game.bulletcounter;
      game.bullets.push(game[name]);
      if (ob.dirx or ob.diry) {
        game[name].dirx= ob.dirx;
        game[name].diry= ob.diry;
      }
      game[name].xtile= ob.xtile;
      game[name].ytile= ob.ytile;
      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;
    }

    首先我們傳遞了一個(gè)obj對(duì)象給函數(shù)。如果開槍的是英雄,這個(gè)obj就是英雄;如果是敵人射擊,obj就是敵人對(duì)象。

    我們通過getTimer()函數(shù)把當(dāng)前的時(shí)間保存到lastshot屬性中。

    接著給game.bulletcounter屬性增加1,如果它大于100,我們就讓他回到0。

    現(xiàn)在用bulletcounter產(chǎn)生一個(gè)新的子彈名字,創(chuàng)建一個(gè)子彈對(duì)象,然后把這個(gè)數(shù)字存到該對(duì)象中,把這個(gè)對(duì)象的地址放入game.bullets數(shù)組中。

    下面的if判斷用來檢測角色對(duì)象是否移動(dòng)過(dirx/diry有值),如果是,則把這兩個(gè)方向?qū)傩詡鬟f給新建的子彈對(duì)象。否則子彈會(huì)有默認(rèn)的運(yùn)行方向。

    為了讓子彈從角色處出現(xiàn),我們把角色的xtile和ytile屬性復(fù)制給子彈對(duì)象。

    代碼的最后部分創(chuàng)建了新的子彈剪輯,計(jì)算坐標(biāo),最后顯示到該位置。有趣的地方在于如何計(jì)算子彈的象素坐標(biāo):

    game[name].x = (ob.x+game[name].dirx*ob.width);

    首先我們得到角色的位置(ob.x),這是角色的中心坐標(biāo)。通常子彈并不是從角色的正中心發(fā)出,我們給他加上了角色的寬度。這個(gè)地方的巧妙之處在于乘了子彈的dirx屬性,dirx取-1,0,1都能適合。當(dāng)dirx為0的時(shí)候,子彈的x坐標(biāo)就是角色的中心,不過這時(shí)候子彈是豎直運(yùn)動(dòng)的,出現(xiàn)在角色的正上方或正下方。


    殺死敵人!

    在detectKeys函數(shù)的結(jié)尾添加一行,調(diào)用一個(gè)新的函數(shù)來移動(dòng)子彈,并且檢查是不是打到了什么東西。

    _root.moveBullets();

    moveBullets函數(shù):

    function moveBullets () {
      for (var i = 0; i<game.bullets.length; ++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.length; ++j) {
          var name = "enemy"+game.currentEnemies[j].id;
          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);
          }
        }
      }
    }

    這個(gè)函數(shù)通過循環(huán)來操作放在bullets數(shù)組中的每一個(gè)子彈對(duì)象。

    使用getMyCorners函數(shù)我們得知子彈下一步是不是打在了障礙物上。如果不是,我們就調(diào)用moveChar函數(shù)移動(dòng)子彈。

    如果子彈確實(shí)擊中了障礙物,我們就要?jiǎng)h除它了。有3樣事情要做:
    刪除子彈mc (使用removeMovieClip)
    刪除子彈對(duì)象 (使用delete函數(shù))
    從子彈數(shù)組中刪除當(dāng)前的子彈

    盡管我們可以只刪除子彈mc而留下子彈對(duì)象,這樣做不會(huì)有大問題。因?yàn)樽訌梞c已經(jīng)不存在,對(duì)它的所有的操作不能被執(zhí)行。但是這樣做會(huì)降低效率,子彈數(shù)組越來越大,而且里面全是垃圾數(shù)據(jù)。

    當(dāng)移動(dòng)了子彈而且子彈沒有撞上障礙物,我們開始檢查它是不是擊中了敵人。循環(huán)currentEnemies數(shù)組,計(jì)算出子彈和敵人的距離。如果他們離得太近,我們就刪除他們——子彈和敵人都消失。

    如果你要永久清楚這個(gè)被殺的敵人(當(dāng)你再一次進(jìn)入這個(gè)房間的時(shí)候,它不會(huì)再出現(xiàn)),在這里再添加一行:
    myEnemies[game.currentMap][obenemy.id]=0;

    你也可以讓射擊變得更加豐富多彩:

    +限制子彈的總數(shù)。你可以設(shè)置一個(gè)變量,每次創(chuàng)建一個(gè)子彈,變量就減1,只有這個(gè)變量值大于0才能創(chuàng)建對(duì)象。
    +限制舞臺(tái)上只有1顆子彈。當(dāng)子彈數(shù)組長度>0時(shí)不創(chuàng)建子彈。
    +讓敵人也能發(fā)彈。讓敵人射擊也很容易做到,方法和他們改變移動(dòng)方向一樣。
    +創(chuàng)建不同的武器。你可以聲明多種子彈模板,規(guī)定不同的傷害值,你可以得到更強(qiáng)的武器,更快的殺死敵人。

    玩的開心 :-)

    下載上例的源文件

    在側(cè)視圖中射擊:

    下載本例的源文件

    拾取物品

    本章我們將會(huì)讓英雄從地上拾取各種物品,例如:水晶、金幣、死的蜘蛛、生命藥水、彈藥……

    物品可以有很多種,有的用來增加生命值,有的用來增加彈藥。這個(gè)例子里面的物品只有一個(gè)功能,增加你的point值(顯示在下面的那個(gè)東西)。創(chuàng)建其他種類的物品就由你自己來完成了。

    我們從“芝麻開門”這一章的影片開始,這樣我們可以去掉許多復(fù)雜的代碼,或許這樣更容易看明白。

    首先要畫一個(gè)物品的影片剪輯。把各種物品的圖片都放在一個(gè)剪輯的不同幀。和英雄、敵人一樣,這個(gè)剪輯的注冊(cè)點(diǎn)也在中心。在第一幀添加stop()動(dòng)作。導(dǎo)出as鏈接,ID是items。和梯子類似,把物品單獨(dú)做成一個(gè)剪輯,放置物品到舞臺(tái)時(shí),不用重新繪制背景方塊。

    你撿了多少?

    為了顯示撿起了多少物品,在舞臺(tái)上添加一個(gè)動(dòng)態(tài)文本。把它放到區(qū)塊的外面,然后把它的“變量(variale)”設(shè)成points:

    變量point用來記錄拾取的物品數(shù)。我們可以先把它添加到game對(duì)象中。改一下定義game對(duì)象的語句,添加一個(gè)points屬性,值為0(大部分都是從0開始的):

    game = {tileW:30, tileH:30, currentMap:1, points:0};


    放置物品

    和其他東西一樣,首先我們要聲明一個(gè)物品(items)對(duì)象,然后創(chuàng)建數(shù)組,用來放置位置信息。

    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數(shù)組的結(jié)構(gòu)和enemies數(shù)組一樣(見《愚蠢的敵人》)。它包含了每個(gè)地圖的物品數(shù)組,我們沒有用到map0,所以第一個(gè)數(shù)組為空。在map1中我們?cè)O(shè)置了3個(gè)物品:[1,1,1],[1,1,2],[2,1,3]。每個(gè)物品都有3個(gè)數(shù)字,第一個(gè)是物品類型(這里的1或2),也就是物品影片剪輯顯示的幀數(shù)。第二、第三個(gè)數(shù)是物品的坐標(biāo),我們已經(jīng)很多次用這種形式了。以[2,1,3]為例,它是第二種類型的物品,坐標(biāo)是x=1,y=3。

    最后部分的代碼聲明了兩種物品類型。目前他們只有一個(gè)point屬性,用來表示玩家得到后增加的point值。Item1只有1點(diǎn),Item2卻有10點(diǎn)。

    現(xiàn)在修改buildMap函數(shù),增加一些創(chuàng)建物品的動(dòng)作,把下面的代碼放在創(chuàng)建英雄部分的前面:

    game.items = myItems[game.currentMap];
    for (var i = 0; i<game.items.length; ++i) {
     var name = "item"+game.items[i][2]+"_"+game.items[i][1];
     game[name]= new game["Item"+game.items[i][0]];
     game[name].position= i;
     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.points;

    首先我們復(fù)制當(dāng)前地圖用到的物品到game.items對(duì)象中,然后循環(huán)game.items對(duì)象(數(shù)組)。

    從這行開始:

    var name = "item"+game.items[i][2]+"_"+game.items[i][1];

    我們給新的物品命名。名字由物品的坐標(biāo)決定,比如一個(gè)物品所在的區(qū)塊坐標(biāo)是x=1,y=3,那它的名稱是item3_1。

    創(chuàng)建了物品對(duì)象以后,我們給他添加了一個(gè)position屬性。這個(gè)屬性就是物品在game.items數(shù)組中的索引值。通過這個(gè)屬性可以很方便地在game.items數(shù)組中找到當(dāng)前的物品,這樣就可以在撿到物品后在數(shù)組中刪除掉它。

    放置物品到正確的位置之后,下一步就是讓他顯示正確的幀。

    最后,我們更新一下points變量,在場景的相應(yīng)地方顯示出來。游戲開始的時(shí)候點(diǎn)數(shù)可能只有0,但是改變地圖的時(shí)候,可能英雄已經(jīng)撿到了幾個(gè)物品。

    拾取

    我們已經(jīng)有了英雄、物品,下一步要做的就是讓英雄碰到物品的時(shí)候撿起它。把這段代碼添加到moveChar函數(shù)的末尾:

    var itemname=game["item"+ob.ytile+"_"+ob.xtile];
    if (itemname and ob == _root.char) {
     game.points=game.points+itemname.points;
     _root.points=game.points;
     removeMovieClip(itemname.clip);
     game.items[itemname.position]=0;
     delete game["item"+ob.ytile+"_"+ob.xtile];
    }

    變量itemname的值由英雄當(dāng)前的位置決定。比如英雄的坐標(biāo)是x=4,y=9,那么itemname的值就是"item9_4"。如果這個(gè)item9_4確實(shí)是存在的,itemname就是物品對(duì)象;如果不巧不存在這樣的item9_4對(duì)象,itemname就是undefined(未定義值)。

    如果英雄得到了物品,我們首先把物品的points值加到game.points變量,然后更新_root.points以顯示到文本框中。

    現(xiàn)在要做的就是把物品從舞臺(tái)上刪除。每一個(gè)物品在三個(gè)地方都要?jiǎng)h除:物品剪輯、物品數(shù)組中相應(yīng)的物品,以及物品對(duì)象本身。目前我們不必在數(shù)組中刪除掉他,僅僅將物品用0替換就行了。另外不要忘了,物品數(shù)組僅僅是myItems數(shù)組的一部分,如果我們離開地圖然后又返回,原先已經(jīng)拾取的物品又會(huì)出現(xiàn)。為了阻止這種現(xiàn)象,我們要更新changeMap函數(shù)。

    添加下面的代碼到changeMap函數(shù)的開始:

    var tempitems=[];
    for (var i = 0; i<game.items.length; ++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]=tempitems;

    這里我們用到了一個(gè)臨時(shí)的數(shù)組:tempitem,用來復(fù)制未被拾取的物品,即myItems數(shù)組中的非零元素。刪除game[name]對(duì)象是我們上面提到的三個(gè)刪除任務(wù)中的一個(gè)。

    點(diǎn)擊這里下載本例的源文件

    這里我做了側(cè)視圖例子:

    點(diǎn)擊這里下載源文件

    浮動(dòng)區(qū)塊

    首先來講一個(gè)小故事,關(guān)于浮動(dòng)的方塊的故事。或許你已經(jīng)聽說過了“移動(dòng)平臺(tái)”這個(gè)名字,不要迷惑,他們是一樣的東西,一樣有趣。

    很久很久以前,在區(qū)塊游戲的世界里,住者一個(gè)年輕的方塊。他是一個(gè)快樂的方塊。有一天,他遇到了一個(gè)英雄。英雄問他:“年輕人,為什么你不會(huì)浮動(dòng)呢?”

    “我不知道怎么移動(dòng)……” 小方塊說道。

    “那真遺憾,”英雄說道,“我想要站在你上面,去以前夠不到的地方。”

    那一天之后,小方塊就不再像以前一樣開心了。

    其實(shí)我們可以幫助他浮動(dòng)的,看:

    在我們開工寫代碼之前,我們還是先來考慮一下規(guī)則。

    我們應(yīng)該做什么?如何做?

    * 浮動(dòng)方塊應(yīng)該是云(cloud)類型的方塊 (云)
    * 方塊可以橫向或縱向移動(dòng)
    * 英雄可以從上方落到上面
    * 當(dāng)英雄站到上面以后,他會(huì)隨著方塊運(yùn)動(dòng)
    * 方塊上的英雄不能穿過障礙物


    踏上浮動(dòng)的方塊

    英雄怎么站到運(yùn)動(dòng)方塊上?第一個(gè)也是最簡單的一個(gè)方法就是跳上去。

    在上圖中,英雄正處于跌落的過程中,下一步他將會(huì)碰到運(yùn)動(dòng)方塊。我們將會(huì)把他放置到方塊上去。注意,英雄必須是在運(yùn)動(dòng)方塊的上方,而且必須是往下運(yùn)動(dòng),否則他就無法站到上面。

    但是,這并不是英雄上去的唯一途徑。下圖中,英雄站在障礙物上,沒有移動(dòng)。

    但是方塊是移動(dòng)的,而且接下來就會(huì)接觸到英雄,若不做任何處理,英雄就會(huì)“陷”到方塊里面。所以,我們要做的就是讓方塊帶者英雄一起往上移動(dòng)。


    離開運(yùn)動(dòng)的方塊

    一旦我們的英雄可以站到方塊上,我們還要保證他能夠以某種方式離開。首先,他可以跳開。它可以走到方塊的邊緣。下圖畫出了許多可能的情形:

    黨英雄站在豎直移動(dòng)的方塊上,而且上方有個(gè)障礙物時(shí),他應(yīng)該掉下來,否則就會(huì)被壓扁。黨方塊水平移動(dòng),黨英雄碰到障礙物,他應(yīng)該被貼著障礙物放置;如果方塊移開的話,英雄就會(huì)掉下去。

    上圖中,英雄隨著方塊往下移動(dòng),黨他碰到障礙物的時(shí)候,他不再隨著方塊移動(dòng),而是停在障礙物上。而方塊則繼續(xù)往下移動(dòng)。


    準(zhǔn)備工作

    畫出移動(dòng)方塊的影片剪輯。你可以做很多種類的移動(dòng)方塊,把他們放在 movingtiles 影片剪輯的不同幀,然后將剪輯連接為movingtiles

    定義 movingtiles 對(duì)象:

    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;

    我們有兩種類型的可移動(dòng)方塊: MovingTilep1可以豎直移動(dòng)(它的diry屬性為非0數(shù)值),MovingTilep2可以水平移動(dòng)(它的dirx值非0)。speed屬性,你一定猜到了,代表方塊一次移動(dòng)的像素距離。

    miny/maxy/minx/maxx 屬性設(shè)置了方塊運(yùn)動(dòng)的邊界。我們當(dāng)然可以把邊界坐標(biāo)設(shè)置成絕對(duì)的數(shù)值范圍,但是如果需要把方塊放到別的地方的話,就要改動(dòng)邊界的范圍了。而在這里,我們用的是相對(duì)于方塊起始位置的數(shù)值。這樣我們可以把方塊放在任意的位置,而不用修改邊界范圍。需要注意的是,移動(dòng)的方塊不會(huì)檢測碰撞,所以你應(yīng)該確保他們運(yùn)動(dòng)時(shí)不撞到障礙物。或者你也可以允許他們穿過障礙物。你在做游戲,你就是上帝。

    來看一個(gè)例子。方塊起始的位置是(x=2,y=5),豎直運(yùn)動(dòng),miny=-1,maxy=4。它會(huì)怎么運(yùn)動(dòng)呢?起始位置-miny=5+(-1)=4,所以最小可以到達(dá)的位置是(x=2,y=4),最大可以到達(dá)的位置是(x=2,y=9)。

    方塊起始位置的數(shù)組和敵人起始位置的數(shù)組類似:

    //浮動(dòng)方塊數(shù)組 [方塊類型, x位置, y位置]
    myMovingTiles = [
    [0],
    [[1, 4, 2]],
    [[2, 4, 4]]
    ];

    在地圖1中,我們定義了一個(gè)浮動(dòng)方塊,它的類型編號(hào)是1(從MovingTile1模版創(chuàng)建),起始位置是(x=4,y=2)。地圖2中同樣有1個(gè)浮動(dòng)方塊。你也可以在一個(gè)地圖中放置多個(gè)浮動(dòng)方塊。

    接下來是要在buildMap函數(shù)中添加浮動(dòng)方塊的生成代碼。在創(chuàng)建敵人部分后面加入:

    game.movingtiles = myMovingTiles[game.currentMap];
    for (var i = 0; i<game.movingtiles.length; ++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].xtile;
     game[name].maxx=game[name].maxx+game[name].xtile;
     game[name].miny=game[name].miny+game[name].ytile;
     game[name].maxy=game[name].maxy+game[name].ytile;
    }

    首先還是取得當(dāng)前地圖的浮動(dòng)方塊數(shù)組(第一句)。變量game.movingtiles保存了當(dāng)前地圖的浮動(dòng)方塊數(shù)據(jù),包括數(shù)目和方位。然后創(chuàng)建新的對(duì)象,放置mc到舞臺(tái)正確的位置,跳轉(zhuǎn)到相應(yīng)的幀。類型1的方塊是第一幀,類型2的方塊則是第二幀。代碼的最后一部分是計(jì)算浮動(dòng)方塊的運(yùn)動(dòng)范圍。雖然變量名稱還是miny/maxy/minx/maxx,但是這些屬性變成了確定的數(shù)字,和原先的含義(相對(duì)起始位置的坐標(biāo))已經(jīng)不同了(或者說他們是絕對(duì)的坐標(biāo),使用的時(shí)候不需要再參考起始位置)。

    在moveChar函數(shù)中,需要添加一行代碼,用來保存y坐標(biāo):

    ob.lasty=ob.y;

    在moveChar函數(shù)中還需要改寫移動(dòng)功能的代碼:

    if (diry == 1) {
      if (ob.downleft and ob.downright and !checkMovingTiles(speed*diry)) {
        ob.y += speed*diry;
      } else {
        ob.jump = false;
        if(ob.onMovingTile){
          ob.y=ob.onMovingTile.y-ob.onMovingTile.height-ob.height;
        }else{
          ob.y = (ob.ytile+1)*game.tileH-ob.height;
        }
      }
    }

    我們使用了checkMovingTiles函數(shù),如果英雄將會(huì)降落在浮動(dòng)方塊上,這個(gè)函數(shù)會(huì)返回true。如果英雄馬上就要落在浮動(dòng)方塊上,我們?cè)O(shè)置他的y坐標(biāo)為剛好在方塊上面。


    英雄在浮動(dòng)方塊上面嗎?

    或許你已經(jīng)從moveChar函數(shù)中增加的部分看出來了,沒錯(cuò),我們需要?jiǎng)?chuàng)建一個(gè)新函數(shù),用來檢測角色是不是站在浮動(dòng)方塊上面。checkMovingTiles函數(shù)不僅僅返回答案(是或者不是),而且還把英雄所在浮動(dòng)方塊的名字保存到char對(duì)象中。

    function checkMovingTiles (y) {
      if(char.diry<>-1){
        var heroymax=char.y+char.height+y;
        var heroxmax=char.x+char.width;
        var heroxmin=char.x-char.width;
        foundit=false;
        for (var i = 0; i<game.movingtiles.length; i++) {
          var ob=game["movingtile"+i];
          var tileymax=ob.y+ob.height;
          var tileymin=ob.y-ob.height;
          var tilexmax=ob.x+ob.width;
          var tilexmin=ob.x-ob.width;
          if(char.lasty+char.height<=tileymin){
            if (heroymax<=tileymax and heroymax>=tileymin) {
              if (heroxmax>tilexmin and heroxmax<tilexmax) {
                char.onMovingTile=ob;
                foundit=true;
                break;
              } else if (heroxmin>tilexmin and heroxmin<tilexmax) {
                char.onMovingTile=ob;
                foundit=true;
                break;
            }
          }
        }
      }
      return(foundit);
      }
    }

    讓我們看看發(fā)生了什么。如果角色不是往上運(yùn)動(dòng)(diry值不是-1),我們就計(jì)算出角色的邊界。然后遍歷浮動(dòng)方塊數(shù)組,看角色是否和當(dāng)前的浮動(dòng)方塊接觸:

    帶有“lasty”屬性的if語句是用來確定角色的上一個(gè)位置是在浮動(dòng)方塊的上方,下面的兩個(gè)if語句則判斷角色是不是和方塊有接觸。如果有碰撞,那就意味著我們找到了正確的移動(dòng)方塊,于是onMovingTile屬性就會(huì)紀(jì)錄下找到的方塊對(duì)象。

    讓他也動(dòng)起來

    請(qǐng)準(zhǔn)備好看史上最丑陋最冗長最小氣的函數(shù)!它很長,因?yàn)樗芏鄸|西。首先,它移動(dòng)所有的浮動(dòng)方塊,然后檢查這些方塊是不是需要反過來運(yùn)動(dòng)了,這些還不夠,它還要處理英雄在浮動(dòng)方塊上面的動(dòng)作,檢查是不是應(yīng)該掉下來。

    function moveTiles () {
      for (var i = 0; i<game.movingtiles.length; 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.diry;
        }
        if (ob.minx>ob.leftX or ob.maxx<ob.rightX) {
          ob.dirx=-ob.dirx;
        }
        ob.x = ob.x + ob.speed*ob.dirx;
        ob.y = ob.y + ob.speed*ob.diry;
        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.height;
          } else {
            char.y = char.ytile*game.tileH+char.height;
            char.jumpspeed = 0;
            char.jump = true;
            char.onMovingTile=false;
          }
        }
        if (char.onMovingTile.diry == 1) {
          if (char.downleft and char.downright) {
            char.y=char.onMovingTile.y-char.onMovingTile.height-char.height;
          } else {
            char.onMovingTile=false;
            char.y = (char.ytile+1)*game.tileH-char.height;
          }
        }
        getMyCorners (char.x+char.onMovingTile.speed*char.onMovingTile.dirx, char.y, char);
        if (char.onMovingTile.dirx == -1) {
          if (char.downleft and char.upleft) {
            char.x += char.onMovingTile.speed*char.onMovingTile.dirx;
          } else {
            char.x = char.xtile*game.tileW+char.width;
            fall (char);
          }
        }
        if (char.onMovingTile.dirx == 1) {
          if (char.upright and char.downright) {
            char.x += char.onMovingTile.speed*char.onMovingTile.dirx;
          } else {
            fall (char);
            char.x = (char.xtile+1)*game.tileW-char.width;
          }
        }
        updateChar (char);
      }
    }

    和上面說的一樣,第一部分的代碼用來移動(dòng)浮動(dòng)方塊。遍歷所有的浮動(dòng)方塊,把它們下一步的坐標(biāo)和miny/maxy(minx/maxx)屬性對(duì)照,看是否需要反過來運(yùn)動(dòng)。

    這幾行代碼:
    if(ob.diry==-1){
      checkMovingTiles(0);
    }

    用來檢查是否要載上英雄,注意滿足的條件是英雄站在障礙物的邊緣上不動(dòng),而且方塊是朝上運(yùn)動(dòng)(diry是-1)。

    在這行以下的函數(shù)部分:
    if(char.onMovingTile){
    用來處理英雄在浮動(dòng)方塊上的動(dòng)作。當(dāng)onMovingTile值不是false,意味著英雄站在某個(gè)浮動(dòng)的方塊上面,而且onMovingTile屬性值就是所在的浮動(dòng)方塊對(duì)象。這里的代碼和moveChar函數(shù)比較相似。我們利用getMyCorners函數(shù)計(jì)算英雄下一步的位置,如果沒有碰到任何障礙物,就讓英雄和方塊一起運(yùn)動(dòng);反之則不能把英雄移動(dòng)過去。

    使用函數(shù)

    在detectKeys函數(shù)的開頭加入這行語句,用來移動(dòng)所有的浮動(dòng)方塊(即使英雄沒有踩在它們上面):

    moveTiles();

    另外,當(dāng)英雄起跳的時(shí)候,我們還要讓他的onMovingTile屬性變回false:

    ob.onMovingTile=false;

    下載源文件

    卷屏

    在我們開始這部分之前,有件事得先解釋清楚。Flash很慢。Flash非常慢。卷屏意味著以每秒20-30次的速度移動(dòng)屏幕上數(shù)以百計(jì)的方塊。而太多移動(dòng)的方塊意味著Flash不能及時(shí)地完成重繪,于是速度下降。你的游戲?qū)⒙孟駰l睡著的蝸牛在爬。
    “什么?”你可能會(huì)問,“難道不用卷屏嗎?怎么會(huì)慢得像睡著的蝸牛在爬呢?”
    你可以用移動(dòng)的方塊,但是你得小心,別讓卷屏的區(qū)域太大,而且移動(dòng)的方塊別太多。多大算“太大”,多少又算“太多”,這就得你自己體會(huì)了。記住,F(xiàn)lash游戲大多數(shù)時(shí)候是在瀏覽器上玩的,游戲者可能同時(shí)打開了多個(gè)窗口,后臺(tái)可能有多個(gè)程序在同時(shí)運(yùn)行,而且不是所有游戲者都有那么好的電腦配置。可以用低配置的電腦來測試你的游戲,如果覺得速度慢,就把它改小點(diǎn)。
    來看看我們接下來要做的:

    原理

    左邊的圖中是非卷屏游戲。主角向右移動(dòng),而其它的東西原地不動(dòng)。現(xiàn)在看右邊的圖,這是個(gè)卷屏游戲。主角仍然是要向右移動(dòng),但是我們實(shí)際上并沒有移動(dòng)主角,為了讓他看起來是在向仍然右移動(dòng),我們將其它東西向左移了。

    所以,原理很簡單:
    當(dāng)需要變換主角的位置時(shí),向反方向移動(dòng)其它部分就行了。但是,由于我們習(xí)慣于將主角與其它背景區(qū)塊一起放入MC中,主角會(huì)跟他們一起往反方向移動(dòng)。為了固定主角的位置,我們?nèi)匀粚⑺形矬w向反方向移,同時(shí),將主角向正確的方向移動(dòng)相同的距離。

    來看個(gè)卷屏游戲的例子。假設(shè)主角要向右移10px。我們先將所有方塊(包括主角)向左移10px,并將主角向右移10px。最后看起來主角并沒有動(dòng),而其它方塊向左移了。

    卷屏的最簡單的方法是將所有方塊都放到屏幕上,但是只顯示其中一小部分,然后整個(gè)一起移動(dòng)。這可能會(huì)讓你的游戲速度非常慢,因?yàn)闆]顯示出來的那上千個(gè)方塊也在移動(dòng)。另一個(gè)辦法是將移出可見區(qū)域的方塊刪除,并在另一頭添加新的方塊。這會(huì)好一點(diǎn)兒,但是Flash花費(fèi)在刪除/復(fù)制MC上的時(shí)間又太多了。
    最后一個(gè)辦法是:只把可見部分的方塊放到舞臺(tái)上,當(dāng)它們移出可見區(qū)域時(shí),我們將相同的方塊移動(dòng)到另一端,重命名,并再次使用相同的MC。這被稱作“gotoAndStop”卷屏引擎:

    如圖所示,當(dāng)方塊從右邊消失,我們將它移動(dòng)到左邊。同時(shí)我們要重命名MC,因?yàn)樗械腗C都是以“t_3_4”(表示它位于y=3,x=4)這樣的名字命名的。而在新位置的方塊可能要顯示不同的幀(圖片)。這就是為什么我們要將它發(fā)送到含有正確圖片的幀,也是這種方法叫做“gotoAndStop”的原因。

    準(zhǔn)備卷屏

    在大多數(shù)卷屏游戲中,主角的位置是始終在屏幕中央的。

    你可以看見,主角左邊的方塊數(shù)和右邊的方塊數(shù)是相等的。這就是說,總的列數(shù)是3、5、7、9、11……而絕不會(huì)是2、4、6、8。行數(shù)也是一樣。
    來聲明一個(gè)游戲?qū)ο螅?br />game = {tileW:30, tileH:30, currentMap:1, visx:7, visy:5, centerx:120, centery:90};
    屬性 visx 代表可見的列數(shù),visy 代表可見的行數(shù)。我們還需要屬性centerx/centery來標(biāo)記影片的中心點(diǎn)。

    當(dāng)我們移動(dòng)方塊時(shí),可能需要顯示一些在地圖數(shù)組中沒有聲明的方塊。例如,當(dāng)主角高高興興地走到地圖數(shù)組中已聲明的最左邊的方塊上以后,就需要顯示更左邊的方塊。對(duì)于這類方塊,我們會(huì)建立新的不含圖片的方塊類型(這樣對(duì)Flash來說負(fù)擔(dān)比較小)。在方塊MC的第20幀創(chuàng)建關(guān)鍵幀。以下是聲明這類方塊的代碼:
    game.Tile4 = function () { };
    game.Tile4.prototype.walkable = false;
    game.Tile4.prototype.frame = 20;

    你可能還想部分地遮住一些方塊,這就需要新建含有名為“frame”的關(guān)聯(lián)的新MC,該MC的中間挖空,只顯示經(jīng)過挖空處的方塊。

    建立卷屏的世界

    讓我們從 buildMap 函數(shù)開始。
    function buildMap (map) {
    _root.attachMovie("empty", "tiles", 1);
    game.halfvisx=int(game.visx/2);
    game.halfvisy=int(game.visy/2);

    我們將為game對(duì)象計(jì)算兩個(gè)新的屬性。halfvisx和halfvisy被分別用來存放主角與可見區(qū)域邊緣之間的列數(shù)和行數(shù)。當(dāng)總列數(shù)visx=5時(shí),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;

    我們得根據(jù)以下兩個(gè)變量的值來決定包含主角和所有方塊的MC的位置:游戲的中心點(diǎn)和主角的位置。游戲的中心點(diǎn)在game對(duì)象中由centerx/centery聲明;主角的位置在char對(duì)象中由xtile/ytile聲明。當(dāng)主角被放在x坐標(biāo)的(char.xtile*game.tileW)+game.tileW/2處時(shí),方塊MC必須被放在相反的坐標(biāo)上,所以我們要用centerx減去這個(gè)值。

    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);
       }
    }

    這個(gè)循環(huán)創(chuàng)建了所有的可見方塊對(duì)象,并且復(fù)制該方塊對(duì)象相應(yīng)的MC到舞臺(tái)上。如你所見,這個(gè)循環(huán)并不是從0開始,而是從ytile-halfvisy開始。同理它也不是在map數(shù)組的末尾結(jié)束的,而是在ytile+halfvisy+1結(jié)束。然后,if條件句檢查將被創(chuàng)建的方塊是否已經(jīng)在map數(shù)組中。如果不在,我們就使用tile4模版中的空白方塊。
    為了使卷屏的過程平滑,我們得在舞臺(tái)右邊和底部分別增加一行和一列方塊。這些方塊可以保證,哪怕我們把半個(gè)方塊移到舞臺(tái)的另一邊,也不會(huì)出現(xiàn)任何空白區(qū)域。
    _root.attachMovie("frame", "frame", 100);
    最后一行復(fù)制幀來覆蓋影片中你不想顯示的區(qū)域。

    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的創(chuàng)建方法與前面一樣。僅有的不同是增加了兩個(gè)新的屬性:xstep和ystep。我們將在后面用它們來檢查當(dāng)前是否是移動(dòng)方塊行或列到另一端的合適時(shí)間。

    卷屏!卷屏!

    既然卷屏的世界已經(jīng)建好了,下面就該讓它真正“卷”動(dòng)起來了。在為主角計(jì)算出ob.xtile/ob.ytile的值以后,在moveChar函數(shù)的末尾加上如下代碼來進(jìn)行卷屏:
    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);

    首先,我們根據(jù)中心點(diǎn)的位置和主角的坐標(biāo)值將包含主角和所有方塊的game.clip放到相應(yīng)的位置。然后我們寫了4段相似的代碼,每一段定義一個(gè)方向。當(dāng)主角的移動(dòng)距離大于方塊的大小時(shí),循環(huán)會(huì)調(diào)用含有相應(yīng)變量的changeTile函數(shù)。循環(huán)結(jié)束,方塊被移動(dòng)/重命名/修改以后,就該更新屬性ystep/xstep的值了。

    現(xiàn)在,讓我們來編寫changeTile函數(shù):

    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);
     }
    }

    這樣,我們會(huì)分別得到方塊的2個(gè)舊的和2個(gè)新的坐標(biāo)值。為了檢查方塊是否還在map數(shù)組之內(nèi),我們還增加了map參數(shù)。 “nameold”是這些方塊的舊名字,而“namenew”是它們的新名字。在我們用新名字創(chuàng)建了新的tile對(duì)象以后,語句:
    game.clip[nameold]._name = namenew;
    將方塊MC重命名。新的MC是空的,所以我們不必將它放到新的_x/_y,讓它留在原處就行了。

    下載源文件

    posted on 2006-05-18 18:10 blog搬家了--[www.ialway.com/blog] 閱讀(1795) 評(píng)論(0)  編輯  收藏 所屬分類: AS2.0
    主站蜘蛛池模板: 日本免费久久久久久久网站| 亚洲精品无码AV人在线播放| 99re在线这里只有精品免费| 污视频网站在线免费看| 在线观看日本亚洲一区| 97亚洲熟妇自偷自拍另类图片| 亚洲国产成人影院播放| 日韩成人在线免费视频| 日本XXX黄区免费看| 在线免费观看国产| 国产精品免费AV片在线观看| 毛片基地看看成人免费| 农村寡妇一级毛片免费看视频 | 国产麻豆成人传媒免费观看| 国产精品自拍亚洲| 亚洲色一区二区三区四区| 亚洲国产成人久久| 亚洲国产精品日韩在线观看| 337p日本欧洲亚洲大胆色噜噜| 国产亚洲精品福利在线无卡一| 国产一区二区三区在线免费观看| 大学生高清一级毛片免费| 青娱乐免费在线视频| 国产成人精品免费视频动漫 | 亚洲国产专区一区| 国产成人无码a区在线观看视频免费| 老司机在线免费视频| 亚洲国产精品免费观看| 67194熟妇在线永久免费观看| 亚洲黄色免费电影| 四虎在线成人免费网站| 99视频全部免费精品全部四虎| 18女人毛片水真多免费| 免费成人激情视频| 全免费毛片在线播放| 免费高清av一区二区三区| 日韩免费高清一级毛片在线| 国产成人无码免费视频97 | 亚洲AV无码XXX麻豆艾秋| 久久综合亚洲色hezyo| 免费大片黄在线观看|