雖然精靈是建立遊戲時使用的最重要的元素,Sprite Kit還提供了許多其他的節點類。這些節點類中的大部分都提供可視化的內容,類似的SKSpriteNode類。剩下的則不直接繪製自己的內容,而是修改它們在節點樹的後代的行為。表6-1列出了所有由Sprite Kit提供的節點類,包括你已經熟悉的SKScene和SKSpriteNode類。
表6-1 SpriteKit節點類
|
|
|
|
類 |
描述 |
|
|
所有的節點類都從該類派生。它不繪製任何東西。 |
|
|
場景是在節點樹的根節點。它控制動畫和動作的處理。 |
|
|
繪製紋理精靈的節點。 |
|
|
渲染文本字符串的節點。 |
|
|
渲染基於Core Graphics路徑的形狀的節點。 |
|
|
播放視頻內容的節點。 |
|
|
創建和渲染粒子的節點。 |
|
|
使用遮罩(mask)修剪其子節點的節點。 |
|
|
應用Core Image濾鏡到其子節點的節點。 |
幾乎所有對精靈節點使用的技術都可以應用到其他節點類型。例如,您可以使用動作讓屏幕上的其他節點對象動起來,操縱它們的渲染順序,並在物理模擬內使用它們。請繼續閲讀來了解如何在你的遊戲中使用這些其他節點類。當你對這些類變得熟悉時,你就會明白Sprite Kit所有的可視化能力了。然後您就可以開始設計遊戲的外觀了。
基礎節點
SKNode類不繪製任何可視化內容。它的主要作用是提供其他節點類使用的基礎行為。然而,這並不意味着在你的遊戲中你不能找到有用的方式使用SKNode對象。下面是一些你可能會在你的遊戲引擎內使用基礎節點的方式:
· 你有一個由多個節點對象組合的內容,無論是精靈或其他內容的節點。不過,你想在你的遊戲中把此內容作為一個單獨的對象,而不想令其中任何一個內容節點成為根節點。這時用基本節點是合適的,因為你可以給定它在場景樹的位置,然後讓所有的其他節點作為其後代。這些個別的零部件,也可以相對於父節點的位置進行移動或調整。
· 使用節點對象組織繪製的內容到一系列的層。例如,許多遊戲有一個世界(world)的背景層,有另一個角色層,而文本和其他的遊戲信息在第三層。其他遊戲有更多的層。創建每個層為基本節點,並把它們按順序插入到場景中。然後,必要時,可以使個別圖層可見或不可見。
· 您需要場景中一個不可見的對象,但要它執行一些其他必要的功能。例如,在一個地牢探索遊戲,一個不可見的節點可能用來代表一個隱藏的陷阱。當另一個節點與它相交時,就會觸發陷阱。(見“搜索物理主體”。)或另一個例子,你可能會添加一個節點作為另一個節點的子節點,而後者代表玩家在視圖中的點的位置。(請參閲“示例:在節點上中心定位場景”。)
在樹中用這樣的節點代表這些概念有以下優勢:
· 您可以通過添加或刪除單個節點來添加或刪除整個子樹。這讓場景管理變得有效率。
· 您可以調整的樹中的一個節點的屬性,這些屬性的效果向下傳播到節點的後代。例如,如果在基本節點有精靈作為其子節點,旋轉基本節點也將旋轉所有精靈內容。
· 您可以利用行動、物理接觸和其他Sprite Kit的功能來實現此概念。
子類化SKNode類是一個非常有用的方式在你的遊戲中建立更復雜的行為。請參閲“使用子類化來創建您自己的節點行為。”
標籤節點顯示文本
幾乎每個遊戲都需要在某些時候顯示文本,即使它只是對玩家顯示“遊戲結束” 。如果你必須自己在OpenGL中實現它,需要相當多的工作才能正確完成。但是Sprite Kit卻很容易!SKLabelNode類完成所有加載字體和創建顯示文本所需要的工作。
清單6-1演示瞭如何創建一個新的文本標籤。
清單6-1 添加文本標籤
SKLabelNode *winner = [SKLabelNode labelNodeWithFontNamed:@“Chalkduster”];
winner.text = “You Win!”
winner.fontSize = 65;
winner.fontColor = [SKColor greenColor];
winner.position = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
[self addChild:winner];
每次你更改標籤節點的屬性後,標籤節點會在下一次渲染場景時自動更新。
形狀節點繪製基於路徑的形狀
SKShapeNode類繪製一個標準的CoreGraphics路徑。圖形路徑是可以定義開放或封閉的子路徑的直線和曲線的集合。形狀節點包含單獨的屬性來指定線條的顏色和內部填充的顏色。
Shape節點對於不能很容易地分解成紋理精靈的內容是有用的。紋理精靈比形狀節點提供更高的性能,所以應在你的遊戲引擎謹慎使用它們。然而,形狀節點對於在你的遊戲內容之上構建和顯示調試信息是非常有用的。
清單6-2展示瞭如何創建一個形狀節點的例子。該示例創建一個藍色填充色和白色邊線的圓圈。路徑被創建並附加到形狀節點的path屬性。
清單6-2 通過路徑創建一個形狀節點
SKShapeNode *ball = [[SKShapeNode alloc] init];
CGMutablePathRef myPath = CGPathCreateMutable();
CGPathAddArc(MYPATH, NULL, 0.0, 15.0, M_PI*2, YES);
ball.path = myPath;
ball.lineWidth = 1.0;
ball.fillColor = [SKColor blueColor];
ball.strokeColor = [SKColor whiteColor];
ball.glowWidth = 0.5;
從代碼中你可以看到形狀有三個基本要素:
· 形狀的內部填充。fillColor屬性指定了用來填充內部的顏色。
· 形狀的邊線渲染為一條線。strokeColor和lineWidth屬性定義線條的筆觸。
· 從邊線擴展的光暈(glow)。glowWidth和strokeColor屬性定義光暈。
你可以通過設置其顏色為[SKColor clearColor] 禁用任何這些元素。
形狀節點提供了一些屬性讓你控制形狀如何融合到幀緩存(framebuffer)中。這些屬性的使用方式與SKSpriteNode類的屬性一樣。請參閲“融合精靈到幀緩衝中。”
視頻節點播放電影
SKVideoNode類使用AV Foundation框架顯示電影內容。與任何其他節點一樣,你可以把電影的節點放在節點樹內的任何地方,Sprite Kit會正確渲染它。例如,某些用動作定義可能代價高昂的可視化行為,你可能會使用視頻節點讓它動起來。
視頻節點與精靈節點類似,但只提供功能的一個子集:
· size屬性被初始化成視頻內容的基本尺寸,但如果你願意,你可以改變它。視頻內容將自動拉伸到新的尺寸。
· anchorPoint屬性定義了內容相對節點位置在什麼地方顯示。
然而,應遵循以下限制:
· 視頻節點總是被均勻拉伸。
· 視頻節點不能被着色。
· 視頻節點總是使用阿爾法混合模式。
像大部分的節點類那樣,創建一個視頻節點非常簡單。清單6-3展示了一個典型的使用模式。它使用存儲在應用程序bundle中的視頻初始化視頻節點,然後把節點添加到場景。調用節點的的play方法來啓動視頻播放。
清單6-3 在場景中顯示視頻
SKVideoNode *sample = [SKVideoNode videoNodeWithVideoFileNamed:@“sample.m4v”];
sample.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:sample];
[sample play];
節點的play和pause方法讓你可以控制播放。
如果你需要更精確地控制視頻的播放行為,你可以使用AV Foundation從你的視頻內容創建AVPlayer對象,然後使用這個對象初始化視頻節點。然後,使用AVPlayer對象來控制播放,而不是使用節點的播放方法。視頻內容將自動顯示在視頻節點中。欲瞭解更多信息,請參閲AV Foundation編程指南。
發射器節點創建粒子特效
當一個SKEmitterNode對象被放置在場景中時,它會自動創建並渲染新的粒子。你可以用發射器節點來自動創建特殊效果,包括下雨、爆炸或發射。
粒子類似於SKSpriteNode對象,它渲染有紋理或無紋理的圖像,圖像有尺寸、有顏色、且可以融合到場景。但是,粒子在兩個重要的方面與精靈不同:
· 粒子的紋理總是均勻拉伸。
· 粒子不能用Sprite Kit中的對象表示(represented)。這意味着你不可以對粒子執行節點相關的任務,也不能給粒子關聯物理主體使它們與其他內容相互作用。
粒子是純粹的可視化對象,他們的行為完全由創建它們的發射器節點定義。發射節點包含很多屬性來控制它生成的粒子的行為,包括:
· 粒子的出生率和壽命。你還可以指定發射器自行關閉前能產生的粒子的最大數量。
· 粒子的初始值,包括它的位置、方向、顏色和尺寸。這些初始值通常是隨機的。
· 在粒子生命期內應用到在粒子的變化。通常,這些被指定為一個隨時間的變化率(rate-of-change)。例如,你可以指定一個粒子以特定的速度旋轉,以弧度每秒為單位。發射器每一幀都自動更新粒子的數據。在大多數情況下,你還可以使用關鍵幀序列(keyframe sequences)創建更復雜的行為。例如,你可以為一個粒子指定一個關鍵幀序列,讓它出來時很小,放大到較大的尺寸,然後在死亡前收縮。
使用粒子發射器編輯器與發射器進行實驗
在大多數情況下,你永遠不需要在你的遊戲中直接配置發射器節點。相反,你應使用Xcode來配置發射器節點的屬性。當你改變發射器節點的行為,Xcode立即為你提供一個更新過的視覺效果。一旦完成之後,Xcode可以歸檔(achieve)配置好的發射器。然後,在運行時,你的遊戲使用此歸檔來實現實例化一個新的發射器節點。
使用Xcode創建你的發射器節點有幾個重要的優勢:
· 這是學習發射器類的能力的最好方式。
· 你可以更迅速地試驗新的粒子效果並立即看到結果。
· 你把粒子效果的設計任務從使用它的編程任務中分離出來。這使得你的美工可以獨立於你的遊戲代碼繼續創作新的粒子效果。
有關使用Xcode創建粒子效果的更多信息,請參閲粒子發射器編輯器指南。
清單6-4展示瞭如何加載由Xcode創建的粒子效果。所有粒子效果都使用Cocoa的標準歸檔機制保存,所以代碼首先創建煙霧效果的一個路徑,然後加載歸檔。
清單6-4 從文件加載粒子效果
- (SKEmitterNode *)newSmokeEmitter
{
NSString *smokePath = [NSBundle mainBundle] pathForResource:@“smoke” ofType:“skn”];
SKEmitterNode *smoke = [NSKeyedUnarchiver unarchiveObjectWithFile:smokePath];
return smoke;
}
手動配置發射器如何創建新的粒子
SKEmitterNode類提供了許多屬性配置發射器節點的行為。事實上,Xcode inspector簡單地設置這些屬性的值。但是,你可以手工創建發射器節點並配置這些屬性,或者你也可以從歸檔創建一個發射器節點並改變其屬性值。例如,假設一下,你使用清單6-4中的煙霧效果來展示對火箭飛船的損壞。隨着船受到更多的損壞,你可以提高發射器的出生率來添加更多的煙霧。
用於配置發射器節點的屬性的完整列表在SKEmitterNode類參考中描述。然而,首先理解如何創建新的粒子,其次理解一個典型粒子的屬性如何在發射器節點中指定,對你是有用的。
只要發射器節點在場景中,它就會發射新粒子。你使用以下屬性定義它要創建多少粒子:
· particleBirthRate屬性指定發射器每秒創建的粒子數。
· numParticlesToEmit屬性指定發射器自行關閉之前要創建多少粒子。發射器還可以被配置為產生無限數量的粒子。
當粒子被創建時,它的初始屬性值是由發射器的屬性決定的。對於每個粒子屬性,發射器類聲明以下四個屬性:
· 屬性的平均初始(starting)值。
· 屬性值的隨機範圍。每次發射一個新的粒子,會在該範圍內計算一個新的隨機值。
· 隨時間的變化率,也被稱為屬性的速度。並非所有屬性都有一個速度屬性。
· 一個可選的關鍵幀序列。
清單6-6展示了你可能如何配置發射器的scale屬性。這是節點的xScale和yScale屬性的一個簡化版本,並確定粒子相比它紋理的尺寸有多大。
清單6-5 配置粒子的scale屬性
myEmitter.particleScale = 0.3;
myEmitter.particleScaleRange = 0.2;
myEmitter.particleScaleSpeed = -0.1;
當創建一個新的粒子,其scale值是從0.2到0.4的一個隨機數。然後scale值以每秒0.1的速度減少。所以,如果一個特定的粒子以平均值開始,即0.3,它會在3秒內從0.3減少到0。
使用關鍵幀序列配置粒子屬性的自定義坡道
關鍵幀序列用來為粒子屬性提供更復雜的行為。一個關鍵幀序列,使你可以指定粒子生命期的多個點,並在每個點為屬性指定一個值。然後關鍵幀序列篡改(interpolate)這些點之間的值,並用它們來模擬粒子的屬性值。
你可以使用關鍵幀序列,實現了許多自定義的行為,包括:
· 更改屬性值,直到它達到某個指定值。
· 在粒子的整個生命期中使用多個不同的屬性值。例如,你可能會在序列中的一部分增加屬性的值,而在另一部分減小屬性的值。或者,在指定顏色時,你可以指定粒子在其生命期中循環顯示多種顏色。
· 使用一個非線性的曲線或分級(stepping)功能改變屬性值。
清單6-6展示你可以如何替換清單6-5中的代碼來使用序列。當你使用一個序列,值不是隨機化的。相反,序列指定所有的屬性值。每個關鍵幀值包含一個值對象和時間戳。時間戳在0到1.0的範圍內指定,其中0表示粒子的誕生而1.0表示它的死亡。因此,在該序列中,粒子以0.2的拉伸比例開始並在序列的四分之一時增加到0.7。到序列的四分之三時,達到它的最小尺寸0.1。它保持這個尺寸直到死亡。
清單6-6 使用序列來改變粒子的尺度屬性
SKKeyframeSequence * scaleSequence = [[SKKeyframeSequence alloc]
initWithKeyframeValues:@[@0.2,@0.7,@0.1 times:@[@0.0,@0.250,@0.75];
myEmitter.particleScaleSequence = scaleSequence;
給粒子添加動作
雖然你沒有能力直接訪問由Sprite Kit創建的粒子,但是你可以指定一個所有粒子都執行的動作。每當創建新的粒子,粒子發射器告訴該粒子執行動作。你使用動作創建的行為,甚至可以比序列所允許的更復雜。
在粒子上使用動作的目的,是你可以把粒子看作是一個精靈。這意味着你可以執行其他有趣的技巧,如讓粒子的紋理動起來。
使用目標節點更改粒子的目的地
當發射器創建粒子時,它們被渲染成發射節點的子節點。這意味着它們獲得發射器節點的所有特性。所以,如果你旋轉發射器節點,所有已產生的粒子的位置也會跟着旋轉。根據你使用發射器所模擬的東西,這未必是正確的行為。例如,假設你要使用發射器節點來創建火箭的排氣。當引擎在全速燃燒,一個錐形的火焰應該在飛船後面噴出來。這用粒子模擬是很容易的。但是,如果粒子是相對於船渲染的,船轉彎時排氣也會跟着旋轉。那樣看起來不對。你真正想要的是粒子產生後,就獨立於發射器節點。當發射器節點旋轉時,新的粒子有新的方向,而舊的粒子仍保持其原來的方向。你要用目標節點來實現它。
清單6-7展示瞭如何使用目標節點來配置火箭的排氣效果。當自定義精靈節點類實例化排氣節點時,它使排氣節點成為它本身的子節點。然而,它使粒子重定向到場景。
清單6-7 使用目標節點來重定向產生粒子的地方
- (void)newExhaustNode
{
SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:
[NSBundle mainBundle] pathForResource:@“exhaust” ofType:@“sks”];
//發射器放置在船的後部。
emitter.position = CGPointMake(0,-40);
emitter.name = @“exhaust”;
//發送粒子到場景。
emitter.targetNode = self.scene;
[self addChild:emitter];
}
當發射器有一個目標節點時,它計算粒子的位置、速度和方向,正如它是該精靈節點的子節點那樣。這意味着,如果飛船精靈旋轉,排氣方向也會自動旋轉。然而,一旦這些值計算好,它們被轉換到目標節點的座標系。此後,他們將只受場景節點的屬性變化影響。
粒子發射器提示
Sprite Kit中的粒子發射器是構建可視化效果最有力的工具之一。但是,使用不當的話,粒子發射器可能會成為你的應用程序的設計和實施的瓶頸。考慮下面的提示:
· 使用Xcode來創建和測試你的粒子效果,然後在你的遊戲中加載歸檔。
· 在你的遊戲的代碼裏有節制地調整發射器的屬性。通常情況下,你這樣做是為了指定那些不能由Xcode inspector指定的屬性,或者在你的遊戲邏輯裏控制屬性。
· 粒子比精靈節點代價低廉(cheaper),但他們仍然有開銷!所以,你應該儘量保持屏幕上的粒子的數量降到最低。儘量以低出生率創建粒子效果,並在粒子一旦不可見時殺掉它們。例如,不是每秒創建數百或數千的粒子,而是降低粒子的出生率並稍微增加粒子的尺寸。通常,你可以用更少的粒子創建效果,但獲得同樣的淨視覺外觀(net visual appearance)。
· 除非沒有另一種解決方案時,才在粒子上使用動作。在單個(individual)粒子上執行動作可能會是非常昂貴的,特別是如果該粒子發射器還具有較高的出生率的話。
· 每當粒子在產生後就應該獨立於發射器節點時,給它們指定目標節點。這通常是發生在發射節點要在場景中移動或旋轉的情況下。
· 考慮當粒子發射器在屏幕上不可見時,把它從場景中移除。只在它變得可見前添加它。
切割節點遮罩部分的場景
SKCropNode對象不像精靈節點那樣直接渲染內容。相反,它改變了它的子節點被渲染時的行為。切割節點允許你裁剪部分由子節點渲染的內容。這使得切割節點對於實現駕駛艙(cockpit)視圖、控件和其他遊戲的指示器、以及任何子節點不應該繪製在場景的一個特定區域之外的效果,是很有用的。圖6-1簡單地使用火箭飛船美術對另一個繪製在場景中的精靈應用遮罩(mask)。
圖6-1 切割節點進行遮罩操作
裁剪區域通過遮罩指定。該遮罩不是一個固定的圖像。它由一個節點渲染,就像Sprite Kit中的任何其他內容。這對簡單的口罩和更復雜的行為都是允許的。例如,這裏有一些可能你會用來指定一個遮罩的方法:
· 無紋理精靈創建一個遮罩,該遮罩限制內容為場景的一個矩形部分。
· 紋理感精靈是一個像素級精確的遮罩。但也要考慮一個非均勻縮放紋理的好處。這可以允許你創建可以調整尺寸的任意形狀的對象。遮罩被調整尺寸和縮放以創建形狀,然後動態內容在該遮罩內繪製。
· 可以動態地生成一個複雜遮罩的節點的集合,該遮罩會在每次幀渲染時改變。
清單6-8展示了遮罩的簡單使用。它用應用程序bundle中的紋理加載遮罩圖像。然後部分場景的內容被渲染,使用遮罩防止它過度繪製(overdrawing)屏幕中游戲用來顯示控件的部分。
清單6-8 創建切割節點
SKCropNode * cropNode = [[SKCropNode alloc] init];
myCropNode.position = CGPointMale(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
cropNode.maskNode = [[SKSpriteNode alloc] initWithImageNamed:@“cockpitMask”];
[cropNode addChild:gamePlayNode];
[self addChild:cropNode];
[self addChild:gameControlNodes];
當切割節點在渲染時,遮罩在繪製其後代前先渲染。只與最終遮罩的alpha分量有關。遮罩中任何alpha值為0.05或更高的像素會呈現。其餘的像素會被裁剪。
效果節點對它們的後代應用特效
SKEffectNode自身不繪製內容。相反,每次使用效果節點渲染新的幀時,效果節點執行一個特效傳遞給它內容。這個傳遞經過以下步驟:
1. 效果節點執行一個單獨的繪圖傳遞(drawing pass)來渲染其子節點到一個私有幀緩衝區(private framebuffer)。
2. 它應用Core Image效果到私有幀緩衝區。這個階段是可選的。
3. 然後它融合它的私有幀緩衝區的內容到它父節點的幀緩衝區,使用標準的精靈混合模式之一。
4. 它丟棄其私人的framebuffer。
圖6-2展示了效果節點的一個可能的用法。在這個例子中,效果節點的子節點是兩個作為燈光節點的精靈。它積累這些燈的效果,應用模糊濾鏡來柔化產生的圖像,然後使用複合混合模式(multiply blend mode)來應用這個照明到牆壁紋理上。
圖6-2 效果節點應用特效到節點的子節點
這裏是燈光效果如何產生的過程:
1. 場景中包含兩個不同的節點。第一個是表示地面的紋理精靈。第二個是應用燈光的效果節點。
self.lightingNode = [[SKEffectNode alloc] init];
2. 燈光是效果節點的子節點。每個都使用附加混合模式來渲染。
SKSpriteNode *light = [SKSpriteNode spriteWithTexture:lightTexture];
light.blendMode = SKBlendModeAdd;
...
[self.lightingNode addChild:light];
3. 應用濾鏡來柔化燈光。
如果你指定一個Core Image濾鏡,它必須是一個接收單一的輸入圖像並生成單一的輸出圖像的濾鏡。
-(CIFilter *)blurFilter
{
CIFilter *filter= [CIFilter filterWithName:@“CIBoxBlur”] // 3
[filter setDefaults];
[filter setValue:[NSNumber numberWithFloat:0] forKey:@“inputRadius”];
return flter;
}
self.lightingNode.filter = [self blurFilter];
4. 效果節點使用一個複合混合模式來應用照明效果。
self.lightingNode.blendMode = SKBlendModeMultiply;
場景是效果節點
你已經學了很多關於SKScene類的東西,但你可能沒有注意到,它是SKEffectNode的一個子類!這意味着,任何場景可以對內容應用濾鏡。雖然應用濾鏡花銷可能會非常昂貴(不是所有濾鏡都為交互效果精心設計),試驗可以幫助你找到一些有趣的方式來使用濾鏡。
使用緩存來提高靜態內容的性能
效果節點通常渲染其內容作為繪製幀的一部分,然後丟棄它們。渲染內容是必要的,因為我們假設內容是每幀都改變的。但是,重新創建這些內容並應用Core Image濾鏡的成本可能會非常高。如果內容是靜態的,那麼這是不必要的。保持渲染的幀緩衝區,而不是拋棄它,可能會更有意義。如果效果節點的內容是靜態的,你可以把節點的shouldRasterize屬性設置為YES。設置此屬性將導致以下行為的改變:
· 幀緩衝區在光柵化(rasterization)的末尾不會被丟棄。這也意味着效果節點正在使用更多的內存,而渲染可能需要稍長的時間。
· 當一個新的幀被渲染時,幀緩衝區僅在效果節點的後代的內容已經改變後才會被渲染。
· 更改Core Image濾鏡的屬性不再導致幀緩衝區的自動更新。你可以通過設置shouldRasterize的屬性為NO強制它更新。