Don't draw node outside of parent node - ios

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:)

Related

SKSpriteNode.centerRect does not work with SKTextureAtlas

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.

Aligning SCNText with SCNBox in iOS SceneKit with boundingBox?

REVISED PROBLEM:
I don't understand why the white node is centered when it's a box or sphere but not when it's text. You can comment/uncomment the whiteGeometry variable to see how each different geometry is displayed. I was originally thinking that I had to manually center the text by determining the width of the box and the text and calculating the position of the text. Do I need to do that? Why is the text behaving differently?
import SceneKit
import PlaygroundSupport
let scene = SCNScene()
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
sceneView.scene = scene
sceneView.backgroundColor = .darkGray
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
PlaygroundPage.current.liveView = sceneView
// Camera
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 25)
sceneView.pointOfView = cameraNode
let blackGeometry = SCNBox(width: 10.0, height: 10.0, length: 10.0, chamferRadius: 0)
blackGeometry.firstMaterial?.diffuse.contents = UIColor.black
print("blackGeometry min=\(blackGeometry.boundingBox.min)")
print("blackGeometry max=\(blackGeometry.boundingBox.max)")
let blackNode = SCNNode(geometry: blackGeometry)
blackNode.position = SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(blackNode)
// let whiteGeometry = SCNBox(width: 3.0, height: 3.0, length: 3.0, chamferRadius: 0)
let whiteGeometry = SCNText(string: "L", extrusionDepth: 0)
whiteGeometry.alignmentMode = kCAAlignmentLeft
whiteGeometry.font = UIFont.systemFont(ofSize: 8.0)
// let whiteGeometry = SCNSphere(radius: 3.0)
whiteGeometry.firstMaterial?.diffuse.contents = UIColor.white
print("whiteGeometry min=\(whiteGeometry.boundingBox.min)")
print("whiteGeometry max=\(whiteGeometry.boundingBox.max)")
let whiteNode = SCNNode(geometry: whiteGeometry)
let boxWidth = blackGeometry.boundingBox.max.x - blackGeometry.boundingBox.min.x
let boxHeight = blackGeometry.boundingBox.max.y - blackGeometry.boundingBox.min.y
print("boxWidth=\(boxWidth)")
print("boxHeight=\(boxHeight)")
let txtWidth = whiteGeometry.boundingBox.max.x - whiteGeometry.boundingBox.min.x
let txtHeight = whiteGeometry.boundingBox.max.y - whiteGeometry.boundingBox.min.y
print("txtWidth=\(txtWidth)")
print("txtHeight=\(txtHeight)")
whiteNode.position = SCNVector3(x: -5.0, y: -5.0, z: 10)
scene.rootNode.addChildNode(whiteNode)
//blackNode.addChildNode(whiteNode)
print("done")
ORIGINAL PROBLEM (OLD):
Let's say I have two SCNBox nodes (I'm simplifying this to make it clear BUT the solution must work for other geometries). A large black box and a small white box. I want to center the white box in front of the black box.
To do this, I need to determine the width and height of the two nodes. Remember that the node could be something other than a box like a sphere or text. From what I can tell, the only way to determine width/height is via the boundingBox property on the geometry. It has a min and max value that is NOT clearly and fully described in Apple's reference manual. To get the height, it seems like I would calculate it based on boundingBox.max.y and boundingBox.min.y. So looking at the example below of a 10x10x10 box, I can't see how I can get 10.0 as the height because max.y 5.20507812
e.g.
let box = SCNBox(width: 10.0, height: 10.0, length: 10.0, chamferRadius: 0)
print("box min=\(box.boundingBox.min)")
print("box max=\(box.boundingBox.max)")
yields:
box min=SCNVector3(x: -5.0, y: -5.0, z: -5.0)
box max=SCNVector3(x: 5.0, y: 5.20507812, z: 5.0)
Why is max.y=5.20507812? How should I determine the height/width?
See also: SCNBoundingVolume
I think I understand now.
It looks like you're trying to put some text in front of an object, with the text centered on the object relative to the camera. And this question isn't about bounding boxes per se. Right?
You see different behavior with the white geometry being a sphere or SCNText because SCNText's uses a different origin convention than the other concrete SCNGeometry classes. Other geometries put the origin at the center. SCNText puts it at the lower left of the text. If I remember correctly, "lower" means the bottom of the font (lowest descender), not the baseline.
So you'll need to compute the text's bounding box, and account for that when you position the text node, to get it centered. You don't need to do that for the other geometry types. If the covered object or the camera is moving, you'll need to compute the ray from the camera to the covered object in the render loop, and update the text's position.
Swift Scenekit - Centering SCNText - the getBoundingBoxMin:Max issue is relevant.
If you just want overlay text, you might find it easier to put the text in an SKScene overlay. An example of that is at https://github.com/halmueller/ImmersiveInterfaces/tree/master/Tracking%20Overlay (warning, bit rot may have set in, I haven't tried that code in a while).

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!

Programmatically create constrained region of physics, SpriteKit

I would like two regions, as shown in the image below, where the yellow region is to contain sprites. For example I'd like to have balls in the yellow region bouncing and reflecting off the boundaries of the yellow region. How can I programmatically do this without using an sks file?
You create an edge based physics body using the +bodyWithEdgeLoopFromRect:
override func didMoveToView(view: SKView) {
//Setup scene's physics body (setup the walls)
physicsBody = SKPhysicsBody(edgeLoopFromRect: frame)
let yellowSprite = SKSpriteNode(color: .yellowColor(), size: CGSize(width: 300, height: 300))
yellowSprite.position = CGPoint(x: frame.midX, y: frame.midY)
//Create the rectangle which will represent physics body.
let rect = CGRect(origin: CGPoint(x: -yellowSprite.size.width/2, y: -yellowSprite.size.height/2), size: yellowSprite.size)
yellowSprite.physicsBody = SKPhysicsBody(edgeLoopFromRect: rect)
addChild(yellowSprite)
//Add Red ball "inside" the yellow sprite
let red = SKShapeNode(circleOfRadius: 20)
red.fillColor = .redColor()
red.strokeColor = .clearColor()
red.position = yellowSprite.position
red.physicsBody = SKPhysicsBody(circleOfRadius: 20)
red.physicsBody?.restitution = 1
red.physicsBody?.friction = 0
red.physicsBody?.affectedByGravity = false
addChild(red)
red.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 15))
}
About rect parameter:
The rectangle that defines the edges. The rectangle is specified
relative to the owning node’s origin.
Hope this helps!

Is it possible to position physicsbody to only one part of the Sprite?

Is it possible to position a physics body on a sprite? I only want a certain part of my sprite node to have collision detection, not the whole image.
Heres my physics body
physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: CGFloat(54.0), height: CGFloat(100.0)))
But i want to position the physics body at the top of the node, where it usually gets placed in the middle of the node.
You can try creating a smaller SKSpriteNode of the same size as the SKPhysicsBody and adding the larger SKSpriteNode as a child to the smaller one. Changing the position of the larger one as you want. For example
override func didMoveToView(view: SKView) {
let smallerSprite = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(30, 30))
smallerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: smallerSprite.size)
smallerSprite.position = CGPointMake(100, 400)
self.addChild(smallerSprite)
let largerSprite = SKSpriteNode(color: UIColor(white: 0.5, alpha: 0.5), size: CGSizeMake(100, 100))
largerSprite.position = CGPointMake(-10, -10)
smallerSprite.addChild(largerSprite)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
As an addition to rakesh's answer...A different approach to get the same result would be to use
+ bodyWithRectangleOfSize:center: method. Like this:
override func didMoveToView(view: SKView) {
let sprite = SKSpriteNode(color: SKColor.whiteColor(), size: CGSize(width: 100.0, height: 100.0))
//I assume that you have initialized view and scene properly. If so, this will position a sprite in the middle of the screen.
sprite.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
var physicsBodySize:CGSize = CGSize(width: sprite.size.width, height: 30.0) //Create a size here. You can play with height parameter.
sprite.physicsBody =
SKPhysicsBody(rectangleOfSize: physicsBodySize, center: CGPoint(x: 0.0, y: sprite.size.height / 2.0 - physicsBodySize.height / 2.0))
//Not needed, just set to restrict that sprite can't off screen
sprite.physicsBody?.affectedByGravity = false
self .addChild(sprite)
}
The result:
If you try to change the height of physics body to 10.0, you will get something like this:

Resources