(譯)如何使用cocos2d製做基於tile地圖的遊戲教程:第一部分

 免責申明(必讀!):本博客提供的全部教程的翻譯原稿均來自於互聯網,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本博客全部人、發表該翻譯稿之人無任何關係。謝謝合做! css

原文連接地址:http://www.raywenderlich.com/1163/how-to-make-a-tile-based-game-with-cocos2d html

教程截圖: java

  在這個2部分的教程中,我將會教你們如何使用cocos2d來作一個基於tile地圖的遊戲,固然還有Tiled地圖編輯器。(咱們小時候玩的小霸王小學機裏面的遊戲,大部分都是基於tile地圖的遊戲,如坦克大戰、冒險島、吞食天地等)咱們將會建立一個忍者在沙漠中找西瓜吃的小遊戲。-_- 安全

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

  在第二部分教程中,我將介紹如何在地圖中建立可碰撞的區域,如何使用tile屬性,如何製做可拾取的物體和動態修改地圖,還有確保忍者不要吃撐了! 框架

  若是你尚未準備好的話,你可能須要先從《如何使用cocos2d來製做簡單的iphone遊戲》系列教程開始學起,由於咱們這個教程使用了大量的基本概念,而這些概念均可以從上面的教程中獲取。 iphone

  好了,讓咱們玩一玩tile地圖吧! 編輯器

建立工程骨架

  讓咱們首先建立整個工程的骨架,這樣能夠確保從此咱們須要的文件都包含進來了,而且可以跑起來。 ide

  所以,啓動XCode,點擊「File\New Project...」,選擇cocos2d Application template,而且把工程命名爲TileGame。 函數

  接下來,下載遊戲資源文件。這個資源文件包裏包含了如下內容:

  • 玩家sprite。這個圖片和《如何使用cocos2d來製做簡單的iphone遊戲》差很少。
  • 我使用cxfr這個工具製做的一些音效。
  • 我使用Garage Band製做的一些背景音樂。(查看這篇博文得到更多的信息)
  • 咱們將會使用的tile集合--它實際上會和tile地圖編輯器一起使用,可是,我想把它放在這裏,餘下的事情會變得更容易。 
  • 一些額外的「特殊」的tile,我將會在後面加以說明。

  一旦你得到了這些資源,解壓並把它拖到你的工程的「Resources」分組下面。確保複選中「Copy items into destination group’s folder (if needed)」,引用類型爲「Relative to Project」,而後點擊增長。

  若是一切順利,全部的文件應該都在你的工程裏了。是時候製做咱們的地圖了!

使用Tile來製做地圖

  cocos2d支持使用開源的Tile地圖編輯器建立的TMX格式的地圖。

  (做者給出的網址如今打不開了,這是我在另外一個地方找到的。我把它放到個人網盤裏了,而且作了一個連接。若是有人下載不了,請留言。這個tile地圖編輯器是java版的,其實還有一個at版的,可是java版的功能強大一些。可是,你們請注意,做者使用的是qt版本的,因此界面會有一些不一致,但這並不影響程序的使用。)

  下載完以後,直接雙擊運行。點擊File\New,而後會出現如下對話框:

  在 orientation部分,你能夠選擇Orthogonal(參考:  Legend of Zelda)或者Isometric(參考:  Disgaea)。咱們這裏將選擇Orthogonal。

  接下來,設置地圖的大小。記住,這個大小是以tile爲單位的,而不是以像素爲單位。咱們將建立一個儘可能小的地圖,所以選擇50×50.

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

  接下來,咱們把製做地圖所須要的tile集合導入進來。點擊菜單欄上面的「TileSets」菜單,「New Tileset...」,而後會出現下面的窗口:

  爲了得到圖片,點擊Browse按鈕,而後定位到你的TestGame文件夾,選擇 tmw_desert_spacing.png文件,而後加到工程中去。它會基於文件名自動填充Name。而後把TileSet name命名爲「tmw_desert_spacing.png」.同時,設置下面的Tile spacing和Margin都爲1.

  你能夠保留寬度和高度爲32×32,由於tile的實際大小也是這麼多。至於margin和spacing,我還沒找到任何好的文檔解釋如何設置這兩個值,下面是個人我的見解:

  • Margin就是當前的tile計算自身的像素的時候,它須要減去多少個像素(寬度和高度都包含在內)。(類比word、css的margin)
  • Spacing 就是相鄰兩個tile之間的間隔(同時考慮寬度和高度)(類比word、css的spacing)

  若是你看看 tmw_desert_spacing.png,你將會看見每個tile都有一個像素的空白邊界圍繞着,這意味着咱們須要把margin和spacing設置爲1.

  一旦你選擇ok,你將會看到Tilesets窗口中顯示了一些tiles。如今,你能夠製做地圖了!點擊工具欄上的「Stamp」按鈕。點擊Tile palette中的tile map,而後選擇一個tile,而後再在地圖上的任意位置單擊,你就會看到你選中的tile出如今點中的地方了。

  所以,繼續製做地圖吧---充分發揮你的聰明才智!確保增長至少一對建築物在地圖上,由於後面咱們須要一些東西來作碰撞。

