《 Learn IPhone and iPad Cocos2d Game Delevopment》的第5章。
一、使用多場景
這個 Scene中用到了兩個Layer,一個Layer位于屏幕上方,標有”Here be your Game Scores etc“字樣的標簽,用于模擬游戲菜單。一個Layer位于屏幕下方,一塊綠色的草地上有一些隨機游動的蜘蛛和怪物,模擬了游戲的場景。
1、加入新場景
一個場景是一個 Scene類。加入新場景就是加入更多的Scene類。
有趣的是場景之間的切換。使用 [CCDirector replaceScene]方法轉場時,CCNode有3個方法會被調用:OnEnter、OnExit、 onEnterTransitionDidFinish。
覆蓋這 3個方法時要牢記,始終要調用super的方法,避免程序的異常(比如內存泄露或場景不響應用戶動作)。
-(void) onEnter {
// node的 init 方法后調用.
// 如果使用 CCTransitionScene方法,在轉場開始后調用.
[super onEnter];
}
-(void ) onEnterTransitionDidFinish {
// onEnter方法后調用.
// 如果使用 CCTransitionScene方法,在轉場結束后調用.
[super onEnterTransitionDidFinish];
}
-(void) onExit
{
// node的 dealloc 方法前調用.
// 如果使用CCTransitionScene方法, 在轉場結束時調用.
[super onExit];
}
當場景變化時,有時候需要讓某個 node干點什么,這時這3個方法就派上用場了。
與在 node的init方法和dealloc方法中做同樣的事情不同,在onEnter方法執行時,場景已經初始化了;而在onExit方法中,場景的node仍然是存在的。
這樣,在進行轉場時,你就可以暫停動畫或隱藏用戶界面元素,一直到轉場完成。這些方法調用的先后順序如下(使用 replaceScene 方法):
1. 第2個場景的 scene 方法
2. 第2個場景的 init 方法
3. 第2個場景的 onEnter 方法
4. 轉場
5. 第1個場景的 onExit 方法
6. 第2個場景的 onEnterTransitionDidFinish 方法
7. 第1個場景的 dealloc 方法
二、請稍候??
切換場景時,如果場景的加載是一個比較耗時的工作,有必要用一個類似“ Loading,please waiting…”的場景來過渡一下。用于在轉場時過渡的場景是一個“輕量級”的Scene類,可以顯示一些簡單的提示內容:
typedef enum
{
TargetSceneINVALID = 0 ,
TargetSceneFirstScene,
TargetSceneOtherScene,
TargetSceneMAX,
} TargetScenes;
@interface LoadingScene : CCScene
{
TargetScenes targetScene_;
}
+( id ) sceneWithTargetScene:(TargetScenes)targetScene;
-( id ) initWithTargetScene:(TargetScenes)targetScene;
@end
#import "LoadingScene.h"
#import "FirstScene.h"
#import "OtherScene.h"
@interface LoadingScene (PrivateMethods)
-( void ) update:(ccTime)delta;
@end
@implementation LoadingScene
+( id ) sceneWithTargetScene:(TargetScenes)targetScene;
{
return [[[ self alloc] initWithTargetScene:targetScene] autorelease];
}
-( id ) initWithTargetScene:(TargetScenes)targetScene
{
if (( self = [ super init]))
{
targetScene_ = targetScene;
CCLabel* label = [CCLabel labelWithString: @"Loading ..." fontName: @"Marker Felt" fontSize: 64 ];
CGSize size = [[CCDirector sharedDirector] winSize];
label.position = CGPointMake(size.width / 2 , size.height / 2 );
[ self addChild:label];
[ self scheduleUpdate];
}
return self ;
}
-( void ) update:(ccTime)delta
{
[ self unscheduleAllSelectors];
switch (targetScene_)
{
case TargetSceneFirstScene:
[[CCDirector sharedDirector] replaceScene:[FirstScene scene]];
break ;
case TargetSceneOtherScene:
[[CCDirector sharedDirector] replaceScene:[OtherScene scene]];
break ;
default :
// NSStringFromSelector(_cmd) 打印方法名
NSAssert2( nil , @"%@: unsupported TargetScene %i" , NSStringFromSelector( _cmd ), targetScene_);
break ;
}
}
-( void ) dealloc
{
CCLOG( @"%@: %@" , NSStringFromSelector( _cmd ), self );
[ super dealloc];
}
@end
首先,定義了一個枚舉。這個技巧使 LoadingScene 能用于多個場景的轉場,而不是固定地只能在某個場景的切換時使用。繼續擴展這個枚舉的成員,使 LoadingScene 能適用與更多目標 Scene 的轉場。
sceneWithTargetScene 方法中返回了一個 autorelease 的對象。在 coco2d 自己的類中也是一樣的,你要記住在每個靜態的初始化方法中使用 autorelease 。
在 方法中,構造了一個 CCLabel ,然后調用 scheduleUpdate 方法。 scheduleUpdate 方法會在下一個時間(約一幀)后調用 update 方法。在 update 方法中,我們根據 sceneWithTargetScene 方法中指定的枚舉參數,切換到另一個 scene 。在這個 scene 的加載完成之前, LoadingScene 會一直顯示并且凍結用戶的事件響應。
我們不能直接在初始化方法 initWithTargetScene 中直接切換 scene ,這會導致程序崩潰。記住,在一個 Node 還在初始化的時候,千萬不要在這個 scene 上調用 CCDirector 的 replaceScene 方法。
LoadingScene 的使用很簡單,跟一般的 scene 一樣:
CCScene * newScene = [ LoadingScene sceneWithTargetScene : TargetSceneFirstScene ];
[[ CCDirector sharedDirector ] replaceScene :newScene];
三、使用 Layer
Layer類似Photoshop中層的概念,在一個scene中可以有多個Layer:
typedef enum
{
LayerTagGameLayer ,
LayerTagUILayer ,
} MultiLayerSceneTags;
typedef enum
{
ActionTagGameLayerMovesBack ,
ActionTagGameLayerRotates ,
} MultiLayerSceneActionTags;
@class GameLayer ;
@class UserInterfaceLayer ;
@interface MultiLayerScene : CCLayer
{
bool isTouchForUserInterface ;
}
+( MultiLayerScene *) sharedLayer;
@property ( readonly ) GameLayer* gameLayer;
@property ( readonly ) UserInterfaceLayer* uiLayer;
+( CGPoint ) locationFromTouch:( UITouch *)touch;
+( CGPoint ) locationFromTouches:( NSSet *)touches;
+( id ) scene;
@end
@implementation MultiLayerScene
static MultiLayerScene* multiLayerSceneInstance;
+( MultiLayerScene *) sharedLayer
{
NSAssert ( multiLayerSceneInstance != nil , @"MultiLayerScene not available!" );
return multiLayerSceneInstance ;
}
-( GameLayer *) gameLayer
{
CCNode * layer = [ self getChildByTag : LayerTagGameLayer ];
NSAssert ([layer isKindOfClass :[ GameLayer class ]], @"%@: not a GameLayer!" , NSStringFromSelector ( _cmd ));
return ( GameLayer *)layer;
}
-( UserInterfaceLayer *) uiLayer
{
CCNode * layer = [[ MultiLayerScene sharedLayer ] getChildByTag : LayerTagUILayer ];
NSAssert ([layer isKindOfClass :[ UserInterfaceLayer class ]], @"%@: not a UserInterfaceLayer!" , NSStringFromSelector ( _cmd ));
return ( UserInterfaceLayer *)layer;
}
+( CGPoint ) locationFromTouch:( UITouch *)touch
{
CGPoint touchLocation = [touch locationInView : [touch view ]];
return [[ CCDirector sharedDirector ] convertToGL :touchLocation];
}
+( CGPoint ) locationFromTouches:( NSSet *)touches
{
return [ self locationFromTouch :[touches anyObject ]];
}
+( id ) scene
{
CCScene * scene = [ CCScene node ];
MultiLayerScene * layer = [ MultiLayerScene node ];
[scene addChild :layer];
return scene;
}
-( id ) init
{
if (( self = [ super init ]))
{
NSAssert ( multiLayerSceneInstance == nil , @"another MultiLayerScene is already in use!" );
multiLayerSceneInstance = self ;
GameLayer * gameLayer = [ GameLayer node ];
[ self addChild : gameLayer z : 1 tag : LayerTagGameLayer ];
UserInterfaceLayer * uiLayer = [ UserInterfaceLayer node ];
[ self addChild : uiLayer z : 2 tag : LayerTagUILayer ];
}
return self ;
}
-( void ) dealloc
{
CCLOG ( @"%@: %@" , NSStringFromSelector ( _cmd ), self );
[ super dealloc ];
}
@end
MultiLayerScene 中使用了多個 Layer: 一個 GameLayer h 和一個 UserInterfaceLayer 。
MultiLayerScene 使用了靜態成員 multiLayerSceneInstance 來實現單例。 MultiLayerScene也是一個Layer,其node方法實際上調用的是實例化方法init——在其中,我們加入了兩個Layer,分別用兩個枚舉 LayerTagGameLayer 和 LayerTagUILayer 來檢索 , 如屬性方法 gameLayer和uiLayer所示。
uiLayer是一個UserInterfaceLayer,用來和用戶交互,在這里實際上是在屏幕上方放置一個菜單,可以把游戲的一些統計數字比如:積分、生命值放在這里:
typedef enum
{
UILayerTagFrameSprite ,
} UserInterfaceLayerTags;
@interface UserInterfaceLayer : CCLayer
{
}
-( bool ) isTouchForMe:( CGPoint )touchLocation;
@end
@implementation UserInterfaceLayer
-( id ) init
{
if (( self = [ super init ]))
{
CGSize screenSize = [[ CCDirector sharedDirector ] winSize ];
CCSprite * uiframe = [ CCSprite spriteWithFile : @"ui-frame.png" ];
uiframe. position = CGPointMake ( 0 , screenSize. height );
uiframe. anchorPoint = CGPointMake ( 0 , 1 );
[ self addChild :uiframe z : 0 tag : UILayerTagFrameSprite ];
// 用 Label模擬UI控件( 這個Label沒有什么作用,僅僅是演示) .
CCLabel * label = [ CCLabel labelWithString : @"Here be your Game Scores etc" fontName : @"Courier" fontSize : 22 ];
label. color = ccBLACK ;
label. position = CGPointMake (screenSize. width / 2 , screenSize. height );
label. anchorPoint = CGPointMake ( 0.5f , 1 );
[ self addChild :label];
self . isTouchEnabled = YES ;
}
return self ;
}
-( void ) dealloc
{
CCLOG ( @"%@: %@" , NSStringFromSelector ( _cmd ), self );
[ super dealloc ];
}
-( void ) registerWithTouchDispatcher
{
[[ CCTouchDispatcher sharedDispatcher ] addTargetedDelegate : self priority :- 1 swallowsTouches : YES ];
}
// 判斷觸摸是否位于有效范圍內 .
-( bool ) isTouchForMe:( CGPoint )touchLocation
{
CCNode * node = [ self getChildByTag : UILayerTagFrameSprite ];
return CGRectContainsPoint ([node boundingBox ], touchLocation);
}
-( BOOL ) ccTouchBegan:( UITouch *)touch withEvent:( UIEvent *)event
{
CGPoint location = [ MultiLayerScene locationFromTouch :touch];
bool isTouchHandled = [ self isTouchForMe :location];
if (isTouchHandled)
{
// 顏色改變為紅色,表示接收到觸摸事件 .
CCNode * node = [ self getChildByTag : UILayerTagFrameSprite ];
NSAssert ([node isKindOfClass :[ CCSprite class ]], @"node is not a CCSprite" );
(( CCSprite *)node). color = ccRED ;
// Action: 旋轉 +縮放 .
CCRotateBy * rotate = [ CCRotateBy actionWithDuration : 4 angle : 360 ];
CCScaleTo * scaleDown = [ CCScaleTo actionWithDuration : 2 scale : 0 ];
CCScaleTo * scaleUp = [ CCScaleTo actionWithDuration : 2 scale : 1 ];
CCSequence * sequence = [ CCSequence actions :scaleDown, scaleUp, nil ];
sequence. tag = ActionTagGameLayerRotates ;
GameLayer * gameLayer = [ MultiLayerScene sharedLayer ]. gameLayer ;
// 重置 GameLayer 屬性 , 以便每次動畫都是以相同的狀態開始
[gameLayer stopActionByTag : ActionTagGameLayerRotates ];
[gameLayer setRotation : 0 ];
[gameLayer setScale : 1 ];
// 運行動畫
[gameLayer runAction :rotate];
[gameLayer runAction :sequence];
}
return isTouchHandled;
}
-( void ) ccTouchEnded:( UITouch *)touch withEvent:( UIEvent *)event
{
CCNode * node = [ self getChildByTag : UILayerTagFrameSprite ];
NSAssert ([node isKindOfClass :[ CCSprite class ]], @"node is not a CCSprite" );
// 色彩復原
(( CCSprite *)node). color = ccWHITE ;
}
@end
為了保證 uiLayer總是第一個收到touch事件,我們在 registerWithTouchDispatcher 方法中使用-1的priority。并且用 isTouchForMe 方法檢測touch是否處于Layer的范圍內。如果在,touchBegan方法返回YES,表示“吃掉” touch事件(即不會傳遞到下一個Layer處理);否則,返回NO,傳遞給下一個Layer(GameLayer)處理。
而在 GameLayer中, registerWithTouchDispatcher 的priority是0
以下是 GameLayer代碼:
@interface GameLayer : CCLayer
{
CGPoint gameLayerPosition ;
CGPoint lastTouchLocation ;
}
@end
@interface GameLayer (PrivateMethods)
-( void ) addRandomThings;
@end
@implementation GameLayer
-( id ) init
{
if (( self = [ super init ]))
{
self . isTouchEnabled = YES ;
gameLayerPosition = self . position ;
CGSize screenSize = [[ CCDirector sharedDirector ] winSize ];
CCSprite * background = [ CCSprite spriteWithFile : @"grass.png" ];
background. position = CGPointMake (screenSize. width / 2 , screenSize. height / 2 );
[ self addChild :background];
CCLabel * label = [ CCLabel labelWithString : @"GameLayer" fontName : @"Marker Felt" fontSize : 44 ];
label. color = ccBLACK ;
label. position = CGPointMake (screenSize. width / 2 , screenSize. height / 2 );
label. anchorPoint = CGPointMake ( 0.5f , 1 );
[ self addChild :label];
[ self addRandomThings ];
self . isTouchEnabled = YES ;
}
return self ;
}
// 為 node加上一個MoveBy的動作(其實就是在圍繞一個方框在繞圈)
-( void ) runRandomMoveSequence:( CCNode *)node
{
float duration = CCRANDOM_0_1 () * 5 + 1 ;
CCMoveBy * move1 = [ CCMoveBy actionWithDuration :duration position : CGPointMake (- 180 , 0 )];
CCMoveBy * move2 = [ CCMoveBy actionWithDuration :duration position : CGPointMake ( 0 , - 180 )];
CCMoveBy * move3 = [ CCMoveBy actionWithDuration :duration position : CGPointMake ( 180 , 0 )];
CCMoveBy * move4 = [ CCMoveBy actionWithDuration :duration position : CGPointMake ( 0 , 180 )];
CCSequence * sequence = [ CCSequence actions :move1, move2, move3, move4, nil ];
CCRepeatForever * repeat = [ CCRepeatForever actionWithAction :sequence];
[node runAction :repeat];
}
// 模擬一些游戲對象 ,為每個對象加上一些動作(繞圈) .
-( void ) addRandomThings
{
CGSize screenSize = [[ CCDirector sharedDirector ] winSize ];
for ( int i = 0 ; i < 4 ; i++)
{
CCSprite * firething = [ CCSprite spriteWithFile : @"firething.png" ];
firething. position = CGPointMake ( CCRANDOM_0_1 () * screenSize. width , CCRANDOM_0_1 () * screenSize. height );
[ self addChild :firething];
[ self runRandomMoveSequence :firething];
}
for ( int i = 0 ; i < 10 ; i++)
{
CCSprite * spider = [ CCSprite spriteWithFile : @"spider.png" ];
spider. position = CGPointMake ( CCRANDOM_0_1 () * screenSize. width , CCRANDOM_0_1 () * screenSize. height );
[ self addChild :spider];
[ self runRandomMoveSequence :spider];
}
}
-( void ) dealloc
{
CCLOG ( @"%@: %@" , NSStringFromSelector ( _cmd ), self );
// don't forget to call "super dealloc"
[ super dealloc ];
}
-( void ) registerWithTouchDispatcher
{
[[ CCTouchDispatcher sharedDispatcher ] addTargetedDelegate : self priority : 0 swallowsTouches : YES ];
}
-( BOOL ) ccTouchBegan:( UITouch *)touch withEvent:( UIEvent *)event
{
// 記錄開始touch時的位置 .
lastTouchLocation = [ MultiLayerScene locationFromTouch :touch];
// 先停止上一次動作,以免對本次拖動產生干擾 .
[ self stopActionByTag : ActionTagGameLayerMovesBack ];
// 吃掉所有 touche
return YES ;
}
-( void ) ccTouchMoved:( UITouch *)touch withEvent:( UIEvent *)event
{
// 記錄手指移動的位置
CGPoint currentTouchLocation = [ MultiLayerScene locationFromTouch :touch];
// 計算移動的距離
CGPoint moveTo = ccpSub ( lastTouchLocation , currentTouchLocation);
// 上面的計算結果要取反 .因為接下來是移動前景,而不是移動背景
moveTo = ccpMult (moveTo, - 1 );
lastTouchLocation = currentTouchLocation;
// 移動前景——修改 Layer的位置,將同時改變Layer所包含的node self . position = ccpAdd ( self . position , moveTo);
}
-( void ) ccTouchEnded:( UITouch *)touch withEvent:( UIEvent *)event
{
// 最后把 Layer的位置復原 .Action: 移動 +漸慢
CCMoveTo * move = [ CCMoveTo actionWithDuration : 1 position : gameLayerPosition ];
CCEaseIn * ease = [ CCEaseIn actionWithAction :move rate : 0.5f ];
ease. tag = ActionTagGameLayerMovesBack ;
[ self runAction :ease];
}
@end
為了讓程序運行起來更有趣, GameLayer中加入了一張青草的背景圖,以及一些游戲對象,并讓這些對象在隨機地移動。這部分內容不是我們關注的,我們需要關注的是幾個touch方法的處理。
1、 ccTouchBegan :
由于 GameLayer是最后收到touch事件的Layer,我們不需要檢測touch是否在Layer范圍(因為傳給它的都是別的Layer“吃剩下”的touch)。所以GameLayer的touchBegan方法只是簡單的返回YES(“吃掉”所有touch)。
2 、 ccTouchMoved:
在這里我們計算手指移動的距離,然后讓 Layer作反向運動。為什么要作“反向”運動? 因為我們想制造一種屏幕隨著手指劃動的感覺,例如: 當手向右劃動時,屏幕也要向右運動。當然,iPhone不可能真的向右運動。要想模擬屏幕向右運動,只需讓游戲畫面向左運動即可。因為當運動物體在向前移動時,如果假設運動物體固定不動,則可以認為是參照物(或背景)在向后運動。
3、 ccTouchEnded:
在這里,我們把 Layer的位置恢復到原位。
四、其他
這一章還討論了很多有用的東西,比如“關卡”。是使用 Scene還是Layer作為游戲關卡?
作者還建議在設計 Sprite時使用聚合而不要使用繼承。即Sprite設計為不從CCNode繼承,而設計為普通的NSObject子類(在其中聚合了CCNode)。
此外還討論了 CCTargetToucheDelegate、CCProgressTimer、CCParallaxNode、vCCRibbon和CCMotionStreak。
這些東西可以豐富我們的理論知識,但沒有必要細讀。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
