您的位置:首页 > 移动开发 > Cocos引擎

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

2012-04-23 19:53 423 查看
本教程基于子龙山人翻译的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游戏:墓碑机制和收尾工作(完)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: