使用cocos2d-x製作基於Tile地圖的遊戲:碰撞檢測和收集物品(二)

本教程基於子龍山人翻譯的cocos2d的IPHONE教程,用cocos2d-x for XNA引擎重寫,加上我一些加工製作。教程中大多數文字圖片都是原作者和翻譯作者子龍山人,還有不少是我自己的理解和加工。感謝原作者的教程和子龍山人的翻譯。本教程僅供學習交流之用,切勿進行商業傳播。

子龍山人翻譯的Iphone教程地址:http://www.cnblogs.com/zilongshanren/archive/2011/04/11/2012852.html

Iphone教程原文地址:http://www.raywenderlich.com/1163/how-to-make-a-tile-based-game-with-cocos2d

程序截圖:

這篇教程是《使用cocos2d-x製作基於Tile地圖的遊戲》的第二部分。在上一個教程中,我們創建了一個簡單的基於tiled地圖的遊戲,裏面有一個忍者在沙漠裏尋找可口的食物!

第一部分教程中,我們介紹瞭如何基於tiled創建地圖,怎樣把地圖增加到遊戲中去,以及如何滾動地圖來跟隨主角移動、還有如何使用對象層。

  在這部分教程中,我們將會介紹如何在地圖中製作可以碰撞的區域,如何使用tile屬性,如果收集遊戲物品並且動態地修改地圖、如何確保你的忍者不會吃得太飽!

  因此,讓我們繼續我們上篇教程所學並且讓它更像一個真實的遊戲吧!

tiled地圖和碰撞檢測

  你可能已經注意到了,目前我們的忍者可以毫無阻攔地穿過牆壁和障礙物。他是一個忍者,但是即使是真正的忍者,他也沒這麼厲害啊!

  因此,我們需要找到一種方法,通過把一些tile標記成“可碰撞的”,這樣的話,我們就可以防止玩家穿過那些點的位置。有很多方法可以做得到(包括使用對象層),但是,我想向你們展示一種新的技術。我認爲它更高效,並且也是一次好的學習鍛鍊--使用一個元層(meta layer)和層屬性。

  讓我們開始動手吧!再一次啓動Tiled軟件,點擊“圖層\添加圖層”,並且命名爲“Meta”,然後選擇OK。我們將在這個層裏面加入一些假的tile代表一些“特殊tile”。

  因此,現在我們需要增加我們的特殊tile。點擊“地圖\新圖塊...”,在你的Resources文件夾下面找到mate_tiles.png,然後選擇打開。點擊確定。

  這時,你可以在圖塊區域看到一個新的標籤。打開它,而且你會看到2個tile:一個紅色的和一個綠色的。

這些tile並沒有什麼特殊的東西--我只是製作了一個簡單的圖片,裏面包含了一個紅色的和一個綠色的半透明tile。接下來,我們把紅色的tile當作是“可碰撞的”(後面我們會用到綠色的),然後,合適地繪製我們的場景。

  因此,確保Meta層被選中,選擇圖章工具,選擇紅色的tile,然後把任何你不想讓忍者通過的地圖都塗一遍。當你做完的時候,應該看起來像下面的圖示一樣:

接下來,我們可以設置tile的屬性,這樣的話,我們在代碼中就可以識別這個tile是“可以碰撞的(穿不過去的)”。在圖塊上右鍵,點擊圖塊屬性。如下圖所示。

然後然後添加Collidable屬性並設置爲True。點擊確定。然後保存地圖並且更新到工程的Resource文件夾(假如你不是直接編輯工程中的TMX文件的話)。
然後往TileMapLayer類中添加一個變量聲明;

        
CCTMXLayer meta = null;

在init方法的loading background後面添加以下代碼:

this.meta = tileMap.layerNamed("Meta");
meta.visible = false;
並且添加一個方法:

        CCPoint tileCoordForPosition(CCPoint position)
        {
            int x = Convert.ToInt32(position.x / tileMap.TileSize.width);
            int y = Convert.ToInt32((tileMap.TileSize.height * tileMap.MapSize.height - position.y) / tileMap.TileSize.height);
            return new CCPoint(x, y);
        }

  好了,讓我們先停一會兒。像之前一樣,我會meta層聲明瞭一個成員變量,而且從tile map中加載了一個引用。注意,我們把這個字當作是不可見的,因爲我們並不想看見這些對象,它們的存在只是爲了說明,那個區域是可以碰撞的。

  接下來,我們增加一個新的幫助方法,這個方法可以幫助我們把x,y座標轉換成”tile座標“。每一個tile都有一個座標,從左上角的(0,0)開始,到右下角的(31,31)。(本例中,地圖的大小是32×32)

  上面的截屏是java版本的tiled界面。我現在用的地圖版本是不支持顯示座標的。可以顯示方格,不過,只是說下原理。不管怎麼說,我們將要使用的一些功能會使用tile座標,而不是x,y座標。因此,我們需要一種方式,將x,y座標轉換成tile座標。這正是那個函數所需要做的。

   獲得x座標非常容易--我們只需要讓它除以一個tile的寬度就可以了。爲了得到y座標,我們不得不翻轉一些東西,因爲,在cocos2d裏面(0,0)是在左下角的,而不是在左上角。

