How to Position Child Skspritenodes Inside Their Parents

How to position child SKSpriteNodes inside their parents

The default anchor point in SpriteKit is (0.5, 0.5), which is the center of a node/sprite. (0, 0) is bottom left corner and (1, 1) is top right corner. Look at this picture.

Sample Image

After choosing a node's anchor point, you can treat it as the origin of a coordinate system whose range is the node's frame size. The position offset is calculated/affected by both the child's anchor point and its parent's anchor point.

For example, a (red) parent node rect is (100, 100) in size and a (green) child node child is (50, 50) in size. Set the child position at (-25, 25) in parent's coordinate system will have the following result.

// parent node
let parent = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(100.0, 100.0))
parent.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(parent)

// child node
let child = SKSpriteNode(color: UIColor.greenColor(), size: CGSizeMake(50.0, 50.0))
child.zPosition = 1
child.position = CGPointMake(-parent.size.width/4, parent.size.height/4) // (-25, 25))
parent.addChild(child)

Sample Image

More experiments of SpriteKit position and anchor point can be found here.

Getting child SKSpriteNode Position

You can use the intersectsNode method on SKScene to check whether the child nodes are visible; intersectsNode will return true whilst the children are visible.

for child in platformGroupNode.children {
if let sprite = child as? SKSpriteNode where !intersectsNode(sprite) {
child.removeFromParent()

// Generate new platform...
}
}

An alternative to removing and then creating a new platform would be to reposition the platform that just moved offscreen, so it will move back onscreen. For example: if scrolling from right to left, when a platform moves off the left edge it will be repositioned on the right. This has the advantage of only needing to create a few platforms at the beginning.


For this to work correctly it's essential that your SKScene's size is equal to its SKView size. For information on this have a look at SpriteKit Coordinates differ between iPad and iPhone

iOS Swift SpriteKit: How to make a child spritenode's position and movements to be the same as its parent spritenode?

Not sure why you are doing all this complicated stuff, but you are not suppose to touch the position. Leave it at 0,0, and never add it to the scene. Instead leave it on the sprite, and it will always follow your sprite.

Of course, you can always use colorBlendFactor to blend the sprite color with your texture to create the highlight

Your class should look like this:

class Host : SKSpriteNode {

var highlight : SKSpriteNode!

func addHighlight(withColour strColour : String) {
highlight.removeFromParent()
highlight = SKSpriteNode(imageNamed: strColour)
//highlight.anchorPoint = CGPoint(x: 0.5, y: 0.5)
highlight.size = CGSize(width: 0.2, height: 0.2)
//highlight.alpha = 1
highlight.zPosition = 3

highlight.moveToParent(self)
}

}

There is no need to add extra inits that do exactly the same thing as other inits

Your touch code should look like this:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

for t in touches {

let cgPointTouched = t.location(in: self)
let skNodeTouched : SKNode = self.atPoint(cgPointTouched)
switch skNodeTouched.name {
case "Highlight":
host.addHighlight(withColour:"whatevermycoloris")
}
}
}

Of course, you can always avoid the extra node by just doing:

class Host : SKSpriteNode {

func addHighlight(withColour : UIColor) {
color = withColour
colorBlendFactor = 0.75 //Change this intensity to 1 to add more color
}

}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

for t in touches {

let cgPointTouched = t.location(in: self)
let skNodeTouched : SKNode = self.atPoint(cgPointTouched)
switch skNodeTouched.name {
case "Highlight":
host.addHighlight(withColour:UIColor.yellow)
}
}
}

How to get a child sprite position in the view

You're nearly there, what you need to use is:

let positionInScene = self.convertPoint(child.position, fromNode: platformGroupNode)
// self in this case is your SKScene, you don't need self here but, IMO, it
// makes it easier to understand what's converting what.

Or an equivalent would be:

let positionInScene = platformGroupNode.convertPoint(child.position, toNode: self)

Child SkShapeNode positioning confusion

