SpriteKit 中的动画路径绘制 [英] Animate Path Drawing in SpriteKit

查看:14
本文介绍了SpriteKit 中的动画路径绘制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 SpriteKit 为路径上的笔画绘制动画.我已经使用 SKActions 实现了一个可行的解决方案,并使用 CABasicAnimations 实现了一个单独的实现.SKAction 解决方案不是很优雅;它在 SKAction.repeatAction(action:count:) 调用的每次迭代中创建并绘制一条新路径,每个新路径都比前一个更完整.

I'm attempting to animate the drawing of a stroke on a path using SpriteKit. I have implemented a working solution using SKActions and a separate implementation using CABasicAnimations. The SKAction solution is not very elegant; it creates and strokes a new path on each iteration of an SKAction.repeatAction(action:count:) call with each new path slightly more complete than the previous.

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 解决方案更加优雅和高效.

While the above code works, it certainly is not pretty or efficient, and is likely not how SKActions were intended to be used. The CABasicAnimation solution is much more elegant and much more efficient.

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 的子类中(以至于如果上述两种解决方案是我唯一的选择,我会选择第一的).有什么方法可以改进我的 SKAction 实现,使其更接近 CoreAnimation 实现,而无需包含 CoreAnimation?从本质上讲,我想知道 SpriteKit 是否有我不知道的功能可以用来改进第一个实现.

My issue is that I would really prefer to have all the code contained within a subclass of SKSpriteNode (so much so that if the above two solutions are the only options I have I would go with the first). Is there any way in which I can improve my SKAction implementation to closer resemble the CoreAnimation implementation, without the need to include CoreAnimation? Essentially, I'm wondering if SpriteKit has functionality that I'm unaware of which could be used to improve the first implementation.

推荐答案

您可以通过提供基于 SKShader 的一些 属性v_path_distanceu_path_length.上面的 rickster 暗示了这一点,完整的代码如下.在着色器中,u_current_percentage 是我们添加的,它指的是我们想要向上描边的路径中的当前点.由此,场景决定了动画抚摸的节奏.另请注意,strokeShader 是一个片段着色器,在每一步都输出一个 RGB,它允许沿路径进行颜色控制,例如使渐变颜色成为可能.

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. This hinted at by rickster above, full code to do so follows. Within the shader, 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 color control along the path, making a gradient color possible for example.

着色器作为文件添加到 Xcode 项目animateStroke.fsh":

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

以及使用它的示例 SKScene 子类:

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

这篇关于SpriteKit 中的动画路径绘制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