【问题标题】:Animate Path Drawing in SpriteKitSpriteKit 中的动画路径绘制
【发布时间】:2014-08-09 05:25:11
【问题描述】:

我正在尝试使用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, 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, 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 是否具有我不知道的功能,可以用来改进第一个实现。

【问题讨论】:

  • 我会在不从我的手机发帖时进一步研究这个问题,但这里有一个开始的提示:如果你创建一个自定义 SKShader 来绘制一个形状节点,你的变量之一可以在着色器中访问的 GLSL 代码是沿路径的弧长。
  • @rickster 啊,有趣。我对 GLSL 不是很熟悉,但我会做一些阅读。
  • 有没有人最终编写了自定义 SKShader 来绘制弧线?
  • @BenMorrow 我研究了它,但最终使用了我原始问题中发布的方法。我认为 SKShader 选项会好得多。抱歉,我无法提供更多帮助。
  • @ScottMielcarski 谢谢,我会使用类似的东西

标签: ios swift sprite-kit ios8 skaction


【解决方案1】:

您可以通过提供基于 SKShader 的 propertiesv_path_distanceu_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, 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  
        }        
    }  
}  

【讨论】:

  • 好东西。您对如何使颜色“越过”线条本身有任何见解。现在我想让线外围的 alpha 从 1.0 淡出到 0.0。 IE。这与 v_path_distance 无关,而是应该引用一些着色器变量(如果存在,我不确定)“水平地”越过这条线。具有默认着色器的 SKShapeNode 使用 glowWidth 属性执行此操作。我的目标是做类似的事情。最终,在线条上也有类似的渐变效果会很好。
  • 我想我发现了如何使用glowWidth 值。我在另一个答案中添加了我更新的着色器。
  • 关于错误 27989113 的评论:这似乎不适用于 iOS 9.3.5(或任何 iOS 9,也许在 iOS 10.2 之前,我没有这样的设备来测试 atm )。我假设 u_path_length 有问题...对我来说,我使用的是一个圆圈 ([path1 addArcWithCenter:CGPointZero radius:RADIUS startAngle:M_PI_2 * 5 endAngle:M_PI_2 * 1 clockwise:NO];),这会弄乱整个 spritekit 视图 - 它全黑。我还没有弄清楚如何解决这个问题。
  • 你能稍微解释一下着色器吗?你不能用这个设置光晕宽度或颜色。
  • 很好的解决方案!知道如何使用 SKAction 为自定义 strokeShader 设置动画吗?
【解决方案2】:

基于 Bobjt 的answer,我的着色器利用了 SKShapeNode 的光晕宽度... 所以当我使用它时,我可以使用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 );
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-14
    • 1970-01-01
    • 2019-04-12
    • 1970-01-01
    • 1970-01-01
    • 2013-05-18
    • 1970-01-01
    • 2017-04-02
    相关资源
    最近更新 更多