What is the difference between these two statements in SpriteKit? - swift

this part is a SKShapeNode
var rect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 30, height: 30))
self.path = CGPathCreateWithRect(rect, nil)
AND
let rect = SKShapeNode(rectOfSize: CGSize(width: 30, height: 30))
They seems to be acting differently in the simulator and I'm wondering if these are different in any way.

The origin, (0, 0) point, of a SKShapeNode is the center of the node and its path is drawn relative to that point. Therefore, a shape node created with the following CGRect
CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 30, height: 30))
will be drawn with its bottom-left corner in the center of the shape node.
The convenience method SKShapeNode(rectOfSize...) centers the rect in the shape node by offsetting the origin argument of the CGRect by half of the rectangle's width and height. In this case, the rect's origin is (-15, -15).
You can create a CGRect that is centered in an SKShapeNode by offsetting its origin:
let height = CGFloat(30)
let width = CGFloat(30)
let anchorPoint = CGPoint(x:0.5, y:0.5)
CGRect(origin: CGPoint(x: -width*anchorPoint.x, y: -height*anchorPoint.y), size: CGSize(width: width, height: height))
where anchorPoint defines the relative "point in the sprite that corresponds to the node’s position."
You can also use SKShapeNode(path:path, centered:true) which will center the path using its bounding box.

Related

Why will the opacity not change on this image I turned into a sprite node?

I'm making a space runner star ship game that shoots meteors. You have 4 lives and I want this image to hover over my ship and fade in opacity the fewer lives you have. I've tried the .alpha property but the image stays the same. Mask is the name of the sprite node.
func setupGame(_ size: CGSize) {
let ship = SKSpriteNode(imageNamed: "Spaceship.png")
ship.position = CGPoint (x: size.width/2.0, y: size.height/2.0)
ship.size = CGSize(width: 40.0, height: 40.0)
ship.name = SpaceshipNodeName
addChild(ship)
let mask = SKSpriteNode(imageNamed: shield)
mask.position = CGPoint (x: ship.size.width/6, y: ship.size.height/6)
mask.size = CGSize(width: 40.0, height: 40.0)
mask.alpha = 0.5
ship.addChild(mask)

Central align subView

I'm trying to centre a UIView in another view (so like a pop up view), but whatever I do, I just cannot align it centrally!
func loadPopUpView() {
let customView = CGRect(x: view.center.x, y: view.center.y, width: 100, height: 100)
popUpView = UIView(frame: customView)
popUpView.backgroundColor = UIColor.black
view.addSubview(popUpView)
popUpView.isHidden = false
}
I've changed the background colour to black just so I know when it appears.
I cannot do this with storyboard because it's going on a tableView, so I'm trying to do it programmatically. Result Image
You also need to minus half value of your custom view height and width from x and y like below:
let customView = CGRect(x: view.center.x - 50, y: view.center.y - 50, width: 100, height: 100)
You are telling it to put top left corner in the center, hence you need to let it get half size in position.
let customView = CGRect(x: view.center.x-50, y: view.center.y-50, width: 100, height: 100)
Another solution is the use anchorpoints.
customView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
customView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
customView.heightAnchor.constraint(equalToConstant: 100).isActive = true
customView.widthAnchor.constraint(equalToConstant: 100).isActive = true
On CGRect(x:, y:, width:, height:), the point (x,y) is the origin. In iOS, that's the top left point.
On the CGRect doc:
In the default Core Graphics coordinate space, the origin is located
in the lower-left corner of the rectangle and the rectangle extends
towards the upper-right corner. If the context has a
flipped-coordinate space—often the case on iOS—the origin is in the
upper-left corner and the rectangle extends towards the lower-right
corner.
So to fix this:
let width = 100.0
let height = 100.0
let customViewFrame = CGRect(x: view.center.x - width/2.0, y: view.center.y - height/2.0, width: width, height: height)
Another solution would be to apply the center once the frame (especially the width/size) have been set.
let customViewFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
customViewFrame = view.center

Centering a UIBezierPath

I have a UIBezierPath thats a rounded square. It somewhat looks like this:
Here's my code for the shape:
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x:(0), y: (0), width: (250), height: (400)), cornerRadius: 64).cgPath
shape.position = CGPoint(x: 0, y: 0)
print(shape.position)
shape.fillColor = UIColor.white
shape.strokeColor = UIColor.white
shape.lineWidth = 5
addChild(shape)
I want to center it in the middle of the screen, but using
shape.position = CGPoint(x: self.frame.width, y: self.frame.height)
doesn't work. Thanks!
I suggest that you center the UIBezierPath within the shape node. For example,
let width:CGFloat = 250
let height:CGFloat = 400
shape.path = UIBezierPath(roundedRect: CGRect(x:-width/2, y: -height/2, width: width, height: height), cornerRadius: 64).cgPath
You can center the shape in the scene by setting its position to (0, 0).

Rounded rectangle with SKShapeNode

