我正在尝试使用SpriteKit在路径上绘制笔画的动画.我已经使用SKActions实现了一个工作解决方案,并使用CABasicAnimations实现了单独的实现. SKAction解决方案不是很优雅;它在SKAction.repeatAction(action:count :)调用的每次迭代中创建并描绘一个新路径,每个新路径比前一个略微更完整.
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) 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解决方案更优雅,更高效.
let path:CGMutablePathRef = CGPathCreateMutable() CGPathAddArc(path,nil,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的子类中(以至于如果以上两个解决方案是我唯一的选择,我会选择第一个).有没有什么方法可以让我的SKAction实现更接近CoreAnimation实现,而不需要包含CoreAnimation?基本上,我想知道SpriteKit是否具有我不知道哪些功能可用于改进第一个实现的功能.
解决方法
您可以通过提供基于SKShader的
properties,v_path_distance和u_path_length的一些输出的自定义strokeShader来为SKShapeNode的路径设置动画.这由上面的rickster暗示,完整的代码如下.在着色器中,u_current_percentage由我们添加,并引用我们想要描绘的路径中的当前点.由此,场景决定了动画抚摸的速度.另请注意,strokeShader是片段着色器,在每一步输出RGB,它允许沿路径进行颜色控制,例如,可以使渐变颜色成为可能.
着色器作为文件添加到Xcode项目“animateStroke.fsh”:
void main() { if ( u_path_length == 0.0 ) { gl_FragColor = vec4( 0.0,0.0,1.0 ); } else if ( v_path_distance / u_path_length <= u_current_percentage ) { gl_FragColor = vec4( 1.0,1.0 ); } else { gl_FragColor = vec4( 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 } } }