zoukankan      html  css  js  c++  java
  • (翻译)介绍Box2D的Cocos2D 2.X教程:弹球

    原文地址:http://www.raywenderlich.com/28602/intro-to-box2d-with-cocos2d-2-x-tutorial-bouncing-balls

    译文更新:2013-04-27

    更新内容:

    1. body统一译为刚体
    2. fixture统一译为夹具

    更新日期:2013-01-09

    更新内容:完全更新至Cocos2D 2.1-beta4

    教程作者:Ray Wenderlich

    教程更新:Brian Broom

    本教程通过演示一个简单应用程序的创建过程,帮助您在Cocos2D中使用Box2D。该应用程序显示一个小球,旋转iPhone利用加速器能够让小球在屏幕上弹来弹去。

    游戏截图如下:

    本教程使用的示例程序以iPhoneDev.net上由Kyle编写的一个非常棒的示例为基础,更新到最新版本的Cocos2D并对其工作原理做了详尽的解释。教程中还会对示例项目中一些由Cocos2D的Box2D应用程序模板提供元素的工作原理逐步进行解释。

    本教程假设您已经学习过《使用Cocos2D 2.X制作一个简单iPhone游戏教程》,或者具备同等知识。

    OK,让我们开始学习Cocos2D 2.X的Box2D吧!

    创建空项目

    首先在Xcode中新建一个项目,选择cocos2d v2.x\cocos2d iOS with Box2d应用程序模板,并将项目命名为Box2D。如果编译并运行此模板,您将看到一个非常酷的示例展示了Box2D的许多内容。然而,出于本教程的目的,我们准备从零开始创建所有内容,以便我们能够很好地理解其工作原理。

    因此,让我们先对模板做一下清理,从而使我们有一个良好的起点。使用如下代码替换HelloWorldLayer.h中的内容:

    #import "cocos2d.h"
     
    #define PTM_RATIO 32.0
     
    @interface HelloWorldLayer : CCLayer {   
    }
     
    + (id) scene;
     
    @end

    然后使用如下代码替换HelloWorldLayer.mm的内容:

    #import "HelloWorldLayer.h"
     
    @implementation HelloWorldLayer
     
    + (id)scene {
     
        CCScene *scene = [CCScene node];
        HelloWorldLayer *layer = [HelloWorldLayer node];
        [scene addChild:layer];
        return scene;
     
    }
     
    - (id)init {
     
        if ((self=[super init])) {
        }
        return self;
    }
     
    @end

    再次编译并运行,您将看到一个空白的屏幕。OK非常好,现在让我们开始创建自己的Box2D场景吧!

    Box2D世界理论

    在继续之前,让我们先简单介绍一下在Box2D中是如何工作的。

    使用Cocos2D需要做的第一件事情是为Box2D创建一个世界(world)对象。该世界对象是Cocos2D中的主对象,负责管理所有对象和物理仿真。

    创建world对象之后,我们需要向世界中添加一些刚体(body)。刚体可以是在游戏中来回移动的对象,如忍者或妖怪,也可以是静止不动的刚体,如平台或墙壁。

    要创建一个刚体,您需要做很多事情——创建一个刚体定义、一个刚体对象、一个形状、一个夹具定义和一个夹具对象。下面逐一解释这些让人抓狂的名词都分别意味着什么!

    • 首先创建一个刚体定义(body definition)用于指定刚体的初始属性,例如位置或者速度。
    • 建立了刚体定义之后,通过指定刚体定义,可以使用世界对象创建一个刚体对象(body object)。
    • 然后创建一个希望仿真模拟的几何形状(shape)。
    • 然后创建一个夹具定义(fixture definition),将夹具定义的形状设置为您所创建的形状,并设置其他属性,例如密度或者摩擦系数。
    • 最后,通过指定夹具定义,使用该刚体对象创建一个夹具对象(fixture object)。
    • 请注意,可以将任意多个夹具对象添加至单个刚体对象。这一特性在创建复杂对象时非常有用。

    将所有需要的刚体添加至世界之后,您只需要周期性地调用Step函数就可以让Box2D接管工作并开始物理仿真了,因此这会占用一定的处理时间。

    但是请注意,Box2D仅仅只更新其内部模型对象的位置,如果想让Cocos2D的精灵同样更新至物理仿真的所在位置,您同样需要周期性地更新精灵的位置。

    Ok,现在我们已经对Box2D的工作机制有了基本的认识,接下来让我们看看在代码中是如何实现的!

    Box2D世界演练

    Ok,首先下载我制作的一张小球图片及其Retina版本,我们准备把这个小球加进场景。下载之后,将它们拖拽至项目中的Resources文件夹,并确保勾选了Copy items into destination group’s folder (if needed)。

    接下来,看一下我们此前在HelloWorldLayer.h中添加的这一行代码:

    #define PTM_RATIO 32.0

    这行代码定义了一个像素与“米”之间的比例。当您在Cocos2D中指定刚体放置位置时,需要给定一个单位。虽然您可能会考虑使用像素,但这样位置是不正确的。根据Box2D参考手册,Box2D在处理小至0.1单位大至10单位的长度做了优化。按照尽可能长的长度推算,大家通常倾向将其视为“米”,因此0.1差不多是一个杯子大小,而10差不多是一个箱子的大小。

    因此,我们不能直接传递像素,因为即便是一个很小的对象也差不多会有60×60像素,这已经超出了Box2D优化时限定的最大值。因此,我们需要有一个方法把像素转换成“米”,于是便就有了上面的比例定义。如果我们有一个64像素的对象,除以PTM_RATIO,可以得到2“米”,这是一个Box2D能够处理进行物理仿真的长度。

    好了,现在可以来点有意思的东西了。在HelloWorldLayer.h的顶部添加如下代码:

    #import "Box2D.h"

    在HelloWorldLayer类的接口定义中添加如下成员变量:

    b2World *_world;
    b2Body *_body;
    CCSprite *_ball;

    然后,将如下代码添加至HelloWorldLayer.mm的init方法:

    CGSize winSize = [CCDirector sharedDirector].winSize;
     
    // Create sprite and add it to the layer
    _ball = [CCSprite spriteWithFile:@"ball.png" rect:CGRectMake(0, 0, 52, 52)];
    _ball.position = ccp(100, 300);
    [self addChild:_ball];
     
    // Create a world
    b2Vec2 gravity = b2Vec2(0.0f, -8.0f);
    _world = new b2World(gravity);
     
    // Create ball body and shape
    b2BodyDef ballBodyDef;
    ballBodyDef.type = b2_dynamicBody;
    ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO);
    ballBodyDef.userData = _ball;
    _body = _world->CreateBody(&ballBodyDef);
     
    b2CircleShape circle;
    circle.m_radius = 26.0/PTM_RATIO;
     
    b2FixtureDef ballShapeDef;
    ballShapeDef.shape = &circle;
    ballShapeDef.density = 1.0f;
    ballShapeDef.friction = 0.2f;
    ballShapeDef.restitution = 0.8f;
    _body->CreateFixture(&ballShapeDef);
     
    [self schedule:@selector(tick:)];

    除了少数几行通过Cocos2D教程已经熟悉的代码之外,这里的大部分代码都很陌生。让我们一点一点地来解释。我会一段一段地复述以上代码,这样应该会解释的更清楚一些。

    CGSize winSize = [CCDirector sharedDirector].winSize;
     
    // Create sprite and add it to the layer
    _ball = [CCSprite spriteWithFile:@"ball.png" rect:CGRectMake(0, 0, 52, 52)];
    _ball.position = ccp(100, 300);
    [self addChild:_ball];

    首先,使用Cocos2D的常规方式将精灵添加至场景。如果您已经学习过之前的Cocos2D教程,这里应该没有什么问题。

    // Create a world
    b2Vec2 gravity = b2Vec2(0.0f, -8.0f);
    _world = new b2World(gravity);

    接下来,创建世界对象。在创建此对象时,需要指定一个初始重力向量。我们将其设置为延Y轴方向-8的向量,这样刚体将出现向屏幕底部下落的现象。

    // Create ball body and shape
    b2BodyDef ballBodyDef;
    ballBodyDef.type = b2_dynamicBody;
    ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO);
    ballBodyDef.userData = _ball;
    _body = _world->CreateBody(&ballBodyDef);
     
    b2CircleShape circle;
    circle.m_radius = 26.0/PTM_RATIO;
     
    b2FixtureDef ballShapeDef;
    ballShapeDef.shape = &circle;
    ballShapeDef.density = 1.0f;
    ballShapeDef.friction = 0.2f;
    ballShapeDef.restitution = 0.8f;
    _body->CreateFixture(&ballShapeDef);

    接下来,我们创建小球刚体。

    • 将其类型指定为动态刚体(dynamic body)。刚体的默认类型是一个静态刚体(static body),表示刚体不能移动也不参与仿真。很显然,我们希望小球参与仿真!
    • 把用户数据(user data)参数设置为小球的CCSprite。可以将刚体上的用户数据参数设置为任何您所想要的对象,但通常将其设置为精灵会很方便,如此一来您便可以在其他地方访问到它,例如两个刚体碰撞时的处理。
    • 我们必须定义一个圆形的形状(shape)。请记住,Box2D不会去查看精灵的图像,我们必须要告诉它精灵的形状,这样它才能够正确地模拟精灵的运动。
    • 最后,设置了一些夹具定义的参数,这些参数的具体含义稍后会介绍。
    [self schedule:@selector(tick:)];

    方法中最后做的事情是调度一个名为tick的方法被尽可能频繁地调用。请注意,这并不是最理想的处理方式,比较好的方式是让tick方法按照一个固定的频率被调用,例如60次每秒。然而为保证教程内容的简单,我们先这么处理。

    下面,让我们来编写tick方法的代码!在init方法之后添加如下代码:

    - (void)tick:(ccTime) dt {
     
        _world->Step(dt, 10, 10);
        for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {    
            if (b->GetUserData() != NULL) {
                CCSprite *ballData = (CCSprite *)b->GetUserData();
                ballData.position = ccp(b->GetPosition().x * PTM_RATIO,
                                        b->GetPosition().y * PTM_RATIO);
                ballData.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
            }        
        }
     
    }

    方法中我们做的第一件事情是,在世界对象上调用Step函数,使其能够执行物理仿真。其中两个参数分别是速度迭代和位置迭代,您通常应该将它们设置为8~10之间的一个值。

    接下来的事情是让精灵与物理仿真匹配。因此我们遍历世界中的所有刚体,查找设置有用户数据的刚体。找到之后,将用户数据转换成一个精灵(此前是将精灵设置成用户数据的!),然后更新精灵的位置和角度与物理仿真匹配。

    最后一件事情——清理内存!在文件末尾添加如下代码:

    - (void)dealloc {
        delete _world;
        _body = NULL;
        _world = NULL;
        
        [_ball release];
        _ball = nil;
        
        [super dealloc];
    }

    编译并运行应用程序,应该能够看到小球直接从屏幕下方掉出去了。哎呀,我们忘记定义一个地面再把小球弹起来了。

    落地反弹

    要表示地面,我们在iPhone的屏幕底部创建一个不可见的边界。按照以下步骤操作即可。

    • 创建一个刚体定义(body definition)并指定该刚体应该位于屏幕的左下角。由于刚体类型默认是我们需要的静态刚体,因此不需要设置。
    • 然后使用世界对象创建刚体对象(body object)。
    • 然后为屏幕的底边创建一个边界形状(edge shape)。此“形状”实际上就是一条线。请注意,此处必须使用前面讨论过的转换比例将像素转换为“米”。
    • 创建一个夹具定义(fixture definition)指定边界形状。
    • 然后使用刚体对象为形状创建一个夹具对象(fixture object)。
    • 另外请注意,一个刚体对象可以包含多个夹具对象!

    将如下代码添加到init方法中创建世界对象和定义小球的代码之间。

        // Create edges around the entire screen
        b2BodyDef groundBodyDef;
        groundBodyDef.position.Set(0,0);
     
        b2Body *groundBody = _world->CreateBody(&groundBodyDef);
        b2EdgeShape groundEdge;
        b2FixtureDef boxShapeDef;
        boxShapeDef.shape = &groundEdge;
     
        //wall definitions
        groundEdge.Set(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));
        groundBody->CreateFixture(&boxShapeDef);

    再次编译并运行,小球落在地面之后会反弹回空中,往复几次之后最终静止停在地面之上。

    如何水平运行?

    现在我们已经有了基础的知识,接下来让我们做一些更有意思的事情——让一只无形的脚每隔几秒踢一下球。在HelloWorldLayer.h中定义一个新方法:

    - (void)kick;

    然后,将该方法的实现添加到HelloWorldLayer.mm

    - (void)kick {
        b2Vec2 force = b2Vec2(30, 30);
        _body->ApplyLinearImpulse(force,_body->GetPosition());
    }

    ApplyLinearImpulse方法可以在小球上作用一个力,使小球移动。移动距离的远近取决于小球的质量,之前我们在定义小球时曾设置过它的密度(density)属性。可以尝试不同的密度以及力的值找出您认为不错的值。坐标系与Cocos2D相同,X方向向右正向延展,Y方向向上正向延展。

    在init方法中增加如下代码行,每隔5秒运行一次kick方法。

        [self schedule:@selector(kick) interval:5.0];

    如果现在生成并运行项目,小球被“踢”之后会飞出屏幕。让我们继续并定义其他的墙壁。在init方法中找到wall definitions注释,并在其后添加如下代码行。注意:每面墙壁需要两行代码,一行设置坐标,另一行将边界添加为ground对象的夹具刚体。

        groundEdge.Set(b2Vec2(0,0), b2Vec2(0,winSize.height/PTM_RATIO));
        groundBody->CreateFixture(&boxShapeDef);
     
        groundEdge.Set(b2Vec2(0, winSize.height/PTM_RATIO),
                       b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));
        groundBody->CreateFixture(&boxShapeDef);
     
        groundEdge.Set(b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO),
                       b2Vec2(winSize.width/PTM_RATIO, 0));
        groundBody->CreateFixture(&boxShapeDef);

    现在生成并运行项目,观赏小球在屏幕上弹来弹去吧。

    集成触摸

    由于HelloWorldLayer仍然是一个Cocos2D图层,我们可以使用所有的工具,包括曾经学习过的触摸事件。为了演示如何与Box2D之间交互,让我们对程序进行一些修改,触摸屏幕时向左侧方向踢球。

    要启用触摸事件,首先在HelloWorldLayer.mm的init方法中添加如下一行代码:

        [self setTouchEnabled:YES];

    然后添加如下方法以处理触摸事件:

    - (void)ccTouchesBegan:(UITouch *)touch withEvent:(UIEvent *)event {
        b2Vec2 force = b2Vec2(-30, 30);
        _body->ApplyLinearImpulse(force, _body->GetPosition());
    }

    与之前类似,我们使用ApplyLinearImpulse方法在小球上作用一个力。给定force的x一个负值将会向左侧踢球。

    关于仿真的注释

    作为承诺,接下来让我们介绍一下前文为小球设置的:density、friction和restitution分别有什么用处。

    • 密度(Density)是单位体积的质量。因此密度越大质量就越大,移动就越困难。
    • 摩擦系数(Friction)是一个系数,用于描述对象表面之间相对滑动的困难程度。其范围介于0和1之间,0表示没有摩擦,而1则表示摩擦非常大。
    • 恢复系数(Restitution)也是一个系数,用于描述一个对象到底有多“弹”。其范围通常介于0和1之间,0表示对象不会反弹,而1则表示是完全弹性,也就是说对象会以相同的速度反弹。

    可以随意修改这些数值,看看修改之后会有什么不同的影响。试试看,能不能让您的小球弹力十足!

    收尾

    如果我们倾斜屏幕就可以让小球在屏幕上弹来弹去,会非常酷!有一件事情可以有助于我们接下来的试验,那就是现在所有的边界都能正常工作! 剩下的事情就简单了。在init方法中添加如下代码:

    [self setAccelerometerEnabled:YES];

    将如下方法添加在文件中的某一位置:

    - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
     
        // Landscape left values
        b2Vec2 gravity(acceleration.y * 30, -acceleration.x * 30);
        _world->SetGravity(gravity);    
    }

    最后,单击项目导航侧边栏顶部的项目。然后选中TARGETS下的Box2D,并选择Summary选项卡。在Supported Interface Orientations部分,单击Landscape Right按钮取消选中该按钮。此时Landscape Left按钮应该是被唯一选中的按钮。这样做是因为我们不希望在旋转手机时iOS改变应用程序的方向。如下图所示:

    我们在这里做的是将用于仿真的重力向量设置为加速器向量的倍数。在设备上编译并运行应用程序,现在倾斜手机应该能够让小球在屏幕上弹来弹去了!

    注释:只有在物理设备上运行程序时,才能够获得加速器数据,这需要一个付费的开发者账号并安装了开发者证书才可以。详细信息请参见developer.apple.com的iOS Provisioning Portal。

    下一步做些什么?

    单击下载本教程的示例代码

    如果您希望学习有关Box2D更多的内容,请看下一篇教程How To Create A Breakout Game with Box2D and Cocos2D

    著作权声明:本文由http://www.cnblogs.com/liufan9翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!


    原创文章,如需转载请注明出处,谢谢!

    欢迎访问本人技术微博 趣味苹果开发 相互交流,共同进步!

    欢迎访问本人新浪微博 可可爸刘凡

    用趣味的心态,开发有趣味的应用!

  • 相关阅读:
    Android_EditText
    JAVA_Gson_example
    JAVA_Gson
    JAVA_eclipse 保留Java文件时自动格式化代码和优化Import
    JAVA_JSON_example
    JAVA_JSON
    JAVA_HttpClientUtils
    Android_Gallery
    JAVA_JDBC
    day05 Pyhton学习
  • 原文地址:https://www.cnblogs.com/liufan9/p/3012275.html
Copyright © 2011-2022 走看看