記住一些方便的快捷方式:

  • 你能夠在Tileset拾取器中拖出一個方框,一次選取多個tile。
  • 你可使用工具欄上的paint按鈕來基於一個基準tile繪製整個地圖。
  • 你可使用「View\Zoom In...」和「View\Zoom out...」來放大和縮小地圖。

  一旦你完成了地圖的繪製工做,在Layers選項卡的層上面雙擊(如今能夠說是「Layer1」),而後重命名爲「Background」。而後點擊「File\Save」而且保存文件到你的工程的資源文件夾中,而且命名爲「TileMap.tmx」。

  後面咱們將會使用這個tmx來作一些有趣的事情,好了,讓咱們把地圖加載到遊戲中去吧!

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

  首先,第一件事情,右鍵點擊Resources,選擇「 Add\Existing Files…」,而後添加TileMap.tmx文件。

  打開HelloWorldScene.h,而後添加一些成員變量,而且申明一聲屬性:

複製代碼
//  Inside the HelloWorld class declaration
CCTMXTiledMap  * _tileMap;
CCTMXLayer 
* _background;

//  After the class declaration
@property (nonatomic, retain) CCTMXTiledMap  * tileMap;
@property (nonatomic, retain) CCTMXLayer 
* background;
複製代碼

 

而後在HelloWorldScene.m文件中作以下修改:

複製代碼
//  Right after the implementation section
@synthesize tileMap  =  _tileMap;
@synthesize background 
=  _background;

//  In dealloc
self.tileMap  =  nil;
self.background 
=  nil;

//  Replace the init method with the following
- (id) init
{
if ( (self = [super init] )) {

self.tileMap 
=  [CCTMXTiledMap tiledMapWithTMXFile: @" TileMap.tmx " ];
self.background 
=  [_tileMap layerNamed: @" Background " ];

[self addChild:_tileMap z:
- 1 ];

}
return  self;
}
複製代碼

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

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

  所以,咱們這裏作的全部這些,就是指向一個tile地圖,而後保存背景層的引用,而且把tile地圖加到HelloWorld層中。

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

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

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

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

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

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

  所以,找到Tiled的菜單,點擊」  Layer\Add Object Group…」,命名爲「Objects」,而後選擇Ok。若是你繪製了地圖,你將會注意到,它並無繪製一個tile,而是畫了一個很難看的灰色矩形,這個矩形咱們以後能夠擴展,使之可以包含多個tiles或者移動它。

  咱們只想要選擇一個tile來讓主角顯示。所以,在你的地圖上選擇一個tile。這個區域的大小實際上並無關係,由於咱們僅僅使用x、y座標。

  而後,上面的黃色對象上面點右鍵, 取名爲「SpawnPoint",而後選擇Ok:

(添加對象方法很簡單,看到左邊的+號和-號了嗎?一個是增長對象,一個是刪除對象。點中加號之後,用鼠標能夠拖出一個矩陣區域。)

(下面給出一些技巧。如何把一個對象準確放置到Background的空白區域,只須要調整背景的opacity就能夠了)

  假設,你能夠作一些奇思妙想。你把對象的類型命名爲cocos2d裏面的class的名字,而後它會爲你建立那個類型(好比CCSprite),可是,我在cocos2d自帶的源代碼裏面代不到這樣的例子。更新:來自GeekAndDad.com的Tyler提供了以前版本的cocos2d裏面使用這種「妙想」的方法,可是,因爲背景有白色,因此被移除了。

  無論怎麼說--咱們僅僅把這個類型設置爲空就好了,最後cocos2d會爲咱們建立NSMutableDictionary,咱們能夠從中得到對象的各類屬性,包含x,y座標。

  保存地圖,而後返回XCode。在HelloWorldScene.h中作以下修改:

//  Inside the HelloWorld class declaration
CCSprite  * _player;

//  After the class declaration
@property (nonatomic, retain) CCSprite  * player;

 

一樣,修改HelloWorldScene.m,代碼以下:

複製代碼
//  Right after the implementation section
@synthesize player  =  _player;

//  In dealloc
self.player  =  nil;

//  Inside the init method, after setting self.background
CCTMXObjectGroup  * objects  =  [_tileMap objectGroupNamed: @" Objects " ];
NSAssert(objects 
!=  nil,  @" 'Objects' object group not found " );
NSMutableDictionary 
* spawnPoint  =  [objects objectNamed: @" SpawnPoint " ]; 
NSAssert(spawnPoint 
!=  nil,  @" SpawnPoint object not found " );
int  x  =  [[spawnPoint valueForKey: @" x " ] intValue];
int  y  =  [[spawnPoint valueForKey: @" y " ] intValue];

self.player 
=  [CCSprite spriteWithFile: @" Player.png " ];
_player.position 
=  ccp(x, y);
[self addChild:_player]; 

