(譯)加入敵人和戰鬥:若是使用cocos2d製做基於tiled地圖的遊戲:第三部分

免責申明(必讀!):本博客提供的全部教程的翻譯原稿均來自於互聯網,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本博客全部人、發表該翻譯稿之人無任何關係。謝謝合做!
程序截圖:
  這篇教程是Ray Wenderlich的《如何使用cocos2d製做基於tiled地圖的遊戲》系列教程的後續。若是你尚未看過前面兩部分的教程,能夠在個人博客上找到另外兩篇我翻譯Ray的教程。
  在第二部分教程中,Ray教你們如何在地圖中製做可碰撞的區域,如何使用tile屬性,如何製做能夠拾取的物品以及如何動態修改地圖、如何使用「Heads up display」來顯示分數。
  在這個教程中,咱們將加入敵人,這樣的話,你的忍者就能夠向它們扔飛鏢啦,同時還增長了勝利和失敗的遊戲邏輯。可是,首先,你得下載一些相關的 資源文件
  這個zip文件裏面包含如下內容:
    1.一個敵人精靈
    2.一個忍者飛鏢,從Ray的《如何使用cocos2d製做一個簡單的iphone遊戲》中拿過來的。
    3.兩張按鈕的圖片,在教程的後面有使用。
  在繼續學習以前,不要忘了把這些資源加入到你的工程中。

增長敵人

  到第二部分教程結束的時候,遊戲已經很酷了,可是它還不是一個完整的遊戲。你的忍者能夠垂手可得地四處遊蕩,想吃就吃。可是,何時玩家會勝利或者失敗呢。咱們不妨想象一下,有2個敵人在追殺你的忍者,那麼這個遊戲會顯得更加有趣。

敵人出現的位置點

  好了,回到Tiled軟件(此教程使用java版),而後打開你的Tile地圖(TileMap.tmx)。
  往對象層中加入一個對象,在player附近就行,可是不要太近,不然敵人一出現玩家就Game over了。這個位置將成爲敵人出現的位置點,把它命名爲「EnemySpawn1」。
  對象組(對象層中的全部對象組成一個對象組)中的對象被存儲在一個NSMutableDictionary中,同時使用對象名字做爲key。這意味着每個位置點必須有一個惟一的名字。儘管咱們能夠遍歷全部的key來比較哪一個是以「EnemySpawn」開頭,可是這樣作效率很低下。相反,咱們採用的作法是,使用一個屬性來表示,每一個給定的對象表明一個敵人出現的位置點。
  給這個對象一個屬性「Enemy」,同時賦一個值1.若是你想在這個教程的基礎上擴展,而且增長其它的不一樣類型的敵人,你可使用這些敵人的屬性值來表示不一樣類型的敵人。
  如今,製做6-10個這種敵人出現位置點對象,相應的它們離player的距離也要有一些不一樣。爲每個對象定義一個「Enemy」屬性,而且賦值爲1.保存這張地圖而且回到Xcode。

開始建立敵人

  好了,如今咱們將把敵人實際顯示到地圖上來。首先在HelloWorldScene.m中添加以下代碼:
複製代碼
// in the HelloWorld class
- ( void )addEnemyAtX:( int )x y:( int )y {
CCSprite 
* enemy  =  [CCSprite spriteWithFile: @" enemy1.png " ];
enemy.position 
=  ccp(x, y);
[self addChild:enemy];
}

//  in the init method - after creating the player
//  iterate through objects, finding all enemy spawn points
//  create an enemy for each one
NSMutableDictionary  *  spawnPoint;

for  (spawnPoint  in  [objects objects]) {
if  ([[spawnPoint valueForKey: @" Enemy " ] intValue]  == 1 ){
=  [[spawnPoint valueForKey: @" x " ] intValue];
=  [[spawnPoint valueForKey: @" y " ] intValue];
[self addEnemyAtX:x y:y];
}
}
複製代碼
  第一個循環遍歷對象列表,判斷它是不是一個敵人出現的位置點。若是是,則得到它的x和y座標值,而後調用addEnemyAtX:Y方法把它們加入到合適的地方去。
  這個addEnemyAtX:Y方法很是直白,它僅僅是在傳入的X,Y座標值處建立一個敵人精靈。
  若是你編譯並運行,你會看到這些敵人出如今你以前在Tiled工具中設定的位置處,很酷吧!
  可是,這裏有一個問題,這些敵人很傻瓜,它們並不會追殺你的忍者。

使它們移動

  所以,如今咱們將添加一些代碼,使這些敵人會追着咱們的player跑。由於,player確定會移動,咱們必須動態地改變敵人的運動方向。爲了實現這個目的,咱們讓敵人每次移動10個像素,而後在下一次移動以前,先調整它們的方向。在HelloWorldScene.m中加入以下代碼:
複製代碼
//  callback. starts another iteration of enemy movement.
-  ( void ) enemyMoveFinished:(id)sender {
CCSprite 
* enemy  =  (CCSprite  * )sender;

[self animateEnemy: enemy];
}

//  a method to move the enemy 10 pixels toward the player
-  ( void ) animateEnemy:(CCSprite * )enemy
{
//  speed of the enemy
ccTime actualDuration  = 0.3 ;

//  Create the actions
id actionMove  =  [CCMoveBy actionWithDuration:actualDuration
position:ccpMult(ccpNormalize(ccpSub(_player.position,enemy.position)), 
10 )];
id actionMoveDone 
=  [CCCallFuncN actionWithTarget:self
selector:@selector(enemyMoveFinished:)];
[enemy runAction:
[CCSequence actions:actionMove, actionMoveDone, nil]];
}

//  add this at the end of addEnemyAtX:y:
//  Use our animation method and
//  start the enemy moving toward the player
[self animateEnemy:enemy];
複製代碼

 

 
  animateEnemy:方法建立兩個action。第一個action使之朝敵人移動10個像素,時間爲0.3秒。你能夠改變這個時間使之移動得更快或者更慢。第二個action將會調用enemyMoveFinished:方法。咱們使用CCSequence action來把它們組合起來,這樣的話,當敵人中止移動的時候就立馬能夠執行enemyMoveFinished:方法就能夠被調用了。在addEnemyAtX:Y:方法裏面,咱們調用animateEnemy:方法來使敵人朝着玩家(player)移動。(其實這裏是個遞歸的調用,每次移動10個像素,而後又調用enemyMoveFinished:方法)
  
  很簡潔!可是,可是,若是敵人每次移動的時候面部都對着player那樣是否是更逼真呢?只須要在animateEnemy:方法中加入下列語句便可:
複製代碼
// immediately before creating the actions in animateEnemy
// rotate to face the player
CGPoint diff  =  ccpSub(_player.position,enemy.position);
float  angleRadians  =  atanf(( float )diff.y  /  ( float )diff.x);
float  angleDegrees  =  CC_RADIANS_TO_DEGREES(angleRadians);
float  cocosAngle  = - 1 *  angleDegrees;
if  (diff.x  < 0 ) {
cocosAngle 
+= 180 ;
}
enemy.rotation = cocosAngle
複製代碼
 
  這個代碼計算每次玩家相對於敵人的角度,而後旋轉敵人來使之面朝玩家。

忍者飛鏢

  已經很不錯了,可是玩家是一個忍者啊!他應該要可以保護他本身!
  咱們將向遊戲中添加模式(modes)。模式並非實現這個功能的最好方式,可是,它比其餘的方法要簡單,並且這個方法在模擬器下也能運行(由於並不須要多點觸摸)。由於這些優勢,因此這個教程裏面,咱們使用這種方法。首先將會創建UI,這樣的話玩家能夠方便地在「移動模式」和「擲飛鏢」模式之間進行切換。咱們將增長一個按鈕來使用這個功能的轉換。(即從移動模式轉到擲飛鏢模式)。
  如今,咱們將增長一些屬性,使兩個層之間能夠更好的通訊。在HelloWorldScene.h裏面增長以下代碼:
複製代碼
//  at the top of the file add a forward declaration for HelloWorld,
//  because our two layers need to reference each other

@class HelloWorld;

//  inside the HelloWorldHud class declaration
HelloWorld  * _gameLayer;

//  After the class declaration
@property (nonatomic, assign) HelloWorld  * gameLayer;

//  Inside the HelloWorld class declaration
int  _mode;

//  After the class declaration
@property (nonatomic, assign)  int  mode;
複製代碼
  同時修改HelloWorldScene.m文件
複製代碼
//  At the top of the HelloWorldHud implementation
@synthesize gameLayer  =  _gameLayer;

//  At the top of the HelloWorld implementation
@synthesize mode  =  _mode;

//  in HelloWorld's init method
_mode  = 0 ;

//  in HelloWorld's scene method
//  after layer.hud = hud
hud.gameLayer  =  layer;
複製代碼
  若是想知道在cocos2d裏面如何使用按鈕,能夠參照我翻譯的另一篇教程 《在cocos2d裏面如何製做按鈕:簡單按鈕、單選按鈕和開關按鈕》。
  在HelloWorldScene.m中添加下面的代碼,這段代碼定義了一個按鈕。
複製代碼
Add the folowing code, which defines a button, to HelloWorldScene.m:

//  in HelloWorldHud's init method
//  define the button
CCMenuItem  * on;
CCMenuItem 
* off;

on 
=  [[CCMenuItemImage itemFromNormalImage: @" projectile-button-on.png "
selectedImage:
@" projectile-button-on.png "  target:nil selector:nil] retain];
off 
=  [[CCMenuItemImage itemFromNormalImage: @" projectile-button-off.png "
selectedImage:
@" projectile-button-off.png "  target:nil selector:nil] retain];

CCMenuItemToggle 
* toggleItem  =  [CCMenuItemToggle itemWithTarget:self
selector:@selector(projectileButtonTapped:) items:off, on, nil];
CCMenu 
* toggleMenu  =  [CCMenu menuWithItems:toggleItem, nil];
toggleMenu.position 
=  ccp( 100 32 );
[self addChild:toggleMenu];

//  in HelloWorldHud
// callback for the button
// mode 0 = moving mode
// mode 1 = ninja star throwing mode
-  ( void )projectileButtonTapped:(id)sender
{
if  (_gameLayer.mode  == 1 ) {
_gameLayer.mode 
= 0 ;
else  {
_gameLayer.mode 
= 1 ;
}
}
複製代碼
  編譯並運行。這時會在左下角出現一個按鈕,而且你能夠打開或者關閉之。可是這並不會對遊戲形成任何影響。咱們的下一步就是增長飛鏢的發射。

發射飛鏢

  接下來,咱們將添加一些代碼來檢查玩家當前處於哪一種模式下面,而且在用戶點擊屏幕的時候影響不一樣的事件。若是是移動模式則移動玩家,若是是射擊模式,則擲飛鏢。在ccTouchEnded:withEvent:方法裏面增長下面代碼:
if  (_mode  == 0 ) {
//  old contents of ccTouchEnded:withEvent:
else  {
//  code to throw ninja stars will go here
}
  這樣可使得移動模式下,玩家只能移動。下一步就是要添加代碼使忍者可以發射飛鏢。在else部分增長,在增長以前,先在HelloWorld.m中添加一些清理代碼:
-  ( void ) projectileMoveFinished:(id)sender {
CCSprite 
* sprite  =  (CCSprite  * )sender;
[self removeChild:sprite cleanup:YES];
}
好了,看到上面的else部分的註釋了嗎:
// code to throw ninja stars will go here
在上面的註釋後面添加下面的代碼:
複製代碼
//  Find where the touch is
CGPoint touchLocation  =  [touch locationInView: [touch view]];
touchLocation 
=  [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation 
=  [self convertToNodeSpace:touchLocation];

//  Create a projectile and put it at the player's location
CCSprite  * projectile  =  [CCSprite spriteWithFile: @" Projectile.png " ];
projectile.position 
=  _player.position;
[self addChild:projectile];

//  Determine where we wish to shoot the projectile to
int  realX;

//  Are we shooting to the left or right?
CGPoint diff  =  ccpSub(touchLocation, _player.position);
if  (diff.x  > 0 )
{
realX 
=  (_tileMap.mapSize.width  *  _tileMap.tileSize.width)  +
(projectile.contentSize.width
/ 2 );
else  {
realX 
= - (_tileMap.mapSize.width  *  _tileMap.tileSize.width)  -
(projectile.contentSize.width
/ 2 );
}
float  ratio  =  ( float ) diff.y  /  ( float ) diff.x;
int  realY  =  ((realX  -  projectile.position.x)  *  ratio)  +  projectile.position.y;
CGPoint realDest 
=  ccp(realX, realY);

//  Determine the length of how far we're shooting
int  offRealX  =  realX  -  projectile.position.x;
int  offRealY  =  realY  -  projectile.position.y;
float  length  =  sqrtf((offRealX * offRealX)  +  (offRealY * offRealY));
float  velocity  = 480 / 1 //  480pixels/1sec
float  realMoveDuration  =  length / velocity;

//  Move projectile to actual endpoint
id actionMoveDone  =  [CCCallFuncN actionWithTarget:self
selector:@selector(projectileMoveFinished:)];
[projectile runAction:
[CCSequence actionOne:
[CCMoveTo actionWithDuration: realMoveDuration
position: realDest]
two: actionMoveDone]];
複製代碼
  這段代碼會在用戶點擊屏幕的方向發射飛鏢。對於這段代碼的完整的細節,能夠查看我翻譯的另外一個文章 《如何使用cocos2d來作一個簡單的iphone遊戲教程(第一部分)》。固然,查看原做者的文章後面的註釋會更加清楚明白一些。
  projectileMoveFinished:方法會在飛鏢移動到屏幕以外的時候移除。這個方法很是關鍵。一旦咱們開始作碰撞檢測的時候,咱們將要循環遍歷全部的飛鏢。若是咱們不移除飛出屏幕範圍以外的飛鏢的話,這個存儲飛鏢的列表將會愈來愈大,並且遊戲將會愈來愈慢。編譯並運行工程,如今,你的忍者能夠向敵人投擲飛鏢了。

碰撞檢測

  接下來,就是當飛鏢擊中敵人的時候,要把敵人銷燬。在HelloWorldClass類中增長如下變量(在HelloWorldScene.h文件中):
NSMutableArray  * _enemies;
NSMutableArray 
* _projectiles;
  而後初使化_projectiles數組:
//  at the end of the launch projectiles section of ccTouchEnded:withEvent:
[_projectiles addObject:projectile];

//  at the end of projectileMoveFinished:
[_projectiles removeObject:sprite];
  而後在addEnemyAtX:y方法的結尾添加以下代碼:
[_enemies addObject:enemy];
  接着,在HelloWorld類中添加以下代碼:
複製代碼
-  ( void )testCollisions:(ccTime)dt {

NSMutableArray 
* projectilesToDelete  =  [[NSMutableArray alloc] init];

//  iterate through projectiles
for  (CCSprite  * projectile  in  _projectiles) {
CGRect projectileRect 
=  CGRectMake(
projectile.position.x 
-  (projectile.contentSize.width / 2 ),
projectile.position.y 
-  (projectile.contentSize.height / 2 ),
projectile.contentSize.width,
projectile.contentSize.height);

NSMutableArray 
* targetsToDelete  =  [[NSMutableArray alloc] init];

//  iterate through enemies, see if any intersect with current projectile
for  (CCSprite  * target  in  _enemies) {
CGRect targetRect 
=  CGRectMake(
target.position.x 
-  (target.contentSize.width / 2 ),
target.position.y 
-  (target.contentSize.height / 2 ),
target.contentSize.width,
target.contentSize.height);

if  (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
}
}

//  delete all hit enemies
for  (CCSprite  * target  in  targetsToDelete) {
[_enemies removeObject:target];
[self removeChild:target cleanup:YES];
}

if  (targetsToDelete.count  > 0 ) {
//  add the projectile to the list of ones to remove
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}

//  remove all the projectiles that hit.
for  (CCSprite  * projectile  in  projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
複製代碼
  最後,初始化敵人來飛鏢數組,而且調度testCollisions:方法,把這些代碼加在HelloWorld類的init方法中。
//  you need to put these initializations before you add the enemies,
//  because addEnemyAtX:y: uses these arrays.
_enemies  =  [[NSMutableArray alloc] init];
_projectiles 
=  [[NSMutableArray alloc] init];
[self schedule:@selector(testCollisions:)];
  上面的全部的代碼,關於具體是如何工做的,能夠在個人博客上查找 《如何使用COCOS2D製做一個簡單的iphone遊戲》教程。固然,原做者的文章註釋部分的討論更加清晰,因此我翻譯的教程,也但願你們多討論啊。代碼儘可能本身用手敲進去,不要爲了省事,alt+c,alt+v,這樣很差,真的!
  好了,如今能夠用飛鏢打敵人,並且打中以後它們會消失。如今讓咱們添加一些邏輯,使得遊戲能夠勝利或者失敗吧!

勝利和失敗

The Game Over Scene

  好了,讓咱們建立一個新的場景,來做爲咱們的「You Win」或者「You Lose」指示器吧。在Xcode中,選擇Classes文件夾,而後點擊File\New File,再選擇Objective-c類,確保NSObject是基類被選中。點擊下一步,而後輸入文件名GameOverScene,而且確保「Also create GameOverScene.h」複選中。
  而後用下面的代碼替換掉模板生成代碼:
複製代碼
#import  " cocos2d.h "

@interface GameOverLayer : CCColorLayer {
CCLabel 
* _label;
}
@property (nonatomic, retain) CCLabel 
* label;
@end

@interface GameOverScene : CCScene {
GameOverLayer 
* _layer;
}
@property (nonatomic, retain) GameOverLayer 
* layer;
@end
複製代碼
  相應地修改GameOverScene.m文件:
複製代碼
#import  " GameOverScene.h "
#import 
" HelloWorldScene.h "

@implementation GameOverScene
@synthesize layer 
=  _layer;

-  (id)init {

if  ((self  =  [super init])) {
self.layer 
=  [GameOverLayer node];
[self addChild:_layer];
}
return  self;
}

-  ( void )dealloc {
[_layer release];
_layer 
=  nil;
[super dealloc];
}

@end

@implementation GameOverLayer
@synthesize label 
=  _label;

- (id) init
{
if ( (self = [super initWithColor:ccc4( 255 , 255 , 255 , 255 )] )) {

CGSize winSize 
=  [[CCDirector sharedDirector] winSize];
self.label 
=  [CCLabel labelWithString: @""  fontName: @" Arial "  fontSize: 32 ];
_label.color 
=  ccc3( 0 , 0 , 0 );
_label.position 
=  ccp(winSize.width / 2 , winSize.height / 2 );
[self addChild:_label];

[self runAction:[CCSequence actions:
[CCDelayTime actionWithDuration:
3 ],
[CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],
nil]];

}
return  self;
}

-  ( void )gameOverDone {

[[CCDirector sharedDirector] replaceScene:[HelloWorld scene]];

}

-  ( void )dealloc {
[_label release];
_label 
=  nil;
[super dealloc];
}

@end
複製代碼
  GameOverLayer僅僅只是在屏幕中間旋轉一個label,而後調度一個transition隔3秒後回到HelloWorld場景中。

勝利場景

  如今,讓咱們添加一些代碼,使得玩家吃完全部的西瓜的時候,遊戲會結束。在HelloWorld類的setPlayerPositoin:方法中添加如下代碼,(位於HelloWorldScene.m中,就是update代碼後面:)
//  put the number of melons on your map in place of the '2'
if  (_numCollected  == 2 ) {
[self win];
}
  而後,在HelloWorld類中建立win方法:
-  ( void ) win {
GameOverScene 
* gameOverScene  =  [GameOverScene node];
[gameOverScene.layer.label setString:
@" You Win! " ];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
}
  不要忘了包含頭文件:
#import  " GameOverScene.h "
  編譯並運行,當你吃完全部的西瓜後,就會出現以下畫面:

失敗場景

  就這個教程而言,咱們的玩家只要有一個敵人碰到他,遊戲是結束了。在HelloWorld類的testCollision方法中添加以列循環:
複製代碼
for  (CCSprite  * target  in  _enemies) {
CGRect targetRect 
=  CGRectMake(
target.position.x 
-  (target.contentSize.width / 2 ),
target.position.y 
-  (target.contentSize.height / 2 ),
target.contentSize.width,
target.contentSize.height );

if  (CGRectContainsPoint(targetRect, _player.position)) {
[self lose];
}
}
複製代碼
  這個循環遍歷全部的敵人,只要有一個敵人精靈的圖片所在的矩形和玩家接觸到了,那麼遊戲就失敗了。接下,再建立lose方法:
-  ( void ) lose {
GameOverScene 
* gameOverScene  =  [GameOverScene node];
[gameOverScene.layer.label setString:
@" You Lose! " ];
[[CCDirector sharedDirector] replaceScene:gameOverScene];
}
  編譯並運行,一旦有一個敵人碰到你,你就會看到下面的場景:

完整源代碼  

  這裏有這個教程的 完整源代碼。謝謝大家有耐心看到這裏。

接下來怎麼作?

建議:
  • 增長多個關卡
  • 增長不一樣類型的敵人
  • 在Hud層中顯示血條和玩家生命
  • 製做更多的道具,好比加血的,武器等等
  • 一個菜單系統,能夠選擇關卡,關閉音效,等等
  • 使用更好的用戶界面,來使遊戲畫面更加精美,投擲飛鏢更加瀟灑。
 
  著做權聲明:本文由 http://www.cnblogs.com/andyque教程,歡迎轉載分享。請尊重做者勞動,轉載時保留該聲明和做者博客連接,謝謝!
相關文章
相關標籤/搜索