-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
Bobjt Jan 12, 2017 2:00 PM (in response to squillop)Squillop, yes you can animate SKShapeNode's path by supplying a custom strokeShader that outputs based on a few of SKShader's properties, v_path_distance and u_path_length. Note that within the shader supplied below "u_current_percentage" is added by us and refers to the current point within the path we want stroked up to. By that, the scene determines the pace of the animated stroking. Also note that strokeShader being a fragment shader, outputs an RGB at every step, it allows you to control the color as you go, allowing the stroke to be a gradient color for example.
The shader is added as a file to the Xcode project "animateStroke.fsh":
void main() { if ( u_path_length == 0.0 ) { gl_FragColor = vec4( 0.0, 0.0, 1.0, 1.0 ); } else if ( v_path_distance / u_path_length <= u_current_percentage ) { gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 ); } else { gl_FragColor = vec4( 0.0, 0.0, 0.0, 0.0 ); } }
And the sample SKScene subclass using it:
import SpriteKit import GameplayKit func shaderWithFilename( _ filename: String?, fileExtension: String?, uniforms: [SKUniform] ) -> SKShader { let path = Bundle.main.path( forResource: filename, ofType: fileExtension ) let source = try! NSString( contentsOfFile: path!, encoding: String.Encoding.utf8.rawValue ) let shader = SKShader( source: source as String, uniforms: uniforms ) return shader } class GameScene: SKScene { let strokeSizeFactor = CGFloat( 2.0 ) var strokeShader: SKShader! var strokeLengthUniform: SKUniform! var _strokeLengthFloat: Float = 0.0 var strokeLengthKey: String! var strokeLengthFloat: Float { get { return _strokeLengthFloat } set( newStrokeLengthFloat ) { _strokeLengthFloat = newStrokeLengthFloat strokeLengthUniform.floatValue = newStrokeLengthFloat } } override func didMove(to view: SKView) { strokeLengthKey = "u_current_percentage" strokeLengthUniform = SKUniform( name: strokeLengthKey, float: 0.0 ) let uniforms: [SKUniform] = [strokeLengthUniform] strokeShader = shaderWithFilename( "animateStroke", fileExtension: "fsh", uniforms: uniforms ) strokeLengthFloat = 0.0 let cameraNode = SKCameraNode() self.camera = cameraNode let strokeHeight = CGFloat( 200 ) * strokeSizeFactor let path1 = CGMutablePath() path1.move( to: CGPoint.zero ) path1.addLine( to: CGPoint( x: 0, y: strokeHeight ) ) // prior to a fix in iOS 10.2, bug #27989113 "SKShader/SKShapeNode: u_path_length is not set unless shouldUseLocalStrokeBuffers() is true" // add a few points to work around this bug in iOS 10-10.1 -> // for i in 0...15 { // path1.addLine( to: CGPoint( x: 0, y: strokeHeight + CGFloat( 0.001 ) * CGFloat( i ) ) ) // } path1.closeSubpath() let strokeWidth = 17.0 * strokeSizeFactor let path2 = CGMutablePath() path2.move( to: CGPoint.zero ) path2.addLine( to: CGPoint( x: 0, y: strokeHeight ) ) path2.closeSubpath() let backgroundShapeNode = SKShapeNode( path: path2 ) backgroundShapeNode.lineWidth = strokeWidth backgroundShapeNode.zPosition = 5.0 backgroundShapeNode.lineCap = .round backgroundShapeNode.strokeColor = SKColor.darkGray addChild( backgroundShapeNode ) let shapeNode = SKShapeNode( path: path1 ) shapeNode.lineWidth = strokeWidth shapeNode.lineCap = .round backgroundShapeNode.addChild( shapeNode ) shapeNode.addChild( cameraNode ) shapeNode.strokeShader = strokeShader backgroundShapeNode.calculateAccumulatedFrame() cameraNode.position = CGPoint( x: backgroundShapeNode.frame.size.width/2.0, y: backgroundShapeNode.frame.size.height/2.0 ) } override func update(_ currentTime: TimeInterval) { // the increment chosen determines how fast the path is stroked. Note this maps to "u_current_percentage" within animateStroke.fsh strokeLengthFloat += 0.01 if strokeLengthFloat > 1.0 { strokeLengthFloat = 0.0 } } }
-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
squillop Jan 12, 2017 3:05 PM (in response to Bobjt)This is awesome, many thanks!!
How exactly can I set the colour of the animation? I've studied the code and I can see how to set the overall colour in the shader, by manipulating the
gl_FragColor
value.
But I can't see how to change this on the fly from withing GameScene??
i figure something like .setValue on the gl_FragColor attibute? am i getting warm??
again, many thanks !
-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
Bobjt Jan 15, 2017 5:59 PM (in response to squillop)No problem. Here's an example that colors the stroke with a gradient:
Shader file "gradientStroke.fsh":
void main() { if ( u_path_length == 0.0 ) { gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 ); // error as u_path_length should never be zero, draw magenta } else if ( v_path_distance / u_path_length <= u_current_percentage ) { // draw color up to the current stroke percentage float c = v_path_distance / u_path_length; // c is a small number that becomes 1.0 toward the end of the stroke float v = 1.0 - c; // v is 1.0 at the start and approaches 0 toward the end of the stroke. // at any point in the stroke, v + c = 1.0 vec4 s = u_color_start; vec4 e = u_color_end; gl_FragColor = vec4( clamp( s.r*v + e.r*c, 0.0, 1.0 ), // clamp between 0.0-1.0 just in case clamp( s.g*v + e.g*c, 0.0, 1.0 ), clamp( s.b*v + e.b*c, 0.0, 1.0 ), clamp( s.a*v + e.a*c, 0.0, 1.0 ) ); } else { // draw nothing! gl_FragColor = vec4( 0.0, 0.0, 0.0, 0.0 ); } }
Scene subclass:
import SpriteKit import GameplayKit func shaderWithFilename( _ filename: String?, fileExtension: String?, uniforms: [SKUniform] ) -> SKShader { let path = Bundle.main.path( forResource: filename, ofType: fileExtension ) let source = try! NSString( contentsOfFile: path!, encoding: String.Encoding.utf8.rawValue ) let shader = SKShader( source: source as String, uniforms: uniforms ) return shader } class GameScene: SKScene { let strokeSizeFactor = CGFloat( 2.0 ) var strokeShader: SKShader! var strokeLengthUniform: SKUniform! // define the start and end colors here var startColorUniform = SKUniform( name: "u_color_start", vectorFloat4: vector_float4( [1.0, 1.0, 0.0, 1.0] ) ) var endColorUniform = SKUniform( name: "u_color_end", vectorFloat4: vector_float4( [1.0, 0.0, 0.0, 1.0] ) ) var _strokeLengthFloat: Float = 0.0 var strokeLengthKey: String! var strokeLengthFloat: Float { get { return _strokeLengthFloat } set( newStrokeLengthFloat ) { _strokeLengthFloat = newStrokeLengthFloat strokeLengthUniform.floatValue = newStrokeLengthFloat } } override func didMove( to view: SKView ) { // percentage is the only variable uniform since start and end color don't change strokeLengthUniform = SKUniform( name: "u_current_percentage", float: 0.0 ) // pass all uniforms to the shader let uniforms: [SKUniform] = [strokeLengthUniform, startColorUniform, endColorUniform] strokeShader = shaderWithFilename( "gradientStroke", fileExtension: "fsh", uniforms: uniforms ) strokeLengthFloat = 0.0 let cameraNode = SKCameraNode() self.camera = cameraNode let path = CGMutablePath() path.addRoundedRect(in: CGRect( x: 0, y: 0, width: 200, height: 150 ), cornerWidth: 35, cornerHeight: 35, transform: CGAffineTransform.identity ) let strokeWidth = 17.0 * strokeSizeFactor let shapeNode = SKShapeNode( path: path ) shapeNode.lineWidth = strokeWidth shapeNode.lineCap = .round addChild( shapeNode ) shapeNode.addChild( cameraNode ) shapeNode.strokeShader = strokeShader let rect = shapeNode.calculateAccumulatedFrame() cameraNode.position = CGPoint( x: rect.size.width/2.0, y: rect.size.height/2.0 ) } override func update(_ currentTime: TimeInterval) { strokeLengthFloat += 0.01 if strokeLengthFloat > 1.0 { strokeLengthFloat = 0.0 } } }
-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
squillop Jan 16, 2017 8:10 AM (in response to Bobjt)thanks again Bob. This is good stuff ! I'm going to explore this now in detail.
-
-
-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
zappy Jan 22, 2017 7:31 PM (in response to Bobjt)Hi, sorry to bump this topic but I think I have run into the problem noted in the comment:
// prior to a fix in iOS 10.2, bug #27989113 "SKShader/SKShapeNode: u_path_length is not set unless shouldUseLocalStrokeBuffers() is true"
Things work well on a iOS 10.2 device, but not on a iOS 9.3.5 device. Indeed it seems like the problem is with u_path_length. I am only using a simple circle with the skshapenode - it seems like u_path_length is not correctly calculated by SpriteKit (or some other part) which makes u_path_length be 0 or just not set... it totally breaks iOS 9.3.5 device, screen goes black. I haven't figured out a workaround. Any info?
-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
zappy Jan 22, 2017 7:45 PM (in response to zappy)I am using a simple circle... if I just use lines, it seems to work.
-
Re: Is it possible to animate a path similar to how StrokeEnd works in SpriteKit ?
Bobjt Jan 23, 2017 10:44 AM (in response to zappy)Hey zappy, confirmed I'm seeing that too - the SKView is black using the Metal renderer in combination with SKShapeNode's strokeShader. Adding the PrefersOpenGL = YES (boolean Info.plist key) to request the OpenGL renderer fixes the black view but u_path_length is mistakenly zero in iOS 9 (tested 9.2 and 9.3.5). The extra points workaround did not seem to work (whether line, rect, or arc). I'll post back if I find a way to support iOS 9, otherwise you might need to deploy to iOS 10.
-
-
-