[iOS] Scrolling Demo (1)

今天的目標就是解決捲動的問題,之後我希望能用不同的方式進行捲動,不過今天,我會使用上次做的小Demo,就是會隨著你的點擊移動的Sprite。這個Demo是希望畫面會跟著那個移動的Sprite而移動。

參考書目:Learning Cocos2D(現在有中文版了!)

有哪些層?


首先,如果想要一個基本的遊戲畫面,然後裡面的角色移動時,畫面會跟著角色移動,讓角色保持在畫面的中央附近,這樣的話,你可以思考一下,這些畫面該怎麼去安排。

首先是不會移動的背景,背景有很多種,有些背景會放兩三層,越靠近使用者的背景移動的速度越快,以製造遠近的效果。

再來是角色所在的層,這一層主要是角色的遊戲邏輯所在,遊戲中大部分的動作發生的地方,這可以取較高的z值,讓這一層中的東西比起背景還更接近使用者。

最後是控制層,這一層也是可以放一些互動的界面,像是時間、分數或是模擬按鍵,這一層完全固定在螢幕上的,當畫面移動時這一層的東西都是保持在畫面上固定的位置。但如果你沒有這些物件時也可以不實作這一層。

這樣你就可以用三個classes去實作這三層,但我這邊只會用到背景和動作層,所以我只會實作兩個層。

你要一個Scene

要統整這些層,你要先實作一個Scene去放這些Layer(都是iOS/Cocoa Touch/Objective-C class):

#import <Foundation/Foundation.h>
#import "CCScene.h"
#import "cocos2d.h"
#import "GameplayScrollingLayer.h"
#import "StaticBackgroundLayer.h"

@interface GameScene : CCScene {
}

@end

以及它的implementation檔:

#import "GameScene.h"

@implementation GameScene

-(id)init {
    self = [super init];
    if (self != nil) {
        // Background Layer
        StaticBackgroundLayer *backgroundLayer =
        [StaticBackgroundLayer node];
        [self addChild:backgroundLayer z:0];
        
        GameplayScrollingLayer *scrollingLayer = [GameplayScrollingLayer node];
        [self addChild:scrollingLayer z:1 tag:1];

       
    }
    return self;
}

@end

可以看到implementation檔中放了兩個Layers,他們的z值都不一樣。

背景class


再來是實作背景類,繼承自CCLayer(iOS/Cocoa Touch/Objective-C class):

#import "CCLayer.h"

@interface StaticBackgroundLayer : CCLayer

@end

和implementation:

#import "StaticBackgroundLayer.h"
#import "cocos2d.h"

@implementation StaticBackgroundLayer

-(id)init {
    self = [super init];
    if (self != nil) {
        CGSize screenSize = [CCDirector sharedDirector].winSize;
        CCSprite *backgroundImage;
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            // Indicates game is running on iPad
            backgroundImage =
            [CCSprite spriteWithFile:@"chap9_scrolling1.png"];
        } else {
            backgroundImage =
            [CCSprite spriteWithFile:@"chap9_scrolling1iPhone.png"];
        }
        [backgroundImage setPosition:ccp(screenSize.width/2.0f, screenSize.height/2.0f)];
        [self addChild:backgroundImage];
    }
    return self;
}

@end

背景檔是我去那本書的網站抓的。裡面只是單純的實作一個初始化,依照你使用的設備不同,使用不同的背景畫面,然後把畫面放在螢幕中間。

捲動層


如果能的話,接下來我會分三個程度介紹捲動:
1.放入一個螢幕兩倍寬的背景。
2.加入有視差(parallax)的node,並且在Layer中捲動背景,裡面每一個層捲動的速度都不一樣。
3.最後是捲動TileMap層,以學習即使創造很大的關卡也能節省記憶體。

我們先實作interface檔:

#import <Foundation/Foundation.h>
#import "CCLayer.h"
#import "cocos2d.h"

@interface GameplayScrollingLayer : CCLayer {
    CCSpriteBatchNode *sceneSpriteBatchNode;
    CCTMXTiledMap *tileMapNode;
    CCParallaxNode *parallaxNode;
    CCSprite *icon;
}

@end

注意,第一個變數是儲存可捲動之背景,之後要製作atlas。第二個變數是製作tiled map,第三個變數是製作視差node,最後一個變數是我們要使用的遊戲物件。

然後先在GameplayScrollingLayer.m中實作兩個方法:

-(void)addScrollingBackground {
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    CGSize levelSize = CGSizeMake(screenSize.width * 2.0f, screenSize.height);
   
    CCSprite *scrollingBackground;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        //Indicate game is running on iPad
        scrollingBackground = [CCSprite spriteWithFile:@"FlatScrollingLayer.png"];
    }else {
        scrollingBackground = [CCSprite spriteWithFile:@"FlatScrollingLayeriPhone.png"];
    }
   
    [scrollingBackground setPosition:ccp(levelSize.width/2.0f, screenSize.height/2.0f)];
    [self addChild:scrollingBackground];
}

