SpriteBuilder 學習筆記六

Chapter 7    Main Scene and Game Statenode

至今,咱們徹底忽略了主場景,而主場景是放置遊戲的main menu的好地方。main menu放置設置菜單的好地方,Settings menu讓你能夠改變音量等。spring

settings popover須要一個grid-based layout,這就是爲何你須要使用Box Layout node。你一樣須要添加一個方法去永久存儲game state----好比,解鎖levels和audio volumes,這樣就不會在app結束的時候丟失這些信息。安全

 

Main Scene Backgroundapp

Designing the Background Imagesless

在以前的章節,你已經添加了Menu和UserInterface Sprite Sheets。轉到Tileless Editor View,在底部,勾選Menu folder。iphone

若是你想要提供你本身的圖片,你應該有3個screen-sized圖片,來構成menu的背景layers。ide

Screen-sized意味着-----568x384 points,若是你想能共同運行在iphones和ipads。oop

考慮到iPad和Retina iPads的scale,full-screen 圖片應該有2272x1536佈局

 pixels。若是你想讓你的app僅僅運行在iPhones上,你的參考size應該是568x320 points。這樣,你僅僅須要1136x640 pixels的圖片來適配iPhone Retina屏幕。動畫

你可能好奇爲何全部的background layer圖片都必須是screen-sized。這不是浪費內存麼?實際上,不是這樣的。若是你在SpriteSheet中放置這些圖片,SpriteBuilder會減掉任何excess transparent areas。

上圖頂部的灰色區域是被SpriteBuilder剪掉的,由於這一區域只包含透明的像素。

對於遊戲開發者來講,有一個永恆的矛盾:視覺質量 vs 內存使用/表現。

上圖顯示的時Menu Sprite Sheet以前的圖片,你能夠注意到M_bg.png圖片和兩個M_monsters.png和M_frontsmoke.png圖片使用了相同的空間。進一步說,一些圖片在SpriteSheet效率更高。

 

Designing the Background CCB

由於你但願在其餘scenes中重複使用menu的background layer,那麼在一個分開的layer中設計就是一個好主意。

右擊UserInterface 文件夾,(不是Sprite Sheet中文件夾),New File,建立MainMenuBackground.ccb,設置類型爲layer,默認568x384尺寸。

像往常同樣,第一件要作的事情是對Layer CCB的root node,設置Content size類型爲%,Content size值爲100.這確保了layer在全部設備上都是居中的。

添加一個常規的Node,到MainMenuBackground.ccb stage上。設置node的位置類型爲%,值爲50,這樣這個node在layer上是居中的。把timeline中得node重命名爲background。background node將是背景圖片的容器。

你能夠從Tileless Editor View中拖動背景圖片到backgroud node中。如圖:

M_bg

M_monsters

M_frontsmoke

它們應該是background node的children,這樣sprites會自動的在layer中居中。

Note:改變一個node的anchor point會影響scale和rotate操做,也會影響bounding box和collision detection。anchor point僅僅會移動一個node的視覺表示,這個表現可能不會被物理的表示。你永遠不該該把anchor point當作改變位置的功能用。總的來講,anchor point通常都是0x0或者0.5x0.5.

Animating the Background

具體來講,你應該對每一個background圖片和node添加3個關鍵幀。首先給background node添加3個scale關鍵幀,按S。

一個關鍵幀在兩端,第三個應該在中間,5-seconds處。移動Timeline Cursor到中間的關鍵幀,在Item Properties中,對background node,設置Scale值爲1.02.

這個值很小,可是一旦你播放動畫,就能馬上看到效果。而後右鍵點擊關鍵幀段,選擇Ease In/Out。對其餘段重複這個過程。

下一步,對M_bg和M_frontsmoke sprites重複添加3個關鍵幀,選擇Ease In/Out。對於M_bg中間的關鍵幀,設置scale值爲1.01和1.02.對於M_frontsmoke,設置中間的關鍵幀Scale值爲1.02和1.06.