接下來,把setPlayerPosition替換成以下內容:

        void setPlayerPosition(CCPoint position)
        {
            CCPoint tileCoord = this.tileCoordForPosition(position);
            int tileGid = meta.tileGIDAt(tileCoord);
            if (tileGid != 0)
            {
                Dictionary<string, string> properties = tileMap.propertiesForGID(tileGid);
                if (properties != null)
                {
                    string collision = String.Empty, temp;
                    if (properties.TryGetValue("Collidable", out temp))
                    {
                        collision = temp;
                    }
                    if (collision.Length > 0 && collision.Equals("True"))
                    {
                        return;
                    }
                }
            }
            player.position = position;
        }

  在這裏,我們把玩家的x,y座標轉換成tile座標。然後,我們使用meta層中的tileGIDAt方法來獲取指定位置點的GID號。

  對了,什麼是GID呢?GID代表”全球唯一標誌符“(我個人意見)。但是,在這個例子中,我認爲它只是我們使用的tile的一種標識,它可以是我們想要移動的紅色區域。

  當我們使用GID來查找指定tile的屬性的時候。它返回一個屬性字典,因此,我們可以遍歷一下,看是否有”可碰撞的“物體被設置成”true“,或者是gij僅僅就是那樣。編譯並運行工程,因此還沒有設置玩家的位置。

  就這麼多!編譯並運行程序,它將會向你展示,現在你不能夠通過那些紅色的tile組成的地方了吧:

PS:這裏在做教程的時候發現了點問題。就是地圖精度的問題。看下圖

這裏我的Player的座標如果是在網格靠近下面話,那麼,在碰撞判定的時候,由於tileCoordForPosition函數的精度計算問題。靠近下面的話,計算的時候四捨五入。那麼,就會發現障礙物的座標上升了一行。所以上圖的player的框我設置略微靠近上面的線。這個網格可以在“視圖-顯示網格”可以使之顯示出來。

動態修改Tiled Map

  目前爲此,我們的忍者已經有一個比較有意思的冒險啦,但是,這個世界有一點點無趣。而且簡單無任務事可做!加上,我們的忍者看起來比較貪吃,而且背景將會隨着玩家移動而移動。因此,讓我們創建一些東西讓忍者來玩吧!

  爲了使之可行,我將不得不創建一個前景層,這樣做可以讓用戶收集東西。那樣的話,我們僅僅從前景層中刪除不用的tile(當tile被玩角拾取的時候),這個過程中,背景將會隨之移動。

  因此,打開地圖,選擇”圖層\添加圖層...“,把這個層命名爲”Foreground“,然後選擇OK。確保前景層被選擇,而且增加一對可以拾取的物品在遊戲中。我喜歡放置一些向西瓜或者別的什麼東西。隨意放置些東西吧。

  現在,我們需要把這些tile標記成可以拾取的,類似的,參照我們是如何把tile標誌成可以碰撞的。選擇Meta層,轉換到Meta_tiles。現在,我們需要使這些tile可以拾取。

接下來,我們需要爲tile增加屬性,這樣把它標記成可拾取的。點鍵點擊圖塊選項卡里的綠色的tile,然後右鍵選擇“屬性...”,再增加一個新的屬性,命名爲“Collectable”,值設置爲“True”。

返回TIleMapLayer類並且增加一個變量聲明;       

 CCTMXLayer foreground = null;

 同時,相應地修改TIleMapLayer類:


      
  //在init方法,loadingbackground後面添加
            foreground = tileMap.layerNamed("Foreground");
        //在setPlayerPosition方法判斷如果是牆壁就返回的if語句後面添加
                    string collectable = String.Empty;
                    if (properties.TryGetValue("Collectable", out temp))
                    {
                        collectable = temp;
                    }
                    if (collectable.Length > 0 && collectable.Equals("True"))
                    {
                        meta.removeTileAt(tileCoord);
                        CCSprite sprite = foreground.tileAt(tileCoord);
                        if (sprite != null)
                            sprite.visible = false;
                    }

  這裏是一個常用的方法,用來保存前景層的句柄。不同之處在於,我們測試玩家正朝之移動的tile是否含有“Collectable”屬性。如果有,我們就使用removeTileAt方法來把tile從mata層和前景層中移除掉。編譯並運行工程,現在你的忍者可以嚐嚐西瓜的滋味啦!

