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.
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)
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:
lineWidth property and it's default value of 1.0. It should be 0.0f
verticalAlignmentMode property and it's default baseline alignment. It should be SKLabelVerticalAlignmentModeCenter.
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
In Swift, When Using "[Weak Self] In", Should I Double Up on It When Nested Inside Another Closure
Get a Unique String for a Given Anyobject
Stopping an Running Skaction - Sprite Kit
Swiftui @Environmentobject Error: May Be Missing as an Ancestor of This View
How to Make a Local Module with the Swift Package Manager
How to Rearrange Views in Swiftui Zstack by Dragging
How to Convert Nsset to [String] Array
Can't Able to Get Video Tracks from Avurlasset for Hls Videos(.M3U8 Format) for Avplayer
Convincing Swift That a Function Will Never Return, Due to a Thrown Exception
Swift Optional Escaping Closure
Add Links to Swift Classes in the Quick Help Documentation Comments
Can You Use String/Character Literals Within Swift String Interpolation