在SpriteKit中animationpath绘制

我正在尝试使用SpriteKit在path上绘制一个笔画的animation。 我已经使用SKActions和一个单独的实现使用CABasicAnimations实现了一个工作解决scheme。 SKAction解决scheme不是很优雅, 它在SKAction.repeatAction(action:count :)调用的每次迭代中创build并抚摸一条新path,每个新path比前一个更加完整。

func start() { var i:Double = 1.0 let spinAction:SKAction = SKAction.repeatAction(SKAction.sequence([ SKAction.runBlock({ self.drawCirclePercent(i * 0.01) if (++i > 100.0) { i = 1.0 } }), SKAction.waitForDuration(0.01) ]), count: 100) runAction(spinAction) } func drawCirclePercent(percent:Double) { UIGraphicsBeginImageContext(self.size) let ctx:CGContextRef = UIGraphicsGetCurrentContext() CGContextSaveGState(ctx) CGContextSetLineWidth(ctx, lineWidth) CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0) CGContextAddArc(ctx, CGFloat(self.size.width/2.0), CGFloat(self.size.height/2.0), radius/2.0, CGFloat(M_PI * 1.5), CGFloat(M_PI * (1.5 + 2.0 * percent)), 0) CGContextStrokePath(ctx) let textureImage:UIImage = UIGraphicsGetImageFromCurrentImageContext() texture = SKTexture(image: textureImage) UIGraphicsEndImageContext() } 

虽然上面的代码工作,但它肯定不是漂亮或有效,可能不是如何使用SKActions。 CABasicAnimation解决scheme更优雅,更高效。

 let path:CGMutablePathRef = CGPathCreateMutable() CGPathAddArc(path, nil, 0, 0, 40.0, CGFloat(M_PI_2 * 3.0), CGFloat(M_PI_2 * 7.0), false) let pathLayer:CAShapeLayer = CAShapeLayer() pathLayer.frame = CGRectMake(100, 100, 80.0, 80.0) pathLayer.path = path pathLayer.strokeColor = SKColor.whiteColor().CGColor pathLayer.fillColor = nil pathLayer.lineWidth = 3.0 self.view.layer.addSublayer(pathLayer) let pathAnimation:CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd") pathAnimation.duration = 2.0 pathAnimation.fromValue = 0.0 pathAnimation.toValue = 1.0 pathLayer.addAnimation(pathAnimation, forKey: "strokeEndAnimation") 

我的问题是,我真的希望所有代码包含在SKSpriteNode的子类(如此以至于如果上述两个解决scheme是我唯一的select,我会去的第一个)。 有没有什么方法可以改进我的SKAction实现,使之更接近CoreAnimation的实现,而不需要包含CoreAnimation? 本质上,我想知道如果SpriteKit有function,我不知道哪些可以用来改善第一个实施。

您可以通过提供一个自定义的strokeShader来生成SKShapeNode的path,该自定义的strokeShader根据一些SKShader的属性 v_path_distanceu_path_length 。 这在上面的rickster暗示,完整的代码如下。 在着色器中,我们添加了u_current_percentage ,并引用了我们想要抚摸的path中的当前点。 由此,现场决定了animation轻抚的速度。 另外请注意, strokeShader是一个片段着色器,每一步输出一个RGB,它允许沿着path进行颜色控制,例如使渐变色成为可能。

着色器作为文件添加到Xcode项目“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 ); } } 

而使用它的样本SKScene子类:

  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 } } } 

基于Bobjt的回答 ,我使用SKShapeNode的glowWidth的着色器…所以,当我使用这个我可以使用shapeNode.glowWidth = 5.0; 等等!

 void main() { vec4 def = SKDefaultShading(); if ( u_path_length == 0.0 ) { gl_FragColor = vec4( 0xff / 255.0f, 0 / 255.0f, 0 / 255.0f, 1.0f ); } else if ( v_path_distance / u_path_length <= u_current_percentage ) { gl_FragColor = vec4( //0x1e / 255.0f, 0xb8 / 255.0f, 0xca / 255.0f, <-- original boring color :-P 0x1e / 255.0f * def.z, 0xb8 / 255.0f * def.z, 0xca / 255.0f * def.z, def.z ); } else { gl_FragColor = vec4( 0.0f, 0.0f, 0.0f, 0.0f ); } }