如今選擇M_monsters node,改變它的Y position屬性爲-15.這將稍微移動一下node。而後添加3個position關鍵幀,按P。移動Timeline Cursor到中間的關鍵幀,編輯Y的position屬性爲0.加上Ease In/out mode。

最後,你還須要讓這個動畫循環。左鍵點擊下方的No chained timeline,選擇Default Timeline。

 

Launching with the Menu

爲了在遊戲中看到這段動畫,你須要完成一些基礎事務。你尚未添加background到MainScene.ccb中。

打開MainScene.ccb,移除gradient 和labelnodes。事實上,移除全部額外的nodes,除了play button;不然,直到下一章以前,你都不能play level。可是你能夠把play button移動到左上角。

清理完MainScene後,拖動MainMenuBackground到stage,從Tileless Editor View或者FileView中拖動均可以。設置位置爲0,0.重命名CCBFile實例爲background。

打開AppDelegate.m,找到startScene方法,改成:

- (CCScene*) startScene
{
    return [CCBReader loadAsScene:@"MainScene"];
}

如今遊戲會從MainScene啓動。

Main Scene Logo and Buttons

如今,添加一些buttons和logo。button圖片play.png和settings.png應該有旋轉動畫。

 

Designing Logo and Buttons

爲buttons建立一個新的CCB文件。右鍵點擊UserInterface文件夾,選擇New File,命名爲MainMenuButtons.ccb,類型爲Layer。保持默認尺寸,點擊Create。改變stage color爲白色。

一樣的,第一個要作的事情是選擇root node,改變Content size類型爲%,值爲100,以確保居中。

添加一個常規Node到stage。改變位置類型爲%,值爲50.在Timeline中命名爲logoAndButtons。

從Tileless Editor View中,拖動play,settins和title圖片到logoAndButtons node中。它們的實際位置不是很重要。

title sprite的position爲0,50;

play sprite的position爲-50,-60;

settins sprite的position爲50,-60;

Tip:僅僅不勾選timeline中得eye symbo只會在SpriteBuilder中隱藏,可是仍然會在底層渲染,這是一個很大的負擔。

buttons須要一些text。

拖動LabelTTF node到play sprite上,再拖動一個到settings sprite上。labels應該是sprite nodes的child。

對於兩個labels,先選擇label,而後改變位置類型爲%,值爲50.這讓label在各自的sprite上居中。而後輸入PLAY和Settings,對應。play label的Font size設爲20,settings爲16。

 

Animating Logo and Buttons

左鍵點擊Timeline List,選擇Edit Timelines,打開對話框。點擊+按鈕,建立第二個Timeline,命名爲loop。雙擊Default Timeline,更名爲entry。不要勾選Autoplay複選框。

目標是讓entry動畫自動播放,而後又entry動畫進入loop動畫,而loop動畫是循環的。

總的來講,這是一個建立循環動畫很方便的方法。

選擇entry Timeline。在Timeline的底部,左擊No chained timeline,設置爲loop。再選擇loop動畫,點擊No chained timeline,選擇loop。這就正確的連接了entry Timeline進入loop,而後loop本身循環。

 

Editing the Entry Animation

選擇entry Timeline.這是你第一個要進行動畫設置的Timeline。想法是buttons初始化時在屏幕外,而後zapping in。這是一個時間很短的動畫效果-----畢竟,用戶但願你的app很快,而且不想等動畫結束,不論你花了多少功夫在動畫上。

點擊duration box,編輯Timeline持續時間。設置爲1秒15幀,即00:01:15.這等於1.5秒,由於SpriteBuilder中,30幀爲1秒。

選擇logoAndButtons node,移動它到layer得左側外。X position爲-20,按P建立一個關鍵幀。移動Cursor到最右邊,按P建立第二幅關鍵幀。編輯logoAndButtons node的位置,爲50.

右鍵點擊關鍵幀段,選擇Elasic Out easing模式。再次點擊關鍵診斷,Easing Setting選項如今已經可選了,點擊Easing Setting。

在這裏,這個floating-point值叫Period,它定義了動畫的springy(彈性)程度,這個值越低,the more the node will move back and forth before coming to rest.在這裏,輸入0.9,點擊Done,

 

entry動畫能夠再增長一些東西。一個好的效果是在合適的時間調整buttons的scale。對play喝settings sprite nodes重複下面的步驟:

1.選擇sprite node(play 和 settings)

2.移動Timeline Cursor到timeline的中間。

3.添加一個Scale關鍵幀,按S。設置X和Y的scale屬性爲0。

4.移動Timeline Cursor到最右端

5.按S,添加另外一個Scale關鍵幀,設置X和Y的Scale屬性爲1。

6.右鍵點擊關鍵幀段,選擇Bounce Out easing模式。

你能夠試着移動play和settings sprite的第一個關鍵幀,這樣它們動畫開始的時間就不同了。

以下:

Editing the Loop Animation

對於entry Timeline來講,這些就夠了。如今,切換到loop Timeline。你會注意到,全部已經存在關鍵幀都消失了。

這些對你沒有影響,可是思考:你製做的動畫效果在接下來的另外一個Timeline上可能不協調。容易造成忽然的移動。

在這個例子中,這一點很好修復,選擇logoAndButtons node,改變它的位置爲50%x50%。由於你已經選擇了loop動畫,這個位置的改變會不影響entry Timeline中得logoAndButtons。

改變Timeline的持續時間爲2秒。而後對於play和settings sprites,作以下:

1.選擇sprite node(play或者settings)

2.移動Timeline Cursor到最左邊。

3.按R,以建立一個旋轉關鍵幀。

4.移動Timeline Cursor到最右邊。

5.按R,建立另外一個旋轉關鍵幀。

6.改變該關鍵幀爲360.

 

注意到sprites旋轉了。同時注意labels和它們各自的parent sprites一塊兒旋轉。可是,這是一個好主意麼?不徹底是的。

你能夠用一個技巧:若是你在child node上播放和parent node同樣的動畫,可是方向相反,那麼這兩個動畫會互相取消!因此,若是你向它們的parent sprites旋轉的相反方向旋轉label,那麼它們會中止旋轉。對兩個labels作以下步驟:

1.選擇sprite node的label(play或者settings)

2.移動Timeline Cursor到最左邊。

3.按R,建立旋轉關鍵幀。

4.改變旋轉屬性的值爲360.

5.移動Cursor到最右邊。

6.按R建立另外一個關鍵幀。

7.改變Rotation屬性爲0.

 

這樣作的結果是sprites如今順時針旋轉,它們的child labels逆時針旋轉。

由於這兩個動畫都是滿旋轉,label的旋轉平衡了它們parent sprites的旋轉,這樣labels就保持不一樣。

如今,右擊每個label的關鍵幀段,改變easing modes爲Ease In/Out,你能夠再右擊,而且改變Easing settings爲低速率的值,好比1.3到1.8之間。

在任何狀況下,由於兩個動畫再也不是同步的----label的旋轉速度由於easing的緣由隨着時間提升或降低,label如今會左右擺動。

若是你讓parent和child的動畫不一樣,你會發現大多數奇怪的方法。

舉例來講,想象你要去移動一個帶有easing  mode的node,向右200points的距離,而且向左移動它的child sprite node 150points距離,而且easing mode也不一樣。這樣就會有組合效果。

若是你運行app,你會發現logo從左邊進入,而後buttons開始出現,而後buttons開始旋轉。

 

Tip:若是你但願讓buttons在entry Timeline運行中就開始旋轉呢?有兩種通用的方法。

一是在entry Timeline中對sprites和它們的labels複製旋轉關鍵幀。這能夠工做,可是會工做兩次,若是你曾經修改了buttons的旋轉,可能會工做更屢次。

