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

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

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

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

程序截图:

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

这是《如何使用cocos2d和box2d制作一个简单的breakout游戏》的第二部分,也是最后一部分教程。如果你还没有读过第一部分,请先阅读《<cocos2d-x for wp7>使用cocos2d-x和BOX2D来制作一个BreakOut(打砖块)游戏(一)》。

在上一个教程中,我们创建了一个屏幕盒子,球可以在里面弹跳,同时,我们可以用手指拖着paddle移动。这部分教程中,我们将添加一些游戏逻辑,当篮球碰到屏幕底部的时候,就Gameover。

Box2D 和碰撞检测

  在Box2D里面,当一个fixture和另一个fixture相互碰撞的时候,我们怎么知道呢?这就需要用到碰撞侦听器了(contact listener)。一个碰撞侦听器是一个对象,它继承至box2d的IContactListner接口的实现,并且要设置给world对象。这样,当有两个对象发生相互碰撞的时候,world对象就会回调contact listener对象的方法,这样我们就可以在那些方法里面做相应的碰撞处理了。

  如何使用contact listener呢?根据BOX2D用户手册,在一个仿真周期内,你不能执行任何修改游戏物理的操作。因为,在那期间,我们可能需要做一些额外的处理(比如,当两个对象碰撞的时候销毁另一个对象)。因此, 我们需要保存碰撞的引用,这样后面就可以使用它。

  另外一点值得注意的是,我们不能存储传递给contact listener的碰撞点的引用,因为,这些点被BOX2D所重用。因此,我们不得不存储这些点的拷贝。

  好了,说得够多了,让我们亲手实践一下吧!

当我们碰到屏幕底部的时候

这里我们添加一个类到Classes文件夹。并且命名为MyContactListener.cs。并且使之继承于接口IContactListener。

并修改这个命名空间内的代码为:

class MyContact

    {

        public Fixture fixtureA;

        public Fixture fixtureB;



    }

    class MyContactListener : IContactListener

    {

        public List<MyContact> contacts = new List<MyContact>();



        public void BeginContact(Contact contact)

        {

            MyContact myContact = new MyContact()

            {

                fixtureA = contact.GetFixtureA(),

                fixtureB = contact.GetFixtureB()

            };

            contacts.Add(myContact);



        }



        public void EndContact(Contact contact)

        {

            contacts.Clear();

        }



        public void PostSolve(Contact contact, ref ContactImpulse impulse)

        {

        }

        public void PreSolve(Contact contact, ref Manifold oldManifold)

        {

        }

    }

 

 这里,我们定义了一个类MyContact来保存数据,当碰撞通知到达的时候,用来保存碰撞点信息。再说一遍,我们需要存储其拷贝,因为它们会被重用,所以不能保存指针。这里有一个问题,就是如果在EndContact的时候要从list里面找到那个点的话,基本是找不到的。我经过多次试验,每次在EndContact的时候,只有一个点在list里面,如果不移除的话就会出问题。但是用什么IndexOf方法压根就找不到。所以这里用了一个很郁闷的方法,直接将其清空。关于为什么找不到的问题。我想了想,估计是在GetFixtureA和GetFixtureB这两个方法的问题,感觉可能是返回的是引用,并不是拷贝。如果是这样的话,我们在这里做的仅仅是Contact的浅拷贝。这样的话,应该做个深拷贝才行。不过我也没有看源码中GetFixtureA是怎么实现的。所以这里仅仅是猜测。有兴趣的朋友可以去看看GetFixtureA是怎么实现的。然后来解决这个问题吧。

PS:C#中的引用,浅拷贝,深拷贝要特别注意,不然会发现很多很奇怪的问题。

好了,现在可以使用它吧。打开BreakOutLayer类,然后添加一个声明:

MyContactListener contactListener;

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

            //Create contact listener

            contactListener = new MyContactListener();

            world.ContactListener = contactListener;

这里,我们创建了contact listener对象,然后调用world对象把它设置为world的contact listener。

