Making Sure Nodes are visible - ios

So I have this sprite kit game, which is coded in swift 2. The game includes these colored circles (Green, Red, Purple, Yellow, Blue) that fall down the screen, starting from the same height, but starting at different widths. When the circles hit the bottom of the screen, the respectable method is called. The problem I am having is, the random x position can sometimes cut half of the circle off because it is on the very side of the screen. How can I prevent the circles from clipping the side of the screen? Here are the methods that are called when the circles hit the bottom of the screen.
func changeGreen(){
Green.position.y = frame.size.height * 0.9
let PositionX = arc4random_uniform(UInt32(self.frame.width))
Green.position.x = CGFloat(PositionX)
}
func changeRed(){
Red.position.y = frame.size.height * 0.9
let PositionX = arc4random_uniform(UInt32(self.frame.width))
Red.position.x = CGFloat(PositionX)
}
func changeBlue() {
Blue.position.y = frame.size.height * 0.9
let PositionX = arc4random_uniform(UInt32(self.frame.width))
Blue.position.x = CGFloat(PositionX)
}
func changeYellow() {
Yellow.position.y = frame.size.height * 0.9
let PositionX = arc4random_uniform(UInt32(self.frame.width))
Yellow.position.x = CGFloat(PositionX)
}
func changePurple() {
Purple.position.y = frame.size.height * 0.9
let PositionX = arc4random_uniform(UInt32(self.frame.width))
Purple.position.x = CGFloat(PositionX)
}

Assuming position.x is the center of the circle, I think something like this might work:
let maxX = 350 // this is your frame width
let radius = 50 // radius of the circle
var positionX = Int(arc4random_uniform(UInt32(maxX - radius))) // max position of x is taken care of here
let pointsFromLeft = positionX - radius
if pointsFromLeft < 0 {
positionX -= pointsFromLeft // move to the right if necessary
}

You have to offset the radius of the circle from both ends of frame so that the circles never get clipped.
let radius = 20 // radius of your circle
let positionX = radius + arc4random_uniform(UInt32(self.frame.width - 2 * radius))

Related

Spritekit Camera Node scale but pin the bottom of the scene

