日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

box2d 碰撞检测_(译)如何使用box2d来做碰撞检测(且仅用来做碰撞检测)

發布時間:2023/12/19 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 box2d 碰撞检测_(译)如何使用box2d来做碰撞检测(且仅用来做碰撞检测) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

程序截圖:

當你使用cocos2d來制作一個游戲的時候,有時,你可能想使用cocos2d的action來移動游戲中的對象,而不是直接使用Box2d物理引擎來做。然而,這并不是說你不能使用box2d提供的強大的碰撞檢測功能!

這個教程的目的,就是一步一步地向你展示如何僅使用box2d來做碰撞檢測---沒有物理效果。我們將創建一個簡單的demo,里面有一輛車在屏幕上奔馳,當它撞到一只貓后就會放聲大笑。是的,我明白,這樣做太殘忍了。

在這篇教程中,我們會引入一些新的、有趣的概念,比如使用sprite sheets,利用Zwoptex工具來制作它。還會涉及box2d的debug drawing和VertexHelper這個工具。

這個簡單假設你已經閱讀過前面的一系列的cocos2d和box2d的教程了(如果沒有,最好把前面的教程先看一遍),或者你有相關經驗也可以。

還有,在我忘記之前--特別要感謝Kim在評論中建議我寫一篇這樣的教程。

Sprites and Sprite Sheets

在我們開始之前,需要簡單地介紹一下sprites和spritesheets。

目前為止,當我們使用cocos2d的時候,我們都是直接使用CCSprite類,然后傳遞一個精靈圖片給它。但是,根據cocos2d最佳實踐,如果使用spritesheet來做的話,那樣會極大地提高效率。

上圖就是一個spritesheet的樣例,這個圖在cocos2d的樣例代碼中可以找到。簡言之,spritesheet就是一張大的圖片,它能夠被裁成許多小的子圖片。為了指定spritesheet中的每個子圖片的位置,你需要為每個圖片指定一個坐標,通常是一個矩形。比如,下面的代碼展示了,如何把spritesheet中前四張圖片摳出來:

CCSpriteSheet*sheet=[CCSpriteSheet

spriteSheetWithFile:@"grossini_dance_atlas.jpg"capacity:1];

CCSprite*sprite1=[CCSprite spriteWithTexture:sheet.texture rect:

CGRectMake(85*0,121*1,85,121)];

CCSprite*sprite2=[CCSprite spriteWithTexture:sheet.texture rect:

CGRectMake(85*1,121*1,85,121)];

CCSprite*sprite3=[CCSprite spriteWithTexture:sheet.texture rect:

CGRectMake(85*2,121*1,85,121)];

CCSprite*sprite4=[CCSprite spriteWithTexture:sheet.texture rect:

CGRectMake(85*3,121*1,85,121)];

你可以會說,寫一大堆硬編碼的坐標太麻煩了,太煩人了!幸運的是,Robert Payne已經寫好了一個非常方便地web工具,叫做Zwoptex,它可以創建精靈表單(spritesheet),同時會導出每個子圖片的坐標,這樣你在cocos2d里面使用這些子圖片就會非常方便了。

制作精靈表單

在我們開始之前,你需要一張圖片。你可以下載我老婆制作的車和貓的圖片,或者使用你自己的。接下來,打開瀏覽器,加載Zwoptex主頁。你將會看到下面的屏幕:

一旦加載完畢后,點擊Zwoptex的File菜單,然后點Import Images。選擇你剛剛下載的車和貓的圖片,然后點click,這時,你的圖片應該出現會互相重疊在一起。拖動其中一張,這樣會看得更清楚一些。

注意,這些圖片已經被自動地把圖片周圍的空白部分給去掉了。這并不是我們想要的(后面你會知道為什么),因此,用鼠標把這兩張圖片都框住,然后選擇菜單Modify\Untrim Selected Images。

現在,我們的圖片看起來非常好了。點擊Arrange\Complex by Height(no spacing),然后它們會排列地更加整齊。

