1. 程式人生 > >[譯]在Tiled Map中使用碰撞檢測(二) TMX地圖中的碰撞檢測

[譯]在Tiled Map中使用碰撞檢測(二) TMX地圖中的碰撞檢測

On 2010年06月20日, in iPhone, by 毛叔

在上一篇裡,我們已經學會了如何建立一個基於tiled map的簡單遊戲。學會了如何製作地圖,如何將地圖載入到遊戲,如何讓主角在螢幕上移動。
在這篇教程裡,我們將學習如何在地圖裡建立可碰撞(不可穿越)區域,如何使用tile屬性,如何使用可碰撞物體和動態修改地圖,如何確定你的主角沒有產生穿越。

Tiled Maps和碰撞

你可能注意到了,上一篇裡完成的遊戲,小忍者可以穿過各種障礙。它是忍者,不是上帝!
所以,我們要想辦法讓地圖裡的障礙物產生碰撞(不可穿越)。有很多辦法可以解決這個問題(包括使用物件層objects layers),但是我準備告訴你種新技術,我認為這種技術更有效,同時也是作為學習課程的好素材。使用meta layer和層屬性。

廢話少說,我們開始吧。
用Tiled Map Editor開啟之前建立的地圖,點選Layer選單的Add Tile Layer取名Meta。我們會在這一層上放置一些假的Tile指示特殊的tile元件。點選Map選單的New Tileset,選擇meta_tile.png圖片。將Margin和Spacing設定為1。
你會在Tilesets視窗看到meta_tiles的標籤。

這些tiles元件其實沒什麼特別的,只是帶有透明特性的紅色和綠色方塊。我們擬定紅色表示“可碰撞”的(綠色的後面會用到)。
選中Meta層,選擇印章(stamp)工具,選擇紅色tile元件。把它繪製到忍者不能穿越的地方。繪製好之後,看起來應該是這樣的:


接下來,我們要給這些Tile元件設定一些標記屬性,這樣在程式碼裡我們可以確定哪些tile元件是不可穿越的。在Tilesets窗口裡右鍵點選紅色tile元件。新增一個新的屬性Collidable”,設定值為true。

儲存地圖,回到xcode。修改HelloWorldScene.h檔案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Inside the HelloWorld class declaration
CCTMXLayer *_meta;
 
// After the class declaration
@property
 (nonatomic, retain) CCTMXLayer *meta;
[\cc]
修改HelloWorldScene.m檔案
[cc lang="objc"]
// Right after the implementation section
@synthesize meta = _meta;
 
// In dealloc
self.meta = nil;
 
// In init, right after loading background
self.meta = [_tileMap layerNamed:@"Meta"];
_meta.visible = NO;
 
// Add new method
- (CGPoint)tileCoordForPosition:(CGPoint)position {
    int x = position.x / _tileMap.tileSize.width;
    int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
    return ccp(x, y);
}

簡單的對上面的程式碼做一些解釋。我們定義了一個CCTMXLayer物件meta作為類成員。注意,我們將這個層設定為不可見,因為它只是用來處理碰撞的。
接下來我們編寫了一個tileCoordForPosition方法,用來將x,y座標轉換為地圖網格座標。地圖左上角為(0,0)右下角為(49,49)。

上面帶有座標顯示的截圖來自java版本的編輯器。順便說一聲,我覺得在Qt版本里這個功能可能不再會被移植了。
不管怎麼樣,用地圖網格座標要比用x,y座標方便。得到x座標比較方便,但是y座標有點麻煩,因為在cocos2d裡,是以左下作為原點的。也就是說,y座標的向量與地圖網格座標是相反的。
接下來,我們要修改一下setPlayerPosition方法。

1
2
3
4
5
6
7
8
9
10
11
12
CGPoint tileCoord = [self tileCoordForPosition:position];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid) {
    NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
    if (properties) {
        NSString *collision = [properties valueForKey:@"Collidable"];
        if (collision && [collision compare:@"True"] == NSOrderedSame) {
            return;
        }
    }
}
_player.position = position;

這裡,我們將主角的座標系從x,y座標(左下原點)系轉換為tile座標系(左上原點)。接下來,我們使用meta layer裡的tileGIDAt函式獲取tile座標系裡的GID。
噢?什麼是GID? GID應該是“全域性唯一標識”(我認為).但是在這個例子裡,把它作為tile層的id更貼切。
我們使用GID來查詢tile層的屬性,返回值是一個包含屬性列表的dictionary。我們檢查“Collidable”屬性是否設定為ture。如果是,則說明不可以穿越。
很好,編譯執行工程,你再也不能走入你在tile裡設定為紅色的區域了。

動態改變Tiled Maps

現在,你的小忍者可以在地圖上漫遊了,不過,整個遊戲還是略顯沉悶。
假設我們的小忍者非常餓,那麼我們設定一些食物,讓小忍者可以找到並吃掉它們。
為了實現這個想法,我們要建立一個前端層,承載所有用於觸碰(吃掉)的物體。這樣,我們可以在忍者吃掉它們的同時,方便的從層上刪除它。並且背景層不受任何影響。
開啟Tiled Map Editor,Layer選單的Add Tile Layer。命名新層為Foreground。選中這個層,新增一些可觸碰的物件。我比較喜歡用西瓜。

接下來,要讓西瓜變為可觸碰的。這次我們用綠色方塊來標記。記得要在meta_tiles裡做這件事。

同樣的,給綠色方塊新增屬性“Collectable”設定值為 “True”.
儲存地圖,回到xcode。修改程式碼:

1
2
3
4
5
6
//in HelloWorldScene.h:
// Inside the HelloWorld class declaration
CCTMXLayer *_foreground;
 
// After the class declaration
@property (nonatomic, retain) CCTMXLayer *foreground;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//in HelloWorldScene.m
// Right after the implementation section
@synthesize foreground = _foreground;
 
// In dealloc
self.foreground = nil;
 
// In init, right after loading background
self.foreground = [_tileMap layerNamed:@"Foreground"];
 
// Add to setPlayerPosition, right after the if clause with the return in it
NSString *collectable = [properties valueForKey:@"Collectable"];
if (collectable && [collectable compare:@"True"] == NSOrderedSame) {
    [_meta removeTileAt:tileCoord];
    [_foreground removeTileAt:tileCoord];
}

這裡有個基本的原則,要同時刪除meta layer 和the foreground layer的匹配物件。
編譯執行,小忍者可以吃到美味的甜西瓜了。

建立分數計數器
小忍者現有吃有喝很開心,但是,我們想知道到底他吃了多少個西瓜。
通常,我們在layer上看著順眼的地方加個label來顯示數量。但是,我們一直在移動層,這樣會給我們帶來很多的困擾。
這是一個演示在一個場景裡使用多個層的好例子。我們保留HelloWorld層來進行遊戲,同時,增加一個HelloWorldHud層用來顯示label(Hub = heads up display)。
當然,這兩個層需要一些方法來互相通訊。Hub層需要知道小忍者吃到了西瓜。有很多很多方法實現兩個層之間的通訊,但是我們使用盡量簡單的方法來實現。我 們會讓HelloWorld層管理一個HelloworldHub層的引用,在忍者遲到西瓜的時候,可以呼叫一個方法來通知Hub層。
修改程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// HelloWorldScene.h
// Before HelloWorld class declaration
@interface HelloWorldHud : CCLayer
{   
    CCLabel *label;
}
 
- (void)numCollectedChanged:(int)numCollected;
@end
 
// Inside HelloWorld class declaration
int _numCollected;
HelloWorldHud *_hud;
 
// After the class declaration
@property (nonatomic, assign) int numCollected;
@property (nonatomic, retain) HelloWorldHud *hud;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// HelloWorldScene.m
// At top of file
@implementation HelloWorldHud
 
-(id) init
{
    if ((self = [super init])) {
        CGSize winSize = [[CCDirector sharedDirector] winSize];
        label = [CCLabel labelWithString:@"0" dimensions:CGSizeMake(5020)
            alignment:UITextAlignmentRight fontName:@"Verdana-Bold" 
            fontSize:18.0];
        label.color = ccc3(0,0,0);
        int margin = 10;
        label.position = ccp(winSize.width - (label.contentSize.width/2) 
            - margin, label.contentSize.height/2 + margin);
        [self addChild:label];
    }
    return self;
}
 
- (void)numCollectedChanged:(int)numCollected {
    [label setString:[NSString stringWithFormat:@"%d", numCollected]];
}
 
@end
 
// Right after the HelloWorld implementation section
@synthesize numCollected = _numCollected;
@synthesize hud = _hud;
 
// In dealloc
self.hud = nil;
 
// Add to the +(id) scene method, right before the return
HelloWorldHud *hud = [HelloWorldHud node];    
[scene addChild: hud];
 
layer.hud = hud;
 
// Add inside setPlayerPosition, in the case where a tile is collectable
self.numCollected++;
[_hud numCollectedChanged:_numCollected];

沒什麼稀奇的,第二個層繼承CCLayer,並且在右下角添加了一個label。我們將第二個層新增到場景(Scene)裡並且把hub層的引用傳遞給HelloWorld層。然後修改HelloWorld層呼叫通知計數改變的方法。
編譯執行,應該可以在右下角看到吃瓜計數器了。

音效和音樂
眾所周知,沒有音效和音樂的遊戲,稱不上是個完整的遊戲。
接下來,我們做一些簡單的修改,讓我們的遊戲帶有音效和背景音。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// HelloWorldScene.m
// At top of file
#import "SimpleAudioEngine.h"
 
// At top of init for HelloWorld layer
[[SimpleAudioEngine sharedEngine] preloadEffect:@"pickup.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"hit.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"move.caf"];
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TileMap.caf"];
 
// In case for collidable tile
[[SimpleAudioEngine sharedEngine] playEffect:@"hit.caf"];
 
// In case of collectable tile
[[SimpleAudioEngine sharedEngine] playEffect:@"pickup.caf"];
 
// Right before setting player position
[[SimpleAudioEngine sharedEngine] playEffect:@"move.caf"];

接下來做點什麼呢?
通過這篇教程,你應該對coco2d有了一些基本的瞭解。
這裡是按照整篇教程完成的工程檔案,猛擊這裡下載
如果你感興趣,我的好朋友Geek和Dad編寫了一篇後續教程:Enemies and Combat: How To Make a Tile-Based Game with Cocos2D Part 3! 。這篇教程將告訴你,如何在遊戲裡新增敵人,武器,勝負場景等。

相關推薦

[]在Tiled Map使用碰撞檢測() TMX地圖碰撞檢測

On 2010年06月20日, in iPhone, by 毛叔 在上一篇裡,我們已經學會了如何建立一個基於tiled map的簡單遊戲。學會了如何製作地圖,如何將地圖載入到遊戲,如何讓主角在螢幕上移動。 在這篇教程裡,我們將學習如何在地圖裡建立可碰撞(不可穿越)區域,

使用BFS,在地圖,尋求特定走法的最短距離。

這個問題,是我從USACO Camelot問題中,抽出的一個小問題,比較典型。 在一個二維地圖中,給定一個初始點,和相應的行走規則,求從這個初始點可到達的點的最小步數。 舉個例子:(camelot

ros如何根據map.yaml和tf資料確定地圖機器人的位置

地圖的yaml格式中有其中origin是建圖時機器人的初始位置,單位是米m問題1:如何將orgin轉換成實際地圖中的位置?1.確定地圖的座標系,為最右上角的畫素為座標(0,0)整副地圖都處於座標系的第三像限2.解析origin x=-2.5m y=-1.6m,將x,y的值除以

[轉載] 在Tiled Map使用碰撞檢測

網上這篇教程的轉載非常氾濫,本來以為沒什麼參考價值。但是當我實際用上 tiledmap 做點東西時,發現TiledMap軟體本身,以及TMXTiledMap類的使用確實存在一些疑惑。所以,對於想真正使用 tiledmap 軟體做地圖的童鞋來說,這篇文章還是值得仔細看一遍的

2D空間使用Quadtree四叉樹進行碰撞檢測優化

很多遊戲中都需要使用碰撞檢測演算法檢測兩個物體的碰撞,但通常這些碰撞檢測演算法都很耗時很容易拖慢遊戲的速度。這裡我們學習一下使用四叉樹來對碰撞檢測進行優化,優化的根本是碰撞檢測時跳過那些明顯離得很遠的物體,加快檢測速度。 【注:這裡演算法的實現使用的J

cocos2d-x 旅程開始--(實現瓦片地圖碰撞檢測

       轉眼隔了一天了,昨天搞了整整一下午加一晚上,楞是沒搞定小坦克跟磚頭的碰撞檢測,帶著個問題睡覺甚是難受啊!還好今天弄成功了,不過感覺程式不怎麼穩定啊。而且發現自己寫的東西讓我重寫一遍的話我肯定寫不出來,還要繼續學習啊! 上次的進度:            實現

2D遊戲碰撞檢測:圓形與矩形碰撞檢測(Javascript&C++版)

這幾天放寒假了,時間也多了起來,當然又有時間搞搞程式了。哈哈~昨天在開發我的塔防遊戲時突然發現人物實際攻擊範圍比規定的範圍小,按理說應該是一樣大的,但偏偏不是,我被這個問題搞得糊里糊塗的,一直沒想出問題所在。最後詢問了一個程式高手——我哥哥。他雖然是搞C++的,但聽了我程式碼

關於cocosdx載入tmx地圖和一些碰撞檢測的問題

這篇文章將的是關於一些載入tmx地圖和實現精靈與金幣的碰撞檢測的問題。 寫這篇文章之前看了很多文章,是、但是個人感覺都說的不明瞭,所以我做出來之後就在微博上來與大家分享一個明瞭的方法,希望能對新手有所幫助。 首先我們需要一張地圖,這張地圖有一些屬性: 大家都看見這張地圖了,

值影象如何檢測

霍夫變換是影象處理中從影象中識別幾何形狀的基本方法之一,應用很廣泛,也有很多改進演算法。最基本的霍夫變換是從黑白影象中檢測直線(線段)。  我們先看這樣一個問題:設已知一黑白影象上畫了一條直線,要求出這條直線所在的位置。我們知道,直線的方程可以用y=k*x+b 來表示,其中

劍指Offer之進制1的個數

基於 不變 () 分析 private [] 一位 code 一個數  思路分析:   首先分析把一個數減去1的情況,如果一個整數不等於0,那麽改整數的二進制表示其中至少有一位是1.先假設這個數的最右邊是1,那麽減去1時,最後一位變成0而其他所有位都保持不變。也就是最後一位

尋找叉樹的最低公共祖先結點----LCA(Lowest Common Ancestor )問題(遞歸)

求解 mon etl 轉換成 right push_back 問題 off == 轉自 劍指Offer之 - 樹中兩個結點的最低公共祖先 題目: 求樹中兩個節點的最低公共祖先。 思路一: ——如果是二叉樹,而且是二叉搜索樹,那麽是可以找到公共節點的。 二叉搜索樹都是排序

() C/C++判斷文件或文件夾是否存在

c/c++ 文件 文件夾 存在 方法1. access函數 適用範圍:所有C/C++項目 頭文件: #include < io.h> 函數原型: intaccess(const char *filename, int mode);

ASP.NETC#生成維碼

保存 mss 軟件 clear 所有 str drawing for visual ASP.NET中用C#語言編寫網頁,將自己輸入的文字生成二維碼。 工具/原料 已安裝好VS2010或其他版本VS軟件的電腦一臺 1、新建 1

(十)Hibernate的多表操作(1):單向多對一

art 保存 int gen round t對象 情況 映射文件 拋出異常 由“多”方可知“一”方的信息,比如多個員工使用同一棟公寓,員工可以知道公寓的信息,而公寓無法知道員工的信息。 案例一: pojo類 public class Department {

、Ansibleplaybook的變量

yml -1 images span etc clas spa vim 變量 先看看debug模塊的使用: msg:輸出調試信息 var:將某個任務執行的輸出作為變量傳給debug模塊,debug模塊將其打印輸出 verbosity:debug的任務級別 1:在play

【只怕沒有幾個人能說清楚】系列之:Unity的特殊文件夾

物體 avi ebp time 編輯模式 tro hive 預覽 打包 參考:http://www.manew.com/thread-99292-1-1.html 1. 隱藏文件夾 以.開頭的文件夾會被忽略。在這種文件夾中的資源不會被導入,腳本不會被編譯。也不會出現

(我是初學者)第一次項目開發()開發遇到的問題和註意事項

持久層 數據庫 認識 碼代碼 操作 出錯 排序 文檔 項目 這周正式開始做項目練習,這才發現實際去做的時候會遇到和出現很多的問題 在這裏說一說我的體會,請指正 首先,實體類 1、實體類中有哪些屬性,類型是什麽,並根據屬性建立sql的相應表格, 2、哪些屬性需要在寫在實體

前序序構建叉樹

[] pri light index blog log highlight tree cnblogs public Node PreMidToTree(int[] pre,int[] mid) { if (pre == null || mi

binary-tree-inorder-traversal——叉樹序遍歷

str () init inorder code while urn value public Given a binary tree, return the inordertraversal of its nodes‘ values. For example:Given

對jquery的$.ajax次封裝 從而多次調用 今天一整天都在想這個事情

send attribute 面試官 clas display str kit || enc 當然了 我封裝的是$.ajax 可以傳參數 多次調用請求接口 為啥我們這地方不註重前端呢 我都不知道為啥去堅持 不說了 上代碼 js文件 $ajax.js $(fun