I have a camera node that is scaled at 1. When I run the game, I want it to scale it down (i.e. zoom out) but keep the "floor" at the bottom. How would I go about pinning the camera node to the bottom of the scene and effectively zooming "up" (difficult to explain). So the bottom of the scene stays at the bottom but the rest zooms out.
I have had a go with SKConstraints but not having any luck (I'm quite new at SpriteKit)
func setConstraints(with scene: SKScene, and frame: CGRect, to node: SKNode?) {
let scaledSize = CGSize(width: scene.size.width * xScale, height: scene.size.height * yScale)
let boardContentRect = frame
let xInset = min((scaledSize.width / 2), boardContentRect.width / 2)
let yInset = min((scaledSize.height / 2), boardContentRect.height / 2)
let insetContentRect = boardContentRect.insetBy(dx: xInset, dy: yInset)
let xRange = SKRange(lowerLimit: insetContentRect.minX, upperLimit: insetContentRect.maxX)
let yRange = SKRange(lowerLimit: insetContentRect.minY, upperLimit: insetContentRect.maxY)
let levelEdgeConstraint = SKConstraint.positionX(xRange, y: yRange)
if let node = node {
let zeroRange = SKRange(constantValue: 0.0)
let positionConstraint = SKConstraint.distance(zeroRange, to: node)
constraints = [positionConstraint, levelEdgeConstraint]
} else {
constraints = [levelEdgeConstraint]
}
}
then calling the function with:
gameCamera.setConstraints(with: self, and: scene!.frame, to: nil)
(This was code from a tutorial I was following) The "setConstraints" function is an extension of SKCameraNode
I'm not sure this will give me the correct output, but when I run the code to scale, it just zooms from the middle and shows the surrounding area of the scene .sks file.
gameCamera.run(SKAction.scale(to: 0.2, duration: 100))
This is the code to scale the gameCamera
EDIT: Answer below is nearly what I was looking for, this is my updated answer:
let scaleTo = 0.2
let duration = 100
let scaleTop = SKAction.customAction(withDuration:duration){
(node, elapsedTime) in
let newScale = 1 - ((elapsedTime/duration) * (1-scaleTo))
let currentScaleY = node.yScale
let currentHeight = node.scene!.size.height * currentScaleY
let newHeight = node.scene!.size.height * newScale
let heightDiff = newHeight - currentHeight
let yOffset = heightDiff / 2
node.setScale(newScale)
node.position.y += yOffset
}
You cannot use a constraint because your scale size is dynamic.
Instead you need to move your camera position to give the illusion it is only scaling in 3 directions.
To do this, I would recommend creating a custom action.
let scaleTo = 2.0
let duration = 1.0
let currentNodeScale = 0.0
let scaleTop = SKCustomAction(withDuration:duration){
(node, elapsedTime) in
if elapsedTime == 0 {currentNodeScale = node.scale}
let newScale = currentNodeScale - ((elapsedTime/duration) * (currentNodeScale-scaleTo))
let currentYScale = node.yScale
let currentHeight = node.scene.size.height * currentYScale
let newHeight = node.scene.size.height * newScale
let heightDiff = newHeight - currentHeight
let yOffset = heightDiff / 2
node.scale(to:newScale)
node.position.y += yOffset
}
What this is doing is comparing the new height of your camera with the old height, and moving it 1/2 the distance.
So if your current height is 1, this means your camera sees [-1/2 to 1/2] on the y axis. If you new scale height is 2, then your camera sees [-1 to 1] on the y axis. We need to move the camera up so that the camera sees [-1/2 to 3/2], meaning we need to add 1/2. So we do 2 - 1, which is 1, then go 1/2 that distance. This makes our yOffset 1/2, which you add to the camera.

Swift: How to get Height and width of a rotated image.

I am rotating a square image View whose width is 160 but after rotation width become 190 same as height.
Rotating it by this function
func transformUsingRecognizer(_ recognizer: UIGestureRecognizer, transform: CGAffineTransform) -> CGAffineTransform {
if let rotateRecognizer = recognizer as? UIRotationGestureRecognizer {
return transform.rotated(by: rotateRecognizer.rotation)
}
return transform
}
tried these two codes to get correct width
imgDraggingImage.bounds.size.width
imgDraggingImage.frame.height
but no success.
Image before rotation width 160
Image after rotation, no change in width but now returning width 190
Seems width value depends on rotation as on any different angle it returns different width value. But I need correct value which in this case is 160. please help
extension CGAffineTransform {
var angle: CGFloat { return atan2(-self.c, self.a) }
var angleInDegrees: CGFloat { return self.angle * 180 / .pi }
var scaleX: CGFloat {
let angle = self.angle
return self.a * cos(angle) - self.c * sin(angle)
}
var scaleY: CGFloat {
let angle = self.angle
return self.d * cos(angle) + self.b * sin(angle)
}
}
The above extension solve the problem.
self.imgDraggingImage.transform.scaleX
self.imgDraggingImage.transform.scaleY
the above code scaleX gave change in Width of image (scale) even if rotated too and scaleY provide the % change in image height.
where imgDraggingImage.bounds.size.width gave constant image size even if image scaled.
while imgDraggingImage.frame.width was creating issue when image was rotated.
Depends, do you want the size in Pixels or in Points:
let heightInPoints = image.size.height
let heightInPixels = heightInPoints * image.scale
let widthInPoints = image.size.width
let widthInPixels = widthInPoints * image.scale

How do I add a button at every x degrees? [duplicate]

I have an array of buttons and when I append them to a view I want the to be positioned around a image view which is in the center. Based on how many objects there are in the array, I want them to be evenly spaced around the whole circle. Below is my attempt to do so. What am I doing wrong and how should I fix it? There is more than one button behind the moose.
var userbutton = [UIButton]()
var upimage = [UIImage]()
var locationpic = [AnyObject]()
func locationsSet(){
for (index, users) in upimage.enumerate() {
let userbutton = UIButton()
userbutton.addTarget(self, action: "buttonAction:", forControlEvents: .TouchUpInside)
userbutton.frame = CGRectMake(100, 100, 50, 50)
userbutton.layer.cornerRadius = userbutton.frame.size.width/2
userbutton.clipsToBounds = true
userbutton.setImage(users, forState: .Normal)
let radians = CGFloat(M_PI) * 2.0 / CGFloat(upimage.count) * CGFloat(index)
let centerx = self.view.bounds.width / 2.0
let radius = currentuserpic.frame.size.width / 2.0
let centery = self.view.bounds.height / 2.0
let pointx = centerx + cos(radians) * (radius + 40)
let pointy = (centery) + (sin(radians)) * (radius + 40)
userbutton.center.x = pointx
userbutton.center.y = pointy
self.userbutton.append(userbutton)
self.view.addSubview(userbutton)
print("x\(pointx)")
print("y\(pointy)")
}
}
How I would do this:
Create an extension to UIView to get the diagonal and radius. These are handy because we want our "satellites" to have predictable placing even when the "planet" isn't square.
extension UIView {
var diagonal : CGFloat {
return sqrt(pow(self.frame.width, 2) + pow(self.frame.height, 2))
}
var radius : CGFloat {
return diagonal / 2
}
}
This will return a point based on an angle and a distance from an origin.
It uses dreadful trigonometry.
func getPoint(fromPoint point: CGPoint, atDistance distance: CGFloat, withAngleRadians angle:CGFloat) -> CGPoint {
let x = point.x
let y = point.y
let dx = (distance * cos(angle))
let dy = (distance * sin(angle))
return CGPoint(x: (dx + x), y: (dy + y))
}
Now the real function. Generate a bunch of points in a circle pattern. I used a running sum for the angle instead of multiplying each time by the index. This just returns the centre points for the views.
func encirclePoint(point : CGPoint, distance:CGFloat, inParts parts: Int) -> [CGPoint] {
let angle = 2 * CGFloat(M_PI) / CGFloat(parts) // critical part, you need radians for trigonometry
var runningAngle : CGFloat = -(CGFloat(M_PI) / 2) // start at the top
var points : [CGPoint] = []
for _ in 0..<parts {
let circlePoint = getPoint(fromPoint: point, atDistance: distance, withAngleRadians: runningAngle)
points.append(circlePoint)
runningAngle += angle
}
return points
}
Now you can create a simple function that takes a view, a margin and an array of "satellite" views. It will set their centre and add them to the superview of the view we used to input. It makes sense not to add them to the view itself since they might not be placed inside it.
func encircleView(view : UIView, withSubViews subViews : [UIView], withMargin margin : CGFloat) {
guard !(subViews.isEmpty) else { // if there are no subviews : abort
return
}
let distance = view.radius + margin
let points = encirclePoint(view.center, distance: distance, inParts: subViews.count)
guard subViews.count == points.count, let uberView = view.superview else { // if the count is not the same or there is no superview: abort
return
}
for (point, subView) in zip(points, subViews) { subView.center = point }
}
Notice how I did nothing except for the centre calculations in these functions. Styling them goes in another function. This makes it super easy to maintain and debug.
I might even let the last function just return the subviews with updated frames and add them later.
Or negative margin :)
Gist
A full circle is 2 * pi radians. You need to divide that by the number of items you have and multiply that by the index of the item you are currently processing. Use trig to find the location on the circle:
for (index, users) in upimage.enumerate() {
let radians = CGFloat(M_PI) * 2.0 / CGFloat(upimage.count) * CGFloat(index)
......
let centerx = self.view.bounds.width / 2.0
let radius = currentuserpic.frame.size.width / 2.0
let centery = self.view.bounds.height / 2.0
let pointx = centerx + cos(radians) * radius
let pointy = centery + sin(radians) * radius
......
}

