SKSpriteNode.centerRect does not work with SKTextureAtlas - ios

I'm adding SKSpriteNode to my scene object using the following code.
let atlas = SKTextureAtlas(named: "Ubiquity")
let node = SKSpriteNode(texture: atlas.textureNamed("roundedBorder"))
node.zPosition = 2000
node.centerRect = CGRect(x: 0.4, y: 0, width: 0.2, height: 1)
addChild(node)
After all, my texture looks distorted (please see attached image wrong image)
But, if I either remove node.centerRect setting or putting my image to xcasset directly the below way
let node = SKSpriteNode(texture: SKTexture(imageNamed: "ubiquity-roundedBorder"))
node.zPosition = 2000
node.centerRect = CGRect(x: 0.4, y: 0, width: 0.2, height: 1)
addChild(node)
everything works as expected (please see attached another image correct image)

I am seeing the same behaviour. This looks like a bug.

Related

Gradient progress bar with rounded corners SpriteKit Swift

I'm trying to build a gradient progress bar with rounded corners in SpriteKit, but I'm completely stuck at this point. I've tried different combinations of SKCropNode, SKShapeNodes etc. but I can't seem to get it to work.
Any help is appreciated, kind regards!
It's about SKCropNode + its maskNode property. From the docs:
SKCropNode is a container node that you use to crop other nodes in the
scene. You add other nodes to a crop node and set the crop node's
maskNode property. For example, here are some ways you might specify a
mask:
An untextured sprite that limits content to a rectangular portion of
the scene.
A textured sprite that works as a precise per-pixel mask.
A collection of child nodes that form a unique shape.
You can animate the shape or contents of the mask to implement
interesting effects such as hiding or revealing.
So, a simple example would be like this:
class GameScene: SKScene {
override func sceneDidLoad() {
super.sceneDidLoad()
createProgressBar()
}
private func createProgressBar(){
let barFrame = CGRect(x: 0, y: 0, width: 300, height: 15)
if let cgImage = createImage(frame: barFrame) {
let texture = SKTexture(cgImage: cgImage)
let sprite = SKSpriteNode(texture: texture)
let cropNode = SKCropNode()
let mask = SKSpriteNode(color: .gray, size: barFrame.size)
cropNode.addChild(sprite)
cropNode.maskNode = mask
sprite.anchorPoint = CGPoint(x: 0.0, y: 0.5)
mask.anchorPoint = CGPoint(x: 0.0, y: 0.5)
var counter:Double = 0
let action = SKAction.run {[weak self, sprite] in
guard let `self` = self, counter < 100 else {
sprite?.removeAction(forKey: "loop")
return
}
counter += 1
let newWidth = self.getWidth(percents: counter, spriteWidth: barFrame.width)
print("Bar width \(newWidth), percentage \(counter)")
mask.size = CGSize(width: newWidth, height: barFrame.height)
}
let wait = SKAction.wait(forDuration: 0.05)
let sequence = SKAction.sequence([wait, action])
let loop = SKAction.repeatForever(sequence)
addChild(cropNode)
cropNode.position = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)
sprite.run(loop, withKey: "loop")
}
}
private func getWidth(percents:Double, spriteWidth:Double)->Double{
let onePercent = spriteWidth / 100.0
return onePercent * percents
}
private func createImage(frame barFrame:CGRect) -> CGImage?{
if let ciFilter = CIFilter(name: "CILinearGradient"){
let ciContext = CIContext()
ciFilter.setDefaults()
let startColor = CIColor(red: 0.75, green: 0.35, blue: 0.45, alpha: 1)
let endColor = CIColor(red: 0.45, green: 0.35, blue: 0.75, alpha: 1)
let startVector = CIVector(x: 0, y: 0)
let endVector = CIVector(x: barFrame.width, y: 0)
ciFilter.setValue(startColor, forKey: "inputColor0")
ciFilter.setValue(endColor, forKey: "inputColor1")
ciFilter.setValue(startVector, forKey: "inputPoint0")
ciFilter.setValue(endVector, forKey: "inputPoint1")
if let outputImage = ciFilter.outputImage {
let cgImage = ciContext.createCGImage(outputImage, from: CGRect(x: 0, y: 0, width: barFrame.width, height: barFrame.height))
return cgImage
}
}
return nil
}
}
Now cause this is just an example I won't go all the way to implement this right, but You can maybe make a class of it with designable and inspectable properties, optimize code, make it reusable etc. But the general idea is shown here.
You use SKCropNode to add progress bar in it, and use maskNode property to reveal progress bar as percentage increases. Also I gave a method to create texture programatically, but You can use just a .png file instead.
Crop node is here used only cause of a gradient (cause we don't wan't to scale image, but rather to show it part by part). Obviously, crop node is not needed if a progress bar had only one color.
Here is final result:

