(译)如何使用cocos2d和box2d来制作一个Breakout游戏:第一部分

2024-01-19 16:40

本文主要是介绍(译)如何使用cocos2d和box2d来制作一个Breakout游戏:第一部分,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(译)如何使用cocos2d和box2d来制作一个Breakout游戏:第一部分

免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

 原文链接地址:http://www.raywenderlich.com/475/how-to-create-a-simple-breakout-game-with-box2d-and-cocos2d-tutorial-part-12

程序截图:

  box2d是一个非常强大的物理引擎库,同时它与cocos2d结合非常适合在iphone上面做游戏开发。著名的angry birds,tiny wings都是用box2d写的。你可以用它做好多事情,当然,最好的学习方法就是使用它来创建一个简单的游戏。

  在这个教程中,我们将一步一步创建一个简单的breakout游戏,完成碰撞检测,篮球反弹物理效果,通过touch拖动paddle(就是上图的白色矩形),以及胜利/失败的场景。

  如果你还不了解cocos2d和box2d,你可能先要读一读《如何使用cocos2d制作一个简单的iphone游戏》以及《在cocos2d里面如何使用box2d物理引擎:弹球》这些教程。

  好了,是时候制作breakout了!

一个永远反弹的球

  首先,打开Xcode,选择 cocos2d-0.99.1 Box2d Application template创建一个工程,命名为“ Box2DBreakout”. 删除掉模板代码,因此你会有一个空的工程来重新开始---具体步骤可以参照 《在cocos2d里面如何使用box2d物理引擎:弹球》这个教程。

  一旦你有了一个很好的干净的工程后,接下来,在HelloWorldScene.h中导入下面的头文件:

#import   " Box2D.h "

  同时在HelloWorld类中添加以下成员变量:

b2World  * _world;
b2Body 
* _groundBody;
b2Fixture 
* _bottomFixture;
b2Fixture 
* _ballFixture;

  然后在HelloWorldScene.mm文件顶部定义比率:

#define  PTM_RATIO 32

  这个比率我们在上一个教程中已经讨论过了,这里就不再啰嗦了。

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

CGSize winSize  =  [CCDirector sharedDirector].winSize;

//  Create a world
b2Vec2 gravity  =  b2Vec2( 0.0f 0.0f );
bool  doSleep  =   true ;
_world 
=   new  b2World(gravity, doSleep);

//  Create edges around the entire screen
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(
0 , 0 );
_groundBody 
=  _world -> CreateBody( & groundBodyDef);
b2PolygonShape groundBox;
b2FixtureDef groundBoxDef;
groundBoxDef.shape 
=   & groundBox;
groundBox.SetAsEdge(b2Vec2(
0 , 0 ), b2Vec2(winSize.width / PTM_RATIO,  0 ));
_bottomFixture 
=  _groundBody -> CreateFixture( & groundBoxDef);
groundBox.SetAsEdge(b2Vec2(
0 , 0 ), b2Vec2( 0 , winSize.height / PTM_RATIO));
_groundBody
-> CreateFixture( & groundBoxDef);
groundBox.SetAsEdge(b2Vec2(
0 , winSize.height / PTM_RATIO), b2Vec2(winSize.width / PTM_RATIO, 
winSize.height
/ PTM_RATIO));
_groundBody
-> CreateFixture( & groundBoxDef);
groundBox.SetAsEdge(b2Vec2(winSize.width
/ PTM_RATIO, winSize.height / PTM_RATIO), 
b2Vec2(winSize.width
/ PTM_RATIO,  0 ));
_groundBody
-> CreateFixture( & groundBoxDef);

  好,这个代码和我们上一个教程中,为整个屏幕创建一个盒子边界差不多。然后,这一次,我们把重力设置为0,因为,在我们的breakout游戏中,我们并不需要重力!注意,我们存储了底部的fixture的一个指针,以方便后面使用(在后面的教程中,我们将用来追踪什么时候篮球与顶部相碰撞了)。

  现在,下载我制作的篮球图片,并且拖到Resources文件夹中,确保 “Copy items into destination group’s folder (if needed)” 被复选中。

  让我们往场景里面添加一个精灵吧。紧接着上面的代码,加入下面的代码片段:

//  Create sprite and add it to the layer
CCSprite  * ball  =  [CCSprite spriteWithFile: @" Ball.jpg "  
rect:CGRectMake(
0 0 52 52 )];
ball.position 
=  ccp( 100 100 );
ball.tag 
=   1 ;
[self addChild:ball];

  这里没什么疑问,我们已经做过好多次类似的事情了。注意,我们为篮球设置了一个tag标识,后面你会看到,这个tag标记有什么用。

  接下来,为shape创建一个body:

//  Create ball body 
b2BodyDef ballBodyDef;
ballBodyDef.type 
=  b2_dynamicBody;
ballBodyDef.position.Set(
100 / PTM_RATIO,  100 / PTM_RATIO);
ballBodyDef.userData 
=  ball;
b2Body 
*  ballBody  =  _world -> CreateBody( & ballBodyDef);

//  Create circle shape
b2CircleShape circle;
circle.m_radius 
=   26.0 / PTM_RATIO;

//  Create shape definition and add to body
b2FixtureDef ballShapeDef;
ballShapeDef.shape 
=   & circle;
ballShapeDef.density 
=   1.0f ;
ballShapeDef.friction 
=   0 .f;
ballShapeDef.restitution 
=   1.0f ;
_ballFixture 
=  ballBody -> CreateFixture( & ballShapeDef);

  这个看起来和上一篇教程中的也很像。再巩固一下吧,为了创建一个body对象,我们先要创建一个body定义结构,然后再创建body,接着是shape,再指定fixture结构,最后是创建fixture对象。

  注意,我们设置这些参数有一点点不一样了:我们把回复力(restitution)设置为1.0,这意味着,我们的球在碰撞的时候,将会是完全弹性碰撞。

  同时,我们也保存了球的fixture,原因和我们为什么保存屏幕底部的fixture是一样的,后面你就会看到了。

  更新:注意,我们也把球的摩擦力设置为0.这样可以防止球在碰撞的时候,由于摩擦损失能量,导致来回碰撞的过程中会有一点点偏差。

  好了,是时候做一些完全不同的事了!紧接上面的代码:

b2Vec2 force  =  b2Vec2( 10 10 );
ballBody
-> ApplyLinearImpulse(force, ballBodyDef.position);

  这里往球上面施加了一个冲力(impulse),这样可以让它初始化的时候朝一个特定的方向运动。

  最后一件事情,就是在init方法中,增加一个tick调度方法:

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

  下面是tick方法的实现:

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

}

}

  当然,这里也和上一个教程中的一样,没有什么特别的。

  最后一件事我们永远不忘记!那就是清理:

-  ( void )dealloc {

delete _world;
_groundBody 
=  NULL;
[super dealloc];

}

好了,让我们试一下吧。编译并运行工程,你将会看到一个球无限地在屏幕里面来回弹!----很酷吧!

增加 Paddle

  如果没有一个paddle的话,那么就不可能称其为一个breakout游戏。下载我制作的paddle图片,然后把它拖到Resources文件夹中,同时确保 “Copy items into destination group’s folder (if needed)” 被复选上。

  然后在HelloWorldScene.h文件中往HelloWorld类中添加下列成员变量:

b2Body  * _paddleBody;
b2Fixture 
* _paddleFixture;

  然后,在init方法中构建paddle body:

//  Create paddle and add it to the layer
CCSprite  * paddle  =  [CCSprite spriteWithFile: @" Paddle.jpg " ];
paddle.position 
=  ccp(winSize.width / 2 50 );
[self addChild:paddle];

//  Create paddle body
b2BodyDef paddleBodyDef;
paddleBodyDef.type 
=  b2_dynamicBody;
paddleBodyDef.position.Set(winSize.width
/ 2 / PTM_RATIO,  50 / PTM_RATIO);
paddleBodyDef.userData 
=  paddle;
_paddleBody 
=  _world -> CreateBody( & paddleBodyDef);

//  Create paddle shape
b2PolygonShape paddleShape;
paddleShape.SetAsBox(paddle.contentSize.width
/ PTM_RATIO / 2
paddle.contentSize.height
/ PTM_RATIO / 2 );

//  Create shape definition and add to body
b2FixtureDef paddleShapeDef;
paddleShapeDef.shape 
=   & paddleShape;
paddleShapeDef.density 
=   10.0f ;
paddleShapeDef.friction 
=   0.4f ;
paddleShapeDef.restitution 
=   0.1f ;
_paddleFixture 
=  _paddleBody -> CreateFixture( & paddleShapeDef);

  我不想花太多的时间解释上面的内容了。因为,和之前的创建篮球的body的过程差不太多。这里只给出不同的地方:

  • 当你创建CCSprite的时候,你并不需要指定精灵的大小。如果你传递一个文件名给它,它会自动计算出大小。
  • 注意,这里不是使用circle shape了。这一次,我们使用polygon shape。我们使用一个辅助方法来创建shape,当然,其形状是个盒子。
  • 我们使用了SetAsBox方法来指定shape相对于body的位置,这个方法在构建复杂的对象的时候比较有用。这里,我们只是让shape在body中间。
  • 我把paddle的密度设置得比球要大得多,同时调节了一下其它的参数。(这些参数要靠试,按照真实的高中物理知识去计算,可能得不到)
  • 同时,我们存储paddleBody和paddleFixture的引用,为了方便后面使用。

  如果你编译并运行的话,你将会看到屏幕中间有一个paddle,而且球碰到它将会反弹。

  然后,这还不是很有趣,因为我们还不能移动paddle!

移动Paddle

  移动paddle需要touch事件,所以先在init方法中允许touch事件:

self.isTouchEnabled  =  YES;

  然后,在HelloWorld类中添加下面的成员变量:

b2MouseJoint  * _mouseJoint;

  现在,让我们实现touch方法!首先是ccTouchesBegan:

-  ( void )ccTouchesBegan:(NSSet  * )touches withEvent:(UIEvent  * ) event  {

if  (_mouseJoint  !=  NULL)  return ;

UITouch 
* myTouch  =  [touches anyObject];
CGPoint location 
=  [myTouch locationInView:[myTouch view]];
location 
=  [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld 
=  b2Vec2(location.x / PTM_RATIO, location.y / PTM_RATIO);

if  (_paddleFixture -> TestPoint(locationWorld)) {
b2MouseJointDef md;
md.bodyA 
=  _groundBody;
md.bodyB 
=  _paddleBody;
md.target 
=  locationWorld;
md.collideConnected 
=   true ;
md.maxForce 
=   1000.0f   *  _paddleBody -> GetMass();

_mouseJoint 
=  (b2MouseJoint  * )_world -> CreateJoint( & md);
_paddleBody
-> SetAwake( true );
}

}

  呃,好多新知识!让我们一点一点来讨论。

  首先,我们把touch坐标转换成coocs2d坐标(convertToGL)然后,再转换成Box2d坐标(locationWorld)。

  然后,我们使用paddle fixture的一个方法来测试这个touch点是否在fixture内部。

  如果是的话,我们就创建一个所谓的”鼠标关节“。在Box2d里面,一个鼠标关节用来让一个body朝着一个指定的点移动---在这里个例子中,就是用户点的方向。

  当你创建一个mouse joint后,你赋值给它两个body。第一个没有被使用,通常都是设置成ground body。第二个,就是你想让它移动的body,在这个例子中就是paddle。

  接下来,你指定移动的终点---这个例子中就是用户点击的位置。

  然后,你告诉box2d,但bodyA和bodyB碰撞的时候,把它当成是碰撞,而不是忽略它。这个很重要!因为,我之前没有设置它为ture,结果不行!因此,当我们用鼠标拖动这个paddle的时候,它并不会与屏幕的边界相碰撞,而且有时候,我的paddle直接就飞出屏幕之外了。这个非常非常奇怪,不过我现在知道是为什么了。因为没有设置bodyA和bodyB是可碰撞的。

  你然后指定移动body的最大的力是多少。如果你减少这个数值的话,paddle body响应鼠标移动时就会慢一些。但是,我们想让paddle快速地响应鼠标的变化。

  最后,我们把这个关节加入到world中,同时,保存这个指针,因为后面有用。同时,我们还要把body设置成苏醒的(awake)。之所以要这么做,是因为如果body在睡觉的话,那么它就不会响应鼠标的移动!

  好了,接下来,让我们添加ccTouchesMoved方法:

- ( void )ccTouchesMoved:(NSSet  * )touches withEvent:(UIEvent  * ) event  {

if  (_mouseJoint  ==  NULL)  return ;

UITouch 
* myTouch  =  [touches anyObject];
CGPoint location 
=  [myTouch locationInView:[myTouch view]];
location 
=  [[CCDirector sharedDirector] convertToGL:location];
b2Vec2 locationWorld 
=  b2Vec2(location.x / PTM_RATIO, location.y / PTM_RATIO);

_mouseJoint
-> SetTarget(locationWorld);

}

  这个方法的开头部分和ccTouchesBegan差不多---我们把touch坐标转换成Box2d坐标。唯一的区别就是,我们更新了鼠标关节的目标位置(也就是我们想让paddle移动的位置的)。

  接下来,我们添加ccTouchesCacelled和ccTouchesEnded方法:

- ( void )ccTouchesCancelled:(NSSet  * )touches withEvent:(UIEvent  * ) event  {

if  (_mouseJoint) {
_world
-> DestroyJoint(_mouseJoint);
_mouseJoint 
=  NULL;
}

}

-  ( void )ccTouchesEnded:(NSSet  * )touches withEvent:(UIEvent  * ) event  {
if  (_mouseJoint) {
_world
-> DestroyJoint(_mouseJoint);
_mouseJoint 
=  NULL;

}

  我们在这些方法中做的只有一件事,就是在我们移动完paddle或者取消移动之后销毁mouse joint。

  编译并运行,你现在可以用鼠标移动paddle了,同时可以让它与篮球相互碰撞了!

  很好。。。不过,等一下,这还不是一个breakout!我们不可以把paddle移动到任何位置,我们只能在屏幕底部左右来回移动它!

限制Paddle的移动

  我们可以很容易地限制paddle的移动,只需要添加另外一个关节,叫做prismatic joint。这个关节会限制一个body的移动沿着一根指定的轴。

  因此,我们可以使用这种方法来限制paddle相对于地面移动,也就是说只能沿着x轴移动。

  让我们看看相关代码。往init方法中加入下列代码:

//  Restrict paddle along the x axis
b2PrismaticJointDef jointDef;
b2Vec2 worldAxis(
1.0f 0.0f );
jointDef.collideConnected 
=   true ;
jointDef.Initialize(_paddleBody, _groundBody, 
_paddleBody
-> GetWorldCenter(), worldAxis);
_world
-> CreateJoint( & jointDef);

  第一件事情就是指定一个沿着x轴的向量。然后,我们需要指定collideConnected为true,因此,我们的球才能够正确的反弹,而不是飞到屏幕之外去。

  然后,初始化关节,指定paddle和ground两个body,再使用world对象来创建关节!

  编译并运行,你现在只能沿关x轴方向移动paddle了,这正是我们想要的,不是吧?

完成touch事件

  现在,你玩一下,可能你会发现,有时候球反弹地特别快,有时候又比较慢。这取决于你是如何控制paddle与球相碰撞的。

  更新:我第一次尝试去修正这个bug的时候,我通过直接调整球的速度,使用SetLinearVelocity方法。然后,Steve Oldmeadow也指出,这非常不好!它会破坏物理仿真,最好的方法是通过调用SetLinearDamping方法,间接影响速度。因此,现在这个教程就是这个做的。(damping就是阻尼的意思)

  接下来,在tick方法中添加下列代码,具体位置是在获得user data之后:

if  (sprite.tag  ==   1 ) {
static   int  maxSpeed  =   10 ;

b2Vec2 velocity 
=  b -> GetLinearVelocity();
float32 speed 
=  velocity.Length();

if  (speed  >  maxSpeed) {
b
-> SetLinearDamping( 0.5 );
else   if  (speed  <  maxSpeed) {
b
-> SetLinearDamping( 0.0 );
}

}

  这里,我们判断sprite的tag,看是否是球的tag。如果是的话,我们就检查它的速度,如果太大的话,就设置它的阻尼为0.5,这样可以让它慢下来。

  如果你编译并运行的话,你将会看到一个球以非常适中的速度在屏幕四周来回反弹。

给我源代码!

  这里是本教程的完整源代码。这只是一部分,第二部分的教程会包含一个完整的breakout的源码。

接下来呢?

  目前为止,我们已经有一个篮球在屏幕四周来回反弹了,同时还有一个paddle可以用鼠标来控制其移动。在下个教程中,我们将创建一些方块,当球碰到它们的时候,方块就会消失。当然,还有游戏胜利和失败的逻辑!

 

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

这篇关于(译)如何使用cocos2d和box2d来制作一个Breakout游戏:第一部分的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/623038

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl