I was also having trouble with .setValue(forAttribute:)
. It did not seem to work. Upon investigation, my conclusion is that...
I believe SKShapeNode.setValue(forAttribute:)
is not working.
The fact that SKSpriteNode.setValue(forAttribute:)
did work correctly under the same set of conditions led me to conclude that it was almost surely some bug in iOS in SKShapeNode
.
The fact was that any SKAttribute
s I might set in
SKShapeNode.fillShader
or SKShapeNode.strokeShader
kept their
values set to 0.0
no matter what I did.
However, the same did not happen with SKSpriteNode.shader
.
So I had to find a way to circumvent this bug.
Some sort of hack.
Fortunately, in my case, I did find a way.
And fortunately, it seems to be enough to solve your problem too.
What you can do, in this case, is to pass the value of your phase as one of the components of the strokeColor
, for example, the red component.
To do this you will have to normalize the phase to values between 0.0
to 1.0
.
You can pass a phase multiplier using a shader.uniform to help denormalize it.
Then read the phase value inside the shader from SKDefaultShading().x
, (the red component).
Make sure to set the alpha component of the
strokeColor
to 1.0
, for SKDefaultShading()
will have its color
component values already premultiplied by its alpha.
Now, because your strokeColor is already being used to pass the phase, you can pass the actual color of the stroked line as a vec4 shader.uniform.
- In my case, I also made use of
u_time
to help in performing my
animation. Stuff like fract(u_time)
can be very useful.
Here is how the corrected code might look like:
> NOTE: There is a much simpler solution. Look by the end of this post.
import SpriteKit
class GameScene: SKScene {
private typealias Me = GameScene
static let phaseMultiplier: Float = 1000
static let marquee: SKShader = {
let shader = SKShader(
source: "void main() {" +
"float red = SKDefaultShading().x;" +
"float phase = red * u_phase_multiplier;" +
"float np = mod(phase + v_path_distance / u_path_length, 1.0);" +
"vec3 hsb = vec3(np, 1.0, 1.0);" +
"vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);" +
"vec3 p = abs(fract(hsb.xxx + K.xyz) * 6.0 - K.www);" +
"vec3 rgb = hsb.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsb.y);" +
"vec4 df = u_stroke_color;" +
"gl_FragColor = vec4(rgb, df.z);" +
"}"
)
let white = vector_float4.init(1, 1, 1, 1)
shader.uniforms = [
SKUniform(name: "u_phase_multiplier", float: Me.phaseMultiplier),
SKUniform(name: "u_stroke_color", vectorFloat4: white)]
return shader
} ()
private let beat: TimeInterval = 0.05
private var phase: Float = 0
override func didMove(to view: SKView) {
let node = SKShapeNode(rectOf: CGSize(width: 256, height: 256), cornerRadius: 16)
node.fillColor = .clear
node.strokeColor = .white
node.strokeShader = Me.marquee
node.lineWidth = 8
node.glowWidth = 4
node.run(
SKAction.repeatForever(
SKAction.sequence(
[
SKAction.wait(forDuration: beat),
SKAction.run() { [weak self] in
if let weak = self {
weak.phase = fmodf(weak.phase + Float(weak.beat), 1)
let phase = weak.phase / Me.phaseMultiplier
node.strokeColor = SKColor.init(red: phase, green: 0, blue: 0, alpha: 1)
}
}
]
)
)
)
addChild(node)
}
}
NOTE: I did not compile the code above posted. I should serve merely as a sketch.
The day after this post, I found out that attribute parameters are not supposed to be used in fragment shaders, only in vertex shaders.
Take a look at StackOverflow question:
In WebGL what are the differences between an attribute, a uniform, and a varying variable?
The SDK documentation of SKAttribute
and SKAttributeValue
, however, seem to suggest that there may be no direct relation between "SKAttributes" and actual shader attribute parameters. They just seem to be named the same.
Simpler Solution:
In the end, however, there is never any need to make use of any hack like the one I show here.
All that needs to be done is to set the value of a uniform through SKShader.uniformNamed("u_phase")?.floatValue = phase
, instead of using .setValue(forAttribute:)
just as jmdecombe, correctly, later pointed out.
I will keep my answer here for reference, as it presents an unconventional solution to a problem.