“指”上谈兵-如何使用Cocos2D制作一款iPhone回合制策略游戏PART-2

本文由eseedo翻译,泰然授权转载,其他转载请通知原版权方!

继续上一部分的学习,我们很快就会搞定一个完整的简单回合制策略游戏了!



在第一部分的教程中,我们学习了如何加载瓦片地图,初始化军事单位(陆战队员,大炮和直升机),以及如何使用A*寻路算法让他们在瓦片地图上运动。

而在这部分的教程中(同时也是最后一部分),我们将让这些军事单位完成自己的使命-开战!



在接下来的内容里,我们将向地图中添加建筑,然后添加游戏的赢输机制。此外,我们还将添加逻辑机制来切换玩家的操控顺序,同时还会增加一些锦上添花的音乐和音效。

当然,最让人开心的是,这个项目预留了扩展的空间,这样你就可以基于它来制作属于自己的回合制策略游戏!

在继续学习之前,确保你已经准备好了上一部分教程结束时的工程(http://d1xzuxjlafny7l.cloudfront.net/downloads/TurnWars-Part1.zip)。

 

然后,前进!冲锋!

 

“给我上,你们这帮猿人!想他妈活一辈子老不死吗?”
—— 一位无名副排长,1918年

 

添加弹出菜单

 

在进入战斗之前,让我们先花个几分钟时间来添加一个弹出菜单。当玩家移动完某个军事单位后,可以使用弹出菜单来选择其行为,如停留在新的位置无所事事,返回原来的位置,或者(可能的话)攻击旁边的敌军单位。

 

首先来添加一些辅助方法。当然,在此之前还是要先添加几个实例变量的声明。在Xcode中切换到HelloWorldLayer.h,然后添加以下实例变量:

 

CCMenu *actionsMenu;
  CCSprite *contextMenuBck;

以上我们定义了一个代表弹出菜单的CCMenu实例变量,并创建了一个精灵实例变量,指向菜单的背景。

为了从Unit类中访问菜单和前一个选中的军事单位,我们需要将actionMenu和selectedUnit定义为属性变量:

@property(nonatomic,assign) Unit *selectedUnit;
@property(nonatomic,assign) CCMenu *actionsMenu;

当然,别忘了在HelloWorldLayer.m中合成这两个属性:

@synthesize selectedUnit;

@synthesize actionsMenu;

 

 

再次切换到HelloWorldLayer.h,并添加两个辅助方法的定义:

-(void) showActionsMenu:(Unit*)unit canAttack:(BOOL)canAttack;

-(void) removeActionsMenu;

 

接下来在HelloWorldLayer.m中实现这两个方法:

-(void)showActionsMenu:(Unit *)unit canAttack:(BOOL)canAttack {
    // 1 - Get the window size
    CGSize wins = [[CCDirector sharedDirector] winSize];
    // 2 - Create the menu background
    contextMenuBck = [CCSprite spriteWithFile:@"popup_bg.png"];
    [self addChild:contextMenuBck z:19];
    // 3 - Create the menu option labels
    CCLabelBMFont * stayLbl = [CCLabelBMFont labelWithString:@"Stay" fntFile:@"Font_dark_size15.fnt"];
    CCMenuItemLabel * stayBtn = [CCMenuItemLabel itemWithLabel:stayLbl target:unit selector:@selector(doStay)];
    CCLabelBMFont * attackLbl = [CCLabelBMFont labelWithString:@"Attack" fntFile:@"Font_dark_size15.fnt"];
    CCMenuItemLabel * attackBtn = [CCMenuItemLabel itemWithLabel:attackLbl target:unit selector:@selector(doAttack)];
    CCLabelBMFont * cancelLbl = [CCLabelBMFont labelWithString:@"Cancel" fntFile:@"Font_dark_size15.fnt"];
    CCMenuItemLabel * cancelBtn = [CCMenuItemLabel itemWithLabel:cancelLbl target:unit selector:@selector(doCancel)];
    // 4 - Create the menu
    actionsMenu = [CCMenu menuWithItems:nil];
    // 5 - Add Stay button
    [actionsMenu addChild:stayBtn];
    // 6 - Add the Attack button only if the current unit can attack
    if (canAttack) {
        [actionsMenu addChild:attackBtn];
    }
    // 7 - Add the Cancel button
    [actionsMenu addChild:cancelBtn];
    // 8 - Add the menu to the layer
    [self addChild:actionsMenu z:19];
    // 9 - Position menu
    [actionsMenu alignItemsVerticallyWithPadding:5];
    if (unit.mySprite.position.x > wins.width/2) {
        [contextMenuBck setPosition:ccp(100,wins.height/2)];
        [actionsMenu setPosition:ccp(100,wins.height/2)];
    } else {
        [contextMenuBck setPosition:ccp(wins.width-100,wins.height/2)];
        [actionsMenu setPosition:ccp(wins.width-100,wins.height/2)];
    }
}

-(void)removeActionsMenu {
    // Remove the menu from the layer and clean up
    [contextMenuBck.parent removeChild:contextMenuBck cleanup:YES];
    contextMenuBck = nil;
    [actionsMenu.parent removeChild:actionsMenu cleanup:YES];
    actionsMenu = nil;
}

以上两个方法分别用于创建弹出菜单,以及在不再需要的时候删除它。现在该菜单仅允许玩家取消或确认军事单位的行动,但最后将提供第三个选项以攻击某个敌军单位。很快我们就会添加该功能。

 

在Xcode中切换到Unit.m,找到popStepAndAnimate:方法,并使用以下代码替代if循环部分:

// Check if there remain path steps to go through
  if ([movementPath count] == 0) {
    moving = NO;
    [self unMarkPossibleMovement];
    BOOL enemiesAreInRange = NO;
    // You'll determine later if there is a nearby enemy to attack.
    [theGame showActionsMenu:self canAttack:enemiesAreInRange];
    return;
  }
  // Get the next step to move toward

在以上代码中,我们调用showActionsMenu:canAttack:这个辅助方法来显示弹出菜单。此时我们将enemiesAreInRange的属性硬性规定为NO,这样就不会在菜单中显示Attack选项。

 

接下来在ccTouchBegan:方法的顶部添加以下代码:

/ If the action menu is showing, do not handle any touches on unit
if (theGame.actionsMenu)
    return NO;
// If the current unit is the selected unit, do not handle any touches
if (theGame.selectedUnit == self)
    return NO;
// If this unit has moved already, do not handle any touches
if (movedThisTurn)
    return NO;

然后我们需要添加新的方法,以便处理玩家通过弹出菜单所选的行动。不够有一种行动需要我们判断当前的军事单位是否属于Solider(很快我们就会接触这段代码)。所以我们需要在Unit.m的顶部导入Solider单位的头文件:

 

#import "Unit_Soldier.h"

 

最后让我们在Unit.m的底部添加以下方法来实现弹出菜单中设定的各种行为:

// Stay on the current tile
-(void)doStay {
    // 1 - Remove the context menu since we've taken an action
    [theGame removeActionsMenu];
    movedThisTurn = YES;
    // 2 - Turn the unit tray to indicate that it has moved
    [mySprite setColor:ccGRAY];
    [theGame unselectUnit];
    // 3 - Check for victory conditions
    if ([self isKindOfClass:[Unit_Soldier class]]) {
        // If this is a Soldier unit and it is standing over an enemy building, the player wins.
        // We'll handle this situation in detail later
    }
}

// Attack another unit
-(void)doAttack {
    // You'll handle attack later
}

// Cancel the move for the current unit and go back to previous position
-(void)doCancel {
    // Remove the context menu since we've taken an action
    [theGame removeActionsMenu];
    // Move back to the previous tile
    mySprite.position = [theGame positionForTileCoord:tileDataBeforeMovement.position];
    [theGame unselectUnit];
}

搞定收工!

编译运行项目,现在我们可以在单位移动完成后看到该菜单,并和它进行交互。

 

鈥溨糕澤咸副-如何制作一款回合制策略游戏2

 

 

 

解决回合制的问题

 

如果你足够细心,会发现几个让人不爽的事情。首先,一旦你移动完某个单位后,就再也不能移动它了。而且你没法在玩家间切换,也就是说两方的单位都可以随时移动!

虽然这些不会影响游戏性,但这篇教程的主题是回合制策略游戏。所以,为了让这个主题得以实现,我们将在屏幕的顶部添加一个HUD,用于说明当前该哪个玩家来操控。HUD中还会提供一个按钮,从而把控制权交给另一个玩家。当然,每个玩家只能操控属于自己的单位。

为了实现上面的目的,我们需要添加几个实例变量。切换到HelloWorldLayer.h,并添加以下代码:

CCMenuItemImage *endTurnBtn;
  CCLabelBMFont *turnLabel;

然后添加以下方法的定义:

-(void)addMenu;
-(void)doEndTurn;
-(void)setPlayerTurnLabel;
-(void)showEndTurnTransition;
-(void)beginTurn;
-(void)removeLayer:(CCNode *)n;
-(void)activateUnits:(NSMutableArray *)units;

切换到HelloWorldLayer.m,并在init的底部添加以下代码:

//Set up turns
    playerTurn = 1;
    [self addMenu];

接下来,在文件的底部添加以上方法的实现代码:

// Add the user turn menu
-(void)addMenu {
    // Get window size
    CGSize wins = [[CCDirector sharedDirector] winSize];
    // Set up the menu background and position
    CCSprite * hud = [CCSprite spriteWithFile:@"uiBar.png"];
    [self addChild:hud];
    [hud setPosition:ccp(wins.width/2,wins.height-[hud boundingBox].size.height/2)];
    // Set up the label showing the turn
    turnLabel = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"Player %d's turn",playerTurn] fntFile:@"Font_dark_size15.fnt"];
    [self addChild:turnLabel];
    [turnLabel setPosition:ccp([turnLabel boundingBox].size.width/2 + 5,wins.height-[hud boundingBox].size.height/2)];
    // Set the turn label to display the current turn
    [self setPlayerTurnLabel];
    // Create End Turn button
    endTurnBtn = [CCMenuItemImage itemFromNormalImage:@"uiBar_button.png" selectedImage:@"uiBar_button.png" target:self selector:@selector(doEndTurn)];
    CCMenu * menu = [CCMenu menuWithItems:endTurnBtn, nil];
    [self addChild:menu];
    [menu setPosition:ccp(0,0)];
    [endTurnBtn setPosition:ccp(wins.width - 3 - [endTurnBtn boundingBox].size.width/2, wins.height - [endTurnBtn boundingBox].size.height/2 - 3)];
}

