cocos2d-x for wp7使用cocos2d-x和BOX2D来制作一个BreakOut(打砖块)游戏(一)

2024-01-19 16:40

本文主要是介绍cocos2d-x for wp7使用cocos2d-x和BOX2D来制作一个BreakOut(打砖块)游戏(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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

程序截图:


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

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

  如果你还不了解cocos2d-x和box2d,你可能先要读一读《如何使用cocos2d-x制作一个简单的window phone 7游戏》以及《在<cocos2d-x for wp7>在cocos2d-x里面使用BOX2D》这些教程。

  好了,是时候制作breakout了!

一个永远反弹的球

打开VS2010。新建一个cocos2d-x项目,命名为cocos2dBox2DBreakOutDemo,并且做好相关的DLL复制和添加。做法可以参照 《在<cocos2d-x for wp7>在cocos2d-x里面使用BOX2D》。

然后新建一个类添加到Classes。并且命名为BreakoutScene.cs。

并且添加以下空间引用。

using cocos2d;
using Box2D.XNA;

现在先在这个文件添加一个类BreakOutLayer,并且使之继承于CCLayer。


        public static double PTM_RATIO = 32.0;World world;Body groundBody;   Fixture bottomFixture;Fixture ballFixture;

在这些声明里面第一个声明定义比率。这个比率我们在上一个教程中已经讨论过了,这里就不再啰嗦了。

然后重载其init方法。并且修改如下:

        public override bool init(){if (!base.init())return false;CCSize winSize = CCDirector.sharedDirector().getWinSize();CCLabelTTF title = CCLabelTTF.labelWithString("Boxing", "Arial", 24);title.position = new CCPoint(winSize.width / 2, winSize.height - 50);this.addChild(title);//Create the worldVector2 gravity = new Vector2(0.0f, 0.0f);bool doSleep = true;world = new World(gravity, doSleep);//Create edges around the entire screenBodyDef groundBodyDef = new BodyDef();groundBodyDef.position = new Vector2(0, 0);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));bottomFixture = 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);return true;}

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

现在,下载我制作的篮球图片,并且添加到Content工程的images文件夹中,让我们往场景里面添加一个精灵吧。紧接着上面的代码,加入下面的代码片段:

            //Create sprite and add it to the layerCCSprite ball = CCSprite.spriteWithFile(@"images/Ball");ball.position = new CCPoint(100, 100);ball.tag = 1;this.addChild(ball);

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

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

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

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

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

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

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

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

            Vector2 force = new Vector2(10, 10);ballBody.ApplyLinearImpulse(force, ballBodyDef.position);

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

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

this.schedule(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 sprite = (CCSprite)b.GetUserData();sprite.position = new CCPoint((float)(b.GetPosition().X * PTM_RATIO),(float)(b.GetPosition().Y * PTM_RATIO));sprite.rotation = -1 * MathHelper.ToDegrees(b.GetAngle());}}}

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

现在,在运行测试之前。还得做些工作。

往BreakOutLayer里面添加一个静态方法来作为初始化层工作。

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

然后让原来没用动过的BreakOutCCScene类继承于CCScene。并且在其构造函数中添加一个BreakOutLayer。

构造函数添加以下代码:

            BreakOutLayer layer = BreakOutLayer.node();this.addChild(layer);

接着修改AppDelegate这个导演类。找到applicationDidFinishLaunching方法。结尾部分修改如下:

            // create a scene. it's an autorelease object//CCScene pScene = cocos2dBox2DBreakOutDemoScene.scene();Classes.BreakoutScene pScene = new Classes.BreakoutScene();//runpDirector.runWithScene(pScene);return true;


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



增加 Paddle:

如果没有一个paddle的话,那么就不可能称其为一个breakout游戏。下载http://dl.dbank.com/c0at986ela,并且添加到images文件夹。然后在往BreakOutLayer类中添加下列成员变量:

        Body paddleBody;Fixture paddleFixture;

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


           //Create paddle and add it to the layerCCSprite paddle = CCSprite.spriteWithFile(@"images/Paddle");paddle.position = new CCPoint(winSize.width / 2, 50);this.addChild(paddle);//Create paddle bodyBodyDef paddleBodyDef = new BodyDef();paddleBodyDef.type = BodyType.Dynamic;paddleBodyDef.position = new Vector2((float)(winSize.width / 2 / PTM_RATIO), (float)(50 / PTM_RATIO));paddleBodyDef.userData = paddle;paddleBody = world.CreateBody(paddleBodyDef);//Create paddle shapePolygonShape paddleShape = new PolygonShape();paddleShape.SetAsBox((float)(paddle.contentSize.width / PTM_RATIO / 2), (float)(paddle.contentSize.height / PTM_RATIO / 2));//Create shape definition and add to bodyFixtureDef paddleShapeDef = new FixtureDef();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事件:

            this.isTouchEnabled = true;

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

        MouseJoint mouseJoint = null;

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

        public override void ccTouchesBegan(List<CCTouch> touches, CCEvent event_){if (mouseJoint != null)return;CCTouch myTouch = touches.FirstOrDefault();CCPoint location = myTouch.locationInView(myTouch.view());location = CCDirector.sharedDirector().convertToGL(location);Vector2 locationWorld = new Vector2((float)(location.x / PTM_RATIO), (float)(location.y / PTM_RATIO));if (paddleFixture.TestPoint(locationWorld)){MouseJointDef md = new MouseJointDef();md.bodyA = groundBody;md.bodyB = paddleBody;md.target = locationWorld;md.collideConnected = true;md.maxForce = 1000.0f * paddleBody.GetMass();mouseJoint = (MouseJoint)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方法:

        public override void ccTouchesMoved(List<CCTouch> touches, CCEvent event_){if (mouseJoint == null)return;CCTouch myTouch = touches.FirstOrDefault();CCPoint location = myTouch.locationInView(myTouch.view());location = CCDirector.sharedDirector().convertToGL(location);Vector2 locationWorld = new Vector2((float)(location.x / PTM_RATIO), (float)(location.y / PTM_RATIO));mouseJoint.SetTarget(locationWorld);}

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

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

        public override void ccTouchesCancelled(List<CCTouch> touches, CCEvent event_){if (mouseJoint != null){world.DestroyJoint(mouseJoint);mouseJoint = null;}    }public override void ccTouchesEnded(List<CCTouch> touches, CCEvent event_){if (mouseJoint != null){world.DestroyJoint(mouseJoint);mouseJoint = null;}}

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

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

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

限制Paddle的移动

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

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

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

            //Restrict paddle along the x axisPrismaticJointDef jointDef = new PrismaticJointDef();Vector2 worldAxis = new Vector2(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方法中添加下列代码,具体位置是在CCSprite sprite = (CCSprite)b.GetUserData();之后:

if (sprite.tag == 1){int maxSpeed = 10;Vector2 velocity = b.GetLinearVelocity();float speed = velocity.Length();if (speed > maxSpeed){b.SetLinearDamping(0.5f);}else if (speed < maxSpeed){b.SetLinearDamping(0.0f);}}

  这里,我们判断sprite的tag,看是否是球的tag。如果是的话,我们就检查它的速度,如果太大的话,就设置它的阻尼为0.5,这样可以让它慢下来。如果对速度啥的还不满意,可以调节maxSpeed,Damping值

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


本次工程下载: http://dl.dbank.com/c0x042rumn,这只是一部分,第二部分的教程会包含一个完整的breakout的源码。

这篇关于cocos2d-x for wp7使用cocos2d-x和BOX2D来制作一个BreakOut(打砖块)游戏(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

springboot的调度服务与异步服务使用详解

《springboot的调度服务与异步服务使用详解》本文主要介绍了Java的ScheduledExecutorService接口和SpringBoot中如何使用调度线程池,包括核心参数、创建方式、自定... 目录1.调度服务1.1.JDK之ScheduledExecutorService1.2.spring

Java使用Tesseract-OCR实战教程

《Java使用Tesseract-OCR实战教程》本文介绍了如何在Java中使用Tesseract-OCR进行文本提取,包括Tesseract-OCR的安装、中文训练库的配置、依赖库的引入以及具体的代... 目录Java使用Tesseract-OCRTesseract-OCR安装配置中文训练库引入依赖代码实

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个