還有一種方法是對每一個button建立一個自定義CCB文檔,而且建立旋轉動畫。可是,這意味着你必須對每一個buttons建立一個自定義類。

Adding the Buttons to MainScene

若是你想要在遊戲中看新的logo和buttons,你還必須添加MainMenuButtons.ccb到MainScene.ccb中。拖動.ccb文件到MainScene.ccb stage或者在Tileless Editor View中拖動它。把新的CCBFile位置改成0,0,起名logoAndButtons。

 

Creating Buttons Out of Ordinary Sprites

是否是哪裏不對呢?至今,buttons還不是真正的buttons,僅僅是一個帶這label得sprit。若是讓它們能夠點擊?

簡單,經過添加一個button!而後移除button的frame和label。惟一有些不一樣的地方是你不能讓按鈕的highlighted state動起來,由於button在僅僅是highlighted的狀況下不會發送消息。可是至少你能夠建立buttons而不用必須按照Button node的規矩讓images動起來。

Note:試試使用play.png或者settings.png做爲button的normal-state sprite frame。或者建立一個Sprite 9 Slice,而且分配它SpriteSheets/Menu/play.png。在內部,Button node使用Sprite 9 Slice。

 

打開MainMenuButtons.ccb,對play和settings sprites重複下面的步驟:

8.拖動一個Button到sprite(play或者settings)的Timeline中,button是sprite的child。

9.改變button的位置類型爲%,值爲50.

10.清空button的Title field。這會讓button的label消失。

11.對於Normal State和Highlighted State,改變Sprite frame 屬性爲<NULL>.這會移除button的背景圖片。如今它使一個不可見的button。

12.button的尺寸有點小,改變button的Preferred size屬性爲60x60.

你如今在play和settings sprites中有了兩個不可見的buttons。

 

Connecting the Buttons

用selectors鏈接到buttons,選擇Item Code Connetions。而後依次選擇每一個button,在Selector field,輸入shouldPlayGame(play button)和shouldShowSettings(settings button)。

還有一件事。selectors須要發送message到某處,而某處指的就是Document root。這個屬於指的是MainMenuButtons.ccb的root node。選擇它的root node,在自定義類中輸入MainMenuButtons。

 打開XCode,添加一個新的OC類。類名必須是MainMenuButtons,CCNode的子類。編輯MainMenuButtons.m,添加以下代碼:

- (void)shouldPlayGame {
    NSLog(@"Play");
}
- (void)shouldShowSettings {
    NSLog(@"SETTINGS");
}

這段代碼是爲了檢測buttons是否正常工做的。

 

Settings Menu

settings menu必須是一個popover,就像pause和game over menu同樣。這裏有幾個須要注意的點。一個是通用的關閉button,經過移除對應的parent node關閉。爲了簡單的建立settings menu layout,Box Layout node被用於安排橫豎排得nodes。

另外一個特性是Slider controls,能夠改變屬性值----在這裏,是audio volume levels。

Designing the Settings Menu with Box Layout

右鍵點擊UserInterface文件夾,選擇New File,建立一個新的CCB文件,命名爲SettingsLayer.ccb,類型是Layer,尺寸爲默認的568x384.改變root node的Content size類型爲%,值爲100.

拖動一個Node到stage,改變position type 爲%,值爲50.重命名這個node爲settingsLayer。

從Tileless Editor View中拖動S_bg圖片到settingsLayer node中。

 

Introducing Box Layout Nodes

如今,處理Box Layout node。這能夠把nodes在水平和垂直方向佈局。settings menu應該有一個label和兩個volume sliders。

這個grid-like layout能夠用parent-child關係來形容。一個垂直Box Layout nodes,有兩個水平Box Layout nodes做爲children。

拖動一個Box Layout node到settingsLayer上,讓它成爲settingsLayer的 child node。在Item Properties中,改變Direction屬性從默認的Horizontal爲Vertical,在Timeline中更名layout node爲verticalLayout。

