天天看點

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

   免責申明(必讀!):本部落格提供的所有教程的翻譯原稿均來自于網際網路,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如産生任何糾紛,均與本部落格所有人、發表該翻譯稿之人無任何關系。謝謝合作! 原文連結位址: http://geekanddad.wordpress.com/2010/06/22/enemies-and-combat-how-to-make-a-tile-based-game-with-cocos2d-part-3/ 程式截圖:

(譯)加入敵人和戰鬥:如果使用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”開頭,但是這樣做效率很低下。相反,我們采用的做法是,使用一個屬性來表示,每個給定的對象代表一個敵人出現的位置點。

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

  給這個對象一個屬性“Enemy”,同時賦一個值1.如果你想在這個教程的基礎上擴充,并且增加其它的不同類型的敵人,你可以使用這些敵人的屬性值來表示不同類型的敵人。   現在,制作6-10個這種敵人出現位置點對象,相應的它們離player的距離也要有一些不同。為每一個對象定義一個“Enemy”屬性,并且指派為1.儲存這張地圖并且回到Xcode。

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

開始建立敵人

  好了,現在我們将把敵人實際顯示到地圖上來。首先在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 ){

x = [[spawnPoint valueForKey: @" x " ] intValue];

y = [[spawnPoint valueForKey: @" y " ] intValue];

[self addEnemyAtX:x y:y];

}

}   第一個循環周遊對象清單,判斷它是否是一個敵人出現的位置點。如果是,則獲得它的x和y坐标值,然後調用addEnemyAtX:Y方法把它們加入到合适的地方去。   這個addEnemyAtX:Y方法非常直白,它僅僅是在傳入的X,Y坐标值處建立一個敵人精靈。   如果你編譯并運作,你會看到這些敵人出現在你之前在Tiled工具中設定的位置處,很酷吧!

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于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:方法)   

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

  很簡潔!但是,但是,如果敵人每次移動的時候面部都對着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     這個代碼計算每次玩家相對于敵人的角度,然後旋轉敵人來使之面朝玩家。

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

忍者飛镖

  已經很不錯了,但是玩家是一個忍者啊!他應該要能夠保護他自己!   我們将向遊戲中添加模式(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 ;

}

}   編譯并運作。這時會在左下角出現一個按鈕,并且你可以打開或者關閉之。但是這并不會對遊戲造成任何影響。我們的下一步就是增加飛镖的發射。

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

發射飛镖

  接下來,我們将添加一些代碼來檢查玩家目前處于哪種模式下面,并且在使用者點選螢幕的時候影響不同的事件。如果是移動模式則移動玩家,如果是射擊模式,則擲飛镖。在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:方法會在飛镖移動到螢幕之外的時候移除。這個方法非常關鍵。一旦我們開始做碰撞檢測的時候,我們将要循環周遊所有的飛镖。如果我們不移除飛出螢幕範圍之外的飛镖的話,這個存儲飛镖的清單将會越來越大,而且遊戲将會越來越慢。編譯并運作工程,現在,你的忍者可以向敵人投擲飛镖了。

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

碰撞檢測

  接下來,就是當飛镖擊中敵人的時候,要把敵人銷毀。在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 "   編譯并運作,當你吃完所有的西瓜後,就會出現如下畫面:

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

失敗場景

  就這個教程而言,我們的玩家隻要有一個敵人碰到他,遊戲是結束了。在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];

}   編譯并運作,一旦有一個敵人碰到你,你就會看到下面的場景:

(譯)加入敵人和戰鬥:如果使用cocos2d制作基于tiled地圖的遊戲:第三部分增加敵人忍者飛镖碰撞檢測勝利和失敗完整源代碼  接下來怎麼做?

完整源代碼  

  這裡有這個教程的 完整源代碼。謝謝你們有耐心看到這裡。

接下來怎麼做?

建議:

  • 增加多個關卡
  • 增加不同類型的敵人
  • 在Hud層中顯示血條和玩家生命
  • 制作更多的道具,比如加血的,武器等等
  • 一個菜單系統,可以選擇關卡,關閉音效,等等
  • 使用更好的使用者界面,來使遊戲畫面更加精美,投擲飛镖更加潇灑。

    著作權聲明:本文由 http://www.cnblogs.com/andyque教程,歡迎轉載分享。請尊重作者勞動,轉載時保留該聲明和作者部落格連結,謝謝!