使用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

程序截圖:


  在這個分爲幾部分教程中(我還沒想好是幾部分⊙﹏⊙b汗),我將會教大家如何使用cocos2d-x來做一個基於tile地圖的遊戲,當然還有Tiled地圖編輯器。(我們小時候玩的小霸王小學機裏面的遊戲,大部分都是基於tile地圖的遊戲,如重裝機兵,坦克大戰、冒險島、吞食天地等)我們將會創建一個忍者在沙漠中找東西吃的小遊戲,並且被怪物追趕的遊戲-_-

  在這第一部分教程中,我將教大家如何使用Tile來創建地圖,怎樣把地圖加到遊戲中,怎麼讓地圖跟隨玩家滾動,以及怎樣使用對象層。

  如果你還沒有準備好的話,你可能需要先從用cocos2d-x做一個簡單的windows phone 7遊戲》系列教程開始學起,因爲我們這個教程使用了大量的基本概念,而這些概念都可以從上面的教程中獲取。

  好了,讓我們玩一玩tile地圖吧!


創建工程

打開VS2010,新建一個cocos2d-x項目。命名爲cocos2dTIleMapGameDemo,同樣的,openxLIve這個服務我們也用不到,所以那個勾同樣去掉。同樣的,添加和修復引用也是必要的。

接下來,下載遊戲資源文件(http://dl.dbank.com/c0p7fmgba7),這個資源文件包裏包含了以下內容:

  • 玩家sprite。這個圖片和用cocos2d-x做一個簡單的windows phone 7遊戲》差不多。
  • 一些音效。
  • 一些背景音樂。
  • 我們將會使用的tile集合--它實際上會和tile地圖編輯器一塊兒使用,但是,我想把它放在這裏,餘下的事情會變得更容易。
  • 一些額外的“特殊”的tile,我將會在後面加以說明。

  一旦你獲得了這些資源,解壓並把它添加到你的Content工程的“Resources”目錄下面。

  如果一切順利,所有的文件應該都在你的工程裏了。是時候製作我們的地圖了!


使用Tile來製作地圖

  cocos2d-x支持使用Tile地圖編輯器(http://www.mapeditor.org/)創建的TMX格式的地圖。

  下載完之後,直接雙擊運行安裝。點擊文件\新文件,然後會出現以下對話框:


在 地圖方向部分,你可以選擇正常。

  接下來,設置地圖的大小。記住,這個大小是以tile爲單位的,而不是以像素爲單位。我們將創建一個儘量小的地圖,因此選擇32×32(現在發現50*50的地圖WP7不能加載全,最大32*32。不過在test工程裏面的示例再怎麼樣的地圖都行。不過人家用的攝影機,不是我這種的設置位置的方法,現在還沒有研究爲啥)

  最後,你指定每個地圖的寬度和高度。你這裏選擇的寬度和高度要根據你的實際的tile圖片的尺寸來做。這個教程使用的樣例tile的尺寸是32×32,所以在上面的選項中選擇32×32.

  接下來,我們把製作地圖所需要的tile集合導入進來。點擊菜單欄上面的“地圖”菜單,“新圖塊.”,然後會出現下面的窗口:


  爲了獲得圖片,點擊Browse按鈕,然後定位到你的Resource文件夾,選擇 tmw_desert_spacing.png文件,然後加到工程中去。它會基於文件名自動填充名字。點擊確定即可。

將會看到圖塊窗口中顯示了一些tiles。現在,你可以製作地圖了!點擊一個Tile。你就可以在任何設置圖塊了。這個就像畫圖工具,什麼畫刷也能用,具體看工具欄,這樣可以快速製作地圖。

充分發揮你的聰明才智!確保增加至少一對建築物在地圖上,因爲後面我們需要一些東西來做碰撞。

一旦你完成了地圖的繪製工作,在Layers選項卡的層上面雙擊(現在可以說是“圖層1”),然後重命名爲“background”。然後點擊“文件\保存”並且保存文件到你的工程的Resource文件夾中,並且命名爲“TileMap.tmx”。

把tile地圖添加到cocos2d的場景中

現在新建一個類,命名爲TileMapScene。來作爲我們遊戲場景。並且裏面的代碼修改爲:

    class TileMapScene:CCScene
    {
        public TileMapScene()
        {
            this.addChild(TileMapLayer.node());
        }
    }

    class TileMapLayer : CCLayer
    {
        public override bool init()
        {
            if (!base.init())
                return false;;
            return true;
        }

        public static new TileMapLayer node()
        {
            TileMapLayer layer = new TileMapLayer();
            if (layer.init())
                return layer;
            return null;
        }
    }

並且修改導演類裏面的applicationDidFinishLaunching。修改如下:

            //CCScene pScene = cocos2dTIleMapGameDemoScene.scene();
            CCScene pScene = new TileMapScene();
            //run

現在需要添加兩個引用。ICSharpCode.SharpZipLib.DLL和zlib.net.DLL。這兩個DLL可以在cocos2d-x的安裝包的Test工程的bin目錄下面找到。然後在解決方案資源管理器裏面選擇TileMap.tmx文件,選擇屬性,確認Content Importer和Content Processor如下面所選,如果沒找到下面選項,確認Content工程的引用中的cocos2d.Content.Pipeline.Importers正確引用


現在打開TileMap.tmx文件。確認image 的source路徑爲相對路徑,相對Tmx文件的相對路徑。

做完上面的工作後,現在再添加一些聲明到TileMapLayer類中。

        CCTMXTiledMap tileMap = null;
        CCTMXLayer background = null;

並且添加以下代碼到Init中。
            tileMap = CCTMXTiledMap.tiledMapWithTMXFile("Resources/TileMap");
            background = tileMap.layerNamed("background");
            addChild(tileMap, -1);

  這裏,我們調用CCTMXTiledMap類的一些方法,把我們剛剛創建的地圖文件加載進去。

  一些簡明的CCTMXTiledMap的背景知識。它是一個CCNode,你可以設置它的位置和比例等。這個地圖的孩子是一些層,而且提供了一個幫助函數可以讓你通過層的名字得到層對象--我們上面就是通過這種方面獲得地圖背景的。每一個層都是一個CCSpriteSheet的子類,這裏考慮了性能的原因--但是這也意味着每一個層只能有一個tile集。

  因此,我們這裏做的所有這些,就是指向一個tile地圖,然後保存背景層的引用,並且把tile地圖加到TileMapLayer層中。

  好了,就這麼多!編譯並運行工程,你將會看到地圖的左下角出現在模擬器中。


  還不錯!但是,這還不是一個遊戲!我們還需要三個東西:a)遊戲主角,b)主角初使位置和c)能夠移動視圖,這樣就好像是第一視角了。

  好了,接下來讓我們來解決這些問題。