而後你應該添加一個LabelTTF和兩個Box Layout nodes做爲verticalLayout node的children。你應該重命名兩個CCLayoutBox爲horizontalLayoutMusic何horizontalLayoutSfx。對於label,改變它的Label text爲Settings,Font Size設置爲32.

每一個slider被添加到horizontal layout nodes中得一個,和label node一塊兒。拖動一個LabelTTF和一個Slider node到horizontalLayoutMusic node中,對於horizontalLayoutSfx node也重複這個步驟。

選擇每一個label,改變它的text爲Music Volume和Effects Volume,以下圖:

verticalLayout node垂直的排列它的children:settings label和兩個horizontal layout nodes,每一個都包含一個LabelTTF和一個Slider node。

verticalLayout node中得內容,label在底部,horizontalLayoutSfx在頂部。

nodes並無在layer中居中,由於Box Layoutnodes的anchor point默認0,0.

因此,即便verticalLayout node的位置在stage中是居中的,可是由於anchor ponit的關係,內容不能居中。

選擇verticalLayout node,改變它的Anchor point屬性爲0.5。這可讓nodes居中。你應該改變verticalLayout node的Spcaing屬性爲30,以增長sliders和labels之間的數值區域。

可是sliders仍然太寬了,而且和labels重疊了。你能夠減小slider的尺寸,選中slider,乾煸Width of thePreferred尺寸屬性,從默認的200改成150.

label和slider仍然重合,這是一個間距問題,經過編輯Spacing 屬性,把spacing設置爲20.

 

Left-Alignment with Box Layout

若是你仔細觀察,你可能已經發現兩個labels和sliders仍然沒有完美對其。sliders並無在同一個X座標位置開始和結束。

把volume label改成FX Volume後,會看的更清楚:

這裏的問題是除非你使用徹底同樣的text,不然labels的size必定是不一樣的。

幸運的是,這能夠經過確保horizontalLayoutSfx和horizontalLayoutMusic有着一樣的內容size解決。若是你選擇它們,能夠看到它們的content size是不一樣的。horizontalLayoutMusic的寬是246,horizontalLayoutSfx的寬是230,若是你已經改變了child label的text爲FX Volume.

這意味着horizontalLayoutSfx的內容少了16points。因此你僅僅須要去讓它再寬16points。考慮到你已經對兩個horizontallayout nodes的Spacing屬性都設置到了20。你須要去增長horizontalLayoutSfx node的spacing。

選擇horizontalLayoutSfx node,改變Spacing屬性爲36----原始的20加上少的16.

 

Center-Alignment with Box Layout

可是若是你想讓labels居中對齊呢,並且不想在改變了label的text後擔憂spacing屬性問題呢?

 

「As of now, you have each label and slider aligned horizontally in a Box Layout node, and the two box layout nodes are aligned vertically in another Box Layout node. You can always reverse this setup. In this instance, you could have both labels in a vertically aligned Box Layout node, and both sliders in another vertically aligned Box Layout node. You would then add both vertical box layout nodes to a horizontally aligned Box Layout node.」

「The result will be different in so far that each vertical column’s width is defined by the node with the largest width. In other words, the widest label now defines the alignment of all labels in relation to all sliders next to the labels.」

摘錄來自: Steffen Itterheim. 「Learn SpriteBuilder for iOS Game Development」。 iBooks.

首先,移除horizontalLayoutSfx和horizontalLayoutMusic nodes。這同時也會移除它們的child nodes。

而後拖動一個Box Layout node到現存的verticalLayout node上,而且命名爲horizontalLayout。拖動另外兩個Box Layout nodes到horizontalLayout node中,改變它們的Direction爲Vertical,命名爲verticalLayoutLabels和verticalLayoutSliders。

添加兩個LabelTTF nodes到verticalLayoutLabels node,改變它們的label text爲FX Volume和Music Volume。