[self setViewpointCenter:_player.position];
複製代碼

 

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

  咱們而後調用CCTMXObjectGroup類的objectNamed方法來得到一個NSMutableDictionary,這個字典包含了關於對象的大量信息,包括x和y座標值,寬度和高度。在這個例子中,咱們只關心x和y座標,所以,咱們提取出這兩個信息,而且設置player的位置。

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

複製代碼
- ( void )setViewpointCenter:(CGPoint) position {

CGSize winSize 
=  [[CCDirector sharedDirector] winSize];

int  x  =  MAX(position.x, winSize.width  / 2 );
int  y  =  MAX(position.y, winSize.height  / 2 );
=  MIN(x, (_tileMap.mapSize.width  *  _tileMap.tileSize.width) 
-  winSize.width  / 2 );
=  MIN(y, (_tileMap.mapSize.height  *  _tileMap.tileSize.height) 
-  winSize.height / 2 );
CGPoint actualPosition 
=  ccp(x, y);

CGPoint centerOfView 
=  ccp(winSize.width / 2 , winSize.height / 2 );
CGPoint viewPoint 
=  ccpSub(centerOfView, actualPosition);
self.position 
=  viewPoint;

}
複製代碼

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

  好比,看看下面這幅圖:

  看一下,何時camera的中心會小於winSize.width/2或者winSize.height/2,部分視圖將會在屏幕以外?相似的,咱們須要檢查上面的界限區間,也和咱們這裏的情形同樣。

  所以,咱們把這個函數看做是設置camera的視角中心點。然而。。。那不徹底是咱們想要的。在cocos2d裏面有一種方式能夠直接操做一個CCNode的camera,可是那會使事情變得更復雜。咱們須要另外一種替代方法,那就是移動整個層

  看看下面的圖:

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

  這個能夠經過使實際的位置減去視圖的中心位置來實現,而後設置HelloWorld層到那個點。

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

使忍者移動

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

  讓咱們使忍者動起來吧,只須要讓忍者移動到用戶點擊的地方就好了。在HelloWorldScene.m中增長如下代碼:

複製代碼
//  Inside init method
self.isTouchEnabled  =  YES;

- ( void ) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self 
priority:
0  swallowsTouches:YES];
}

- (BOOL) ccTouchBegan:(UITouch  * )touch withEvent:(UIEvent  * ) event
{
return  YES;
}

- ( void )setPlayerPosition:(CGPoint)position {
_player.position 
=  position;
}

- ( void ) ccTouchEnded:(UITouch  * )touch withEvent:(UIEvent  * ) event
{

CGPoint touchLocation 
=  [touch locationInView: [touch view]]; 
touchLocation 
=  [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation 
=  [self convertToNodeSpace:touchLocation];

CGPoint playerPos 
=  _player.position;
CGPoint diff 
=  ccpSub(touchLocation, playerPos);
if  (abs(diff.x)  >  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.height;
}
}

if  (playerPos.x  <=  (_tileMap.mapSize.width  *  _tileMap.tileSize.width)  &&
playerPos.y 
<=  (_tileMap.mapSize.height  *  _tileMap.tileSize.height)  &&
playerPos.y 
>= 0 &&
playerPos.x 
>= 0  ) 
{
[self setPlayerPosition:playerPos];
}

[self setViewpointCenter:_player.position];

}
複製代碼

 

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

  你可能會問,爲何我要講這個,由於咱們在 《如何使用cocos2d來製做簡單的iphone遊戲》裏面使用的是ccTouchesBegan和ccTouchesEnded方法。那兩個方法能夠,在這個教程裏用兩種方法均可以。可是,我想向你們介紹一個新方法,由於它有兩個優勢:

  • 「你不須要處理NSSet,劃分UITouch集合並調度的工做所有由cocos2d框架來完成。每一次方法調用,你只得到了一個UITouch。「
  • 「你能夠在ccTouchBegan中返回yes,這樣當前的層就能夠接收touch事件回調。並且,只有當你返回yes的時候,纔會響應move/ended/cancelled回調. 這個就使你從一些複雜的多觸摸判斷中解放出來了。

  無論怎麼說,在咱們的ccTouchEnded裏面,咱們轉換屏幕touch座標爲局部view座標,而後再轉換成GL的座標。這兩個步驟,在新的cocos2d版本中,只須要一步完成,即調用 [self convertToNodeSpace:touchLocation].就能夠了。

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

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

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

  更新:注意,咱們不得不添加一個安全檢查,來確保咱們的player不會移到地圖以外!這一點,是Geek&Dad指出來的,謝謝你!

  編譯並運行!你如今能夠點擊鼠標,想讓盡者移到哪,它就移到哪兒!

何去何從?

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

  這裏有咱們目前爲止用的完整源代碼

  接下,期待第二部分教程吧!在那裏,我將教你們如何在地圖中添加碰撞檢測,若是使咱們的忍者沿着牆壁快樂的奔跑!

相關文章
相關標籤/搜索