Nicholas714 Nicholas714 - 1 month ago 21
Swift Question

Resize sprite without shrinking contents

I have created a big circle with a UIBezierPath and turned it into a Sprite using this,

let path = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: CGFloat(226), startAngle: 0.0, endAngle: CGFloat(M_PI * 2), clockwise: false)

// create a shape from the path and customize it
let shape = SKShapeNode(path: path.cgPath)
shape.lineWidth = 20
shape.position = center
shape.strokeColor = UIColor(red:0.98, green:0.99, blue:0.99, alpha:1.00)

let trackViewTexture = self.view!.texture(from: shape, crop: outerPath.bounds)
let trackViewSprite = SKSpriteNode(texture: trackViewTexture)
trackViewSprite.physicsBody = SKPhysicsBody(edgeChainFrom: innerPath.cgPath)
self.addChild(trackViewSprite)


It works fine. It creates the circle perfectly. But I need to resize it using

SKAction.resize(byWidth: -43, height: -43, duration: 0.3)


Which will make it a bit smaller. But, when it resizes the 20 line width I set now is very small because of the aspect fill. So when I shink it looks something like this:

enter image description here

But I need it to shrink like this-- keeping the 20 line width:

enter image description here

How would I do this?

Don't know if this would affect anything, but the sprites are rotating with an SKAction forever

Answer

Since by scaling down the circle, not only its radius gets scaled but its line's width too, you need to set a new lineWidth proportional with the scale. For example, when scaling the circle down by 2, you will need to double the lineWidth.

This can be done in two ways:

  1. Setting the lineWidth in the completion block of the run(SKAction, completion: @escaping () -> Void) method. However this will result in seeing the line shrinking while the animation is running, then jumping to its new width once the animation finishes. If your animation is short, this may not be easy to observe tough.

  2. Running a parallel animation together with the scaling one, which constantly adjusts the lineWidth. For this, you can use SKAction's customAction method. Here is an example for your case:

    let scale = CGFloat(0.5)
    let finalLineWidth = initialLineWidth / scale
    let animationDuration = 1.0
    
    let scaleAction = SKAction.scale(by: scale, duration: animationDuration)
    
    let lineWidthAction = SKAction.customAction(withDuration: animationDuration) { (shapeNode, time) in
        if let shape = shapeNode as? SKShapeNode {
            let progress = time / CGFloat(animationDuration)
    
            shape.lineWidth = initialLineWidth + progress * (finalLineWidth - initialLineWidth)
        }
    }
    
    let group = SKAction.group([scaleAction, lineWidthAction])
    
    shape.run(group)
    

In this example, your shape will be scaled by 0.5, therefore in case of an initial line width of 10, the final width will be 20. First we create a scaleAction with a specified duration, then a custom action which will update the line's width every time its actionBlock is called, by using the progress of the animation to make the line's width look like it's not changing. At the end we group the two actions so they will run in parallel once you call run.

As a hint, you don't need to use Bezier paths to create circles, there is a init(circleOfRadius: CGFloat) initializer for SKShapeNode which creates a circle for you.

Comments