用cocos2d-x做一個簡單的windows phone 7遊戲:更猛的怪獸和更多的關卡(三)

來源:互聯網
上載者:User

本教程基於子龍山人翻譯的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遊戲:墓碑機制和收尾工作(完)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.