There are few issues with your code:

  1. lineWidth property and it's default value of 1.0. It should be 0.0f

  2. verticalAlignmentMode property and it's default baseline alignment. It should be SKLabelVerticalAlignmentModeCenter.

  3. Wrong positioning of a shape node. It should be (0,0)

To fix it, change label's vertical alignment:

replayButton.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;

set shapenode's lineWidth property to 0.0f:

bgNode.lineWidth = 0.0f;

and remove this line:

//bgNode.position should be CGPointZero which is (0,0)
bgNode.position = replayButton.position;

Still, I would stay away of this approach. SKShapeNode is not needed in this situation. You can do the same with SKSpriteNode. What is important is that both SKShapeNode and SKLabelNode can't be drawn in batches, which means, can't be drawn in a single draw pass when rendered like SKSpriteNode. Take a look at this. Your example is too simple to make performance issues, but in general you should keep all this in mind.

If your button's text never change during the game, you should consider using SKSpriteNode initialized with texture. If interested in a pre made buttons for SpriteKit, take a look at SKAButton.

Hope this helps!

zPosition of SKNode relative to its parent?

When reading the Apple Documentation regarding this, you can find:

Maintaining the order of a node’s children can be a lot of work. Instead, you can give each node an explicit height in the scene. You do this by setting a node’s zPosition property. The z position is the node’s height relative to its parent node, much as a node’s position property represents its x and y position relative to parent’s position. So you use the z position to place a node above or below the parent’s position.

When you take z positions into account, here is how the node tree is
rendered:

  • Each node’s global z position is calculated.
  • Nodes are drawn in order from smallest z value to largest z value.
  • If two nodes share the same z value, ancestors are rendered first, and siblings are rendered in child order.

The point I found - which i didn't know it was like this - is that the children zPosition is calculated summating their parents zPosition, so if node1.zPosition + (node1's)child3.zPosition = 4, and node2.zPosition + (node2's)child3.zPosition = 3, the node1's child will be painted above.

Solution: Set node2.zPosition bigger than (node1's)lastChild.zPosition + node1.zPosition.

SKSpriteNode gets hidden below parent node

What you're looking for is the ignoresSiblingOrder property on SKView:

A Boolean value that indicates whether parent-child and sibling
relationships affect the rendering order of nodes in the scene.

The default Xcode 6.x (and maybe 5, I haven't checked) SpriteKit template sets that to true when setting up the SKView in the view controller. With it set to true, the order that you add nodes to the scene does not affect their z-position. If you set it to false, they'll be layered in the order you add them to the scene (or parent node).

That being said, SpritKit can do some optimizations when ignoresSiblingOrder is true (which is why that's the default in the template), so it's probably best to keep it that way if at all possible. In that case, you'll have to manually set the zPosition property of each node though.

Adding SKShapeNode as Child to SKSpriteNode

So what it looks like from your code is that you are trying to create a shapeNode and then position it inside your SKSpriteNode. Most of it you got right, you just did the positioning code incorrectly.

When you add a child to a node, that child uses the parent node as its origin point for reference. So if you tell the child node to be -50 X and 150 Y, that child node will be -50 X and 150 Y from the center point of its parent node.

When I do child nodes (especially ones that have a ton of similarities to the parent node) I like to reference the parent node for positioning, width, and height. I don't know if it might be bad practice, but its what I've done for two years now.

This is how I would have done it:

var sky = SKSpriteNode()
var ground = SKShapeNode()

func createSky() {
sky = SKSpriteNode(color: SKColor.darkGray, size: CGSize(width: self.frame.size.wdith, height: self.frame.size.height))
// self.frame.size.width/self.frame.size.height calls its info from the ViewController, which is tied directly to your screen size (unless otherwise changed)
sky.position = CGPoint(x: 0,y: 0)
self.addChild(sky)
}

func createGround() {
ground = SKShapeNode((rectOf: CGSize(width: sky.width, height: sky.height * 0.25))
ground.position = CGPoint(x: 0, y: sky.position.y - (ground.size.height / 1.5))
}


Related Topics



Leave a reply



Submit