How do I place the objects in the array around the center image?

I have an array of buttons and when I append them to a view I want the to be positioned around a image view which is in the center. Based on how many objects there are in the array, I want them to be evenly spaced around the whole circle. Below is my attempt to do so. What am I doing wrong and how should I fix it? There is more than one button behind the moose.
var userbutton = [UIButton]()
var upimage = [UIImage]()
var locationpic = [AnyObject]()
func locationsSet(){
for (index, users) in upimage.enumerate() {
let userbutton = UIButton()
userbutton.addTarget(self, action: "buttonAction:", forControlEvents: .TouchUpInside)
userbutton.frame = CGRectMake(100, 100, 50, 50)
userbutton.layer.cornerRadius = userbutton.frame.size.width/2
userbutton.clipsToBounds = true
userbutton.setImage(users, forState: .Normal)
let radians = CGFloat(M_PI) * 2.0 / CGFloat(upimage.count) * CGFloat(index)
let centerx = self.view.bounds.width / 2.0
let radius = currentuserpic.frame.size.width / 2.0
let centery = self.view.bounds.height / 2.0
let pointx = centerx + cos(radians) * (radius + 40)
let pointy = (centery) + (sin(radians)) * (radius + 40)
userbutton.center.x = pointx
userbutton.center.y = pointy
self.userbutton.append(userbutton)
self.view.addSubview(userbutton)
print("x\(pointx)")
print("y\(pointy)")
}
}
How I would do this:
Create an extension to UIView to get the diagonal and radius. These are handy because we want our "satellites" to have predictable placing even when the "planet" isn't square.
extension UIView {
var diagonal : CGFloat {
return sqrt(pow(self.frame.width, 2) + pow(self.frame.height, 2))
}
var radius : CGFloat {
return diagonal / 2
}
}
This will return a point based on an angle and a distance from an origin.
It uses dreadful trigonometry.
func getPoint(fromPoint point: CGPoint, atDistance distance: CGFloat, withAngleRadians angle:CGFloat) -> CGPoint {
let x = point.x
let y = point.y
let dx = (distance * cos(angle))
let dy = (distance * sin(angle))
return CGPoint(x: (dx + x), y: (dy + y))
}
Now the real function. Generate a bunch of points in a circle pattern. I used a running sum for the angle instead of multiplying each time by the index. This just returns the centre points for the views.
func encirclePoint(point : CGPoint, distance:CGFloat, inParts parts: Int) -> [CGPoint] {
let angle = 2 * CGFloat(M_PI) / CGFloat(parts) // critical part, you need radians for trigonometry
var runningAngle : CGFloat = -(CGFloat(M_PI) / 2) // start at the top
var points : [CGPoint] = []
for _ in 0..<parts {
let circlePoint = getPoint(fromPoint: point, atDistance: distance, withAngleRadians: runningAngle)
points.append(circlePoint)
runningAngle += angle
}
return points
}
Now you can create a simple function that takes a view, a margin and an array of "satellite" views. It will set their centre and add them to the superview of the view we used to input. It makes sense not to add them to the view itself since they might not be placed inside it.
func encircleView(view : UIView, withSubViews subViews : [UIView], withMargin margin : CGFloat) {
guard !(subViews.isEmpty) else { // if there are no subviews : abort
return
}
let distance = view.radius + margin
let points = encirclePoint(view.center, distance: distance, inParts: subViews.count)
guard subViews.count == points.count, let uberView = view.superview else { // if the count is not the same or there is no superview: abort
return
}
for (point, subView) in zip(points, subViews) { subView.center = point }
}
Notice how I did nothing except for the centre calculations in these functions. Styling them goes in another function. This makes it super easy to maintain and debug.
I might even let the last function just return the subviews with updated frames and add them later.
Or negative margin :)
Gist
A full circle is 2 * pi radians. You need to divide that by the number of items you have and multiply that by the index of the item you are currently processing. Use trig to find the location on the circle:
for (index, users) in upimage.enumerate() {
let radians = CGFloat(M_PI) * 2.0 / CGFloat(upimage.count) * CGFloat(index)
......
let centerx = self.view.bounds.width / 2.0
let radius = currentuserpic.frame.size.width / 2.0
let centery = self.view.bounds.height / 2.0
let pointx = centerx + cos(radians) * radius
let pointy = centery + sin(radians) * radius
......
}