最后,在tick方法底部添加下列代码:

            foreach (var item in contactListener.contacts)

            {

                if ((item.fixtureA == bottomFixture && item.fixtureB == ballFixture) ||

                (item.fixtureA == ballFixture && item.fixtureB == bottomFixture))

                {

                    Debug.WriteLine("Ball hit the bottom!");

                }

            }

这里遍历所有缓存的碰撞点,然后看看是否有一个碰撞点,它的两个碰撞体分别是篮球和屏幕底部。目前为止,我们只是使用NSLog来打印一个消息,因为我们只想测试这样是否可行。

  因此,在debug模式下编译并运行,你会发现,不管什么时候,当球和底部有碰撞的时候,你会看到控制台输出一句话“Ball hit the bottom"!

添加Game Over场景

新建一个类添加到Classes文件夹,命名为GameOverScene.cs。并且使之继承于CCScene

修改代码为:

class GameOverScene:CCScene

    {

        public GameOverScene(bool isWin)

        {

            string msg;

            if (isWin)

                msg = "YOU WIN";

            else

                msg = "YOU LOSE";

            CCLabelTTF label = CCLabelTTF.labelWithString(msg, "Arial", 24);

            label.position = new CCPoint(CCDirector.sharedDirector().getWinSize().width / 2, CCDirector.sharedDirector().getWinSize().height - 100);

            this.addChild(label);

        }

    }


然后,把Debug语句替换成下列代码:

                        GameOverScene pScene = new GameOverScene(false);

                        CCDirector.sharedDirector().replaceScene(pScene);

好了,我们已经实现得差不多了。但是,如果你游戏你永远不能赢,那有什么意思呢?

增加一些方块

  下载我制作的方块图片,然后把它添加到images文件夹下面. 

  然后往init方法中添加下列代码:

 for (int i = 0; i < 4; i++)

            {

                int padding = 20;

                

                //Create block and add it to the layer

                CCSprite block = CCSprite.spriteWithFile(@"images/Block");

                float xOffset = padding + block.contentSize.width / 2 + (block.contentSize.width + padding) * i;

                block.position = new CCPoint(xOffset, 400);

                block.tag = 2;

                this.addChild(block);



                //Create block body

                BodyDef blockBodyDef = new BodyDef();

                blockBodyDef.type = BodyType.Dynamic;

                blockBodyDef.position = new Vector2((float)(xOffset / PTM_RATIO), (float)(400 / PTM_RATIO));

                blockBodyDef.userData = block;

                Body blockBody = world.CreateBody(blockBodyDef);



                //Create block shape

                PolygonShape blockShape = new PolygonShape();

                blockShape.SetAsBox((float)(block.contentSize.width / PTM_RATIO / 2), (float)(block.contentSize.height / PTM_RATIO / 2));

                

                //Create shape definition and add to body

                FixtureDef blockShapeDef = new FixtureDef();

                blockShapeDef.shape = blockShape;

                blockShapeDef.density = 10.0f;

                blockShapeDef.friction = 0.0f;

                blockShapeDef.restitution = 0.1f;

                blockBody.CreateFixture(blockShapeDef);

            }


 

  现在,你应该可以很好地理解上面的代码了。就像之前我们为paddle创建一个body类似,这里,我们每一次也会一个方块创建一个body。注意,我们把方块精灵对象的tag设置为2,这样将来可以用到。

  编译并运行,你应该可以看到篮球和方块之间有碰撞了。

