• <cocos2dx for wp7>使用cocos2dx和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和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:令人伤心的所谓搬家工具。。。搞得显示乱七八糟的。。。。。。又得手动重来。

     

  • 相关阅读:
    51nod1042
    51nod1009
    分库分表Mycat总结
    RocketMQ事务消息实现分析
    RocketMQ消费模式
    mysql中的隐式转换总结
    EXPLAIN用法和结果分析
    MySQL日期时间处理函数总结
    RocketMQ在windows环境下的安装
    深入分析Synchronized原理
  • 原文地址:https://www.cnblogs.com/fengyun1989/p/2476220.html
Copyright © 2020-2023  润新知