(译)如何使用cocos2d制作基于tile地图的游戏教程:第一部分
免責申明(必讀!):本博客提供的所有教程的翻譯原稿均來自于互聯網,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本博客所有人、發表該翻譯稿之人無任何關系。謝謝合作!
原文鏈接地址:http://www.raywenderlich.com/1163/how-to-make-a-tile-based-game-with-cocos2d
教程截圖:
在這個2部分的教程中,我將會教大家如何使用cocos2d來做一個基于tile地圖的游戲,當然還有Tiled地圖編輯器。(我們小時候玩的小霸王小學機里面的游戲,大部分都是基于tile地圖的游戲,如坦克大戰、冒險島、吞食天地等)我們將會創建一個忍者在沙漠中找西瓜吃的小游戲。-_-
在第一部分教程中,我將教大家如何使用Tile來創建地圖,怎樣把地圖加到游戲中,怎么讓地圖跟隨玩家滾動,以及怎樣使用對象層。
在第二部分教程中,我將介紹如何在地圖中創建可碰撞的區域,如何使用tile屬性,如何制作可拾取的物體和動態修改地圖,還有確保忍者不要吃撐了!
如果你還沒有準備好的話,你可能需要先從《如何使用cocos2d來制作簡單的iphone游戲》系列教程開始學起,因為我們這個教程使用了大量的基本概念,而這些概念都可以從上面的教程中獲取。
好了,讓我們玩一玩tile地圖吧!
創建工程骨架
讓我們首先創建整個工程的骨架,這樣可以確保今后我們需要的文件都包含進來了,并且能夠跑起來。
因此,啟動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 declarationCCTMXTiledMap *_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 declarationCCSprite *_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);
x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width)
- winSize.width /2);
y = 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 methodself.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地圖的基礎了,而且知道如何把它導入到游戲當中。
這里有我們目前為止用的完整源代碼。
接下,期待第二部分教程吧!在那里,我將教大家如何在地圖中添加碰撞檢測,如果使我們的忍者沿著墻壁快樂的奔跑!
?
著作權聲明:本文由http://www.cnblogs.com/andyque翻譯,歡迎轉載分享。請尊重作者勞動,轉載時保留該聲明和作者博客鏈接,謝謝!
轉載于:https://www.cnblogs.com/zilongshanren/archive/2011/04/11/2012852.html
總結
以上是生活随笔為你收集整理的(译)如何使用cocos2d制作基于tile地图的游戏教程:第一部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Office SharePoint Se
- 下一篇: WCF发布到IIS7问题的解决方案