最后,讓我們把畫布(canvas)的大小調整為合適的大小。點擊Modify\Canvas Width,并把它設置為128px。同樣的,點擊Modify\Canvas Height,然后把它設置為64px。你的屏幕最后看起來應該是下面這個樣子。

最后,是時候導出sprite sheet和相應的坐標了!點擊File\Export Texture,然后保存sprite sheet為“sprites.jpg”。然后點擊File\Export Coordinates“并且保存為”sprites.plist“。注意,這里必須把spritesheet和坐標文件的名字取成相同的,因為,spritesheet為假設它的坐標文件為相應名字的plist文件。

接下來,打開sprites.plist。你會看到Zwopte已經自動地幫你把原圖中每一個子圖片的坐標計算出來了,并且存儲成了一個plist文件。我們接下來就可以使用這些坐標,而不用手工去輸入它們了!

從Sprite sheet中添加我們的精靈

好,是時候寫一些代碼了!

打開Xcode并創建一個新的工程,選擇 cocos2d-0.99.1 Box2d Application template,取名為Box2DCollision。然后把自帶的樣例代碼全部刪除,你可能從彈球的教程中找到具體的做法。

當然,我們要在HelloWorldScene.mm的頂部加入下面一行代碼:

#definePTM_RATIO 32.0

如果你不明白我在這里講的是些什么,你可能需要看看彈球的教程來獲取更多的信息。

接下來,讓我們把sprite sheet和坐標plist文件都加入到工程中去。把sprites.jpg和sprites.plist文件都拖到Resouces文件夾中,同時確保 ?“Copy items into destination group’s folder (if needed)”被復選中。

然后,在HelloWorldScene.h文件的HelloWorld類中,添加下面的成員變量:

CCSpriteSheet*_spriteSheet;

現在,讓我們修改HelloWorldScene.mm中的init方法來加載我們的spritesheet和plist文件。具體修改如下:

-(id)init {if((self=[super init])) {//Create our sprite sheet and frame cache_spriteSheet=[[CCSpriteSheet spriteSheetWithFile:@"sprites.jpg"capacity:2] retain];

[[CCSpriteFrameCache sharedSpriteFrameCache]

addSpriteFramesWithFile:@"sprites.plist"];

[self addChild:_spriteSheet];

[self spawnCar];

[self schedule:@selector(secondUpdate:) interval:1.0];

}returnself;

}

第一件事情就是創建一個spritesheet對象,它是一個可以用來高效地繪制它的所有的CCSprite結點的對象。很明顯,這些CCSprite必須共享相同的紋理(texture)。當我們把車子和貓加入到場景中的時候,我們需要將它們當作spritesheet的孩子加進去。

然后,我們使用CCSpriteFrameCache類來加載坐標屬性列表文件。這個函數會自動地查找一個與之同名的圖片(即sprites.jpg).這也就是前面說的,為什么要把”sprites.jpg“和"sprites.plist”取成相同名字的原因。

在這之后,我們調用一個函數在場景中顯示一輛車。同時,還設置一個計時器,每隔一秒調用一次secondUpdate函數。

接下來,讓我們實現spawnCar方法。我們的做法是讓車子永遠地在屏幕中間做路徑為三角形的運動。在init函數的上面添加下面函數代碼:

-(void)spawnCar {

CCSprite*car=[CCSprite spriteWithSpriteFrameName:@"car.jpg"];

car.position=ccp(100,100);

car.tag=2;

[car runAction:[CCRepeatForever actionWithAction:

[CCSequence actions:

[CCMoveTo actionWithDuration:1.0position:ccp(300,100)],

[CCMoveTo actionWithDuration:1.0position:ccp(200,200)],

[CCMoveTo actionWithDuration:1.0position:ccp(100,100)],

nil]]];

[_spriteSheet addChild:car];

}

注意,這里創建sprite的時候,不是使用spriteWithFile,而是使用spriteWithSpriteFrameName。這里指的是使用spritesheet紋理中代表Car的圖片來創建精靈。

還有一點需要注意,我們不是把Car作為HelloWorld層的函數添加進去,而是把Car作為Spritesheet的孫子添加進去的。

這個函數的后面部分你應該比較熟悉了。因此,讓我們添加一些貓吧!在上面的spawnCar方法后面添加下面的方法:

-(void)spawnCat {

CGSize winSize=[CCDirector sharedDirector].winSize;

CCSprite*cat=[CCSprite spriteWithSpriteFrameName:@"cat.jpg"];intminY=cat.contentSize.height/2;intmaxY=winSize.height-(cat.contentSize.height/2);intrangeY=maxY-minY;intactualY=arc4random()%rangeY;intstartX=winSize.width+(cat.contentSize.width/2);intendX=-(cat.contentSize.width/2);

CGPoint startPos=ccp(startX, actualY);

CGPoint endPos=ccp(endX, actualY);

cat.position=startPos;

cat.tag=1;

[cat runAction:[CCSequence actions:

[CCMoveTo actionWithDuration:1.0position:endPos],

[CCCallFuncN actionWithTarget:self

selector:@selector(spriteDone:)],

nil]];

[_spriteSheet addChild:cat];

}-(void)spriteDone:(id)sender {

CCSprite*sprite=(CCSprite*)sender;

[_spriteSheet removeChild:sprite cleanup:YES];

}-(void)secondUpdate:(ccTime)dt {

[self spawnCat];

}

你應該對上面的代碼線路熟悉了,如果不熟悉,建議看相關的教程后再繼續。編譯并運行代碼,如果一切ok,你將會看到一輛車在屏幕上來回動,同時有一只貓從右至左穿過屏幕。接下來,我們將添加一些碰撞檢測的代碼。

為這些精靈創建Box2d的body

接下來的一步就是為每個精靈創建一個body,這樣box2d就能知道它們的位置了,這樣的話,當碰撞發生的時候,我們就可以被告知了。下面所做的事情和之前的教程做法差不多。

然后,這一次,我們不是更新box2d的body,然后再更新sprite。這里,我們是先更新sprite(使用action或者別的),然后再更新box2d的body。

因此,讓我們首先創建world。打開HelloWorldScene.h,并在文件頂部添加下面的代碼:

#import"Box2D.h"

然后在HelloWorld類中添加下面的成員變量:

b2World*_world;

然后在HeloWorldScene.mm的init方法中加入下列代碼:

b2Vec2 gravity=b2Vec2(0.0f,0.0f);booldoSleep=false;

_world=newb2World(gravity, doSleep);

注意,這里有兩件事情非常重要!首先,我們把重力向量設置成(0,0)。因為我們并不想讓這些對象自動運動,而是人為控制其運動。其次,我們告訴box2d不能讓這些對象sleep。這一點非常重要,因為我們是人為地移動對象,所以必須設置。

然后,在spawnCat方法上面添加下列代碼:

-(void)addBoxBodyForSprite:(CCSprite*)sprite {

b2BodyDef spriteBodyDef;

spriteBodyDef.type=b2_dynamicBody;

spriteBodyDef.position.Set(sprite.position.x/PTM_RATIO,

sprite.position.y/PTM_RATIO);

spriteBodyDef.userData=sprite;

b2Body*spriteBody=_world->CreateBody(&spriteBodyDef);

b2PolygonShape spriteShape;

spriteShape.SetAsBox(sprite.contentSize.width/PTM_RATIO/2,

sprite.contentSize.height/PTM_RATIO/2);

b2FixtureDef spriteShapeDef;

spriteShapeDef.shape=&spriteShape;

spriteShapeDef.density=10.0;

spriteShapeDef.isSensor=true;

spriteBody->CreateFixture(&spriteShapeDef);

}

這段代碼對你來說應該比較熟悉了---它和我們在breakout教程中是一樣的。然后,這里有一點區別,我們把shape定義的isSensor成員設置成了true。