同時,添加兩個Slider nodes到verticalLayoutSliders node。如圖:

如今你有一個以前的問題:sliders和labels位置太近了。設置horizontalLayout何verticalLayoutSliders的Spacing屬性爲25,verticalLayoutLabels的Spacing爲18.

 

Changing the Slider Visuals

默認的sliders很差看。若是你選擇一個slider,轉到Item Properties,你會注意到CCSlider屬性區域,如圖:

這些設置容許你改變slider的背景圖片和handle。

改變Backgroud image爲SpriteSheets/UserInterface/S_bar.png,設置Handle的normal state圖片爲SpriteSheets/UserInterface/S_ball.png,對於Background和Handle images的Highlighted State爲<NULL>。這意味着highlighted state------用戶拖動handle的狀態------會使用和normal state相同的圖片。

注意到slider 背景圖片應該有有一個特殊的尺寸,由於它根據須要被拉伸。

在內部,slider和button用CCSprite 9 Slice node做爲images;你可使用28x10尺寸做爲你本身的背景images,而且設置Scale 爲x2.

 

Connecting the Sliders

選擇Item Code Connections,輸入volumeDidChange:在Selector field中,勾選Continuous check box。同時在Doc root var field中輸入_effectsSlider。

對於music slider重複:設置Slector爲volumeDidChange:,勾選ontinuous,在Doc root var field中輸入_musicSlider.

使得,兩個sliders使用同一個selector。注意到selectors後面的冒號-----若是在一個selector後面有一個冒號,這個方法接受一個參數。

「The parameter is always the CCControl object running the selector—in this case, the CCSlider instances. I’ll say more on this shortly.」

摘錄來自: Steffen Itterheim. 「Learn SpriteBuilder for iOS Game Development」。 iBooks.

 

少了什麼呢?固然是root node的自定義類了。選擇root node,輸入SettingsLayer。

如今在Xcode中,建立SettingsLayer類。

編輯SettingsLayer.h,以下:

#import "CCNode.h"
@class MainMenuButtons;
@interface SettingsLayer : CCNode
@property (weak) MainMenuButtons *mainMenuButtons;
@end

編輯SettingsLayer.m,以下:

#import "SettingsLayer.h"
#import "MainMenuButtons.h"
@implementation SettingsLayer {
    __weak CCSlider *_musicSlider;
    __weak CCSlider *_effectsSlier;
}
- (void)volumeDidChange:(CCSlider*) sender {
    NSLog(@"volume changed,sender:%@",sender);
}

@end

sender參數永遠是CCControl*類型,可是由於你能夠確定只有sliders會運行這個selector,你能夠安全的把參數類型設爲CCSlider*。CCSlider是CCControl的子類。兩個CCSlider變量用於決定是哪一個slider觸發了volumeDidChange:selector。

在你能夠試用sliders以前,你須要load而且在MainMenuButtons.m中顯示settings layer。準確的說,換掉已經存在的shouldShowSettings方法。以下:

- (void)shouldShowSettings {
    SettingsLayer *settingsLayer = (SettingsLayer*)[CCBReader load:@"UserInterface/SettingsLayer"];
    settingsLayer.mainMenuButtons = self;
    [self.parent addChild:settingsLayer];
    self.visible = NO;
    NSLog(@"SETTINGS");
}

SettingsLayer經過CCBReader載入,使用SettingsLayer.ccb文件的完整路徑。而後分配mainMenuButtons的引用,而且,settingsLayer做爲child被添加,不是MainMenuButtons類而是它的parent(MainScene實例)。這是由於MainMenuButtons實例自身被設置爲invisible-----若是setting layer是MainMenuButtons的child,它也會被隱藏。

你如今能夠運行game,敲擊Settings按鈕,打開settings popover。若是你移動sliders,你會發現Console的log:

每一個sender目標是一個CCSlider的不一樣實例。若是你在ItemProperties中給sliders一個名字,名字也會logged。

 

