Resize sprite without shrinking contents
Asked Answered
L

1

1

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

-- EDIT -- Now, how do I use this method to scale to a specific size? Like turn 226x226 to 183x183?

Lining answered 16/10, 2016 at 15:37 Comment(0)
P
4

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.

Plan answered 16/10, 2016 at 17:31 Comment(11)
Small issue with this. I need the type to be a SKSpriteNode not a SKShapeNode. Maybe I should add a SKAction that will turn the SKShapeNode that was animated into a SKSpriteNode? I have googled a lot and it is said SKShapeNode should only be used for debuging or drawing. So after it finishes the animation I should convert it back right? Also I would love to use just init(circleOfRadius) but some of the circles need to be open (like a 3/4 circle). But since when I covert the SKShapeNode to a texture then to a sprite it loses its line width property. So I should convert back and forth?Lining
In case you have to use an SKSpriteNode, I think the only solution is to convert between SKShapeNode <-> SKSpriteNode each time you scale up/down. But if you only need to display simple shapes (either full or open circles), I would use an SKShapeNode initialized with a Bezier path. This way you will be able to animate the line's width even if it's an open circle.Plan
Thanks I will do what you did and change it around a bit for that. Thanks a lot for this!Lining
your way is working very well. But I am stuck. How do I use scale to scale to a specific x and y? For example, I want to turn 226x226 to 183x183. But the scale factor takes some random value. How would I get the exact value I need? I have tried 183/226 but that is not the right number (?)Lining
Your logic is good, it should be 183.0/226.0. What happens if you use this value?Plan
I type that in and it comes out fine. But I am trying to use a for loop and do it to a bunch of shapes. For some reason the numbers are a bit off for the shapes frame I think @PlanLining
@Nicholas714 can you maybe post your code, or at least a screenshot where you reproduce the problem?Plan
I posted a question here because there was not enough space here, https://mcmap.net/q/1780656/-skspritenode-frame-way-offLining
Peter, this is a great answer. Please excuse this ignorant question: Why use if let shape = shapeNode as? SKShapeNode { when it should be possible to apply the linewidth changes directly to the shapeNode?Jackdaw
@Confused: shapeNode here is actually just an SKNode which doesn't have a lineWidth property. That's why I used as? SKShapeNodePlan
argh! It's kinda like a placeholder, waiting for a real node?Jackdaw

© 2022 - 2024 — McMap. All rights reserved.