SKCropNode fails when I add extra SKNode children in hierarchy

Update: It looks like iOS 10 has fixed this issue. I upgraded to Swift 3 and Xcode 8 and everything is working as expected.
I've run into this issue a couple times now and I can't tell if it's a bug in SKCropNode or if I'm just misusing it. Perhaps there's some bit of documentation I'm missing to explain why this is happening?
I have a crop node with a 100x100 rectangle shape as the mask. If I place a blue circle inside it, it gets cropped properly.
// Create a crope node with a small square.
let cropNode = SKCropNode()
let cropNodeMask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
cropNodeMask.fillColor = UIColor.whiteColor()
cropNode.maskNode = cropNodeMask
self.addChild(cropNode)
// Create a blue circle and put it in the crop node.
let blueCircle = SKShapeNode(circleOfRadius: 110)
blueCircle.fillColor = UIColor.blueColor()
blueCircle.strokeColor = UIColor.clearColor()
cropNode.addChild(blueCircle)
Now, when I place that same circle inside of an otherwise empty SKNode and place that container inside the same crop node, cropping fails.
// Create a crope node with a small square.
let cropNode = SKCropNode()
let cropNodeMask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
cropNodeMask.fillColor = UIColor.whiteColor()
cropNode.maskNode = cropNodeMask
self.addChild(cropNode)
// Create a container to hold the circle.
let container = SKNode()
cropNode.addChild(container)
// Create a blue circle and put it in the container.
let blueCircle = SKShapeNode(circleOfRadius: 110)
blueCircle.fillColor = UIColor.blueColor()
blueCircle.strokeColor = UIColor.clearColor()
container.addChild(blueCircle)
But a sprite in that same container seems to be cropped fine.
// Create a crope node with a small square.
let cropNode = SKCropNode()
let cropNodeMask = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
cropNodeMask.fillColor = UIColor.whiteColor()
cropNode.maskNode = cropNodeMask
self.addChild(cropNode)
// Create a container to hold the sprite.
let container = SKNode()
cropNode.addChild(container)
// Create a spaceship and add it to the container.
let spaceshipNode = SKSpriteNode(imageNamed: "Spaceship")
spaceshipNode.anchorPoint = CGPointZero
container.addChild(spaceshipNode)
SKShapeNode is bugged, best to avoid it at all costs. Use it to create your shapes, then convert it to a texture for use with SKSpriteNode

Don't draw node outside of parent node

I'm making a SpriteKit game on iOS using Swift (although an Objective-C answer would still help), and I have an SKSpriteNode whose parent is another SKSpriteNode. However, I only want to sub-node to be drawn when it is within the frame of the parent node. So for example, I have:
let scene = GameScene(size: CGSize(width: 10, height: 10))
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let skView = self.view as! SKView
skView.presentScene(scene)
let superNode = SKSpriteNode(color: UIColor.blueColor(), size: CGSize(width: 1, height: 1))
scene.addChild(superNode)
let subNode = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 0.5, height: 0.5))
superNode.addChild(subNode)
It looks as follows:
However, if I move subNode to (1.5, 1.5):
subNode.position = CGPoint(1.5, 1.5)
Then subNode is visually outside of superNode, but it is still drawn:
However, I would like it to look as follows:
As a further example, if I set subNode's position to (1.0, 1.0), it looks as follows:
But I would like it to look like this:
How do I get it to only be drawn when it is within superNode? Any help would be much appreciated. Thanks:)

Adding an SKLabelNode to Scene Kit View

