Swift: position an SKshapeNode to the far left/right but not offscreen - ios

Im trying to get two SKshapeNodes to be positioned on the far left and far right of the screen without any part of the nodes offscreen like this http://i.stack.imgur.com/9rhAH.png. I don't want one node to be positioned according to the other node though.
Ive been trying to use minX and maxX and use the width of the screen and SKshapeNodes and I can't figure it out.
This is what I have so far
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let firstShapeNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
firstShapeNode.position = CGPoint(x: 0, y: self.frame.height / 2)
firstShapeNode.fillColor = SKColor.purpleColor()
self.addChild(firstShapeNode)
let secondShapeNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 50, height: 50))
secondShapeNode.position = CGPoint(x: 0, y: self.frame.height / 2)
secondShapeNode.fillColor = SKColor.cyanColor()
self.addChild(secondShapeNode)
}
Any suggestions?

To align the nodes to the left and right sides of the screen, respectively, try this:
firstShapeNode.position = CGPoint(x: firstShapeNode.size.width / 2, y: self.frame.height / 2)
secondShapeNode.position = CGPoint(x: self.frame.maxX - secondShapeNode.size.width / 2, y: self.frame.height / 2)
In SpriteKit, unlike UIKit, the default origin (0,0) point on the screen is in the bottom left corner. Additionally, by default, the position of an SKNode is the center of the node, which is why they need to be offset by half of their width.

Related

update point after transform

I'm trying to change a view position using CGAffineTransform but the transform statement doesn't affect it.
For example, let's call the view that moves movingView:
At the start, movingView is at point CGPoint(x: 100, y: 100)
then I'm moving movingView by this code :
movingView.transform = CGAffineTransform(translationX: 100, y: 300)
now if I do : print(movingView.layer.position), I still get
CGPoint(x: 100, y:100)
instead of
CGPoint(x: 200, y: 400)
Applying a transform to a view (or layer) doesn't update its center location (or position). It changes how and where the view (or layer) appear on screen but not the values of its center, (or position), or bounds properties.
If you want to know what how the point is affected by a translation, then you can compute a new point by applying the transform to it:
let point = CGPoint(x: 100, y: 200)
let transform = CGAffineTransform(translationX: 20, y: 40)
let transformedPoint = point.applying(transform) // x: 120, y: 240
Actually What changes is the frame of View if the view is added to a SuperView.
Check : movingView.frame.origin.
The transform of CALayer of UIView has already changed:
CGPoint.init(x: movingView.layer.transform.m41, y: movingView.layer.transform.m42)
So the transform of view is based on transform of CALayer.

Drawing rectangles using the CGPoint coordinate system - SWIFT

I am able to draw a rectangle using the code below (works). However, I am using the Vison framework to detect rectangles but it is giving me back CGPoint values that is less than 1.0. When I enter these coordinates to draw a rectangle I get nothing back. Please can some advise?
Works - i get a rectangle
let rectangle = UIBezierPath.init()
rectangle.move(to: CGPoint.init(x: 100, y: 100))
rectangle.addLine(to: CGPoint.init(x: 200, y: 130))
rectangle.addLine(to: CGPoint.init(x: 300, y: 400))
rectangle.addLine(to: CGPoint.init(x: 100, y: 500))
rectangle.close()
let rec = CAShapeLayer.init()
rec.path = rectangle.cgPath
rec.fillColor = UIColor.red.cgColor
self.view.layer.addSublayer(rec)
Does not work (no rectangle):
let rectangle = UIBezierPath.init()
rectangle.move(to: CGPoint.init(x: 0.154599294066429, y: 0.904223263263702))
rectangle.addLine(to: CGPoint.init(x: 0.8810795545578, y: 0.970198452472687))
rectangle.addLine(to: CGPoint.init(x: 0.16680309176445, y: 0.0157230049371719))
rectangle.addLine(to: CGPoint.init(x: 0.878569722175598, y: 0.128135353326797))
rectangle.close()
let rec = CAShapeLayer.init()
rec.path = rectangle.cgPath
rec.fillColor = UIColor.red.cgColor
self.view.layer.addSublayer(rec)
The points returned by the Vision framework are coordinates represented by a percentage from the full size of the viewport. If your viewport is 640 x 480 (for example) then your first point is CGPoint(x: 0.154599294066429 * 640, y: 0.904223263263702 * 480). Taking that into account change your code to something like this and it should be fine:
let rectangle = UIBezierPath.init()
let width = // your width - Maybe UIScreen.main.bounds.size.width ?
let height = // your height - Maybe UIScreen.main.bounds.size.height ?
rectangle.move(to: CGPoint.init(x: width * 0.154599294066429, y: height * 0.904223263263702))
rectangle.addLine(to: CGPoint.init(x: width * 0.8810795545578, y: height * 0.970198452472687))
rectangle.addLine(to: CGPoint.init(x: width * 0.16680309176445, y: height * 0.0157230049371719))
rectangle.addLine(to: CGPoint.init(x: width * 0.878569722175598, y: height * 0.128135353326797))
rectangle.close()
let rec = CAShapeLayer.init()
rec.path = rectangle.cgPath
rec.fillColor = UIColor.red.cgColor
self.view.layer.addSublayer(rec)
Let's just analyse what it is you've got here. In the first example, you're drawing a rectangle of size of roughly 200px by 400px, ish... Of course, this will display exactly as you expect it to.
In the second example, you're drawing a rectangle of roughly 0.7px by 0.8px, ish. Now think about this logically, how is the screen supposed to represent 0.7 of a pixel? It can't! A pixel is the smallest representation you can have, that is, a single coloured square.
The code doesn't work because of physical constraints of the screen/system. You need to be using size (and position) values greater than 1 to see the rectangle. The Vision framework is no different, it will only see what you can see, if that makes sense.

How to set shadow at left right and bottom in UIView in ios Swift?

I want shadow at left, right and bottom in view. But Swift 3 shadow offset provide only height or width. I found similar answer see below link it show shadow top left right shadow. I want left right and bottom.
see link little similar answer
It's almost the same task as link provided, just change the BezierPath.
let block1 = UIView(frame: CGRect.init(x: 50, y: 50, width: 300, height: 300))
block1.backgroundColor = UIColor.red
block1.layer.masksToBounds = false
block1.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
block1.layer.shadowRadius = 5.0
block1.layer.shadowOpacity = 0.7
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
// Move to center between two top points instead of bottom
path.addLine(to: CGPoint(x: block1.frame.size.width/2.0, y: block1.frame.size.height/2.0))
path.addLine(to: CGPoint(x: block1.frame.size.width, y: 0.0))
path.addLine(to: CGPoint(x: block1.frame.size.width, y: block1.frame.size.height))
path.addLine(to: CGPoint(x: 0.0, y: block1.frame.size.height))
path.close()
block1.layer.shadowPath = path.cgPath

Change Node Positioning Depending on Screen Size Swift/Sprite Kit

I want to change to positioning of my nodes depending on the size of the screen. For example, when I run my app on an iPhone 4s, not all of the nodes fit on the screen, since I developed the app with iPhone 6 dimensions in mind. How can I make it so the nodes reposition themselves depending on which device it's running on? I know I can achieve this with constraints normally, but I don't know how to do that in Sprite Kit. I have included a screen shot down below.
lblRocketCount.position = CGPoint(x: 100, y: 150)
lblRocketCount.text = "Bullets: 30"
self.addChild(lblRocketCount)
lblMissileCount.position = CGPoint(x: -100, y: 150)
lblMissileCount.text = "Missiles: 5"
self.addChild(lblMissileCount)
leftBorder.position = CGPoint(x: -333, y: 0)
self.addChild(leftBorder)
rightBorder.position = CGPoint(x: 333, y: 0)
self.addChild(rightBorder)
topBorder.position = CGPoint(x: 0, y: 187)
self.addChild(topBorder)
bottomBorder.position = CGPoint(x: 0, y: -187)
self.addChild(bottomBorder)
buttonDirUp.position = CGPoint(x: -200, y: -50)
buttonDirUp.setScale(2.0)
self.addChild(buttonDirUp)
ship.setScale(0.33)
shootButton.setScale(0.7)
missileButton.setScale(0.5)
buttonDirLeft.position = CGPoint(x: -250, y: -100)
buttonDirLeft.setScale(2.0)
self.addChild(buttonDirLeft)
buttonDirDown.position = CGPoint(x: -200, y: -150)
buttonDirDown.setScale(2.0)
self.addChild(buttonDirDown)
buttonDirRight.position = CGPoint(x: -150, y: -100)
buttonDirRight.setScale(2.0)
self.addChild(buttonDirRight)
self.view?.multipleTouchEnabled = true
self.backgroundColor = SKColor.blackColor()
self.anchorPoint = CGPointMake(0.5, 0.5)
self.addChild(base)
base.position = CGPointMake(200, -100)
self.addChild(ball)
ball.position = base.position
self.addChild(ship)
ship.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(shootButton)
shootButton.position = CGPoint(x: self.frame.midX, y: -100)
self.addChild(missileButton)
missileButton.position = CGPoint(x: -200, y: 50)
Screen Shot
This is a technique that I've found to be effective for dealing with multiple resolutions. You'll need to create two global variables in your GameViewController that reflect the size of the current view:
import SpriteKit
// global variables
var SKViewSize: CGSize?
var SKViewSizeRect: CGRect?
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let skView = self.view as! SKView
SKViewSize = skView.bounds.size
let scene = GameScene(size: SKViewSize!)
scene.scaleMode = .AspectFill
skView.presentScene(scene)
SKViewSizeRect = getViewSizeRect()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
SKViewSize = self.view.bounds.size
SKViewSizeRect = getViewSizeRect()
let skView = self.view as! SKView
if let scene = skView.scene {
if scene.size != self.view.bounds.size {
scene.size = self.view.bounds.size
}
}
}
func getViewSizeRect() -> CGRect {
return CGRect(x: ((SKViewSize!.width * 0.5) * -1.0), y: ((SKViewSize!.height * 0.5) * -1.0), width: SKViewSize!.width, height: SKViewSize!.height)
}
}
Then create an extension for all SKNode types that allows you to position them using a percentage of the current screen size:
public extension SKNode {
public func posByScreen(x: CGFloat, y: CGFloat) {
self.position = CGPoint(x: CGFloat((SKViewSizeRect!.width * x) + SKViewSizeRect!.origin.x), y: CGFloat((SKViewSizeRect!.height * y) + SKViewSizeRect!.origin.y))
}
}
For example, to create a sprite node and center it on any screen size, you'd use this in your GameScene:
let sprite = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(32, 32))
addChild(sprite)
sprite.posByScreen(0.5, y: 0.5)
There is no easy answer to what you are looking for, you first need to decide what your game should be like at the different aspect ratios, and plan around that. In a lot of cases, you want to just give the 16:9 phones more FOV (They can see more of the game world). To do this, design your game with 2 layers: a game layer, and a hud overlay.
The game layer will utilize .AspectFill to keep your game the same "size" across devices, with the iPhone 4s folk losing the ability to see some of the screen above and below them (You can adjust where they lose some of the screen by moving the game layer nodes position)
The HUD overlayer will handle the things that need to stay at a fixed position on screen, like buttons and life bars. Now you can use the % based coordinates like some people have mentioned, or you can keep the absolute coordinates, and just add/subtract the pixel difference from your overlay nodes if your game detects a 3:2 (or 4:3 if you want iPad) ratio
Basic principle: (Do not copy verbatum)
Lets say we create a game scene that is 160x284 points
This means on an iphone 4s our resolution is 320x480, aspect fill is going to take the smallest of the distance between top/bottom or left/right and scale to that. In our case 160 is going to scale to 320, 284 is going to scale to 568. Uh oh, iphone only has 480, so you are losing out on 88 pixels, which means in the game world we are losing 44 points.
//This means that the top and bottom will lose out on 22 points each, so lets make that our iphone4s padding
class GameScene
{
let gameNode = SKNode();
let overlayNode = SKNode();
let iPhone4sPadding = 22;
func init()
{
//add all of the games nodes that you would add to the scene to gameNode instead
self.addChild(gameNode);
//add all of your overlay nodes (buttons, lifebars, etc) to the overlayNode
//do a check on uiscreen to see if the height is 480, if it is
// all nodes on the bottom add iphone4spadding to the y
// all nodes on the top subtract iphone4spadding from the y
self.addChild(overlayNode);
}
}

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!

Resources