// End the turn, passing control to the other player
-(void)doEndTurn {
    // Do not do anything if a unit is selected
    if (selectedUnit)
        return;
    // Switch players depending on who's currently selected
    if (playerTurn ==1) {
        playerTurn = 2;
    } else if (playerTurn ==2) {
        playerTurn = 1;
    }
    // Do a transition to signify the end of turn
    [self showEndTurnTransition];
    // Set the turn label to display the current turn
    [self setPlayerTurnLabel];
}

// Set the turn label to display the current turn
-(void)setPlayerTurnLabel {
    // Set the label value for the current player
    [turnLabel setString:[NSString stringWithFormat:@"Player %d's turn",playerTurn]];
    // Change the label colour based on the player
    if (playerTurn ==1) {
        [turnLabel setColor:ccRED];
    } else if (playerTurn == 2) {
        [turnLabel setColor:ccBLUE];
    }
}

// Fancy transition to show turn switch/end
-(void)showEndTurnTransition {
    // Create a black layer
    ccColor4B c = {0,0,0,0};
    CCLayerColor *layer = [CCLayerColor layerWithColor:c];
    [self addChild:layer z:20];
    // Add a label showing the player turn to the black layer
    CCLabelBMFont * turnLbl = [CCLabelBMFont labelWithString:[NSString stringWithFormat:@"Player %d's turn",playerTurn] fntFile:@"Font_silver_size17.fnt"];
    [layer addChild:turnLbl];
    [turnLbl setPosition:ccp([CCDirector sharedDirector].winSize.width/2,[CCDirector sharedDirector].winSize.height/2)];
    // Run an action which fades in the black layer, calls the beginTurn method, fades out the black layer, and finally removes it
    [layer runAction:[CCSequence actions:[CCFadeTo actionWithDuration:1 opacity:150],[CCCallFunc actionWithTarget:self selector:@selector(beginTurn)],[CCFadeTo actionWithDuration:1 opacity:0],[CCCallFuncN actionWithTarget:self selector:@selector(removeLayer:)], nil]];
}