销毁方块

 为了使breakout游戏是一个真实的游戏,当篮球和方块有交集的时候,我们需要销毁这些方块。我们已经添加了一些代码来追踪碰撞,因此,我们对tick方法做一改动。

  具体改动方式如下:

            List<Body> toDestroy = new List<Body>();

            foreach (var item in contactListener.contacts)

            {

                if ((item.fixtureA == bottomFixture && item.fixtureB == ballFixture) ||

                (item.fixtureA == ballFixture && item.fixtureB == bottomFixture))

                {

                    GameOverScene pScene = new GameOverScene(false);

                    CCDirector.sharedDirector().replaceScene(pScene);



                }

                Body bodyA = item.fixtureA.GetBody();

                Body bodyB = item.fixtureB.GetBody();

                if (bodyA.GetUserData() != null && bodyB.GetUserData() != null)

                {

                    CCSprite spriteA = (CCSprite)bodyA.GetUserData();

                    CCSprite spriteB = (CCSprite)bodyB.GetUserData();



                    //Sprite A = ball, Sprite B = Block

                    if (spriteA.tag == 1 && spriteB.tag == 2)

                    {

                        if (toDestroy.IndexOf(bodyB) == -1)

                        {

                            toDestroy.Add(bodyB);

                        }

                    }

                    //Sprite B = block ,Sprite A = ball

                    else if (spriteA.tag == 2 && spriteB.tag == 1)

                    {

                        if (toDestroy.IndexOf(bodyA) == -1)

                        {

                            toDestroy.Add(bodyA);

                        }

                    }

                }

            }



            foreach (var item in toDestroy)

            {

                if (item.GetUserData() != null)

                {

                    CCSprite sprite = (CCSprite)item.GetUserData();

                    this.removeChild(sprite, true);

                }

                world.DestroyBody(item);

            }

  好,让我们解释一下。我们又一次遍历所有的碰撞点,但是,这一次在我们测试完篮球和屏幕底部相撞的时候,我们将检查碰撞点。我们可以通过fixture对象的GetBody方法来找对象。 

 接着,我们基于精灵的tag,看看到底是哪个在发生碰撞。如果一个精灵与一个body相交的话,我们就把该body添加到待销毁的对象列表里面去。

  但是也需要注意,只有确定它并不存在于销毁列表中时才把它添加进去。为什么一定要用一个list把需要销毁的存储起来而不是直接销毁。因为直接销毁会导致contact listener中留下一些已被删除指针的垃圾数据。 

 最后,遍历我们想要删除的body列表。

  编译并运行,现在你可以销毁bricks了!

加入游戏胜利条件

  接下来,我们需要添加一些逻辑,让用户能够取得游戏胜利。修改你的tick方法的开头部分,像下面一样:

           bool blockFind = false;

            world.Step(dt, 10, 10);

            for (Body b = world.GetBodyList(); b != null;b = b.GetNext() )

            {

                if (b.GetUserData() != null)

                {

                    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);

                        }

                    }

                    else if (sprite.tag == 2)

                    {

                        blockFind = true;

                    }


 

  我们需要做的,仅仅是遍历一下场景中的所有对象,看看是否还有一个方块----如果我们确实找到了一个,那么就把blockFound变量设置为true,否则就设置为false.

  然后,在这个函数的末尾添加下面的代码:

if (!blockFind)

            {

                GameOverScene gameOverScene = new GameOverScene(true);

                CCDirector.sharedDirector().replaceScene(gameOverScene);

            }


这里,如果方块都消失了,我们就会显示一个游戏结束的场景。编译并运行,看看,你的游戏现在有胜利终止条件了!

完成touch事件

 这个游戏非常酷,但是,毫无疑问,我们需要音乐!你可以下载好听的blip声音。(背景音乐自己弄,MP3格式),和之前一样,在Content工程新建一个resources文件夹,把它添加到你的resources文件夹下。

添加CocosDenshion.dll这个DLL的引用。

在tick方法的末尾添加下面的代码:

            if (toDestroy.Count > 0)

            {

                SimpleAudioEngine.sharedEngine().playEffect(@"resources/blip");

            }

终于完成了!你现在拥有一个使用Box2d物理引擎制作的breakout游戏了

 

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

 

何去何从?

  很明显,这是一个非常简单的beakout游戏,但是,你还可以在此教程的基础上实现更多。我可以添加一些逻辑,比如打击一个白色块就计一分,或者有些块需要打中很多下才消失。或者你也可以添加新的不同类型的block,并且让paddle可以发射出激光等等。你可以充分发挥想象。

 

 PS:令人伤心的所谓搬家工具。。。搞得显示乱七八糟的。。。。。。又得手动重来。

 

你可能感兴趣的:(cocos2d-x)