I'm just starting out looking into Swift 3 - never coded before so I'm completely new to it all. Been looking at a really basic Node tutorial and have the following code:
class GameScene: SKScene {
override func didMove(to view: SKView) {
// SKLabelNode
var exampleLabelNode = SKLabelNode(fontNamed: "Helvetica")
exampleLabelNode.text = "Example Label Node!"
exampleLabelNode.fontSize = 30
exampleLabelNode.fontColor = SKColor.white
exampleLabelNode.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height * 0.75)
self.addChild(exampleLabelNode)
// SKSpriteNode
var exampleSpriteNode = SKSpriteNode(imageNamed: "myImage")
exampleSpriteNode.size = CGSize(width: 180, height: 180)
exampleSpriteNode.anchorPoint = CGPoint(x: 0.5, y: 0.5)
exampleSpriteNode.position = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height * 0.25)
exampleSpriteNode.zPosition = 100
exampleSpriteNode.name = "exampleName"
self.addChild(exampleSpriteNode)
}
}
It's meant to position exampleLabelNode centre and 3/4 at the top of the screen and exampleSpriteNode 1/4 at the bottom of the screen.
However its positioning exampleSpriteNode 3/4 to the top of the screen and half off to the right with the exampleLabelNode positioned off the screen.
The tutorial is for Swift 2, so is the positioning syntax etc different for Swift 3?
This is happening because the default scene anchorPoint is (0.5, 0.5) meaning the centre of the scene. That's also the default anchorPoint for any node. That makes the point (x: 0, y: 0) at the centre of the scene.
I suggest you read more about this or ask questions to get a better understanding of it if you're having trouble.
Official Apple Docs
SKSpriteNode anchorPoint
SKScene anchorPoint
Note: The docs for the SKScene is incorrect when it says that the default value is (0, 0).
To solve your issue, you can add this line to the beginning of your didMove(to view):
self.anchorPoint = CGPoint(x: 0, y: 0)
This sets the scene anchorPoint to the bottom left of the scene, which used to be how it was (The tutorial is outdated).
Alternatively, you could redo the sprite placement now that you know how anchorPoint works. I suggest this because I find building games to be easier when building the scene from the centre outwards rather than from the bottom left corner.
Related
I have a child view controller whose view has been added as a subview on the parent view. I want to scale this by 1.1x from its top left corner. I tried doing this:
override func viewDidLoad() {
self.view.layer.anchorPoint = CGPoint(x: 0, y: 0)
}
func scaleMyView() {
self.view.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
}
But that seems to re-centre the view over it's top left corner and then scale.
After reading some answers on here, I then tried setting the position of the view's layer like this:
let center = self.view.frame.origin
self.view.layer.anchorPoint = CGPoint(x: 0, y: 0)
self.view.layer.position = center
But that moves the view somewhere to the center of the screen. How can I just make it scale from its top left corner without it moving in the parent view??
You're exactly right and extremely close. To change where a transform is anchored, you need to change the layer's anchor point. But when you do that, you need to change the layer's position to compensate, or the layer will move.
Let self.v be the view we want to transform:
let lay = self.v.layer
let p = lay.frame.origin
lay.anchorPoint = CGPoint(x: 0, y: 0)
lay.position = p
lay.setAffineTransform(CGAffineTransform(scaleX: 1.1, y: 1.1))
I am running through an entry level xCode course and I am working with SKLabelNode. I have quadruple checked my code to be duplicate of this, yet my label is appearing 99% off the screen it appears? Please help.
By Default Anchor point of your main scene is set to x= 0.5 and y=0.5, So, when you add a new node, it's coordinates starts from center of screen.
Now, either you can change the anchor points of your scene as
self.anchorPoint = CGPoint(x: 0.0, y: 0.0)
let label = SKLabelNode(text: "Player Score")
label.fontColor = SKColor.white
label.fontSize = 60
label.position = CGPoint(x: self.frame.width/2, y: self.frame.height/2)
self.addChild(label)
or you can change the position of label to :-
label.position = CGPoint(x: 0, y: 0)
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.
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);
}
}
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!