0

I want to animate the drawing of a rounded box. I've seen an answer where a guy used CGLinePath's to draw a house shape. I thought it was neat, honestly.

I know very little about Core Animation and how to implement it in an SKScene. I don't know what sort of thing you would add. I've seen a couple different solutions as to get where I want to go using Core Animation, but this doesn't help me draw my rounded-edge rectangle in my scene as a node.

I've seen this one where a guy uses Core Animation to draw a circle, and loads it into a UIView: Animate drawing of a circle

I want this to be a node, however, so I can apply some SKAction's to it.

Community
  • 1
  • 1
DDPWNAGE
  • 1,423
  • 10
  • 37

2 Answers2

2

Use SKShapeNode with actions.

This details rounded rectangle with skshapenode: SKSpriteNode - create a round corner node?

Now just animate the change with an action.

Community
  • 1
  • 1
Jake Crastly
  • 462
  • 3
  • 9
  • I didn't know that you could assign a path to an `SKShapeNode` until now, and I didn't know that there was a `CGCreatePathWithRoundedRect` identifier until just now. Thank you! – DDPWNAGE May 09 '15 at 01:20
0

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
        }

    }
}
Bobjt
  • 4,040
  • 1
  • 29
  • 29
  • i've got this working but I can't for the life of me figure out how to change the colour within the GameScene ?? – Adam Jan 14 '17 at 09:47
  • one other thing, is it also possible to change the lineWidth at the same time as the length? so width is a percentage of length and changes during the animation ? thanks. – Adam Jan 14 '17 at 09:48