tiled對象層和設置tile地圖位置

  tiled支持兩類層--tile層(就是我們目前使用的層),還有對象層。

  對象層允許你在地圖上圈出一些區域,來指定一些事件的發生。比如,你可能想製作一個區域,在那裏怪物將會跳出來,或者是一個區域,只要進入就會死掉。這我們這個例子中,我們將創建一個區域來顯示我們的遊戲主角。

  因此,找到Tiled的菜單,點擊” 圖層\添加對象層”,命名爲“Objects”,然後按回車鍵。

  我們只想要選擇一個tile來讓主角顯示。先在工具欄上面選擇添加對象。因此,在你的地圖上選擇一個tile。這個區域的大小實際上並沒有關係,因爲我們僅僅使用x、y座標。

 然後,上面的灰藍色對象框上面點右鍵,選擇“對象屬性”, 取名爲“SpawnPoint",然後選擇確定:

現在,保存TMX文件,並且記得更新到Resource文件夾。

然後往TileMapLayer裏面添加一個聲明:

        CCSprite player = null;

同時修改Init方法(在return true前面添加):

            CCTMXObjectGroup objects = tileMap.objectGroupNamed("Objects");
            int x, y;
            if (objects != null)
            {
                //Debug.WriteLine(objects.Properties.ToString());
                foreach (var item in objects.Objects)
                {
                    if (item.ContainsValue("SpawnPoint"))
                    {
                        x = Convert.ToInt32(item["x"]);
                        y = Convert.ToInt32(item["y"]);
                        player = CCSprite.spriteWithFile(@"Resources/Player");
                        player.position = new CCPoint(x, y);
                        this.addChild(player);
                    }
                }
            }
            this.setViewpointCenter(player.position);

  好了,讓我們先歇會兒,來解釋一下對象層和對象組。首先,注意你通過CCTMXTiledMap對象的objectGroupNamed方法來獲得對象層(而不是layerNamed方法)。它返回一個特殊的CCTMXObjectGroup對象。

  我們然後調用CCTMXObjectGroup類的Objects是一個List<IDictionary<string,string>>對象,其有一個name的key的value就是我們定義的SpawnPoint,這個字典包含了關於對象的大量信息,包括x和y座標值,寬度和高度。在這個例子中,我們只關心x和y座標,因此,我們提取出這兩個信息,並且設置player的位置。

