随笔 - 132  文章 - 51  trackbacks - 0
<2011年4月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用链接

留言簿(7)

随笔分类

随笔档案

文章分类

文章档案

cocos2d-x

OGRE

OPenGL

搜索

  •  

最新评论

阅读排行榜

评论排行榜

本教程基于子龙山人翻译的cocos2d的IPHONE教程,用cocos2d-x for XNA引擎重写,加上我一些加工制作。教程中大多数文字图片都是原作者和翻译作者子龙山人,还有不少是我自己的理解和加工。感谢原作者的教程和子龙山人的翻译。本教程仅供学习交流之用,切勿进行商业传播。

子龙山人翻译的Iphone教程地址:http://www.cnblogs.com/zilongshanren/archive/2011/06/08/2074582.html

Iphone教程原文地址:http://www.raywenderlich.com/606/how-to-use-box2d-for-just-collision-detection-with-cocos2d-iphone

当你使用cocos2d-x来制作一个游戏的时候,有时,你可能想使用cocos2d-x的action来移动游戏中的对象,而不是直接使用Box2d物理引擎来做。然而,这并不是说你不能使用box2d提供的强大的碰撞检测功能!

  这个教程的目的,就是一步一步地向你展示如何仅使用box2d来做碰撞检测---没有物理效果。我们将创建一个简单的demo,里面有一辆车在屏幕上奔驰,当它撞到一只猫后就会放声大笑。是的,我明白,这样做太残忍了。

这个简单假设你已经阅读过前面的一系列的cocos2d-xbox2d的教程了(如果没有,最好把前面的教程先看一遍),或者你有相关经验也可以。

开始吧

现在打开VS2010,新建一个cocos2d-x项目,并且命名为cocos2dBox2DCollisionDemo,因为我们不需要社区支持,所以新建时去掉那个Live社区的勾。接着干和以前教程一样的事情,修复DLL,添加DLL。并且添加BOX2D.XNA.DLL这个引用。

现在下载我们需要的两个图片,猫(http://115.com/file/c2kz3opf#cat.png)和车(http://115.com/file/e7ist5yn#car.png),并且添加到images文件夹。

这次,我们直接在HelloWorld这个层做修改。打开HelloWorldScene.cs这个文件。先把init()清理下,把那些label,图片精灵等代码删除。

现在往这个类里面添加一个声明:

public static double PTM_RATIO = 32.0;  

如果你不明白我在这里讲的是些什么,你可能需要看看以前的
BOX2D的教程来获取更多的信息。

接着修改Init的代码为:

public override bool init()   
{   
    CCDirector.sharedDirector().deviceOrientation 
= ccDeviceOrientation.CCDeviceOrientationLandscapeLeft;   
    
if (!base.init())   
    
{   
        
return false;   
    }
   
   
    CCLabelTTF title 
= CCLabelTTF.labelWithString("Collision""Arial"24);   
    title.position 
= new CCPoint(400400);   
    
this.addChild(title);   
   
    spawnCar();   
   
    
this.schedule(secondUpdate, 1.0f);   
    
return true;   
}
  



我们先添加一个Label,作用就是为了能够使精灵透明化,不添加的话精灵的背景是黑色的。在这之后,我们调用一个函数在场景中显示一辆车。同时,还设置一个计时器,每隔一秒调用一次secondUpdate函数。

  接下来,让我们实现spawnCar方法。我们的做法是让车子永远地在屏幕中间做路径为三角形的运动。在init函数的后面添加下面函数代码:

  1. void spawnCar()  
  2. {  
  3.     CCSprite car = CCSprite.spriteWithFile(@"images/car");  
  4.     car.position = new CCPoint(100, 100);  
  5.     car.tag = 2;  
  6.   
  7.     car.runAction(CCRepeatForever.actionWithAction(  
  8.         (CCActionInterval)CCSequence.actions(  
  9.         CCMoveTo.actionWithDuration(1.0f, new CCPoint(300, 100)),  
  10.         CCMoveTo.actionWithDuration(1.0f, new CCPoint(200, 200)),  
  11.         CCMoveTo.actionWithDuration(1.0f, new CCPoint(100, 100))  
  12.         )));  
  13.     this.addChild(car);  
  14. }  

这个函数你应该比较熟悉了。那么,让我们添加一些猫吧!在上面的spawnCar方法后面添加下面的方法:

  1.          void spawnCat()  
  2.         {  
  3.             CCSize winSize = CCDirector.sharedDirector().getWinSize();  
  4.             CCSprite cat = CCSprite.spriteWithFile(@"images/cat");  
  5.             float minY = cat.contentSize.height / 2;  
  6.             float maxY = winSize.height - cat.contentSize.height / 2;  
  7.             float rangeY = maxY - minY;  
  8.             Random random = new Random();  
  9.             float acturalY = random.Next() % rangeY;  
  10.   
  11.   
  12.             float startX = winSize.width + cat.contentSize.width / 2;  
  13.             float endX = -cat.contentSize.width / 2;  
  14.             CCPoint startPos = new CCPoint(startX, acturalY);  
  15.             CCPoint endPos = new CCPoint(endX, acturalY);  
  16.   
  17.   
  18.             cat.position = startPos;  
  19.             cat.tag = 1;  
  20.   
  21.   
  22.             cat.runAction(CCSequence.actions(  
  23.                 CCMoveTo.actionWithDuration(1.0f, endPos), CCCallFuncN.actionWithTarget(this, spriteDone)));  
  24.             this.addChild(cat);  
  25.         }  
  26.   
  27.   
  28.         void spriteDone(object sender)  
  29.         {  
  30.             CCSprite sprite = sender as CCSprite;  
  31.             this.removeChild(sprite, true);  
  32.         }  
  33.   
  34.   
  35.         void secondUpdate(float dt)  
  36.         {  
  37.             this.spawnCat();  
  38.         }  

你应该对上面的代码线路熟悉了,如果不熟悉,建议看相关的教程后再继续。编译并运行代码,如果一切ok,你将会看到一辆车在屏幕上来回动,同时有一只猫从右至左穿过屏幕。接下来,我们将添加一些碰撞检测的代码。



为这些精灵创建Box2d的body

  接下来的一步就是为每个精灵创建一个body,这样box2d就能知道它们的位置了,这样的话,当碰撞发生的时候,我们就可以被告知了。下面所做的事情和之前的教程做法差不多。

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

现在我们先在HelloWorldScene里面添加命名空间的引用。

  1. using Box2D.XNA;  

然后往类中添加变量声明;

  1. World world;  

然后在init方法中加入下列代码:

  1. Vector2 gravity = new Vector2(0, 0);  
  2. bool doSleep = false;  
  3. world = new World(gravity, doSleep);  

注意,这里有两件事情非常重要!首先,我们把重力向量设置成(0,0)。因为我们并不想让这些对象自动运动,而是人为控制其运动。其次,我们告诉box2d不能让这些对象sleep。这一点非常重要,因为我们是人为地移动对象,所以必须设置。

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

  1. void addBoxBodyForSprite(CCSprite sprite)  
  2. {  
  3.     BodyDef spriteBodyDef = new BodyDef();  
  4.     spriteBodyDef.type = BodyType.Dynamic;  
  5.     spriteBodyDef.position = new Vector2((float)(sprite.position.x / PTM_RATIO), (float)(sprite.position.y / PTM_RATIO));  
  6.     spriteBodyDef.userData = sprite;  
  7.     Body spriteBody = world.CreateBody(spriteBodyDef);  
  8.   
  9.     PolygonShape spriteShape = new PolygonShape();  
  10.     spriteShape.SetAsBox((float)(sprite.contentSize.width / PTM_RATIO / 2), (float)(sprite.contentSize.height / PTM_RATIO / 2));  
  11.     FixtureDef spriteShapeDef = new FixtureDef();  
  12.     spriteShapeDef.shape = spriteShape;  
  13.     spriteShapeDef.density = 10.0f;  
  14.     spriteShapeDef.isSensor = true;  
  15.     spriteBody.CreateFixture(spriteShapeDef);  
  16. }  

这段代码对你来说应该比较熟悉了---它和我们在breakout教程中是一样的。然后,这里有一点区别,我们把shape定义的isSensor成员设置成了true。

  根据Box参考手册,如果你想让对象之间有碰撞检测但是又不想让它们有碰撞反应,那么你就需要把isSensor设置成true。这正是我们想要的效果!

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

  1. this.addBoxBodyForSprite(cat);  
同样的,你需要在spawnCar的相应位置添加下列代码:

  1. this.addBoxBodyForSprite(car);  

我们的sprites被销毁的时候,我们需要销毁Box2d的body。因此,把你的spriteDone方法改写成下面的形式:

  1. void spriteDone(object sender)  
  2. {  
  3.     CCSprite sprite = (CCSprite)sender;  
  4.   
  5.     Body spriteBody = null;  
  6.     for (Body b = world.GetBodyList(); b != null; b = b.GetNext())  
  7.     {  
  8.         if (b.GetUserData() != null)  
  9.         {  
  10.             CCSprite curSprite = (CCSprite)b.GetUserData();  
  11.             if (sprite == curSprite)  
  12.             {  
  13.                 spriteBody = b;  
  14.             }  
  15.         }  
  16.     }  
  17.   
  18.     if (spriteBody != null)  
  19.     {  
  20.         world.DestroyBody(spriteBody);  
  21.     }  
  22.     this.removeChild(sprite, true);  
  23. }  

现在,最重要的一部分,我们需要根据sprite的位置改变来更新box2d的body。因此,在init方法里面添加下面一行代码:

  1. this.schedule(tick);  

然后,把tick方法写成下面的形式:
  1. void tick(float dt)  
  2. {  
  3.     world.Step(dt, 10, 10);  
  4.     for (Body b = world.GetBodyList(); b != null; b = b.GetNext())  
  5.     {  
  6.         if (b.GetUserData() != null)  
  7.         {  
  8.             CCSprite sprite = (CCSprite)b.GetUserData();  
  9.   
  10.             Vector2 position = new Vector2((float)(sprite.position.x / PTM_RATIO), (float)(sprite.position.y / PTM_RATIO));  
  11.             float angle = -1 * MathHelper.ToRadians(sprite.rotation);  
  12.             b.SetTransform(position, angle);  
  13.         }  
  14.     }  
  15. }  

这个方法和我们之前在breakout中写的tick方法差不多,唯一的差别就是,我们现在是基于cocos2d的精灵位置来更新box2d的body位置。


碰撞检测

  现在,是时候压死几只猫了!

像之前的breakout游戏一样,我们将往world对象里面注册一个contact listener对象。写法和之前的一样,如果你没写过,请直接到<cocos2d-x for wp7>使用cocos2d-x和BOX2D来制作一个BreakOut(打砖块)游戏(二)里面复制代码即可。把MyContactListener类添加到Classes文件夹。

同时,你还可以下载音效文件http://115.com/file/anefd61t#resource.zip,直接把resource文件夹添加到Content工程。另外添加对CocosDenshion.dll的引用

往HelloWorldScene类中添加一个变量声明:

  1. MyContactListener contactListener;  

然后,在init方法中加入下面的代码:
  1. //Create contact listener  
  2. contactListener = new MyContactListener();  
  3. world.ContactListener = contactListener;  
  4. //Preload effect  
  5. SimpleAudioEngine.sharedEngine().preloadEffect(@"resource/hahaha");  

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

  1. List<Body> toDestroy = new List<Body>();  
  2.             foreach (var item in contactListener.contacts)  
  3.             {  
  4.                 Body bodyA = item.fixtureA.GetBody();  
  5.                 Body bodyB = item.fixtureB.GetBody();  
  6.                 if (bodyA.GetUserData() != null && bodyB.GetUserData() != null)  
  7.                 {  
  8.                     CCSprite spriteA = (CCSprite)bodyA.GetUserData();  
  9.                     CCSprite spriteB = (CCSprite)bodyB.GetUserData();  
  10.                     if (spriteA.tag == 1 && spriteB.tag == 2)  
  11.                     {  
  12.                         toDestroy.Add(bodyA);  
  13.                     }  
  14.                     else if (spriteA.tag == 2 && spriteB.tag == 1)  
  15.                         toDestroy.Add(bodyB);  
  16.                 }  
  17.             }  
  18.             foreach (var item in toDestroy)  
  19.             {  
  20.                 if (item.GetUserData() != null)  
  21.                 {  
  22.                     CCSprite sprite = (CCSprite)item.GetUserData();  
  23.                     this.removeChild(sprite,true);  
  24.                 }  
  25.                 world.DestroyBody(item);  
  26.             }  
  27.             if (toDestroy.Count > 0)  
  28.             {  
  29.                 SimpleAudioEngine.sharedEngine().playEffect(@"resource/hahaha");  
  30.             }  
这段代码和我们之前的breakout教程中的代码基本上差不多,因此你应该比较熟悉了。
再运行一下工程看看!变态的猫被压死还会叫。。。。


调整shape的边界

  你可能已经注意到了,我们的box2d shape的边界并不是和sprite的边界完全吻合。对于有些碰撞检测要求不是特别精确的游戏,这也许够了,但是,有些游戏确不行!我们需要严格地定义shape的边界和精灵的边界重合在一起。

  在Box2d里面,你可以通过指定shape的顶点来指定shape的形状。然后,硬编码这些顶点数据会很耗时,而且容易出错。MAC上有个工具叫 VertexHelper,它可以用来非常方便地定义顶点,而且可以导出box2d所需要的格式的数据。但是window上没有,,这个有点纠结了。不过你如果看过这个软件,其实也没什么大不了的,也就一个点的坐标读取转换工具。有兴趣的朋友可以做这么一个软件。软件原理可以从下面的教程基本看出。

我们先看下这个叫VertexHelper软件生成的数据:


看上图,这个右下角的框生成的数据是相对那只猫而言的。仔细看的朋友就会发现。其实没什么大不了的数据。也就在猫上点了6个点,然后再猫图上把这6个点连线,然后生成代码。那么这些点怎么定义的呢。仔细的朋友应该也发现了。其实坐标就是以图片中心为原点的做的一个坐标轴。然后就读取相关的点的数据。但是我们在没有这类软件情况下,怎么才能快速得到这些点的坐标呢。

那么我们来用window强大的画图工具来获取吧:

用画图工具打开猫的图片:


看上图,就会发现,我在猫的左耳上面把鼠标放置在上面,在右下角能看到,当前像素是(8,1),然而,我们这张图的像素是64*64.中心点的坐标应该为(32,32)。如果用这点作为原点的坐标轴,我们用笔算下上面(8,1)的坐标的相对坐标就为(-24,31)。哈哈,大家是否没想到强大的画图软件也有这么用的。

这样我们就能确定猫的shape的顶点,同样的,车的也能够获取。

后修改 addBoxBodyForSprite方法。首先注释掉一些代码,如下所示:

  1. <span style="font-family: Verdana, 'Lucida Grande', Arial, Helvetica, sans-serif; font-size: 16px; ">            //spriteShape.SetAsBox((float)(sprite.contentSize.width / PTM_RATIO / 2), (float)(sprite.contentSize.height / PTM_RATIO / 2));  
  2.             if (sprite.tag == 1)  
  3.             {  
  4.                 //for the cat   
  5.                 int num = 6;  
  6.                 Vector2[] verts = { new Vector2((float)(4.5f/PTM_RATIO), (float)(-17.7f/PTM_RATIO)),  
  7.                                    new Vector2((float)(20.5f/PTM_RATIO), (float)(7.2f/PTM_RATIO)),  
  8.                                   new Vector2((float)(22.8f/PTM_RATIO), (float)(29.5f/PTM_RATIO)),  
  9.                                   new Vector2((float)(-24.7f/PTM_RATIO), (float)(31.0f/PTM_RATIO)),  
  10.                                   new Vector2((float)(-20.2f/PTM_RATIO), (float)(4.7f/PTM_RATIO)),  
  11.                                   new Vector2((float)(-11.7f/PTM_RATIO), (float)(-17.5f/PTM_RATIO))  
  12.                                   };  
  13.                 spriteShape.Set(verts, num);  
  14.   
  15.             }  
  16.             else  
  17.                 spriteShape.SetAsBox((float)(sprite.contentSize.width / PTM_RATIO / 2), (float)(sprite.contentSize.height / PTM_RATIO / 2));  
  18. </span>  
一旦做完之后,编译并运行工程。当然碰撞检测就会更加真实了!
恩,你已经看到了使用box2d来做碰撞检测的强大了!

本次工程下载:http://115.com/file/dpw5vl8o#cocos2dBox2DCollisionDemo.zip
原文地址:http://blog.csdn.net/fengyun1989/article/details/7523654

posted on 2012-07-11 17:10 风轻云淡 阅读(6211) 评论(0)  编辑 收藏 引用 所属分类: cocos2d

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理