Dismissing the Settings Popover

你如今還不能消去settings popover。你應該經過引入一個通用關閉buton來解決這個問題。

你能夠對其餘layers使用相同的button。

可是在不少狀況下,你不能這樣作,由於buttons和其餘CCControl nodes向document root(CCB root)的自定義類發送它們的selector。特別是,若是BUtton沒有本身的CCB file,你就必須對每一個特殊的任務建立一個分開的button,也許是相同任務,可是不一樣使用狀況時,也要建立單獨的buttons。有一個簡單的解決方法。

右鍵點擊UserInterface文件夾,選擇New File。命名爲CloseButton.ccb,使用Node類型。選擇root node,轉到Item Code Connections,設置自定義類,命名爲CloseButton。

拖動一個Butotn node到stage。在Item Properties中,改變button的normal state和Highlighted State的sprite frame爲S_back.png。改變Highlighted State的Background color爲a light gray color.在Item Code Connections的Selector中,輸入shouldClose。同時清除Title field,以去除button的text。

這就是整個button的CCB file。你應該打開SettingsLayer.ccb,拖動CloseButton.ccb到settingsLayer node中。

移動button到右上角,如圖:

在Xcode中,建立另外一個OC類,命名爲CloseButton,父類是CCNode,打開CloseButton.m文件,添加以下代碼:

#import "CloseButton.h"

@implementation CloseButton
- (void)shouldClose {
    CCNode *aParent = self.parent;
    do {
        if([aParent respondsToSelector:_cmd]) {
            [aParent performSelector:_cmd];
            break;
        }
        aParent = aParent.parent;
    }while (aParent != nil);
}
@end

shouldClose方法在進入do/while循環以前採用button的parent。這檢查了是否parent對_cmd selector迴應,selector指的是shouldClose selector。_cmd的用法是讓這段代碼能夠簡單的用在其餘buttons上,對於不一樣的selector你不用更新代碼。

若是給定的parent響應了同一個selector,跳出;不然,aParent變量被賦值爲aParent的parent。

如今,剩下的工做時實現shouldClose方法。打開SettingsLayer.m,以下:

- (void)shouldClose {
    [self removeFromParent];
    [_mainMenuButtons show];
}

打開MainMenuButtons.h,以下:

#import "CCNode.h"

@interface MainMenuButtons : CCNode
- (void)show;
@end

在MainMenuButtons.m中,添加代碼:

- (void)show {
    self.visible = YES;
}

 我已經給了關於當SettingsLayer關閉後你能夠作什麼的建議,除了移除settings layer和顯示buttons。若是兩個CCB類須要播放一個本身的Timeline動畫呢,一個是SettingsLayer out,一個是MainMenuButtons in。

無論它們看起來怎麼樣,它們應該須要使用本身的animtionManager實例去運行animation,SettingsLayer必須對Callbacks selector做出迴應。


Persisting Game States

如今是時候引入GameState類來記錄game的變化狀態了。

Introducing the GameState Class

GameState類是NSUserDefaults的包裝,以免在代碼中用相同的string keys注入相同的代碼。就像NSUserDefaults,這回一個單例類。一個單例類是一個只能被初始化一次的類。

singletons是一個熱門的討論問題-----一些人甚至頗有意見。Singletons常常被過度使用或者誤用,特別是對於新手,由於它們由於有着簡單的全局可訪問性,使得數據的傳輸十分簡單,還有者誘人的結構,

在你使用singleton以前,確保你沒有其餘的方案了。相對而言,只有不多的變量和方法須要被全局化。

Tip:對於對象引用,避免它們全在一個singleton類中。任何類型爲id的屬性或者類指針都是strong類型,已阻止object deallocating。由於singleton自身在app運行期間,正常狀況下永遠不會deallocate。

在這個特殊情形下,這樣作是合理的。建立另外一個OC類,命名爲GameState,其父類爲NSObject。

建立後,打開GameState.h,添加以下:

#import <Foundation/Foundation.h>

@interface GameState : NSObject
+ (GameState*)sharedGameState;
@property CGFloat musicVolume;
@property CGFloat effectsVolume;
@end

sharedGameState前面有一個+,這使得它成爲一個類方法,能夠被其餘任何類訪問。

在GameState.m中添加代碼,這會建立一個single實例。

#import "GameState.h"

@implementation GameState
+ (GameState*) sharedGameState {
    static GameState *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{sharedInstance = [[GameState alloc]init];
    });
    return sharedInstance;
}
@end

NOTE:這種方法是ARC認同的。

 

如今,添加musicVolume屬性setter和getter方法,以下:

static NSString * KeyForMusicVolume = @"musicVolume";
- (void)setMusicVolume:(CGFloat)musicVolume {
    [[NSUserDefaults standardUserDefaults] setDouble:volume forKey:KeyForMusicVolume];
}
- (CGFloat)musicVolume {
    NSNumber *number = [[NSUserDefaults standardUserDefaults]objectForKey:KeyForMusicVolume];
    return (number ? number.doubleValue : 1.0);
}

NSUserDefaults key被聲明爲static NSString*變量,這樣你就不須要第二次再寫這個string了。

屬性的setter和getter方法遵循一個命名規則,getter和屬性名同樣,setter是屬性名前加一個set,屬性的第一個字母大寫。

NSUserDefaults是一個能夠保存任何內置數據類型的類,包括:NSData,NSString,NSNumber,NSDate,NSArray,NSDictionary。因此它不會保存你的自定義類。可是你能夠單獨存儲你的類的屬性,好比這裏的volumes。

standaardUserDefaults是一個singleton accessor,就像sharedGameState.

setDouble:forKey:方法根據給定的key存儲一個double類型的值,必須是一個NSString*對象。這個相同的key接下來被用在objectForKey:中,以接受相關聯的NSNumber對象。NSUserDefaults永遠都返回內置數據類型,能夠返回nil,這一版是你第一次啓動app的情形。

在GameState.m中添加以下方法:

static NSString * KeyForEffectsVolume = @"effectsVolume";
- (void)setEffectsVolume:(CGFloat)volume {
    [[NSUserDefaults standardUserDefaults]setDouble:volume forKey:KeyForEffectsVolume];
}
- (CGFloat)effectsVolume {
    NSNumber *number = [[NSUserDefaults standardUserDefaults]objectForKey:KeyForEffectsVolume];
    return (number ? number.doubleValue:1.0);
}

Persisting the Volume Slider Values

在SettingsLayer.m中添加代碼:

#import "SettingsLayer.h"
#import "MainMenuButtons.h"
#import "GameState.h"
@implementation SettingsLayer {
    __weak CCSlider *_musicSlider;
    __weak CCSlider *_effectsSlier;
}
- (void)didLoadFromCCB {
    GameState *gameState = [GameState sharedGameState];
    _musicSlider.sliderValue = gameState.musicVolume;
    _effectsSlier.sliderValue = gameState.effectsVolume;
}
- (void)volumeDidChange:(CCSlider*) sender {
    if (sender == _musicSlider) {
        [GameState sharedGameState].musicVolume = _musicSlider.sliderValue;
    }else if (sender == _effectsSlier) {
        [GameState sharedGameState].effectsVolume = _effectsSlier.sliderValue;
    }
    NSLog(@"volume changed,sender:%@",sender);
}
- (void)shouldClose {
    [[NSUserDefaults standardUserDefaults]synchronize];
    [self removeFromParent];
    [_mainMenuButtons show];
}

@end

在didLoadFromCCB中,volume 值被分配給slider values。由於sliderValue和volumes的值都在0.0到1.0中。首先,GameState volumes會返回1.0,這會把兩個volume slider handles置於最右邊。不論何時volumeDidChange方法,

未完待續

相關文章
相關標籤/搜索