zoukankan      html  css  js  c++  java
  • <cocos2dx for wp7>在cocos2dx里面使用BOX2D

     

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

    子龙山人翻译的Iphone教程地址:http://www.cnblogs.com/zilongshanren/archive/2011/05/27/2059453.html

    Iphone教程原文地址:http://www.raywenderlich.com/457/intro-to-box2d-with-cocos2d-tutorial-bouncing-balls

     

    BOX2D这个引擎,是个很好的模拟真实物理世界的引擎。愤怒的小鸟就是基于这个引擎做的。想想愤怒的小鸟,是不是很有冲动来学习下使用这个引擎了呢。

    本教程目的就是让你们熟悉在cocos2d里面如何使用box2d,所采用的例子就是制作一个简单的应用。里面有一个篮球,使篮球自己碰到墙壁反弹。

    本教程假定你学过前面的教程《用cocos2d-x做一个简单的windows phone 7游戏》,或者有同等的相关经验。

     

    下载XNA版的BOX2D:

    http://box2dxna.codeplex.com/最新的BOX2D。然后解压。到其目录下的Box2D.XNA.TestBed\bin\Windows Phone\Debug这里把这个BOX2D.XNA.DLL这个DLL文件复制出来。其实下载这么个文件我们就需要这个DLL。然后估计有人会想,怎么到这里复制呢,那么不是有个Box2D.XNA工程么,其实,经过我实践,发现原来Box2D.XNA\bin\Windows Phone\Debug这个目录下的DLL有些问题。所以到人家的示例工程里面把DLL复制出来。

     

    这里先介绍下BOX2D世界的相关理论。(其实推荐看Box2d参考手册(英文)和Box2d参考手册(中文),不是很多,才30+页,看了就了解了BOX2D的理论,再进行以下的理解就简单了)

    在我们开始之前,让我们先交待一下Box2D具体是如何运作的。

      你需要做的第一件事情就是,当使用cocos2d来为box2d创建一个world对象的时候。这个world对象管理物理仿真中的所有对象。

      一旦我们已经创建了这个world对象,接下来需要往里面加入一些body对象。body对象可以随意移动,可以是怪物或者飞镖什么的,只要是参与碰撞的游戏对象都要为之创建一个相应的body对象。当然,也可以创建一些静态的body对象,用来表示游戏中的台阶或者墙壁等不可以移动的物体。

      为了创建一个body对象,你需要做很多事情--首先,创建一个body定义结构,然后是body对象,再指定一个shap,再是fixture定义,然后再创建一个fixture对象。下面会一个一个解释刚刚这些东西。

    · 你首先创建一个body定义结构体,用以指定body的初始属性,比如位置或者速度。

    · 一旦创建好body结构体后,你就可以调用world对象来创建一个body对象了。

    · 然后,你为body对象定义一个shape,用以指定你想要仿真的物体的几何形状。

    · 接着创建一个fixture定义,同时设置之前创建好的shapefixture的一个属性,并且设置其它的属性,比如质量或者摩擦力。

    · 最后,你可以使用body对象来创建fixture对象,通过传入一个fixture的定义结构就可以了。

    · 请注意,你可以往单个body对象里面添加很多个fixture对象。这个功能在你创建特别复杂的对象的时候非常有用。比如自行车,你可能要创建2个轮子,车身等等,这些fixture可以用关节连接起来。

      只要你把所有需要创建的body对象都创建好之后,box2d接下来就会接管工作,并且高效地进行物理仿真---只要你周期性地调用world对象的step函数就可以了。

      但是,请注意,box2d仅仅是更新它内部模型对象的位置--如果你想让cocos2d里面的sprite的位置也更新,并且和物理仿真中的位置相同的话,那么你也需要周期性地更新精灵的位置



     

    cocos2d-x for wp7上使用BOX2D:

     

    我们新建个cocos2d-x的工程,命名为cocos2dBOX2DDemo,同样的,OpenXLive的那个勾去掉。因为我们不需要这个服务。同样的,我们往工程里面新建一个lib的文件夹。然后把需要的DLL添加到该工程的这个lib文件夹内。然后把带叹号的引用移除后并添加该工程lib文件夹内的相同的DLL。并且在cocos2dBOX2DDemo工程中添加BOX2D.XNA.DLL这个引用。

    然后新建一个类添加到Classes文件夹。命名为BOX2DLayer。并使之继承于CCLayer

    下载http://dl.dbank.com/c0md7urpb3并且添加到cocos2dBOX2DDemoContent工程的images文件夹。

    接下来,在BOX2DLayer类里面添加以下声明:

    public static double PTM_RATIO = 32.0;


     

    这里定义了一个像素/的比率。当你在cocos2d里面指定一个body在哪个位置时,你使用的单位要是米。但是,我们之前使用的都是像素作为单位,那样的话,位置就会不正确。根据Box2d参考手册Box2d在处理大小在0.110个单元的对象的时候做了一些优化。这里的0.1米大概就是一个杯子那么大,10的话,大概就是一个箱子的大小。

      因此,我们并不直接传递像素,因为一个很小的对象很有60×60个像素,那已经大大超过了box2d优化时所限定的大小。因此,如果我们有一个64像素的对象,我们可以把它除以PTM_RATIO,得到2---这个长度,box2d刚好可以很好地用来做物理仿真。

    另外,为什么要用double类型呢,这个有个精度的问题,如果用int的话,我曾经尝试过,篮球直接嵌在墙壁不反弹。

      好了,现在来点有意思的东西。在BOX2DLayer类里面添加以下声明

            World world;
    
            Body body;
    
            CCSprite ball;


    然后重载Init方法并且修改为:

     

           public override bool init()
    
            {
    
                if (!base.init())
    
                    return false;
    
                if (!base.init())
    
                {
    
                    return false;
    
                }
    
     
    
                CCSize winSize = CCDirector.sharedDirector().getWinSize();
    
     
    
                CCLabelTTF title = CCLabelTTF.labelWithString("Boxing", "Arial", 24);
    
                //title.Color = new ccColor3B(0, 255, 255);
    
                title.position = new CCPoint(winSize.width / 2, winSize.height - 50);
    
                this.addChild(title, 1);
    
     
    
                //Create sprite and add it to the layer
    
                ball = CCSprite.spriteWithFile(@"images/Ball");
    
                ball.position = new CCPoint(100, 300);
    
                this.addChild(ball);
    
     
    
                //Create the world
    
                Vector2 gravity = new Vector2(0.0f, -30.0f);
    
                bool doSleep = true;
    
                world = new World(gravity, doSleep);
    
     
    
                //Create edges around the entire screen
    
                BodyDef groundBodyDef = new BodyDef();
    
                groundBodyDef.position = new Vector2(0, 0);
    
                Body groundBody = world.CreateBody(groundBodyDef);
    
                PolygonShape groundBox = new PolygonShape();
    
                FixtureDef boxShapeDef = new FixtureDef();
    
                boxShapeDef.shape = groundBox;
    
                groundBox.SetAsEdge(new Vector2(0, 0), new Vector2((float)(winSize.width / PTM_RATIO), 0));
    
                groundBody.CreateFixture(boxShapeDef);
    
                groundBox.SetAsEdge(new Vector2(0, 0), new Vector2(0, (float)(winSize.height / PTM_RATIO)));
    
                groundBody.CreateFixture(boxShapeDef);
    
                groundBox.SetAsEdge(new Vector2(0, (float)(winSize.height / PTM_RATIO)),
    
                    new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)));
    
                groundBody.CreateFixture(boxShapeDef);
    
                groundBox.SetAsEdge(new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)),
    
                    new Vector2((float)(winSize.width / PTM_RATIO), 0));
    
                groundBody.CreateFixture(boxShapeDef);
    
     
    
                //Create ball body and shape
    
                BodyDef ballBodyDef = new BodyDef();
    
                ballBodyDef.type = BodyType.Dynamic;
    
                ballBodyDef.position = new Vector2((float)(100 / PTM_RATIO), (float)(300 / PTM_RATIO));
    
                ballBodyDef.userData = ball;
    
                body = world.CreateBody(ballBodyDef);
    
     
    
                CircleShape circle = new CircleShape();
    
                circle._radius = (float)(26.0 / PTM_RATIO);
    
     
    
                FixtureDef ballShapeDef = new FixtureDef();
    
                ballShapeDef.shape = circle;
    
                ballShapeDef.density = 1.0f;
    
                ballShapeDef.friction = 0.0f;
    
                ballShapeDef.restitution = 1.0f;
    
                body.CreateFixture(ballShapeDef);
    
     
    
                this.schedule(tick);
    
                return true;
    
            }



    呃,这里有很多陌生的代码。我们一点点来解释一下。下面,我会一段段地重复上面的代码,那样可以解释地更加清楚一些。

     

               CCSize winSize = CCDirector.sharedDirector().getWinSize();
    
     
    
                CCLabelTTF title = CCLabelTTF.labelWithString("Boxing", "Arial", 24);
    
                //title.Color = new ccColor3B(0, 255, 255);
    
                title.position = new CCPoint(winSize.width / 2, winSize.height - 50);
    
                this.addChild(title, 1);
    
     
    
                //Create sprite and add it to the layer
    
                ball = CCSprite.spriteWithFile(@"images/Ball");
    
                ball.position = new CCPoint(100, 300);
    
                this.addChild(ball);


    首先,我们往屏幕中间加入一个精灵。如果你看了前面的教程的话,这里应该没有什么问题。这个加入一个Label是为了让精灵能够更好的显示,这个是cocos2d-x的一些问题,如果不添加一个Label,精灵的背景是黑的不是透明的。

                //Create the world
    
                Vector2 gravity = new Vector2(0.0f, -30.0f);
    
                bool doSleep = true;
    
                world = new World(gravity, doSleep);


    接下来,我们创建了world对象。当我们创建这个对象的时候,需要指定一个初始的重力向量。这里,我们设置y轴方向为-30,因此,所有的body都会往屏幕下面下落。同时,我们还指定了一个值,用以指明对象不参与碰撞时,是否可以休眠。一个休眠的对象将不会花费处理时间,直到它与其实对象发生碰撞的时候才会过来。

                //Create edges around the entire screen
    
                BodyDef groundBodyDef = new BodyDef();
    
                groundBodyDef.position = new Vector2(0, 0);
    
                Body groundBody = world.CreateBody(groundBodyDef);
    
                PolygonShape groundBox = new PolygonShape();
    
                FixtureDef boxShapeDef = new FixtureDef();
    
                boxShapeDef.shape = groundBox;
    
                groundBox.SetAsEdge(new Vector2(0, 0), new Vector2((float)(winSize.width / PTM_RATIO), 0));
    
                groundBody.CreateFixture(boxShapeDef);
    
                groundBox.SetAsEdge(new Vector2(0, 0), new Vector2(0, (float)(winSize.height / PTM_RATIO)));
    
                groundBody.CreateFixture(boxShapeDef);
    
                groundBox.SetAsEdge(new Vector2(0, (float)(winSize.height / PTM_RATIO)),
    
                    new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)));
    
                groundBody.CreateFixture(boxShapeDef);
    
                groundBox.SetAsEdge(new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)),
    
                    new Vector2((float)(winSize.width / PTM_RATIO), 0));
    
                groundBody.CreateFixture(boxShapeDef);



      接下来,我们为整个屏幕创建了一圈不可见的边。具体的步骤如下:

    · 首先创建一个body定义结构体,并且指定它应该放在左下角。

    · 然后,使用world对象来创建body对象。(注意,这里一定要使用world对象来创建,不能直接new,因为world对象会做一些内存管理操作。)

    · 接着,为屏幕的每一个边界创建一个多边形shape。这些“shape”仅仅是一些线段。注意,我们把像素转换成了“meter”。通过除以之前定义的比率来实现的。

    · 再创建一个fixture定义,指定shapepolygon shape

    · 再使用body对象来为每一个shape创建一个fixture对象。

    · 注意:一个body对象可以包含许许多多的fixture对象。 

                //Create ball body and shape
    
                BodyDef ballBodyDef = new BodyDef();
    
                ballBodyDef.type = BodyType.Dynamic;
    
                ballBodyDef.position = new Vector2((float)(100 / PTM_RATIO), (float)(300 / PTM_RATIO));
    
                ballBodyDef.userData = ball;
    
                body = world.CreateBody(ballBodyDef);
    
     
    
                CircleShape circle = new CircleShape();
    
                circle._radius = (float)(26.0 / PTM_RATIO);
    
     
    
                FixtureDef ballShapeDef = new FixtureDef();
    
                ballShapeDef.shape = circle;
    
                ballShapeDef.density = 1.0f;
    
                ballShapeDef.friction = 0.0f;
    
                ballShapeDef.restitution = 1.0f;
    
                body.CreateFixture(ballShapeDef);



     接下来,我们创建篮球的body。这个步骤和之前创建地面的body差不多,但是有下面一些差别需要注意一下:

    · 我们指定body的类型为dynamic body。默认值是static body,那意味着那个body不能被移动也不会参与仿真。很明显,我们想让篮球参与仿真。

    · 设置bodyuser data属性为篮球精灵。你可以设置任何东西,但是,你设置成精灵会很方便,特别是当两个body碰撞的时候,你可以通过这个参数把精灵对象取出来,然后做一些逻辑处理。

    · 这里使用了一个不同的shape类型--circle shape

    · 在这里,我们需要为这个fixture指定一些参数,因此,我们没有使用便捷方法来创建fixture。后面我们会讲到这些参数的具体意义。  

    this.schedule(tick);


    最后一件事情就是调度一个tick方法。

    实现tick方法:

            void tick(float dt)
    
            {
    
                world.Step(dt, 10, 10);
    
                for (Body b = world.GetBodyList(); b != null;b = b.GetNext() )
    
                {
    
                    if (b.GetUserData() != null)
    
                    {
    
                        CCSprite ballData = (CCSprite)b.GetUserData();
    
                        ballData.position = new CCPoint((float)(b.GetPosition().X * PTM_RATIO),
    
                            (float)(b.GetPosition().Y * PTM_RATIO));
    
                        ballData.rotation = -1 * MathHelper.ToDegrees(b.GetAngle());
    
                    }
    
                }
    
            }



    第一件事情就是调用world对象的step方法,这样它就可以进行物理仿真了。这里的两个参数分别是速度迭代次数位置迭代次数”--你应该设置他们的范围在8-10之间。(译者:这里的数字越小,精度越小,但是效率更高。数字越大,仿真越精确,但同时耗时更多。8一般是个折中,如果学过数值分析,应该知道迭代步数的具体作用)。

      接下来,我们要使我们的精灵匹配物理仿真。因此,我们遍历world对象里面的所有body,然后看bodyuser data属性是否为空,如果不为空,就可以强制转换成精灵对象。接下来,就可以根据body的位置来更新精灵的位置了。

    现在我们再添加一些代码作为层的初始化用。

            public static new CCLayer node()
    
            {
    
                BOX2DLayer layer = new BOX2DLayer();
    
                if (layer.init())
    
                {
    
                    return layer;
    
                }
    
                else
    
                    layer = null;
    
                return layer;
    
            }
    
     

    然后再修改AppDelegate.cs里面的applicationDidFinishLaunching。在这个导演类里面修改初始场景。

    修改如下:

                // create a scene. it's an autorelease object
    
                //CCScene pScene = cocos2dBOX2DDemoScene.scene();
    
                CCScene pScene = CCScene.node();
    
                pScene.addChild(Classes.BOX2DLayer.node());
    
                //run
    
                pDirector.runWithScene(pScene);
    
     
    
                return true;
    
     

    编译并运行,你应该可以看到球会往下掉,并且会从屏幕底部往上面弹起来。

     

    关于仿真的一些注意事项


      前面我们说后面会讨论densityfrictionrestitution参数的意义。

    · Density就是单位体积的质量(密度)。因此,一个对象的密度越大,那么它就有更多的质量,当然就会越难以移动.

    · Friction 就是摩擦力。它的范围是0-1.0, 0意味着没有摩擦,1代表最大摩擦,几乎移不动的摩擦。

    · Restitution回复力。它的范围也是01.0. 0意味着对象碰撞之后不会反弹,1意味着是完全弹性碰撞,会以同样的速度反弹。


      建议多去改一改这些参数,看看具体会给小球带来什么影响。一定要去试哦!
    如果你想学习更多有关box2d相关的内容,请继续关注。接下来会带来更好的教程。




    本次工程下载:http://dl.dbank.com/c0mx9imdgw


     深入学习:<cocos2d-x for wp7>使用cocos2d-x和BOX2D来制作一个BreakOut(打砖块)游戏(一)

     

  • 相关阅读:
    内存泄漏检测
    qt 关于内存泄漏的检测
    Valgrind 安装与使用
    Qt应用中检测内存泄露——VLD
    Visual C++内存泄露检测—VLD工具使用说明
    ArcGIS Runtime支持的GP工具列表(转 )
    c# 调用ArcEngine的GP工具
    ArcEngine 数据导入经验(转载)
    在ArcEngine中使用Geoprocessing工具-执行工具
    利用C#与AE调用GP工具
  • 原文地址:https://www.cnblogs.com/fengyun1989/p/2476040.html
Copyright © 2011-2022 走看看