I have the following code that rounds an already existing rectangle from the scene builder...
btOk.path = UIBezierPath(roundedRect: CGRect(x: -62.5,y:-25,width: 125, height: 50), cornerRadius: 10).cgPath
However, if i try to use the same init to round the corners of another rectangle that is much larger, it does not even come close to working. It just makes the width HUUUUGE (imagine Trump).
scene.enumerateChildNodes(withName: "//*"){
node,stop in
if let name = node.name{
if name.contains("round"){
if let shapeNode = node as? SKShapeNode{
print(shapeNode.frame.width) //500
shapeNode.path = UIBezierPath(roundedRect: CGRect(x: -250,y:-100,width: 500, height: 200), cornerRadius: 50).cgPath
print(shapeNode.frame.width) //5735
}
}
}
}
btOk is a SKShapeNode as well. What am i missing between the two that is so different? One thing to note is i am enumerating through the children of the scene like this because this scene is in a SKReferenceNode. Perhaps that has something to do with it?
EDIT
Taking direction from #Ron Myschuk, i've solved this and since it's such a PITA, i also created an extension. So now i can round the corners of any SKShapeNode very easily when needed. Even if it was created in the scene editor. Note, this should only be used if there are no children of the shape node. Otherwise those children will be removed also.
extension SKShapeNode {
func roundCorners(topLeft:Bool,topRight:Bool,bottomLeft:Bool,bottomRight:Bool,radius: CGFloat,parent: SKNode){
let newNode = SKShapeNode(rect: self.frame)
newNode.fillColor = self.fillColor
newNode.lineWidth = self.lineWidth
newNode.position = self.position
newNode.name = self.name
self.removeFromParent()
parent.addChild(newNode)
var corners = UIRectCorner()
if topLeft { corners = corners.union(.bottomLeft) }
if topRight { corners = corners.union(.bottomRight) }
if bottomLeft { corners = corners.union(.topLeft) }
if bottomRight { corners = corners.union(.topRight) }
newNode.path = UIBezierPath(roundedRect: CGRect(x: -(newNode.frame.width / 2),y:-(newNode.frame.height / 2),width: newNode.frame.width, height: newNode.frame.height),byRoundingCorners: corners, cornerRadii: CGSize(width:radius,height:radius)).cgPath
}
}
And use it like so...
aShapeNode.roundCorners(topLeft: true, topRight: true, bottomLeft: false, bottomRight: false, radius: 5,parent: popup)
Not what you're going to want to hear but it's because you cannot set the width of an SKShapeNode in the Scene editor (To my knowledge). In order to get that ShapeNode to have a width of 500 you would have had to adjust the xScale. The xScale then reapplies itself to the path when you adjust it (kind of growing exponentially). If you create the SKShapeNode in code there is no problem adjust the rounded corners
let round = SKShapeNode(rect: CGRect(x: 0, y: -150, width: 500, height: 200))
round.fillColor = .red
addChild(round)
print(round.frame.width)
round.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(round.frame.width)
Edit
If you have your heart set on using the Scene editor you can place your ShapeNode and stretch it to where you want it then you could just do a small conversion in code to get the results that you want
if let round = self.childNode(withName: "biground") as? SKShapeNode {
let biground = SKShapeNode(rect: round.frame)
biground.fillColor = round.fillColor
biground.position = round.position
addChild(biground)
round.removeFromParent()
print(biground.frame.width)
biground.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(biground.frame.width)
}
this just recreates the shape in code based on what you outlined in the Scene editor and rounds the edges perfectly
edit 2
I've always been under the impression that SKShapeNodes are really inefficient (i'm pretty sure they leaked memory as well). So i always setup my round rectangles as so.
let outsideTexture = SKTexture(imageNamed: "round_square_white")
let outside = SKSpriteNode(texture: outsideTexture)
let insetX: CGFloat = 20
let insetY: CGFloat = 20
let cellSize = CGSize(width: 500.0, height: 500.0)
outside.centerRect = CGRect(x: CGFloat(insetX / outside.size.width), y: CGFloat(insetY / outside.size.height), width: CGFloat((outside.size.width - insetX * 2) / outside.size.width), height: CGFloat((outside.size.height - insetY * 2) / outside.size.height))
//outside.position = CGPointMake(gameModel.gameWidth / 2, (outside.size.height) / 2);
outside.xScale = cellSize.width / outside.size.width
outside.yScale = cellSize.height / outside.size.height
outside.zPosition = 10
outside.position = CGPoint(x: CGFloat(0), y: CGFloat(-0.35 * self.size.height / 2))
self.addChild(outside)
worth noting that this lays out a rounded square/rectangle perfectly however similar to the scale issues from the scene editor you have to place an empty cell over this to add children to, otherwise they scale to the rounded square.

Add SKShapeNode in another one

I want to draw a square in another square; but I can't position the second one in the right way.
What can I do for positioning based on first square coordinates?
var tileBackground = SKShapeNode(rectOfSize: CGSize(width: screenWidth, height: screenWidth))
tileBackground.name = "tileBackground"
tileBackground.fillColor = SKColor.whiteColor()
tileBackground.position = CGPoint(x: frame.midX, y: frame.midY);
self.addChild(tileBackground)
var tile = SKShapeNode(rectOfSize: CGSize(width: tileSize, height: tileSize))
tile.name = "tile1"
tile.fillColor = SKColor.redColor()
tile.position = CGPointMake(100,100)
tileBackground.addChild(tile)
You should use SKSpriteNode instead. That way you can add it easily and without setting the position manually. Because in an SKSpriteNode, the center is always the center of the node:
var tileBackground = SKSpriteNode(color: UIColor.whiteColor(), size: CGSizeMake(width: screenWidth, height: screenWidth))
tileBackground.name = "tileBackground"
tileBackground.position = CGPoint(x: frame.midX, y: frame.midY);
self.addChild(tileBackground)
var tile = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: tileSize, height: tileSize))
tile.name = "tile1"
tileBackground.addChild(tile)
A node's position is relative to its parent position.
If you want the tile node to be in the center of its parent then set its position as:
tile.position = CGPoint(x: tileBackground.size.width / 2.0, y: tileBackground.size.height / 2.0)
which will place the tile in center of the tileBackground node.
also note that you do not need to update the tile position to "follow" the tileBackground node. Updating the tileBackground position will automatically keep the tile in its center