• 用cocos2dx做一个简单的windows phone 7游戏:更猛的怪兽和更多的关卡(三)


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

    子龙山人翻译的Iphone教程地址:http://www.cnblogs.com/andyque/articles/1997966.html

    Iphone教程原文地址:http://www.raywenderlich.com/782/harder-monsters-and-more-levels

    上一篇教程我们有一个可以旋转的炮塔,有怪物可以射杀,还有很棒的音效。

      但是,我们的炮塔觉得这太简单了。这些怪物只要开一枪就挂了,而且现在只有一个关卡!它还没有热身呢!

      在这个教程里,我将会扩展我们的工程,并增加一些不同种类和难度的怪物,然后实现多个关卡。

    为了好玩,让我们创建两种不同类型的怪物:一种不怎么经打,但是移动速度很快,还有一种很能抗(坦克级别),但是移动速度很慢!为了使玩家可以区分这两种不同类型的怪物,下载修改的怪物图片并把它们添加到工程里。同时,下载我制作的爆炸音效,也把它们添加到Content工程中去。图片添加到images文件夹,音效添加到resource文件夹。

    好了,让我们来创建Monster类。这里有许多方法来为Monster类建模,但是,我们选择最简单的方式,即把Monster类当作CCSprite的一个子类。同时,我们会创建两个Monster类的子类:一个为我们的虚弱快速怪创建,另一个为我们的强悍缓慢怪创建。

    添加一个类到Classes文件夹。命名为Monster.cs。并让之继承于CCSprite

    接下来,把Monster.cs中的代码替换成下面的:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using cocos2d;
    namespace cocos2dSimpleGame.Classes
    {
        class Monster:CCSprite
        {
            private int _curHp;
            private int _minMoveDuration;
            private int _maxMoveDuration;
            public int hp { get {
                return _curHp;
            }
                set {
                    _curHp = value; 
                }
            }
    
            public int minMoveDuration { 
                get {
                    return _minMoveDuration;
                }
                set {
                    _minMoveDuration = value;
                }
            }
    
            public int maxMoveDuration { 
                get {
                    return _maxMoveDuration;
                }
                set {
                    _maxMoveDuration = value;
                }
            }
        }
    
        class WeakAndFastMonster : Monster
        {
            public static WeakAndFastMonster monster()
            {
                WeakAndFastMonster monster = new WeakAndFastMonster();
                if (monster.initWithFile(@"images/Target"))
                {
                    monster.hp = 1;
                    monster.minMoveDuration = 3;
                    monster.maxMoveDuration = 5;
                }
                return monster;
            }
        }
    
        class StrongAndSlowMonster : Monster
        {
            public static StrongAndSlowMonster monster()
            {
                StrongAndSlowMonster monster = new StrongAndSlowMonster();
                if (monster.initWithFile(@"images/Target2"))
                {
                    monster.hp = 3;
                    monster.minMoveDuration = 6;
                    monster.maxMoveDuration = 12;
                }
                return monster;
            }
        }
    }


    这里非常直白:我们从CCSprite派生一个Monster类,然后增加了一些成员变量来记录monster的状态。然后,我们又从Monster类派生出两个不同的monster子类。这里代码很简单的,只有我们为每个类添加的一个静态方法,用来返回这个类的实例。然后初使化了默认的HP和移动所需要的时间。

    然后,返回到GamePlayLayer里面,修改addTarget方法来构造我们新创建的类的实例,而不是直接创建精灵(sprite)。替换spriteWithFile那一行,如下所示:

                Monster target = null;
                if (random.Next() % 2 == 0)
                    target = WeakAndFastMonster.monster();
                else
                    target = StrongAndSlowMonster.monster();

    这里将会有50%的机率来出现不同类型的monster。当然,我们把怪物的speed定义移到了类当中,因此,我们需要修改min/max移动间隔,把它改成下面的样子:

                float minDuration = target.minMoveDuration;//2.0f;
                float maxDuration = target.maxMoveDuration;//4.0f;


    最后,在updates方法里面做一些修改。首先,在遍历所有的_targets前,也就是foreach (CCSprite target in _targets)前,添加一个boolean值。

    bool monsterHit = false;


    然后,在CCRectIntersetsRect里面,不是马上把对象添加到targetsToDelete里面,而是改成下面的:

    //targetToDelete.Add(target);
                            monsterHit = true;
                            Monster monster = (Monster)target;
                            monster.hp--;
                            if (monster.hp <= 0)
                            {
                                targetToDelete.Add(target);
                            }
                            break;

     这里,我们不是马上杀死怪物,而是减少它的HP,而且只有当它的生命值小于0的时候,才kill它。注意,如果projectile击中一个怪物的话 我们就跳出循环,这意味着一个飞盘射击一次只能打一个怪物。

      最后,我们把projectilesToDelete测试的这段代码:

                     if (targetToDelete.Count > 0)
                    {
                        projectilesToDelete.Add(projectile);
                    }

    改成下面所示:

                    if (monsterHit)
                    {
                        projectilesToDelete.Add(projectile);
                        SimpleAudioEngine.sharedEngine().playEffect("resource/explosion");
                    }


    编译并运行代码,如果一切顺利,那么你将会看到两种不同类型的怪物在屏幕上飞过---这使得我们的炮塔的生活更加富有挑战了!

     

    多个关卡

      为了使游戏支持多个关卡,首先我们需要重构。这个重构的工作非常简单,但是在这个项目里,有许多工作要做。如果把所有的内容都放在这个帖子上,那将会是一篇又长又乏味的帖子。

      相反,我会从一个更高的角度来谈谈我做了什么,并且提供一个功能完整的样例工程。

      抽象出一个Level类。目前,HelloWorldScene类里面把“level”的概念硬编码进去了,比如发射哪种类型的monster,发射频率如何等等。因此,我们的第一步就是要把这些信息提取出来,放到一个Level类里面。这样,在HelloWorldScene里面我们就可以为不同的关卡重用相同的逻辑。

      重用场景。目前,我们每一次转换场景(scene)的时候都是重新创建了一个新的场景类。这里有一个缺点就是效率问题。每一次在场景对象的init方法里加载资源,这会影响游戏frame。

      因为我们是一个简单的游戏,我们需要做的就是,每一个scene创建一个实例,并且提供一个reset方法来清除任何老的状态(比如上一关中的飞盘或者怪物)。

      使用应用程序委托来当做跳板。目前,我们并没有任何全局的状态,比如:我们在哪一个关卡或者当前关卡的设置是什么。每一个场景仅仅是硬编码它需要跳转的下一个场景是谁。

      我们将会修改这些内容,使用App Delegate来存储指向一些全局状态(比如关卡信息)的指针。因为,所有的场景(scene)都可以很方便地得到delegate对象。我们也会在App Delegate类里面放置一些方法,用来实现不同场景之间的切换的集中控制。并且减少场景之间的相互依赖。

      好了,上面就是我所做的主要的重构内容,记住,这只是实现功能的方式之一,如果你有其它更好的组织场景和游戏对象的方法,请在这里分享出来吧!

     

    上面多个关卡的设计是原作者的话。但是对于入门者来说,讲了那么多的理论还是不会。。。

    下面我就不怕帖子又长又乏,来彻底实现下多个关卡吧。虽然设计得可能不是太好,不过还是能用了。。。

    现在来看下我们的游戏逻辑实现。我们如要重构出Level。那么level类包含什么元素呢,Monster的hp,speed.还有每个关卡需要完成的打击数。我们决定用Level类来完成当前关卡的Monster获取。

    那么修改Monster.cs里面的代码,修改如下:

     

    class WeakAndFastMonster : Monster
        {
            public static WeakAndFastMonster monster(int _hp,int _minMoveDuration,int _maxMoveDuration)
            {
                WeakAndFastMonster monster = new WeakAndFastMonster();
                if (monster.initWithFile(@"images/Target"))
                {
                    monster.hp = _hp;
                    monster.minMoveDuration = _minMoveDuration;//3;
                    monster.maxMoveDuration = _maxMoveDuration;//5;
                }
                return monster;
            }
        }
    
        class StrongAndSlowMonster : Monster
        {
            public static StrongAndSlowMonster monster(int _hp, int _minMoveDuration, int _maxMoveDuration)
            {
                StrongAndSlowMonster monster = new StrongAndSlowMonster();
                if (monster.initWithFile(@"images/Target2"))
                {
                    monster.hp = _hp;//3;
                    monster.minMoveDuration = _minMoveDuration;//6;
                    monster.maxMoveDuration = _maxMoveDuration;//12;
                }
                return monster;
            }
        }

    我们把速度和hp作为参数了。

    那么我们新建一个类添加到Classes。命名为Level.cs。Level类的代码如下:

    class Level
        {
            int _level;
            int _levelCount;
    
            public int levelCount { get { return _levelCount; } }
            public int level { get { return _level; } }
            public Level()
            { }
    
            /// <summary>
            /// 默认有7个关卡
            /// </summary>
            /// <param name="l"></param>
            public Level(int l)
            {
                if (l <= 0 || l > 7)
                    _level = 1;
                else
                    _level = l;
                _levelCount = GetLevelCount(_level);
            }
    
            /// <summary>
            /// 获取每个关卡要完成的打击数
            /// </summary>
            /// <param name="level"></param>
            /// <returns></returns>
            private int GetLevelCount(int level)
            {
                switch (level)
                {
                    case 1:
                        return 10;
                    case 2:
                        return 10;
                    case 3:
                        return 35;
                    case 4: return 50;
                    case 5: return 55;
                    case 6: return 60;
                    case 7: return 65;
                    default:
                        return 30;
                }
            }
    
            /// <summary>
            /// 跳转到下一关
            /// </summary>
            public void NextLevel()
            {
                _level++;
                if (_level > 7)
                {
                    _level = 1;
                }
                _levelCount = GetLevelCount(_level);
            }
    
            /// <summary>
            /// 有Level来生成怪兽。每个关卡的怪兽都不一样。
            /// </summary>
            /// <returns></returns>
            public Monster GetMonster()
            {
                Monster monster;
                Random random = new Random();
                switch (level)
                {
                    case 1: monster = WeakAndFastMonster.monster(1, 5, 8); break;
                    case 2: monster = WeakAndFastMonster.monster(1, 4, 7); break;
                    case 3: monster = WeakAndFastMonster.monster(1, 3, 5); break;
                    case 4:
                        {
                            if (random.Next() % 7 == 0)
                                monster = StrongAndSlowMonster.monster(3, 6, 12);
                            else
                                monster = WeakAndFastMonster.monster(1, 3, 6);
                            break;
                        }
                    case 5:
                        {
                            if (random.Next() % 5 == 0)
                                monster = StrongAndSlowMonster.monster(3, 6, 12);
                            else
                                monster = WeakAndFastMonster.monster(1, 3, 6);
                            break; 
                        }
                    case 6:
                        {
                            if (random.Next() % 4 == 0)
                                monster = StrongAndSlowMonster.monster(3, 6, 12);
                            else
                                monster = WeakAndFastMonster.monster(1, 2, 6);
                            break;
                        }
                    case 7:
                        {
                            if (random.Next() % 3 == 0)
                                monster = StrongAndSlowMonster.monster(3, 6, 12);
                            else
                                monster = WeakAndFastMonster.monster(1, 3, 6);
                            break;
                        }
                    default:
                        monster = WeakAndFastMonster.monster(1, 3, 7);break;
                }
                return monster;
            }
        }


    接下来要修改GamePlayLayer类。在类中添加两个声明:

            Level level = new Level(1);
            int life = 40;


    如果下载了我前两个教程的工程代码,就发现我在GamePlayLayer里面添加了一个Label作为信息显示,如果您现在的工程没有添加。那么在类再添加一个声明:

     

    CCLabelTTF label;

    下面在init里面添加label的初始化:

                string msg = String.Format("Count:{0},life:{1},Level:{2}", projectilesDestroyed, life, level.level);
                label = CCLabelTTF.labelWithString(msg, "Arial", 24);
                label.position = new CCPoint(label.contentSize.width / 2, screenHeight - label.contentSize.height / 2);
                addChild(label);


    这里用这个label来显示杀敌数,大炮剩余的生命值,和当前关卡。

    然后,修改addTarget方法来构造我们新创建的类的实例,用level来创建Monster。

                //CCSprite target = CCSprite.spriteWithFile(@"images/Target");
                Monster target = null;
                //if (random.Next() % 2 == 0)
                //    target = WeakAndFastMonster.monster();
                //else
                //    target = StrongAndSlowMonster.monster();
                target = level.GetMonster();


    接着修改spriteMoveFinished方法。

    if (sprite.tag == 1)//target
                {
                    _targets.Remove(sprite);
                    life--;
                    string msg = String.Format("Count:{0},life:{1},Level:{2}", projectilesDestroyed, life, level.level);
                    label.setString(msg);
                    if (life <= 0)
                    {
                        GameOverScene pScene = new GameOverScene(false);
                        CCDirector.sharedDirector().replaceScene(pScene);
                    }
                }


    上面修改了个判断,当生命值为0的时候,跳转到GamOverScene。

    下面修改胜利判断。找到updates方法中foreach (CCSprite target in targetToDelete)这里。修改如下:

                    foreach (CCSprite target in targetToDelete)
                    {
                        _targets.Remove(target);
                        projectilesDestroyed++;
                        string msg = String.Format("Count:{0},life:{1},Level:{2}", projectilesDestroyed, life, level.level);
                        label.setString(msg);
                        if (projectilesDestroyed >= level.levelCount)
                        {
                            GameOverScene pScene = new GameOverScene(true);
                            CCDirector.sharedDirector().replaceScene(pScene);
                        }
                        this.removeChild(target, true);
                    }


    上面胜利判断的做法很明显了。就不多说了。

    到这里,逻辑修改好了,Level的重构就算是完了。

    大家应该发现了,上面GameOverScene的调用改了。一会再说怎么修改。

    接下来要做的场景重用,场景重用,就要保留原来的场景,但是在WP7的程序里面,全局变量怎么保存呢,我们用PhoneApplicationService来保存。

    首先添加两个引用。Microsoft.Phone.dll和System.Windows.dll这两个引用。

    场景重用要去掉场景中原来的需要去掉的精灵等元素,我们添加一个方法到GamePlayLayer来完成。

           /// <summary>
            /// 清除任何老的状态
            /// </summary>
            /// <param name="replay">是否重玩当前关卡</param>
            public void Reset(bool replay)
            {
                foreach (var item in _targets)
                {
                    this.removeChild(item,true);
                }
                foreach (var item in _projectiles)
                {
                    this.removeChild(item, true);
                }
                _targets.Clear();
                _projectiles.Clear();
                projectilesDestroyed = 0;
                nextProjectile = null;
                if (replay)
                    life = 40;
                else
                    level.NextLevel();
                this.schedule(gameLogic, 1.0f);
                this.schedule(updates);
                string msg = String.Format("Count:{0},life:{1},Level:{2}", projectilesDestroyed, life, level.level);
                label.setString(msg);
            }


    注意,场景一旦跳转,重回场景后那些schedule事件都无效了。所以要重置。这里设置的逻辑是不重玩就是下一关。在这里,我们主要清除的状态也就是_targets和_projectiles里面的精灵,remove后,把这两个List清空。

    那么,我们要保存这个GamePlayScene。

    GamePlayScene这个类修改如下:

        class GamePlayScene:CCScene
        {
            public GamePlayScene()
            {
                CCLayerColor colorLayer = CCLayerColor.layerWithColor(new ccColor4B(255, 255, 255, 255));
                this.addChild(colorLayer);
                GamePlayLayer pLayer = (GamePlayLayer)GamePlayLayer.node();
                pLayer.tag = 3;
                this.addChild(pLayer);
                PhoneApplicationService.Current.State["PlayScene"] = this;
            }
        }


    为了获取到游戏层,我们为其添加了一个tag元素。并且在构造函数中,把这个类保存到了PhoneApplicationService里面。

     

    接下来修改的是GameOverScene。我们要使这个GameOverScene这个场景作为一个跳板。来实现关卡的跳转。

    下面是胜利的界面:

    拥有三个选项,重玩,回到菜单,下一关。那么我们就需要一些图片。可以到这里下载:http://dl.dbank.com/c0g3z4wmma,并且将图片添加到Content工程的images目录下。

    PS;图片有些大,懒得整了,将就着用吧。

    GameOverScene类修改如下:

        class GameOverScene:CCScene
        {
            public CCLabelTTF label;
            public GameOverScene()
            {        }
            public GameOverScene(bool isWin)
            {
                CCLayerColor colorLayer = CCLayerColor.layerWithColor(new ccColor4B(255, 255, 255, 255));
                this.addChild(colorLayer);
                CCSize winSize = CCDirector.sharedDirector().getWinSize();
                string msg;
                if (isWin)
                    msg = "YOU WIN";
                else
                    msg = "YOU LOSE";
                label = CCLabelTTF.labelWithString(msg, "Arial", 32);
                label.Color = new ccColor3B(0, 0, 0);
                label.position = new CCPoint(winSize.width / 2, winSize.height / 2 + 100);
                this.addChild(label);
                //this.runAction(CCSequence.actions(CCDelayTime.actionWithDuration(3), CCCallFunc.actionWithTarget(this, gameOverDone)));
                var itemReplay = CCMenuItemImage.itemFromNormalImage(@"images/reload", @"images/reload", this, replay);
                var itemMainMenu = CCMenuItemImage.itemFromNormalImage(@"images/mainmenu", @"images/mainmenu", this, mainMenu);
                var itemNextLevel = CCMenuItemImage.itemFromNormalImage(@"images/nextlevel", @"images/nextlevel", this, nextLevel);
                if (!isWin)
                    itemNextLevel.visible = false;
                var menu = CCMenu.menuWithItems(itemReplay, itemMainMenu, itemNextLevel);
                menu.alignItemsHorizontally();
                menu.position = new CCPoint(winSize.width / 2, winSize.height / 2 - 100);
                this.addChild(menu);
            }
    
            void nextLevel(object sender)
            {
                GamePlayScene pScene;
                if (PhoneApplicationService.Current.State.ContainsKey("PlayScene"))
                {
                    pScene = (GamePlayScene)PhoneApplicationService.Current.State["PlayScene"];
                    GamePlayLayer pLayer = (GamePlayLayer)pScene.getChildByTag(3);
                    pLayer.Reset(false);
                }
                else
                    pScene = new GamePlayScene();
                CCDirector.sharedDirector().replaceScene(pScene);
            }
    
            void mainMenu(object sender)
            {
                CCScene pScene = CCScene.node();
                pScene.addChild(cocos2dSimpleGame.Classes.MainMenu.node());
                CCDirector.sharedDirector().replaceScene(pScene);
            }
    
            void replay(object sender)
            {
                GamePlayScene pScene;
                if (PhoneApplicationService.Current.State.ContainsKey("PlayScene"))
                {
                    pScene = (GamePlayScene)PhoneApplicationService.Current.State["PlayScene"];
                    GamePlayLayer pLayer = (GamePlayLayer)pScene.getChildByTag(3);
                    pLayer.Reset(true);
                }
                else
                    pScene = new GamePlayScene();
                CCDirector.sharedDirector().replaceScene(pScene);
            }
    
            void gameOverDone()
            {
                CCScene pScene = CCScene.node();
                pScene.addChild(cocos2dSimpleGame.Classes.MainMenu.node());
                CCDirector.sharedDirector().replaceScene(pScene);
            }
        }


    上面基本的逻辑估计都能看懂了。就是添加了三个菜单选项。在重玩和下一关中,先取到那个场景,然后取到游戏层,调用Reset,完成重玩或者下一关的设置。然后场景跳转。

      到这里,不管怎么说,我们有一个非常不错的游戏了----一个旋转的炮塔,成千上万的不同类型的敌人,多个关卡,win/lose场景,当然,还有很棒的音效!

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

    继续学习:用cocos2d-x做一个简单的windows phone 7游戏:墓碑机制和收尾工作(完)

  • 相关阅读:
    python 正则表达式
    python 递归查找
    MYSQL 索引优化,避免回表
    MYSQL ibtmp文件暴增
    mysql 主从复制刷新参数
    MYSQL 复制数据过滤
    快速入门Kubernetes
    ansible之playbook的编写
    ansible的安装及常用模块详解
    ERROR Failed to discover available identity versions when contacting http://ct:5000/v3.
  • 原文地址:https://www.cnblogs.com/fengyun1989/p/2476044.html
Copyright © 2020-2023  润新知