Adding to the answer since the OP clearly requested animation of stroke length over time, noting there is no SKAction for that. Instead, 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
as a fragment shader, outputs an RGB at every step, it allows you to control the color as you go, enabling the stroke to be a gradient color if you wish.
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 ); // draw blue // this is an error
} else if ( v_path_distance / u_path_length <= u_current_percentage ) {
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 ); // draw red
} else {
gl_FragColor = vec4( 0.0, 0.0, 0.0, 0.0 ); // draw nothing
}
}
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
// make your rounded rect
let path = CGMutablePath()
path.addRoundedRect(in: CGRect( x: 0, y: 0, width: 200, height: 150 ), cornerWidth: 35, cornerHeight: 35, transform: CGAffineTransform.identity )
// note that prior to a fix in iOS 10.2, bug #27989113 "SKShader/SKShapeNode: u_path_length is not set unless shouldUseLocalStrokeBuffers() is true" requires that a path be "long" - past a certain number of points - I don't know how many. So if the rounded rect doesn't work for you on iOS 10 - iOS 10.1, try this path instead:
// let path1 = CGMutablePath()
// path1.move( to: CGPoint.zero )
// path1.addLine( to: CGPoint( x: 0, y: strokeHeight ) )
// for i in 0...15 {
// path.addLine( to: CGPoint( x: 0, y: strokeHeight + CGFloat( 0.001 ) * CGFloat( i ) ) )
// }
// path.closeSubpath()
let strokeWidth = 17.0 * strokeSizeFactor
let shapeNode = SKShapeNode( path: path )
shapeNode.lineWidth = strokeWidth
shapeNode.lineCap = .round
shapeNode.addChild( cameraNode )
shapeNode.strokeShader = strokeShader
shapeNode.calculateAccumulatedFrame()
addChild( shapeNode )
// center the camera
cameraNode.position = CGPoint( x: shapeNode.frame.size.width/2.0, y: shapeNode.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
}
}
}