免責申明(必讀!):本博客提供的全部教程的翻譯原稿均來自於互聯網,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本博客全部人、發表該翻譯稿之人無任何關係。謝謝合做!
程序截圖:
這篇教程是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
){
x
=
[[spawnPoint valueForKey:
@"
x
"
] intValue];
y
=
[[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;
在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]];
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層中顯示血條和玩家生命
- 製做更多的道具,好比加血的,武器等等
- 一個菜單系統,能夠選擇關卡,關閉音效,等等
- 使用更好的用戶界面,來使遊戲畫面更加精美,投擲飛鏢更加瀟灑。