advanced east
by Luke Konior
盧克·科尼爾(Luke Konior)
SpriteKit Advanced —如何構建2,5D游戲(第二部分) (SpriteKit Advanced — How to build a 2,5D game (Part II))
介紹 (Intro)
This article shows how to write basic shaders in the SpriteKit. It’s split into two parts: first we play, then we learn.
本文介紹如何在SpriteKit中編寫基本著色器。 它分為兩個部分:首先我們玩,然后學習。
It also contains information how to use SKAttribute
and SKAttributeValue
classes that were added in iOS SDK 10.0.
它還包含有關如何使用iOS SDK 10.0中添加的SKAttribute
和SKAttributeValue
類的信息。
If you haven’t already read it, here’s part 1 of this article series.
如果您還沒有閱讀它,請參閱本系列文章的第1部分 。
準備項目 (Prepare the project)
Let’s get quick and dirty.
讓我們變得快速又骯臟。
- Open XCode 8 and create a new project from template: iOS > Game. 打開XCode 8,然后從以下模板創建一個新項目:iOS>游戲。
Open the
GameScene.sks
and remove the label in the center of the screen.打開
GameScene.sks
并刪除屏幕中心的標簽。Download this and put it inside
Assets.xcassets
下載此文件并將其放入
Assets.xcassets
- Name it “Trees” 命名為“樹木”
Open the
GameScene.m
打開
GameScene.m
- remove all instance variables 刪除所有實例變量
- remove all methods 刪除所有方法
片段著色器 (The Fragment Shader)
Now we create an empty fragment shader
In XCode:
現在,我們在XCode中創建一個空的片段shader
:
- In the Project Navigator select Supporting Files 在項目瀏覽器中,選擇“支持文件”
- Choose: File > New > File… 選擇:文件>新建>文件…
- Select: Other > Empty 選擇:其他>空
Name it “
myShader.fsh
” and press Create.將其命名為“
myShader.fsh
”,然后按創建。- Put this inside: 放在里面:
// currently a boring pass-thru shader void main( void ) { vec4 color = texture2D(utexture, vtexcoord); // here will emerge something worthy glFragColor = color;}
Above fragment shader
does nothing perceptible. Quick explanation:
片段shader
上方沒有任何可察覺的內容。 快速說明:
void main()
void main()
this function gets called for each pixel of the sprite and outputs color for that pixel
為精靈的每個像素調用此函數,并輸出該像素的顏色
Gets input data from surrounding globals and must set the
從周圍的全局變量獲取輸入數據,并且必須設置
gl_FragColor
variablegl_FragColor
變量vec2
,vec3
andvec4
are the types similar to C's:float array[2]
,float array[3]
andfloat array[4]
vec2
,vec3
和vec4
與C的類型相似:float array[2]
,float array[3]
和float array[4]
u_texture is a texture ID
u_texture是紋理ID
Leave it alone :-)
不要管它 :-)
v_tex_coord
is avec2
which contains our current position in texturev_tex_coord
是一個vec2
,其中包含我們當前的紋理位置texture2D(tex , p)
is a function that returns color from texturetex
inpoint p
asvec4
texture2D(tex , p)
是一個函數,將point p
處紋理tex
顏色返回為vec4
which contains rgba
其中包含rgba
gl_FragColor
is an output colorgl_FragColor
是輸出顏色We must assign it a
我們必須給它分配一個
vec4
vec4
正在加載代碼 (Loading code)
What’s left is the loading code.
剩下的就是加載代碼。
Open the
GameScene.m
打開
GameScene.m
add method
-didMoveToView:
添加方法
-didMoveToView:
- (void)didMoveToView:(SKView *)view { // 1. load the shader's source from myShaderFile.fsh NSString *file = [[NSBundle mainBundle] pathForResource:@"myShader" ofType:@"fsh"]; NSString *sourceString = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil]; // 2. create the shader SKShader *shader = [SKShader shaderWithSource:sourceString]; // 3. assign the shader to a newly created sprite node SKSpriteNode *spriteNode = [SKSpriteNode spriteNodeWithImageNamed:@"Trees"]; spriteNode.shader = shader; // 4. finally add the sprite to the scene [self addChild:spriteNode];}
Ensure that myShader.fsh
figures in ProjectFile > Target > Build Phases > Copy Bundle Resources!
確保myShader.fsh
數字位于ProjectFile>目標>構建階段>復制捆綁資源中!
You may now run the project on the iOS device. There shall be no errors in the XCode’s console and you should see a screen similar to this below:
您現在可以在iOS設備上運行該項目。 XCode的控制臺中應該沒有錯誤,并且您應該看到類似于以下的屏幕:
讓我們玩吧! (Let’s play a bit!)
Now is the fun part. We’ll replace the shader’s main function.
現在是有趣的部分。 我們將替換著色器的主要功能。
顏色為紅色,保留alpha (Color with red with alpha preservation)
void main( void ){ vec4 color = texture2D(u_texture, v_tex_coord); float alpha = color.a; gl_FragColor = vec4(1,0,0, 1.0) * alpha; //google "premultiplied alpha"}
縮小2倍 (Scale down by 2x)
void main( void ){ vec4 color = texture2D(u_texture, v_tex_coord * 2.0); gl_FragColor = color;}
1秒后交換顏色 (Swap colors after 1 second)
void main( void ){ vec4 color = texture2D(u_texture, v_tex_coord); float alpha = color.a; float phase = mod(u_time, 3); vec3 outputColor = color.rgb; if (phase < 1.0) { outputColor = color.bgr; } else if (phase < 2.0) { outputColor = color.brg; } gl_FragColor = vec4(outputColor, 1.0) * alpha;}
隨時間著色 (Colorize over time)
void main( void ){ vec4 color = texture2D(u_texture, v_tex_coord); float alpha = color.a; float r = (sin(u_time+ 3.14 * 0.00)+1.0)*0.5; float g = (sin(u_time+ 3.14 * 0.33)+1.0)*0.5; float b = (sin(u_time+ 3.14 * 0.66)+1.0)*0.5; gl_FragColor = vec4(r,g,b, 1.0) * alpha;}
波浪 (Waves)
void main( void ){ float deltaX = sin(v_tex_coord.y*3.14*10 + u_time * 4)*0.01; vec2 coord = v_tex_coord; coord.x = coord.x + deltaX; vec4 color = texture2D(u_texture, coord); gl_FragColor = color;}
新屬性 (New Attributes)
At WWDC 2016 Apple introduced an important update to SpriteKit — the SKAttribute
and SKAttributeValue
classes.
蘋果在WWDC 2016上對SpriteKit進行了重要更新,即SKAttribute
和SKAttributeValue
類。
Before this SDK update, if we wanted to pass custom parameters into the shader
program, we had to pass the data through a uniform value.
在此SDK更新之前,如果我們想將自定義參數傳遞給shader
程序,則必須通過統一值傳遞數據。
This had two serious drawbacks:
這有兩個嚴重的缺點:
- every uniform change caused shader recompilation 每次統一更改都會導致著色器重新編譯
- shader program handled every sprite in the exact same way 著色器程序以完全相同的方式處理每個精靈
For example: if we wanted to dye a group of sprites red, and one of them blue, we had two ways. First we create two separate SKShader
instances and change our custom myColor
uniform.
例如:如果我們想將一組精靈染成紅色,而其中一個染成藍色,則有兩種方法。 首先,我們創建兩個單獨的SKShader
實例,并更改我們的自定義myColor
制服。
Second we make one shader
instance and change its uniform which causes a recompilation.
其次,我們制作一個shader
實例并更改其統一性,從而導致重新編譯。
Both ways cannot be drawn on same pass. And the second one requires complex management code.
兩種方法不能同時繪制。 第二個要求復雜的管理代碼。
SDK 10.0 introduced the SKAttribute
and SKAttributeValue
classes. These two allow (finally!) passing data to the shader programs without recompilation. The usage algorithm is simple:
SDK 10.0引入了SKAttribute
和SKAttributeValue
類。 這兩個允許(最終!)將數據傳遞到著色器程序,而無需重新編譯。 用法算法很簡單:
- The shader part: 著色器部分:
Create a shader program
創建一個著色器程序
Create a shader program
SKShader
創建一個著色器程序
SKShader
Create an array of
SKAttributes
創建一個
SKAttributes
數組- Assign array of attributes to the shader program 將屬性數組分配給著色器程序
The
sprite
part:sprite
部分:
- Assign the shader program to a sprite 將著色器程序分配給精靈
Assign a dictionary of
SKAttributeValues
分配
SKAttributeValues
字典
屬性示例 (Example with attributes)
In the last example, we’ll add two more sprites. Every one of them will have the same shader program and will differ only in attributes. Let’s modify the -didMoveToView: inGameScene.m:
在最后一個示例中,我們將再添加兩個精靈。 它們中的每一個將具有相同的著色器程序,并且僅在屬性上有所不同。 讓我們修改- didMoveToView: inGameScene.m:
- (void)didMoveToView:(SKView *)view { NSString *file = [[NSBundle mainBundle] pathForResource:@"myShader" ofType:@"fsh"]; NSString *sourceString = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil]; SKShader *shader = [SKShader shaderWithSource:sourceString]; // 1. Add a custom attribute to shader SKAttribute *attrProgress = [SKAttribute attributeWithName:@"THE_MIGHTY_DARK_FACTOR" type:SKAttributeTypeFloat]; shader.attributes = @[attrProgress]; // 2. Create tree sprites NSArray *trees = @[ [self createTreeWithShader:shader mightyFactor:0.3f zPosition:1], [self createTreeWithShader:shader mightyFactor:0.6f zPosition:2], [self createTreeWithShader:shader mightyFactor:0.9f zPosition:3], ]; for (SKSpriteNode *tree in trees) { [self addChild:tree]; }}- (SKSpriteNode*)createTreeWithShader:(SKShader*)shader mightyFactor:(CGFloat)mightyFactor zPosition:(CGFloat)zPosition { SKSpriteNode *treeNode = [SKSpriteNode spriteNodeWithImageNamed:@"Trees"]; treeNode.shader = shader; // 3. Fill the custom attribute on the sprite treeNode.attributeValues = @{@"THE_MIGHTY_DARK_FACTOR": [SKAttributeValue valueWithFloat:mightyFactor]}; treeNode.zPosition = zPosition;return treeNode;}
… and the shader program:
…和著色器程序:
void main( void ){ vec4 color = texture2D(u_texture, v_tex_coord * (2.5 * THE_MIGHTY_DARK_FACTOR)); float alpha = color.a; vec3 baseColor = color.rgb * THE_MIGHTY_DARK_FACTOR; gl_FragColor = vec4(baseColor, 1.0) * alpha;}
... and see the parameterized result!
...并查看參數化結果!
注意事項 (Caveats)
The shader’s source code is typically loaded from a
.fsh
file to a plainNSString
著色器的源代碼通常從
.fsh
文件加載到純NSString
This code must compile on the target device during the runtime
此代碼必須在運行時在目標設備上編譯
no buildtime checks!
沒有構建時間檢查!
Older devices may use different version of OpenGL ES so beware GLSL syntax differences!
較舊的設備可能使用不同版本的OpenGL ES,因此請注意GLSL語法的不同!
In Raft Challenge’s case there was the need to replace
在Raft Challenge的情況下,需要更換
__constant
(valid in OpenGL ES 3.0) toconst
for OpenGL ES 2.0.__constant
(在OpenGL ES 3.0中有效)以const
表示OpenGL ES 2.0。It’s a good idea to keep a reference to SKShader object somewhere and reuse it as frequently as needed to avoid visible frame rate drop
最好在某個地方保留對SKShader對象的引用,并根據需要重復使用它,以避免可見的幀速率下降
While allocation and shader compilation takes less than 1/60 sec, it may become a huge burden in render loop
雖然分配和著色器編譯花費的時間少于1/60秒,但它可能會成為渲染循環中的巨大負擔
When using SpriteKit’s Texture Atlases be cautious of
vtexcoord
使用SpriteKit的Texture
vtexcoord
謹慎使用vtexcoord
XCode may rotate some textures which swap
XCode可能會旋轉一些交換的紋理
X
andY
axisX
和Y
軸X
andY
axisColor modification is safe, geometry is notX
和Y
軸顏色修改是安全的,幾何圖形不是
摘要 (Summary)
We learned by examples how to use fragment shaders in the Sprite Kit. We added parameters to sprites so our shader program can render every instance in a different way without any performance loss.
我們通過示例學習了如何在Sprite Kit中使用片段著色器。 我們向精靈添加了參數,以便我們的著色器程序可以以不同的方式呈現每個實例而不會造成性能損失。
The complete project is available for a download.
完整的項目可下載。
You can read part 3 of this series here.
您可以在這里閱讀本系列的第3部分 。
About the author: Kamil Zi?tek is an iOS Developer at www.allinmobile.co
關于作者:KamilZi?tek是www.allinmobile.co上的iOS開發人員
翻譯自: https://www.freecodecamp.org/news/spritekit-advanced-how-to-build-a-2-5d-game-part-ii-30ddb613b568/
advanced east