// Begin the next turn
-(void)beginTurn {
    // Activate the units for the active player
    if (playerTurn ==1) {
        [self activateUnits:p2Units];
    } else if (playerTurn ==2) {
        [self activateUnits:p1Units];
    }
}

// Remove the black layer added for the turn change transition
-(void)removeLayer:(CCNode *)n {
    [n.parent removeChild:n cleanup:YES];
}

此时你会发现我们少了一个方法activateUnits:的实现代码,这是因为该方法将激活所有属于当前玩家的军事单位。遗憾的是在Unit类中还没有方法实现它。

所以让我们切换到Unit.h,添加一个新的辅助方法的定义:

 

-(void)startTurn;

 

接下来当然是在Unit.m中添加该方法的实现代码:

// Activate this unit for play
-(void)startTurn {
  // Mark the unit as not having moved for this turn
  movedThisTurn = NO;
  // Mark the unit as not having attacked this turn
  attackedThisTurn = NO;
  // Change the unit overlay colour from gray (inactive) to white (active)
  [mySprite setColor:ccWHITE];
}

现在辅助方法就绪了,接下来让我们切换回HelloWorldLayer.m,并在文件的最后添加activateUnits:的实现代码:

// Activate all the units in the specified array (called from beginTurn passing the units for the active player)
-(void)activateUnits:(NSMutableArray *)units {
    for (Unit *unit in units) {
        [unit startTurn];
    }
}

如你所见,该方法将会遍历当前玩家的军事单位数组,并使用startTurn方法来依次激活每个军事单位。

 

现在搞定了没?差不多了~不过还有一个小小的问题需要解决,此时我们还无法阻止玩家选择不属于自己的军事单位。

要修复这个问题很简单。在Xcode中切换到Unit.m,然后在ccTouchBegan:方法的顶部添加以下代码:

// Was a unit belonging to the non-active player touched? If yes, do not handle the touch
if (([theGame.p1Units containsObject:self] && theGame.playerTurn == 2) || ([theGame.p2Units containsObject:self] && theGame.playerTurn == 1))
    return NO;

以上代码的作用是检查所触碰的单位是否属于玩家1或2,如果所触碰的单位不属于当前的玩家,则什么也不做。

 

编译运行游戏,我们会看到右上角有个按钮,可以让当前玩家将操控权交给下一个玩家。每个玩家都可以在自己的操控时间内移动属于自己的某个单位,然后该单位在下一回合前将不可用。

 

当你结束一个回合时,将可以看到切换效果。

 

鈥溨糕澤咸副-如何制作一款回合制策略游戏2

 

 

攻击其它军事单位

 

搞了大半天,我们的战争机器还没有真正运转起来,不打仗还叫军事单位?首先我们来设定下逻辑:

1.当某个军事单位移动完成后,如果它在攻击范围内(临近某个敌军单位,大炮除外,因为其攻击范围很广),那么它可以攻击到的单位将使用红色标出。

2.如果玩家选择某个使用红色标出的单位,则战斗开始。

3.攻击单位将会首先开火,而如果防御单位幸免于难,则将开火反击。

4.每个单位所造成的伤害将由双方的单位类型来决定,和剪刀石头布的游戏有点像,每种单位都有自己的克星,同时也克制着另外几种单位。

 

首先要做的事情就是在移动完成后检查周边的敌军单位。为此,切换到Unit.m,并使用以下代码替换popStepAndAnimate:方法的#1部分:

// 1 - Check if the unit is done moving
if ([movementPath count] == 0) {
    // 1.1 - Mark the unit as not moving
    moving = NO;
    [self unMarkPossibleMovement];
   // 1.2 - Mark the tiles that can be attacked
    [self markPossibleAction:kACTION_ATTACK];
    // 1.3 - Check for enemies in range
    BOOL enemiesAreInRange = NO;
    for (TileData *td in theGame.tileDataArray) {
        if (td.selectedForAttack) {
            enemiesAreInRange = YES;
            break;
        }
    }
    // 1.4 - Show the menu and enable the Attack option if there are enemies in range
    [theGame showActionsMenu:self canAttack:enemiesAreInRange];
    return;
}

在以上代码中的1.3部分,我们检查了tileDataArray,该数组保存了背景层中所有显示的瓦片,并判断是否有瓦片被标记为selectedForAttack。那么一个瓦片是如何被设置为selectedForAttack的呢?实际上在1.2部分中当我们调用markPossibleAction:时就已完成了设置。不过既然我们到现在为止还没有真正实施攻击,就必须将该功能添加到markPossibleAction:方法中。

 

在实际修改该方法前,还需要添加一些辅助方法。为什么呢?因为目前我们还没办法来检查包含敌军单位的瓦片。和之前一样,当我们所需的辅助方法在游戏区域中时,会把这些方法添加到HelloWorldLayer中。

 

首先在HelloWorldLayer.h中添加以下方法定义:

-(BOOL)checkAttackTile:(TileData *)tData unitOwner:(int)owner;
-(BOOL)paintAttackTile:(TileData *)tData;
-(void)unPaintAttackTiles;
-(void)unPaintAttackTile:(TileData *)tileData;

然后切换到HelloWorldLayer.m,并在文件的最后添加方法的实现代码如下:

// Check the specified tile to see if it can be attacked
-(BOOL)checkAttackTile:(TileData *)tData unitOwner:(int)owner {
    // Is this tile already marked for attack, if so, we don't need to do anything further
    // If not, does the tile contain an enemy unit? If yes, we can attack this tile
    if (!tData.selectedForAttack && [self otherEnemyUnitInTile:tData unitOwner:owner]!= nil) {
        tData.selectedForAttack = YES;
        return NO;
    }
    return YES;
}

// Paint the given tile as one that can be attacked
-(BOOL)paintAttackTile:(TileData *)tData {
    CCSprite * tile = [bgLayer tileAt:tData.position];
    [tile setColor:ccRED];
    return YES;
}

// Remove the attack marking from all tiles
-(void)unPaintAttackTiles {
    for (TileData * td in tileDataArray) {
        [self unPaintAttackTile:td];
    }
}

// Remove the attack marking from a specific tile
-(void)unPaintAttackTile:(TileData *)tileData {
    CCSprite * tile = [bgLayer tileAt:tileData.position];
    [tile setColor:ccWHITE];
}

ok辅助方法已经完全准备好了,接下来我们可以让Unit对象调用checkAttackTile:unitOwner方法,以检查完成移动的军事单位附近的每个瓦片。

 

我们需要在Unit.m的markPossibleAction:方法中几次调用该方法。首先,将之前注释的else if(action ==kAction_ATTACK)语句替换为以下语句:

else if (action == kACTION_ATTACK) {
    [theGame checkAttackTile:startTileData unitOwner:owner];
}

 

然后替换for循环中的被注释的else if条件语句:

else if (action == kACTION_ATTACK) {
    [theGame checkAttackTile:_neighbourTile unitOwner:owner];
}

最后,紧接着上面的语句还有一个else if条件语句,不过它并没有被注释,使用以下语句替换其中的内容:

else if (action == kACTION_ATTACK) {
    // Is the tile not in attack range?
    if ([_neighbourTile getGScoreForAttack]> attackRange) {
        // Ignore it
        continue;
    }
}

我们还需要实现doAttack:方法,当玩家从弹出菜单中选择Attack选项时将调用该方法。使用以下代码替换doAttack方法的内容:

// Attack another unit
-(void)doAttack {
    // 1 - Remove the context menu since we've taken an action
    [theGame removeActionsMenu];
    // 2 - Check if any tile has been selected for attack
    for (TileData *td in theGame.tileDataArray) {
        if (td.selectedForAttack) {
            // 3 - Mark the selected tile as attackable
            [theGame paintAttackTile:td];
        }
    }
    selectingAttack = YES;
}

一旦某个军事单位完成了当前的回合,我们将会取消瓦片上的可攻击标记。让我们来添加一个方法来实现这一点。和之前一样,首先在Unit.h中添加一个方法定义:

-(void)unMarkPossibleAttack;

 

然后在Unit.m中添加该方法的实现代码:

// Remove attack selection marking from all tiles
-(void)unMarkPossibleAttack {
    for (TileData *td in theGame.tileDataArray) {
        [theGame unPaintAttackTile:td];
        td.parentTile = nil;
        td.selectedForAttack = NO;
    }
}

最后,在unselectUnit方法的结尾处添加代码调用刚才的方法:

[self unMarkPossibleAttack];

 

 

编译运行游戏。如果你移动某个单位靠近敌军单位,并从弹出菜单中选择attack,敌军单位就会变红。

 

鈥溨糕澤咸副-如何制作一款回合制策略游戏2

 

 

可是现在又有了一些小问题。当我们触碰要攻击的敌军单位时,并没有发生其它事情。此外,一旦某个单位被选择用于攻击,就无法结束当前的回合。

接下来,我们将学习如何处理真实的火力交锋,伤害计算和单位的损伤,以及解决回合结束的问题。

 

首先还是要在HelloWorldLayer中添加一个新的辅助方法,用于判断攻击中所受到的伤害。在HelloWorldLayer.h中添加以下方法定义:

 

-(int)calculateDamageFrom:(Unit *)attacker onDefender:(Unit *)defender;

 

然后切换到HelloWorldLayer.m,首先导入所需的单位类型头文件:

#import "Unit_Soldier.h"

#import "Unit_Tank.h"

#import "Unit_Cannon.h"

#import "Unit_Helicopter.h"

 

然后添加该方法的实现代码:

// Calculate the damage inflicted when one unit attacks another based on the unit type
-(int)calculateDamageFrom:(Unit *)attacker onDefender:(Unit *)defender {
    if ([attacker isKindOfClass:[Unit_Soldier class]]) {
        if ([defender isKindOfClass:[Unit_Soldier class]]) {
            return 5;
        } else if ([defender isKindOfClass:[Unit_Helicopter class]]) {
            return 1;
        } else if ([defender isKindOfClass:[Unit_Tank class]]) {
            return 2;
        } else if ([defender isKindOfClass:[Unit_Cannon class]]) {
            return 4;
        }
    } else if ([attacker isKindOfClass:[Unit_Tank class]]) {
        if ([defender isKindOfClass:[Unit_Soldier class]]) {
            return 6;
        } else if ([defender isKindOfClass:[Unit_Helicopter class]]) {
            return 3;
        } else if ([defender isKindOfClass:[Unit_Tank class]]) {
            return 5;
        } else if ([defender isKindOfClass:[Unit_Cannon class]]) {
            return 8;
        }
    } else if ([attacker isKindOfClass:[Unit_Helicopter class]]) {
        if ([defender isKindOfClass:[Unit_Soldier class]]) {
            return 7;
        } else if ([defender isKindOfClass:[Unit_Helicopter class]]) {
            return 4;
        } else if ([defender isKindOfClass:[Unit_Tank class]]) {
            return 7;
        } else if ([defender isKindOfClass:[Unit_Cannon class]]) {
            return 3;
        }
    } else if ([attacker isKindOfClass:[Unit_Cannon class]]) {
        if ([defender isKindOfClass:[Unit_Soldier class]]) {
            return 6;
        } else if ([defender isKindOfClass:[Unit_Helicopter class]]) {
            return 0;
        } else if ([defender isKindOfClass:[Unit_Tank class]]) {
            return 8;
        } else if ([defender isKindOfClass:[Unit_Cannon class]]) {
            return 8;
        }
    }
    return 0;
}

接下来,我们需要在Unit类中添加方法来执行攻击和处理攻击。切换到Unit.h,并添加以下方法定义:

-(void)doMarkedAttack:(TileData *)targetTileData;
-(void)attackedBy:(Unit *)attacker firstAttack:(BOOL)firstAttack;
-(void)dealDamage:(NSMutableDictionary *)damageData;
-(void)removeExplosion:(CCSprite *)e;

然后切换到Unit.m,并在文件的最后添加以上方法的实现代码:

// Attack the specified tile
-(void)doMarkedAttack:(TileData *)targetTileData {
    // Mark the unit as having attacked this turn
    attackedThisTurn = YES;
    // Get the attacked unit
    Unit *attackedUnit = [theGame otherEnemyUnitInTile:targetTileData unitOwner:owner];
    // Let the attacked unit handle the attack
    [attackedUnit attackedBy:self firstAttack:YES];
    // Keep this unit in the curren location
    [self doStay];
}