How to Wrap Text Around a Circle in Sprite Kit / Swift

I am making a game with SpriteKit / Swift and I want to have an effect on the menu scene where I bend a string around a circle. The following picture is almost exactly what I am looking to accomplish. http://www.heathrowe.com/tuts/typeonaapathimages/4.gif
The following code wraps the characters in a string around the top half of a circle by creating a label node for each character in the string, setting the position of the label to the appropriate location on the circle, and then rotating each label node so that it is tangent to the circle at that position.
class GameScene:SKScene {
override func didMove(to view:SKView) {
let radius = CGFloat(50.0)
let circleCenter = CGPoint.zero
let string = "Your Text Here"
let count = string.lengthOfBytes(using: String.Encoding.utf8)
let angleIncr = CGFloat.pi/(CGFloat(count)-1)
var angle = CGFloat.pi
// Loop over the characters in the string
for (_, character) in string.characters.enumerated() {
// Calculate the position of each character
let x = cos(angle) * radius + circleCenter.x
let y = sin(angle) * radius + circleCenter.y
let label = SKLabelNode(fontNamed: "Arial")
label.text = "\(character)"
label.position = CGPoint(x: x, y: y)
// Determine how much to rotate each character
label.zRotation = angle - CGFloat.pi / 2
label.fontSize = 30
addChild(label)
angle -= angleIncr
}
}
}
Binary tree is a non linear data structure. It should and must have atleast two children to a single parent node.

Resources