-(id) init{
    CGSize winSize = [CCDirector sharedDirector].winSize;
        self = [super init];
    if (self != nil) {
       
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            [[CCSpriteFrameCache sharedSpriteFrameCache]
             addSpriteFramesWithFile:@"scene1atlas.plist"];
            sceneSpriteBatchNode = [CCSpriteBatchNode batchNodeWithFile:@"scene1atlas.png"];
        } else {
            [[CCSpriteFrameCache sharedSpriteFrameCache]
             addSpriteFramesWithFile:@"scene1atlasiPhone.plist"];
            sceneSpriteBatchNode = [CCSpriteBatchNode batchNodeWithFile:@"scene1atlasiPhone.png"];
        }
        [self addChild:sceneSpriteBatchNode];
       
        self.isTouchEnabled =YES;
        icon = [CCSprite spriteWithFile:@"Icon-Small.png"];
        icon.position = ccp(winSize.width/2, winSize.height/2);
        [self addChild:icon];
       
        [self addScrollingBackground];
        [self scheduleUpdate];
       
    }
    return self;
}

addScrollingBackground方法是先取得螢幕的大小,然後用螢幕大小製作關卡的實際大小(我這邊和書上不一樣),然後用if迴圈根據使用的設備選用適當的背景圖,最後把背景圖位置設定好,注意這邊是以關卡大小為主,寬度為關卡大小的寬度一半,高度則是螢幕高度的一半。然後再加入這個方法:

-(void) adjustLayer {
    float iconXPosition = icon.position.x;
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    float halfOfTheScreen = screenSize.width/2.0f;
    CGSize levelSize = CGSizeMake(screenSize.width * 2, screenSize.height);
   
    if ((iconXPosition > halfOfTheScreen) && (iconXPosition < levelSize.width - halfOfTheScreen)) {
        float newXPosition = halfOfTheScreen -iconXPosition;
        [self setPosition:ccp(newXPosition, self.position.y)];
    }
}

首先取得icon位置的x座標,然後取得螢幕大小,再取得螢幕寬度的一半大小,最後製作出關卡的小,再用這些東西製作if迴圈,回圈是要進行捲動,條件是,如果icon在整個關卡裡面距離頭尾一半螢幕寬度的範圍內的話,那麼算出新的X,就是一半的螢幕寬度減去icon的x位置,最後就是重新設定層的位置。

最後是加入update方法:

-(void) update:(ccTime)delta {
    [self adjustLayer];
}

好,編譯看看吧!這時你會發現icon好像在捲動背景後面,你可以調整icon的z值。

使用視差層


在cocos2D裡面,有一個類別叫做CCParallaxNode,這讓你不用自己去寫code去製作視差。Parallax node世一個特別的Cocos2D parent node,它可以設定好讓他的children按不同比率去捲動,現在來直接體驗看看吧!

打開GameplayScrollingLayer.m,加入以下方法:

-(void) addScrollingBackgroundWithParallax {
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    CGSize levelSize = CGSizeMake(screenSize.width * 2, screenSize.height);
   
    CCSprite *BGLayer1;
    CCSprite *BGLayer2;
    CCSprite *BGLayer3;
   
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) {
        BGLayer1 = [CCSprite spriteWithFile:@"chap9_scrolling4.png"];
        BGLayer2 = [CCSprite spriteWithFile:@"chap9_scrolling3.png"];
        BGLayer3 = [CCSprite spriteWithFile:@"chap9_scrolling2.png"];
    }else {
        BGLayer1 = [CCSprite spriteWithFile:@"chap9_scolling4iPhone.png"];
        BGLayer2 = [CCSprite spriteWithFile:@"chap9_scolling3iPhone.png"];
        BGLayer3 = [CCSprite spriteWithFile:@"chap9_scolling2iPhone.png"];
    }
   
    parallaxNode = [CCParallaxNode node];
    [parallaxNode setPosition:ccp(levelSize.width/2.0f, screenSize.height/2.0f)];
    float xOffset = 0;
   
    [parallaxNode addChild:BGLayer1 z:40 parallaxRatio:ccp(1.0f, 1.0f) positionOffset:ccp(0.0f, 0.0f)];
   
    xOffset = (levelSize.width/2) *0.3f;
    [parallaxNode addChild:BGLayer2 z:20 parallaxRatio:ccp(0.2f, 1.0f) positionOffset:ccp(xOffset, 0)];
   
    xOffset = (levelSize.width/2) * 0.8f;
    [parallaxNode addChild:BGLayer3 z:30 parallaxRatio:ccp(0.7f, 1.0f) positionOffset:ccp(xOffset, 0)];
   
    [self addChild:parallaxNode z:10];
}

他首先加入了三個層,然後根據設備的不同會載入不同的圖。接著初始化parallaxNode變數,設定該變數要放置的位置,然後將剛剛建立的三個層加入,並指定不同的比率和z值。

再來,回到init方法裡面,把:

[self addScrollingBackground];

給comment out,換成:

[self addScrollingBackgroundWithParallax];

現在來看一下為什麼要有那些offset,可以進到addChild:z:parallaxRatio:positionOffset:方法裡面:

    CGPoint pos = self.position;
    pos.x = pos.x * ratio.x + offset.x;
    pos.y = pos.y * ratio.y + offset.y;
    child.position = pos;

子層的位置,等於CCParallazNode的位置乘上比率後再加上offset。舉例來說,第三個子層的x位置的比率是0.7,你把它設置在1024像素的位置,所以乘起來就是1024 * 0.7 = 716.8,這一層其實歪斜到左邊去,自然要補上offset把它移回來。

至於無限大的捲動關卡,我想寫在下一回好了。

留言