創建一個計分器

  我們忍者非常高興地吃西瓜啦,但是,作爲一個遊戲玩家,我們想知道自己到底吃了多少個西瓜。你懂的,我們並不想讓他吃得太胖。

  通常的做法是,我們在層上面添加一個label。但是,等一下:我們在不停地移動這個層,那樣的話,label就會看不到了,怎麼辦?

  這是一個非常好的機會,如果在一個場景中使用多個層--這正是我們現在面臨的難題。我們將保留TileMapLayer層,但是,我們會再增加一個TileMapHud層來顯示我們的label。(Hud意味着Heads up display,大家可以google一下,遊戲中常用的技術)

  當然,這兩個層之間需要一種方式聯繫起來--Hud層應該知道什麼時候忍者吃了一個西瓜。有許許多多的方式可以使2個不同的層相互通信,但是,我只介紹最簡單的。我們在HelloWorld層裏面保存一個HelloWorldHud層的句柄,這樣的話,當忍者吃了一個西瓜就可以調用Hud層的一個方法來進行通知。

  因此,在TileMapScene.cs裏面增加下面的代碼:

    class TileMapHud : CCLayer
    {
        CCLabelTTF label;


        public override bool init()
        {
            if (!base.init())
                return false;
            CCSize winSize = CCDirector.sharedDirector().getWinSize();
            label = CCLabelTTF.labelWithString("0", "Arial", 18);
            label.Color = new ccColor3B(0, 0, 0);
            int margin = 10;
            label.position = new CCPoint(winSize.width - label.contentSize.width / 2 - margin, label.contentSize.height / 2 + margin);
            this.addChild(label);
            return true;
        }


        public void numCollectedChanged(int numCollected)
        {
            label.setString(numCollected.ToString());
        }


        public static new TileMapHud node()
        {
            TileMapHud layer = new TileMapHud();
            if (layer.init())
                return layer;
            return null;
        }
    }
  一切很明瞭。我們的第二個層從CCLayer派生,只是在它的右下角加了一個label。現在要做的是在TileMapLayer類裏面添加一個聲明,作爲Hud層的引用,並且添加這樣一個方法,作爲這個TileMapLayer的初始化用。

        int numCollected = 0;
        TileMapHud hud = null;
        public static TileMapLayer initWithHudLayer(TileMapHud hud)
        {
            TileMapLayer ret = new TileMapLayer();
            if (ret.init())
            {
                ret.hud = hud;
                return ret;
            }
            return null;
        }

現在修改TileMapScene的構造函數如下:

        public TileMapScene()
        {
            TileMapHud hud = TileMapHud.node();
            this.addChild(TileMapLayer.initWithHudLayer(hud));
            this.addChild(hud);
        }

並且修改setPlayerPosition方法。在處理tile是否含有“Collectable”屬性,並且屬性爲True的時候。

                        this.numCollected++;
                        hud.numCollectedChanged(numCollected);

  編譯並運行,如果一切ok,你將會在屏幕右下角看到統計忍者吃西瓜的Label。

來點音效和音樂

  如果沒有很cool的音效和背景音樂的話,這就不能算作是一個完整的遊戲教程了。

  增加音效和音樂非常簡單,先添加CocosDenshion.dll的引用,只需在TileMapLayer類作如下修改:

            //在Init添加如下:
            //Effect preload
            SimpleAudioEngine.sharedEngine().preloadEffect(@"Resources/hit");
            SimpleAudioEngine.sharedEngine().preloadEffect(@"Resources/move");
            SimpleAudioEngine.sharedEngine().preloadEffect(@"Resources/pickup");
            SimpleAudioEngine.sharedEngine().playBackgroundMusic(@"Resources/TilesMap",true);
        // In case for collidable tile
        SimpleAudioEngine.sharedEngine().playEffect(@"Resources/hit");
            // In case of collectable tile
            SimpleAudioEngine.sharedEngine().playEffect(@"Resources/pickup");
            // Right before setting player position
            SimpleAudioEngine.sharedEngine().playEffect("Resources/move");


現在,我們的忍者可以開懷大吃了!

何去何從?

 通過這個教程的學習,你對cocos2d裏面的tiled map的使用,應該有一個非常好的理解了。這裏有這個教程的完整源代碼(http://dl.dbank.com/c02vzbkoyl)。

  接下來,繼續第三部分 <cocos2d-x for wp7>使用cocos2d-x製作基於Tile地圖的遊戲:加入敵人和戰鬥(三)

  如果你看了這個教程,有什麼好的意見或建議,可以自由發言,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章