// Handle the attack from another unit
-(void)attackedBy:(Unit *)attacker firstAttack:(BOOL)firstAttack {
    // Create the damage data since we need to pass this information on to another method
    NSMutableDictionary *damageData = [NSMutableDictionary dictionaryWithCapacity:2];
    [damageData setObject:attacker forKey:@"attacker"];
    [damageData setObject:[NSNumber numberWithBool:firstAttack] forKey:@"firstAttack"];
    // Create explosion sprite
    CCSprite *explosion = [CCSprite spriteWithFile:@"explosion_1.png"];
    [self addChild:explosion z:10];
    [explosion setPosition:mySprite.position];
    // Create explosion animation
    CCAnimation *animation = [CCAnimation animation];
    for (int i=1;i

需要注意的是,buildingInTile:会返回它在指定瓦片上找到的第一个建筑。如果两个建筑在同一个瓦片上,这样做可能会导致麻烦。好在我们的游戏不是这样设定的,所以暂且无需为这一点而担忧。

 

最后一步是确保当陆战队单位移动到敌军总部时让游戏结束。切换到Unit.m,在doStay:方法的#3部分的if循环中添加以下代码:

// Get the building on the current tile
Building *buildingBelow = [theGame buildingInTile:[theGame getTileData:[theGame tileCoordForPosition:mySprite.position]]];
// Is there a building?
if (buildingBelow) {
    // Is the building owned by the other player?
    if (buildingBelow.owner != self.owner) {
        NSLog(@"Building captured!!!");
        // Show end game message
        [theGame showEndGameMessageWithWinner:self.owner];
    }
}

终于搞定了!编译运行游戏,你会看到屏幕的两端都会出现敌军总部。试着把某个陆战队员单位移动到敌军总部上,游戏届时将会结束。

 

鈥溨糕澤咸副-如何制作一款回合制策略游戏2

 

 

 

音乐和音效

 

整个项目差不多完工了。不过如果一款游戏没有音乐和音效,对玩家的吸引力也会大打折扣。我从Incompetech.com中搞了点音乐,然后使用CXFR制作了音效。

 

首先在HelloWorldLayer.m中导入SimpleAudioEngine的头文件:

#import "SimpleAudioEngine.h"

 

在init方法的最后,在[self addMenu]语句之后添加以下代码:

 

// Play background music
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"Five Armies.mp3" loop:YES];

最后让我们给不同事件添加一些音效。

 

首先是当玩家选择”End Turn”按钮的时候,在doEndTurn方法的第一个if语句后添加以下代码:

// Play sound

[[SimpleAudioEngine sharedEngine] playEffect:@"btn.wav"];

 

然后在Unit.m的顶部添加代码:

#import "SimpleAudioEngine.h"

,并在attackedBy:firstAttack:方法的最后一行代码(播放爆炸动画)前添加以下代码:

// Play damage sound

[[SimpleAudioEngine sharedEngine] playEffect:@"hurt.wav"];

 

在dealDamage:方法的#4部分的开始处添加以下代码:

 

// Unit destroyed sound

[[SimpleAudioEngine sharedEngine] playEffect:@"explosion.wav"];

 

最后的最后的最后,在doStay,doAttack和doCancel方法的顶部添加以下代码:

// Play menu selection sound

[[SimpleAudioEngine sharedEngine] playEffect:@"btn.wav"];

 

于是,终于,我们完全搞定了!编译运行游戏,一款简单但完整的回合制策略游戏就大功告成了!

 

接下来做什么?

 

首先这里是本教程的完整示例:

http://d1xzuxjlafny7l.cloudfront.net/downloads/TurnWars-Part2.zip

 

不过现在还不是自鸣得意的时候。即便游戏的雏形已经有了,但如果要把它变成一款真正的产品,还需要很多事情要做:

1.何不添加一些新的更有趣的单位?比如,只能在水里移动的舰艇,可以搭载士兵的单位,可以治疗其它单位的医护兵或救护车,等等。

2.何不添加一些其它类型的建筑,比如可以使用现金来修建坦克的工厂,或是医院。

3.何不多提供几种地图选择?玩家可以选择其它地形,或是可以被摧毁的物体?

4.你也可以完全改变游戏的风格,比如一个中世纪魔幻风格的游戏,或是充满了外星异形战士的科幻题材游戏。

 

尽情发挥你的创意和想象力吧,期待着你的佳作!

标签: none

?>