根據Box參考手冊,如果你想讓對象之間有碰撞檢測但是又不想讓它們有碰撞反應,那么你就需要把isSensor設置成true。這正是我們想要的效果!

接下來,在spawnCat的最后一行addChild之前添加下列代碼:

[self addBoxBodyForSprite:cat];

同樣的,你需要在spawnCar的相應位置添加下列代碼:

[self addBoxBodyForSprite:car];

當我們的sprites被銷毀的時候,我們需要銷毀Box2d的body。因此,把你的spriteDone方法改寫成下面的形式:

-(void)spriteDone:(id)sender {

CCSprite*sprite=(CCSprite*)sender;

b2Body*spriteBody=NULL;for(b2Body*b=_world->GetBodyList(); b; b=b->GetNext()) {if(b->GetUserData()!=NULL) {

CCSprite*curSprite=(CCSprite*)b->GetUserData();if(sprite==curSprite) {

spriteBody=b;break;

}

}

}if(spriteBody!=NULL) {

_world->DestroyBody(spriteBody);

}

[_spriteSheet removeChild:sprite cleanup:YES];

}

現在,最重要的一部分,我們需要根據sprite的位置改變來更新box2d的body。因此,在init方法里面添加下面一行代碼:

[self schedule:@selector(tick:)];

然后,把tick方法寫成下面的形式:

-(void)tick:(ccTime)dt {

_world->Step(dt,10,10);for(b2Body*b=_world->GetBodyList(); b; b=b->GetNext()) {if(b->GetUserData()!=NULL) {

CCSprite*sprite=(CCSprite*)b->GetUserData();

b2Vec2 b2Position=b2Vec2(sprite.position.x/PTM_RATIO,

sprite.position.y/PTM_RATIO);

float32 b2Angle=-1*CC_DEGREES_TO_RADIANS(sprite.rotation);

b->SetTransform(b2Position, b2Angle);

}

}

}

這個方法和我們之前在breakout中寫的tick方法差不多,唯一的差別就是,我們現在是基于cocos2d的精靈位置來更新box2d的body位置。

編譯并運行工程,可能看起來和之前并沒有什么差別。你可能會想,到底我們剛剛寫了這么多代碼有用沒啊!好,接下來我就會展示給你看---激活debug drawing!

激活 Box2D 的Debug Drawing

因為,你是使用box2d的模板建的項目,所以里面已經包含了一個GLES-Render.h和GLES-Render.mm文件,如果想要激活debug drawing,有這兩個文件就足夠了!

接下來,讓我們在HelloWorldScene.h的頂部包含下面的頭文件:

#import"GLES-Render.h"

然后,在HelloWorld類中添加以下成員變量:

GLESDebugDraw*_debugDraw;

接下來,在init方法中添加下面的代碼:

//Enable debug draw_debugDraw=newGLESDebugDraw( PTM_RATIO );

_world->SetDebugDraw(_debugDraw);

uint32 flags=0;

flags+=b2DebugDraw::e_shapeBit;

_debugDraw->SetFlags(flags);

這段代碼是創建一個GLESDebug類,并且把它注冊到world對象里面。我們傳遞一個flag來指定我們需要顯示的仿真細節---這里我們只想顯示box2d的shape。對于你可以設置的所有的flag標志,可以參考b2WorldCallbacks.h文件。

接下來,我們需要添加一個draw方法。在init方法后面添加draw方法:

-(void) draw

{

glDisable(GL_TEXTURE_2D);

glDisableClientState(GL_COLOR_ARRAY);

glDisableClientState(GL_TEXTURE_COORD_ARRAY);

_world->DrawDebugData();

glEnable(GL_TEXTURE_2D);

glEnableClientState(GL_COLOR_ARRAY);

glEnableClientState(GL_TEXTURE_COORD_ARRAY);

}

老實說,我現在仍然對opengl陌生,因此,我并不是很清楚這里的opengl代碼做了些什么事。然后,這是激活debug draw的標準代碼,它可以工作!:)

最后需要注意的一點,在dealloc函數中添加清理代碼:

-(void)dealloc {

delete _world;

delete _debugDraw;

[super dealloc];

}

現在,編譯并運行工程,你將會看到所有的box2d shape周圍都有一個粉紅色的矩形區域。如果一切ok的話,你會看到這些粉紅色的shape會跟著sprite運動。

碰撞檢測

現在,是時候壓死幾只貓了!

像之前的breakout游戲一樣,我們將往world對象里面注冊一個contact listener對象。你可以下載我為這個教程制作的contact listener代碼,然后把 MyContactListener.h 和 MyContactListener.mm兩個文件添加到工程中去:

同時,你還可以下載我為本教程制作的美妙的音效。

回到代碼。在HelloWorldScene.h中添加下面的代碼:

#import"MyContactListener.h"#import"SimpleAudioEngine.h"

然后,在HelloWord類中添加下面成員變量:

MyContactListener*_contactListener;

然后,在init方法中加入下面的代碼:

//Create contact listener_contactListener=newMyContactListener();

_world->SetContactListener(_contactListener);//Preload effect[[SimpleAudioEngine sharedEngine] preloadEffect:@"hahaha.caf"];

最后,在tick方法的底部加入下面的代碼:

std::vectortoDestroy;

std::vector::iterator pos;for(pos=_contactListener->_contacts.begin();

pos!=_contactListener->_contacts.end();++pos) {

MyContact contact=*pos;

b2Body*bodyA=contact.fixtureA->GetBody();

b2Body*bodyB=contact.fixtureB->GetBody();if(bodyA->GetUserData()!=NULL&&bodyB->GetUserData()!=NULL) {

CCSprite*spriteA=(CCSprite*) bodyA->GetUserData();

CCSprite*spriteB=(CCSprite*) bodyB->GetUserData();if(spriteA.tag==1&&spriteB.tag==2) {

toDestroy.push_back(bodyA);

}elseif(spriteA.tag==2&&spriteB.tag==1) {

toDestroy.push_back(bodyB);

}

}

}

std::vector::iterator pos2;for(pos2=toDestroy.begin(); pos2!=toDestroy.end();++pos2) {

b2Body*body=*pos2;if(body->GetUserData()!=NULL) {

CCSprite*sprite=(CCSprite*) body->GetUserData();

[_spriteSheet removeChild:sprite cleanup:YES];

}

_world->DestroyBody(body);

}if(toDestroy.size()>0) {

[[SimpleAudioEngine sharedEngine] playEffect:@"hahaha.caf"];

}

這段代碼和我們之前的breakout教程中的代碼基本上差不多,因此你應該比較熟悉了。

還有一件事別忘了,往dealloc方法里面加入清理代碼!這個灰常重要!

delete _contactListener;

[_spriteSheet release];

再運行一下工程看看!

調整shape的邊界

你可能已經注意到了,我們的box2d shape的邊界并不是和sprite的邊界完全吻合。對于有些碰撞檢測要求不是特別精確的游戲,這也許夠了,但是,有些游戲確不行!我們需要嚴格地定義shape的邊界和精靈的邊界重合在一起。

在Box2d里面,你可以通過指定shape的頂點來指定shape的形狀。然后,硬編碼這些頂點數據會很耗時,而且容易出錯。幸運的是, Johannes Fahrenkrug已經開發出了一個非常方便的工具,叫做 VertexHelper,它可以用來非常方便地定義頂點,而且可以導出box2d所需要的格式的數據。

好了,先去下載VertexHelper吧。他是一個Mac應用程序,同時包含了源代碼,因此,你只需要打開 ?VertexHelper.xcodeproj,然后編譯并運行就可以了。當運行工程的時候,你會看到下面的屏幕輸出:(為了方便起見,你可以把編譯好的工程放到Application文件夾下面,以后就直接打開就可以了)

繼續,把sprites.jpg拖到VertexHelper中,拖的時候,放置在 “Drop Sprite Image here”標簽上面。在Rows/Cols部分,把相應的數字設置成1和2.VertexHelper會自動地把圖片劃分成兩個部分。

接下來,把”Edit Mode"復選框打上勾,緊接著,你需要按照逆時針方向在精靈的四周定義一些頂點。注意,box2d將自動地把最后一個頂點與第一個頂點連接起來,因此,我們不需要連接它們。

另外一件非常重要的事情是,當你定義頂點的時候,你需要確保定義的多邊形是凸多邊形。這意味著多邊形的內部沒有一個角大于180度。或者說是,多邊形內任何兩個頂點的連線都在多邊形的內部。如果你對這個定義不是很清楚的話,建議百度一下凸多邊形和凹多邊形。當然,也可以看看下面這個js制作的demo。

最后,注意box2d定義了b2_maxPolygonVertices,它限制了每一個shape最多可以定義的頂點的個數,默認值是8.當然,你可以在b2Settings.h中把這個值改掉。但是,這個教程中,我們只需要8個頂點就夠了。

(這個過程最好用一個視頻來演示一下,不過翻不了墻,也看不了了。不過自己多摸索一下,這個工具還是很容易使用的)

一旦你做完之后,在下拉列表中選擇Box2d,Style選擇“Initialization”。然后把右邊生成的代碼copy下來并粘貼到工程中去。

好,讓我們把工程中的shape定義換一下。打開HelloWorldScene.mm,然后修改 addBoxBodyForSprite方法。首先注釋掉一些代碼,如下圖所示:

/*spriteShape.SetAsBox(sprite.contentSize.width/PTM_RATIO/2,

sprite.contentSize.height/PTM_RATIO/2);*/if(sprite.tag==1) {//Uncomment this and replace the number with the number of vertices//for the cat that you defined in VertexHelper//int num = 6;//b2Vec2 verts[] = {b2Vec2(4.5f / PTM_RATIO, -17.7f / PTM_RATIO),//b2Vec2(20.5f / PTM_RATIO, 7.2f / PTM_RATIO),//b2Vec2(22.8f / PTM_RATIO, 29.5f / PTM_RATIO),//b2Vec2(-24.7f / PTM_RATIO, 31.0f / PTM_RATIO),//b2Vec2(-20.2f / PTM_RATIO, 4.7f / PTM_RATIO),//b2Vec2(-11.7f / PTM_RATIO, -17.5f / PTM_RATIO)};//Then add this//spriteShape.Set(verts, num);}else{//Do the same thing as the above, but use the car data this time}

再多啰嗦一句:在選擇Style的時候一定要選擇“Initialization”,因為b2PolygonShape:Set方法對于“Initialization”會自動計算shape的法向量和質心。

一旦做完之后,編譯并運行工程。這時,你會看到shape的邊界基本上和sprite的邊界吻合在一起了,當然碰撞檢測就會更加真實了!

恩,你已經看到了使用box2d來做碰撞檢測的強大了!

發揮想象,去做更多好玩的物理效果游戲吧!

總結!

這里有本教程的完整源代碼。

注意,這里用來做碰撞檢測的代碼,僅僅是box2d里面實現碰撞檢測的一種方式。Lam在cocos2d論壇里面提出了另外一種方法,使用b2CollidePolygons來做碰撞檢測,詳情請參考這里。如果你只想做碰撞檢測,你可能想看看lam的實現。

對于其它開發者全體在他們的項目里面使用cocos2d和box2d,我非常之感興趣!你在你的項目中使用box2d嗎?或者僅僅用它來做碰撞檢測,或者根本不使用它?或者如果你使用box2d來做碰撞檢測,你是怎么做的呢?

歡迎大家踴躍發言,分享自己的經驗和看法,謝謝!

總結

以上是生活随笔為你收集整理的box2d 碰撞检测_(译)如何使用box2d来做碰撞检测(且仅用来做碰撞检测)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。