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

          常用鏈接

          留言簿(6)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          朋友的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=false; Tile1.prototype.frame=2;
          那么我們每次在地圖數組中遇到1的時候,就會創建一個類似的對象(Tile1),我們還定義了這個方塊不能被通行(walkable=false),而且在那個點上的方塊mc顯示第二幀。

          關于地圖的東西

          你或許會考慮,為什么我要選擇這種方式呢?我可以告訴你這是最好的方法。我可以說這個地圖格式可以最快地創建地圖,可以產生最小的文件。我只能說在和區塊游戲大了多年交道之后,我發現這個格式最適合我的需要。但是我們也可以看看其他可能的方法,保存地圖數據的方法。

          JailBitch的方法

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


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

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

          當你只有很少的方塊類型,而且方塊不會變化很多時,這是一個很好的很簡單的方式。

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

          沙漠中的樹

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

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

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

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

          S,M,XXXL

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

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

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

          game.tileW
          game.tileH

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

          下面的幾行代碼在game對象里面構造了Tile0對象,然后用prototype構造了他的2個屬性。
          game.Tile0= function () {};
          game.Tile0.prototype.walkable=true;
          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.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作為一個函數,并且參數是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 < mapHeight; ++i) {
          for (var j = 0; j < mapWidth; ++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=true;
          game.Tile0.prototype.frame=1;

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

          讓我們聲明一個通用的區塊類:

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

          這里我們用了一個假設。假設每個區塊都是不可通行的,而且都顯示在第20幀。當然了,實際的區塊不全是不可通行的,否則我們不能移動。而且他們也不會都顯示在第20幀。問題看起來很嚴重,實際上不然。我們只是定義了這兩個通用屬性而已,我們會讓他完美工作的。

          現在我們創建新的區塊類型:

          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__,我們不需要重復寫同樣的屬性了。我們的區塊從TileClass類中獲得了所有必要的材料。當我們這樣創建新的區塊類型后:

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

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

          game.Tile0 = function () {};
          game.Tile0.prototype.__proto__ = game.TileClass.prototype;
          game.Tile0.prototype.walkable=true;
          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;
          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.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();
              }
          }

          首先我們定義了兩個變量: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.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函數接收了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的時候,英雄就可以從上面走過(這個東西叫做“邏輯”:)。

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

          這是完美的墻的碰撞: 

          英雄貼著墻站著,而且下一步他就會進到墻里面。我們不會讓它發生的。

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

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

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

           

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

          “這么難?!”,你也許會喊,“不太可能辦到吧?”不用擔心,實際上很簡單的~


          檢查四個角

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

          為了實現這個功能,讓我們寫個函數: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;
          }

          這個函數接收了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*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函數通過鍵盤檢測函數傳遞過來的值得到對象和方向。
          這一行:
          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*diry;
            } else {
             ob.y = ob.ytile*game.tileH+ob.height;
            }
           }

          這塊代碼是用來檢測向上的移動的。當上箭頭鍵被按下去后,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 = 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);

          你可能已經猜到了,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.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]);
          }

          這個函數很好理解,我們從門對象(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.height;

          因為我們的視圖是側視的,英雄剛開始的位置可能是“漂”在空中的,我們應該讓他站到地面上來。

          changeMap函數和getMyCorners函數不需要任何變動。

          騰空的感覺

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

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

          注意看我們怎么避免跳躍過程中空格鍵觸發新的跳躍(聽起來很拗口,哈哈),實際上就是當處于跳躍中時,忽略空格鍵。如果按了空格鍵,而且英雄沒有處于跳躍過程,那就開始跳吧,把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.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函數,因為加入了跳躍過程,跳躍時的速度jumpspeed和水平移動的速度speed是不同的:

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

           

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

          如果這一步移動結束后,角色的頂到了障礙物,就要把jumpspeed值改成0:
          ob.y = ob.ytile * game.tileH + ob.height;
          ob.jumpspeed = 0;

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

          在左右移動后,我們還要看看角色是不是還站在平臺上,如果不是,就應該落下來:
          ob.x += speed * dirx;
          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 = true;
            }
           }
          }

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

          騰云駕霧

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

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

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

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

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

          它的walkable屬性是true,意味著英雄可以穿行過去。
          為了讓英雄能站到上面,我們需要創建新的函數。

          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。

          現在我們需要在兩個地方調用這個函數:
          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))
          {
           ...

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

          enjoy :)


          梯子

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

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

          在圖中,有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 = 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";

          這兩個不同的方塊(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 = false;


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


          為了檢測往下的攀爬動作,我們需要英雄腳底下方塊的ladder屬性。
          和往上爬不同,我們還要考慮到英雄接下來(移動結束)所在的方塊的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函數中,我們首先設置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.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;
          }

          這段代碼什么意思?首先我們得到當前地圖的敵人數組,轉存為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.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);
            }
          }

          }

           

          正如你所看到的,我們又要遍歷數組了。我們把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 或 1
          random(2)*2 : 0 或 2
          random(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.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;
              }
            }
          }

          這回我們先檢查當前的方向。例如,如果原先是垂直移動(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.length; ++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].id;

          我通過currentEnemies數組來放置舞臺上活動的敵人。一旦敵人被殺死,我們就會把它從currentEnemies數組中刪除。新的屬性“id”幫助我們找到敵人在enemies數組中的位置。

          在detectKeys函數檢查完按鍵后添加代碼:

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

          如果SHIFT鍵被按下,而且時間間隔足夠長,調用shoot函數。

          在moveChar函數的開頭添加兩行代碼,用來保存當前對象的方向:

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

          我們可以用這個來指定子彈運行的方向。


          開槍

          為了成功的讓子彈按照我們預定的想法飛行,我們要寫一個新的函數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;
          }

          首先我們傳遞了一個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.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);
                }
              }
            }
          }

          這個函數通過循環來操作放在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.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;

          首先我們復制當前地圖用到的物品到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.points;
           _root.points=game.points;
           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.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;

          這里我們用到了一個臨時的數組: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.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;
          }

          首先還是取得當前地圖的浮動方塊數組(第一句)。變量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*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函數,如果英雄將會降落在浮動方塊上,這個函數會返回true。如果英雄馬上就要落在浮動方塊上,我們設置他的y坐標為剛好在方塊上面。


          英雄在浮動方塊上面嗎?

          或許你已經從moveChar函數中增加的部分看出來了,沒錯,我們需要創建一個新函數,用來檢測角色是不是站在浮動方塊上面。checkMovingTiles函數不僅僅返回答案(是或者不是),而且還把英雄所在浮動方塊的名字保存到char對象中。

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

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

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

          讓他也動起來

          請準備好看史上最丑陋最冗長最小氣的函數!它很長,因為它要很多東西。首先,它移動所有的浮動方塊,然后檢查這些方塊是不是需要反過來運動了,這些還不夠,它還要處理英雄在浮動方塊上面的動作,檢查是不是應該掉下來。

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

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

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

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

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

          使用函數

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

          moveTiles();

          另外,當英雄起跳的時候,我們還要讓他的onMovingTile屬性變回false:

          ob.onMovingTile=false;

          下載源文件

          卷屏

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

          原理

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

          所以,原理很簡單:
          當需要變換主角的位置時,向反方向移動其它部分就行了。但是,由于我們習慣于將主角與其它背景區塊一起放入MC中,主角會跟他們一起往反方向移動。為了固定主角的位置,我們仍然將所有物體向反方向移,同時,將主角向正確的方向移動相同的距離。

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

          卷屏的最簡單的方法是將所有方塊都放到屏幕上,但是只顯示其中一小部分,然后整個一起移動。這可能會讓你的游戲速度非常慢,因為沒顯示出來的那上千個方塊也在移動。另一個辦法是將移出可見區域的方塊刪除,并在另一頭添加新的方塊。這會好一點兒,但是Flash花費在刪除/復制MC上的時間又太多了。
          最后一個辦法是:只把可見部分的方塊放到舞臺上,當它們移出可見區域時,我們將相同的方塊移動到另一端,重命名,并再次使用相同的MC。這被稱作“gotoAndStop”卷屏引擎:

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

          準備卷屏

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

          你可以看見,主角左邊的方塊數和右邊的方塊數是相等的。這就是說,總的列數是3、5、7、9、11……而絕不會是2、4、6、8。行數也是一樣。
          來聲明一個游戲對象:
          game = {tileW:30, tileH:30, currentMap:1, visx:7, visy:5, centerx:120, centery:90};
          屬性 visx 代表可見的列數,visy 代表可見的行數。我們還需要屬性centerx/centery來標記影片的中心點。

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

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

          建立卷屏的世界

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

          我們將為game對象計算兩個新的屬性。halfvisx和halfvisy被分別用來存放主角與可見區域邊緣之間的列數和行數。當總列數visx=5時,halfvisx=2,就是說主角右邊有2列、左邊有2列,中間就是主角所在的那一列。

          game.clip = _root.tiles;
          game.clip._x = game.centerx-(char.xtile*game.tileW)-game.tileW/2;
          game.clip._y = game.centery-(char.ytile*game.tileH)-game.tileH/2;

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

          for (var y = char.ytile-game.halfvisy; y<=char.ytile+game.halfvisy+1; ++y) {
              for (var x = char.xtile-game.halfvisx;
              x<=char.xtile+game.halfvisx+1; ++x) {
                  var name = "t_"+y+"_"+x;
                  if(y>=0 and x>=0 and y<=map.length-1 and x<=map[0].length-1){
                      game[name] = new game["Tile"+map[y][x]]();
                  }else{
                      game[name] = new game.Tile4();
                  }
                  game.clip.attachMovie("tile", name, 1+y*100+x*2);
                  game.clip[name]._x = (x*game.tileW);
                  game.clip[name]._y = (y*game.tileH);
                  game.clip[name].gotoAndStop(game[name].frame);
             }
          }

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

          char.clip = game.clip.char;
          char.x = (char.xtile*game.tileW)+game.tileW/2;
          char.y = (char.ytile*game.tileW)+game.tileW/2;
          char.width = char.clip._width/2;
          char.height = char.clip._height/2;
          char.clip._x = char.x;
          char.clip._y = char.y;
          char.clip.gotoAndStop(char.frame);
          char.xstep=char.x;
          char.ystep=char.y;

          char的創建方法與前面一樣。僅有的不同是增加了兩個新的屬性:xstep和ystep。我們將在后面用它們來檢查當前是否是移動方塊行或列到另一端的合適時間。

          卷屏!卷屏!

          既然卷屏的世界已經建好了,下面就該讓它真正“卷”動起來了。在為主角計算出ob.xtile/ob.ytile的值以后,在moveChar函數的末尾加上如下代碼來進行卷屏:
          game.clip._x = game.centerx-ob.x;
          game.clip._y = game.centery-ob.y;
          if(ob.xstep<ob.x-game.tileW){
              var xtile = Math.floor(ob.xstep/game.tileW) + 1;
              var xnew=xtile+game.halfvisx+1;
              var xold=xtile-game.halfvisx-1;
              for (var i = ob.ytile-game.halfvisy-1; i<=ob.ytile+game.halfvisy+1; ++i) {
                 changeTile (xold, i, xnew, i, _root["myMap"+game.currentMap]);
              }
              ob.xstep=ob.xstep+game.tileW;
          } else if(ob.xstep>ob.x){
              var xtile = Math.floor(ob.xstep/game.tileW);
              var xnew=xtile+game.halfvisx+1;
              var xold=xtile-game.halfvisx-1;
              for (var i = ob.ytile-game.halfvisy-1; i<=ob.ytile+game.halfvisy+1; ++i) {
                  changeTile (xold, i, xnew, i, _root["myMap"+game.currentMap]);
              }
              ob.xstep=ob.xstep-game.tileW;
          }
          if(ob.ystep<ob.y-game.tileH){
              var ytile = Math.floor(ob.ystep/game.tileH)+1;
              var ynew=ytile+game.halfvisy+1;
              var yold=ytile-game.halfvisy-1;
              for (var i = ob.xtile-game.halfvisx-1; i<=ob.xtile+game.halfvisx+1; ++i) {
                  changeTile (i, yold, i, ynew, _root["myMap"+game.currentMap]);
              }
              ob.ystep=ob.ystep+game.tileH;
          } else if(ob.ystep>ob.y){
              var ytile = Math.floor(ob.ystep/game.tileH);
              var yold=ytile+game.halfvisy+1;
              var ynew=ytile-game.halfvisy-1;
              for (var i = ob.xtile-game.halfvisx-1; i<=ob.xtile+game.halfvisx+1; ++i) {
                  changeTile (i, yold, i, ynew, _root["myMap"+game.currentMap]);
              }
              ob.ystep=ob.ystep-game.tileH;
          }
          return (true);

          首先,我們根據中心點的位置和主角的坐標值將包含主角和所有方塊的game.clip放到相應的位置。然后我們寫了4段相似的代碼,每一段定義一個方向。當主角的移動距離大于方塊的大小時,循環會調用含有相應變量的changeTile函數。循環結束,方塊被移動/重命名/修改以后,就該更新屬性ystep/xstep的值了。

          現在,讓我們來編寫changeTile函數:

          function changeTile (xold, yold, xnew, ynew, map) {
           var nameold = "t_"+yold+"_"+xold;
           var namenew = "t_"+ynew+"_"+xnew;
           if(ynew>=0 and xnew>=0 and ynew<=map.length-1 and xnew<=map[0].length-1){
            game[namenew] = new game["Tile"+map[ynew][xnew]]();
            game.clip[nameold]._name = namenew;
            game.clip[namenew].gotoAndStop(game[namenew].frame);
            game.clip[namenew]._x = (xnew*game.tileW);
            game.clip[namenew]._y = (ynew*game.tileH);
           }else{
            game[namenew] = new game.Tile4();
            game.clip[nameold]._name = namenew;
            game.clip[namenew].gotoAndStop(game[namenew].frame);
           }
          }

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

          下載源文件

          posted on 2006-05-18 18:10 blog搬家了--[www.ialway.com/blog] 閱讀(1796) 評論(0)  編輯  收藏 所屬分類: AS2.0
          主站蜘蛛池模板: 宁南县| 城步| 革吉县| 大英县| 黎川县| 商城县| 恩施市| 微山县| 得荣县| 淳化县| 乃东县| 广宁县| 汝阳县| 衡东县| 松滋市| 孟村| 体育| 修文县| 蒙城县| 保定市| 襄城县| 大理市| 汤阴县| 尼玛县| 含山县| 西宁市| 彩票| 青神县| 桐庐县| 慈利县| 乐山市| 三江| 昌江| 喀喇| 墨竹工卡县| 错那县| 张家界市| 石阡县| 常宁市| 北流市| 宜黄县|