I am building a game using Scene Kit. In order to present the score I wanted to use an SKLabelNode on the screen, however, when I attach it to a SCNNode, it looks very blurry:
Here is the code that I have written to do this, please let me know if there is a better way to go about doing this without having the text be so blurry. Thank you so much!
func initHUD() {
let skScene = SKScene(size: CGSize(width: 100, height: 100))
skScene.backgroundColor = UIColor(white: 0.0, alpha: 0.0)
labelNode = SKLabelNode()
labelNode.fontSize = 20
labelNode.position.y = 50
labelNode.position.x = 50
skScene.addChild(labelNode)
let plane = SCNPlane(width: 1, height: 1)
let material = SCNMaterial()
material.lightingModelName = SCNLightingModelConstant
material.doubleSided = true
material.diffuse.contents = skScene
plane.materials = [material]
hudNode = SCNNode(geometry: plane)
hudNode.name = "HUD"
hudNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: 3.14159265)
hudNode.position = SCNVector3(x:0, y: 1, z: -5)
}
func updateHUD() {
labelNode.text = "\(score)"
}
The typical way to do a HUD for a SceneKit scene is to create a SpriteKit SKScene and set it as the overlaySKScene of your SceneKit view. Then it renders at full resolution and always at the same view-relative size and position.

Sprite-Kit and Swift Update Position to Another Sprite

Like many here asking questions I'm new to programing, but I'm going to do my best to describe my dilemma. Thanks for any help in advance.
I've included a picture of my goal.
The Green line is a node that rotates around the center of the screen. I have its anchor point set to 1,1 and position x,y to the center of the device. Using SKActions I've made the node rotate so many degrees causing it to move like the second hand of a clock would around its center post. All the code to this point I've got down. Here is where I've reached a mental brick wall. I want to have a sprite constantly placed at the tip of the rotating node so that the sprite will move around the center in a circular motion. The sprite is represented by the Pink circle.
This was my plan of attack, but my lack of knowledge of the swift language prevents me from executing it.
Is there a way to add a reference point to the tip of the node? If so I was thinking of doing that, then in the update function set the position of the sprite to the x,y of that reference point. I hope I've included enough information. If anyone has a better approach to this, please let me hear it. Thanks so much in advance.
I am posting an answer, even though it was answered in the comments. If Leo posts the answer, I'll delete this one.
I am assuming that your ball is a child of the line, so you'd just offset the x position of your ball the width of the line. Which as noted in comments is the radius of the circle path your ball is following.
Your secondary issue could be solved by using another SKAction to offset the zRotation of the line. For example :
let lineRotateAction = SKAction.rotateByAngle(CGFloat(360).degreesToRadians, duration: 6)
let ballRotateAction = SKAction.rotateByAngle(CGFloat(-360).degreesToRadians, duration: 6)
You can simply put create the hands as children of an empty SKNode, position both hands correctly in relation to these SKNodes and then rotate the SKNodes themselves.
Two different ways: First using a parent node (which is potentially what you want):
let body = SKSpriteNode(color: .clear, size: CGSize(width: 20, height: 150))
body.anchorPoint = CGPoint(x: 0.5, y: 0)
body.position = CGPoint(x: frame.midX, y: frame.midY)
body.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI * 2), duration: 10)))
let hand = SKSpriteNode(color: .blue, size: CGSize(width: body.size.width/2, height: body.size.height))
hand.position = CGPoint(x: 0, y: body.size.height/2)
let ball = SKSpriteNode(color: .green, size: CGSize(width: body.size.width, height: body.size.width))
ball.position = CGPoint(x: 0, y: body.size.height - ball.size.height/2)
body.addChild(hand)
body.addChild(ball)
addChild(body)
Second is using physics, which produces a similar effect only on the surface but it might give you other ideas too:
let hand = SKSpriteNode(color: .blue, size: CGSize(width: 10, height: 150))
hand.anchorPoint = CGPoint(x: 0.5, y: 1)
hand.position = CGPoint(x: frame.midX, y: frame.midY)
hand.physicsBody = SKPhysicsBody(rectangleOf: hand.size)
hand.physicsBody?.affectedByGravity = false
hand.physicsBody?.isDynamic = false
hand.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI * 2), duration: 10)))
addChild(hand)
let ball = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
ball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
ball.position = CGPoint(x: frame.midX, y: frame.midY - hand.size.height)
ball.physicsBody = SKPhysicsBody(rectangleOf: ball.size)
addChild(ball)
let joint = SKPhysicsJointPin.joint(withBodyA: hand.physicsBody!,
bodyB: ball.physicsBody!,
anchor: ball.position)
physicsWorld.add(joint)
Have fun!

Resources