PS:在這裏隨意提下調試技巧吧。本來我也不懂這個CCTMXObjectGroup類是怎麼回事,也不懂裏面的數據是怎麼保存的。雖然知道是一個字典的List,但是不知道我需要的信息在哪,那麼就在上面的for循環前面添加一個斷點。然後運行到斷點處對objects.Objects對象進行快速監視,在快速監視窗口查找需要的信息。這個方法對於不認識的對象可以很好了解其情況。

  最後,我想設置這個視圖爲玩家所在的位置。因此,添加下面一個新方法到文件中:

        void setViewpointCenter(CCPoint position)
        {
            CCSize winSize = CCDirector.sharedDirector().getWinSize();
            float x = Math.Max(position.x, winSize.width / 2);
            float y = Math.Max(position.y, winSize.height / 2);
            x = Math.Min(x, (tileMap.MapSize.width * tileMap.TileSize.width) - winSize.width / 2);
            y = Math.Min(y, tileMap.MapSize.height * tileMap.TileSize.height - winSize.height / 2);
            CCPoint acturalPosition = new CCPoint(x, y);

            CCPoint centerView = new CCPoint(winSize.width / 2, winSize.height / 2);
            CCPoint viewPoint = CCPointExtension.ccpSub(centerView, acturalPosition);
            this.position = viewPoint;
        }

  好了,讓我解釋一下。假設這個函數是設置camera的中心。我們允許用戶傳入地圖上任何x、y座標值--但是如果你仔細想一下,有些東西我們並不想讓它顯示出來--比如,我們不想讓屏幕超過地圖的邊界(那些區域僅僅是一個空白區域!)

  比如,看看下面這幅圖:


  看一下,什麼時候camera的中心會小於winSize.width/2或者winSize.height/2,部分視圖將會在屏幕之外?類似的,我們需要檢查上面的界限區間,也和我們這裏的情形一樣。

  因此,我們把這個函數看作是設置camera的視角中心點。然而。。。那不完全是我們想要的。在cocos2d裏面有一種方式可以直接操作一個CCNode的camera,但是那會使事情變得更復雜。我們需要另一種替代方法,那就是移動整個層。

  看看下面的圖:


  想像一個大的地圖,我們查看從0到winSize.height/width的座標。我們的視圖的中心點是centerOfView,而且我們知道我們要把這個中心設置到哪裏(actualPositon)。因此,爲了使實際的位置和視圖中心相吻合,我們只需要把地圖往左下角移動即可!

  這個可以通過使實際的位置減去視圖的中心位置來實現,然後設置TileMapLayer層到那個點。

  唉!太多理論了--讓我們看點實際的吧!編譯並運行項目,如果一切順利,你將會看到忍者在場景當中,然而視角也移過來了。


似乎上面的忍者放置太靠左下角了。。。。不過也可以看出來視覺移過來了

使忍者移動

  我們已經有一個好的開端了,但是我們的忍者只是站在那兒不動!這可不像真正的忍者!

  讓我們使忍者動起來吧,只需要讓忍者移動到用戶點擊的地方就行了。在TileMapLayer類中的init增加以下代碼:

this.isTouchEnabled = true;
然後往類中添加添加以下方法:

        public override void registerWithTouchDispatcher()
        {
            CCTouchDispatcher.sharedDispatcher().addTargetedDelegate(this, 0, true);
        }

        void setPlayerPosition(CCPoint position)
        {
            player.position = position;
        }

        public override bool ccTouchBegan(CCTouch touch, CCEvent event_)
        {
            return true;
        }

        public override void ccTouchEnded(CCTouch touch, CCEvent event_)
        {
            CCPoint touchLocation = touch.locationInView(touch.view());
            touchLocation = CCDirector.sharedDirector().convertToGL(touchLocation);
            touchLocation = this.convertToNodeSpace(touchLocation);

            CCPoint playerPos = new CCPoint(player.position.x, player.position.y);
            CCPoint diff = CCPointExtension.ccpSub(touchLocation, playerPos);
            if (Math.Abs(diff.x) > Math.Abs(diff.y))
            {
                if (diff.x > 0)
                {
                    playerPos.x += tileMap.TileSize.width;
                }
                else
                    playerPos.x -= tileMap.TileSize.width;
            }
            else
            {
                if (diff.y > 0)
                {
                    playerPos.y += tileMap.TileSize.height;
                }
                else
                    playerPos.y -= tileMap.TileSize.width;
            }

            if (playerPos.x <= tileMap.MapSize.width * tileMap.TileSize.width &&
                playerPos.y <= tileMap.MapSize.height * tileMap.TileSize.height &&
                playerPos.x >= 0 &&
                playerPos.y >= 0)
            {
                this.setPlayerPosition(playerPos);
            }
            this.setViewpointCenter(player.position);
            
        }

  首先,在init方法中設置層能夠接收touch事件。如果我們覆蓋registerWithTouchDispatcher方法,來使這個層能夠處理目標touch事件。這樣會導致ccTouchBegan和ccTouchEnded方法被調用(注意是單數形式,而不是複數形式的ccTouchesBegan和ccTouchesEnded方法)

  你可能會問,爲什麼我要講這個,因爲我們在 用cocos2d-x做一個簡單的windows phone 7遊戲》裏面使用的是ccTouchesBegan和ccTouchesEnded方法。那兩個方法可以,在這個教程裏用兩種方法都可以。但是,我想向大家介紹一個新方法,因爲它有兩個優點:

  • 劃分UITouch集合並調度的工作全部由cocos2d-x框架來完成。每一次方法調用,你只獲得了一個UITouch。“
  • “你可以在ccTouchBegan中返回true,這樣當前的層就可以接收touch事件回調。而且,只有當你返回yes的時候,纔會響應move/ended/cancelled回調. 這個就使你從一些複雜的多觸摸判斷中解放出來了。

  不管怎麼說,在我們的ccTouchEnded裏面,我們轉換屏幕touch座標爲局部view座標,然後再轉換成GL的座標。這兩個步驟,只需要一步完成,即調用 [self convertToNodeSpace:touchLocation].就可以了。

  這是因爲,touch位置只是告訴我們屏幕視口的座標(比如100,100)。但是,我們我們滾動了地圖,這個位置實際可能對應地圖的(800,800)。因此,調用這個方法基於我們當前層的位置來決定touch的偏移。

  接下來,計算出touch點和player的位置之差。我們必須基於touch位置選擇一個方向,因此,首先,我們需要計算出是上下移動還是左右移動。然後,我們比較正負值,決定具體的方向。

  相應的,我們再調整player的位置,並且設置player的位置爲視口的中心位置,這個在上一節中已經用到了。

  

  編譯並運行!你現在可以點擊鼠標,想讓盡者移到哪,它就移到哪兒!

何去何從?

  這只是這個教程的一部分。此時,你應該瞭解一些創建tile地圖的基礎了,而且知道如何把它導入到遊戲當中。

  這裏有我們目前爲止用的完整源代碼(http://dl.dbank.com/c0ffx1qcib

  接下,繼續學習第二部分教程吧!在那裏,我將教大家如何在地圖中添加碰撞檢測,如果使我們的忍者沿着牆壁快樂的奔跑!



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