Obtaining width and height of an SKScene using Swift - ios

I am developing a little game using Xcode 6.0.1 for iOS 8. I couldn't find anything useful on the web and I tried everything. I need an explanation to why I am getting 0.000 instead of the width and height in pixels of the device I'm developing for.
To let you know what I did:
used self.frame.size.width, self.size.width and many more.
override func didMoveToView(view: SKView) {
let testImage:SKSpriteNode = SKSpriteNode(imageNamed: "GamePreview3.png")
testImage.position = CGPointMake(self.size.width/2, self.size.height/2)
self.addChild(testImage)
NSLog("Width: %f", CGRectGetWidth(self.frame))
NSLog("Height: %f", CGRectGetHeight(self.frame))
}
Width: 0.000000
Height: 0.000000
Is it because now with the new Xcode engine the screen size doesn't matter anymore?

I believe the frame of the scene is always 0-sized because it has no particular "frame" (its content is not limited to a virtual frame). You should use the size property of the scene instead because that'll give you the size the scene was initialized with, which is typically (but doesn't have to be) the SKView's size.

Related

Pull up and drop UIView with a change in the x position

Edit
This is what I want visualised (ignore the ugly red line, it just indicates the movement of the UIView):
I want to have a UIView that is initialised in the middle of the screen. After that, I want to give it a push upwards and the gravity pulls it down till it is off the screen. My old question works with a UIPushBehaviour, UIDynamicBehaviour and a UIGravityBehaviour (see below). Matt pointed out a UIPushBehaviour is maybe not the right choice, since it not work out well across every screen size available on iOS.
I can do this with a UIView.animate function, but it is really static and does not look natural. With the UIPushBehaviour, UIDynamicBehaviour and UIGravityBehaviour, it looks really nice but the UIPushBehaviour's magnitude can not be calculated across every screen size to give the same ending point of the UIView's x and y position.
Question
How can I initialise a UIView in the middle of the screen, 'pull up' that UIView (with some change in the x position) and let the gravity (or something else) pulls it down until it is off the screen? It is important that the change in the x and y position will be the same on every screen size.
Below is my old question
I have a UIPushBehaviour with instantaneous as mode in which I push some UIViews around. The greater the screen size, the less it pushes.
I also have a UIDynamicItemBehavior with resistance set to 1, I think this is one the main reasons it is different in each screen size (correct me if I am wrong).
I want a function that will push the UIView to the same ending point, with the same speed, duration and ending point regardless of the screen size.
I tried to make a relative magnitude without any luck:
For the iPhone 5S, let's say a magnitude of 0.5 would touch a UIView from the middle to the top. I wanted to calculate the magnitude across all devices like this:
let y = 0.5 / 520 // 5S screen height
magnitude = self.view.frame.height * y
For the iPhone 8, it has a very different output and is not working. When reading the docs, I thought I would understand it. I thought 1 magnitude represents 100 pixels, but it is clearly not that case.
Is there any way I can calculate a magnitude to, for example, move a UIView from the middle to the right?
I made a project here. There is a black UIView that get's pushed to the edges on an iPhone 5, but not on the iPhone 8.
Solution
You need to scale the push amount relative to the size of the screen so your view always ends in the same place. To do this, adjusting the UIPushBehavior's pushDirection vector works quite well. In this case, I set the push direction to be proportional to the bounds of the view, and scaled it down by a constant factor.
let push = UIPushBehavior(items: [pushView], mode: .instantaneous)
let pushFactor: CGFloat = 0.01
push.pushDirection = CGVector(dx: -view.bounds.width * pushFactor, dy: -view.bounds.height * pushFactor)
animator.addBehavior(push)
You may need to adjust some constants to get the exact animation you want. The constants you can adjust are:
Gravity magnitude (currently 0.3)
Push factor (currently 0.01)
Depending on your needs, you may need to scale the gravity magnitude proportional to the size of the screen as well.
Note: These constants will need to change based on the size of your animated view, since UIKit Dynamics treats the size of the view as its mass. If your view needs to be dynamically sized, you will need to scale your constants according to the size of the animated view.
Edit regarding comments on the original question:
Views of varying sizes: Like I mentioned in my note above, you'll need to apply an additional factor to account for the "mass" of the views. Something like view.frame.height * view.frame.width * someConstant should work well.
iPad screen size: Currently the pushFactor is applied to both the dx and dy components of the vector. Because iPads have a different aspect ratio, you'll need to split this into two constants, maybe xPushFactor and yPushFactor, which can account for the differences in aspect ratio.
Examples
iPhone 8
iPhone SE
Full Playground Source Code
Copy and paste this code into a Swift playground to see it in action. I've included the sizes of various iPhone screens, so just uncomment the size you want to easily test the animation on different device sizes. Most of the interesting/relevant code is in viewDidAppear.
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let pushView = UIView()
var animator: UIDynamicAnimator!
override func viewDidLoad() {
super.viewDidLoad()
view.frame = CGRect(x: 0, y: 0, width: 568, height: 320) // iPhone SE
// view.frame = CGRect(x: 0, y: 0, width: 667, height: 375) // iPhone 8
// view.frame = CGRect(x: 0, y: 0, width: 736, height: 414) // iPhone 8+
// view.frame = CGRect(x: 0, y: 0, width: 812, height: 375) // iPhone X
view.backgroundColor = .white
let pushViewSize = CGSize(width: 200, height: 150)
pushView.frame = CGRect(x: view.bounds.midX - pushViewSize.width / 2, y: view.bounds.midY - pushViewSize.height / 2, width: pushViewSize.width, height: pushViewSize.height)
pushView.backgroundColor = .red
view.addSubview(pushView)
animator = UIDynamicAnimator(referenceView: self.view)
let dynamic = UIDynamicItemBehavior()
dynamic.resistance = 1
animator.addBehavior(dynamic)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let gravity = UIGravityBehavior(items: [pushView])
gravity.magnitude = 0.3
animator.addBehavior(gravity)
let push = UIPushBehavior(items: [pushView], mode: .instantaneous)
let pushFactor: CGFloat = 0.01
push.pushDirection = CGVector(dx: -view.bounds.width * pushFactor, dy: -view.bounds.height * pushFactor)
animator.addBehavior(push)
}
}
let vc = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = vc.view

Spritekit scaling for universal app

I am having trouble properly setting up my app so that it displays correctly on all devices. I want my game to look best for iPhone and I understand that setting my scene size using GameScene(size: CGSize(width: 1334, height: 750)) and use .aspectFill means that on iPads there will be less space to display things which I'm fine with. The problem is, how do I position my nodes so that they are relative to the each devices frame height and width? I use self.frame.height, self.frame.width, self.frame.midX, etc. for positioning my nodes and when I run my game, it positions things properly considering I run on my iPhone 6, but on my iPad, everything seems blown up and nodes are off the screen. I'm going crazy trying to figure this out
I solved this in my game by using scaleFactors, numbers which tell my app how much to enlarge each length, width, height etc. Then I just make my game look well for one phone, and use that phone's width and height to calculate with which factor I need to enlarge it for other devices. In this example I use the iPhone 4 as a base, but you can use any device just change the numbers according to that device.
Portrait mode:
var widthFactor = UIScreen.main.bounds.width/320.0 //I divide it by the default iPhone 4 width
var heightFactor = UIScreen.main.bounds.height/480.0
Landscape mode:
var widthFactor = UIScreen.main.bounds.width/480.0 //I divide it by the default iPhone 4 landscape width
var heightFactor = UIScreen.main.bounds.height/320.0
Then when you make a node, a coin image for example, multiply its coordinates or width/height by the scaleFactors:
let coin = SKSpriteNode(imageNamed: "coin")
coin.position = CGPoint(x: 25 * widthFactor, y: self.size.height - 70 * heightFactor)
I think what you're looking for might be in this answer: https://stackoverflow.com/a/34878528/6728196
Specifically I think this part is what you're looking for (edited to fit your example):
if UIDevice.current.userInterfaceIdiom == .pad {
// Set things only for iPad
// Example: Adjust y positions using += or -=
buttonNode.position.y += 100
labelNode.position.y -= 100
}
Basically, this just adds or subtracts a certain amount from the iPhone position if the user is using an iPad. It's not too complicated, and you can increase or decrease both x and y values of position by a certain value of percentage of the screen (self.size.width * decimalPercentage).
Another benefit of using this way is that you're just modifying the iPhone positions, so it starts by using the default values that you set. Then if on iPad, it will make changes.
If this is hard to understand let me know so I can clear up the explanation

How to prevent an object from leaving screen in swift?

I know there are a couple other questions regarding this but none of them seem to work or they all are incomplete or half complete. I need to know how to keep an object within the visible screen! So I have a spaceship sprite that uses the device's tilt and core motion to fly across the screen. If I tilt my device, the spaceship will at one point leave my screen and continue going in that direction. How do I keep the spaceship stay inside my screen so that even if I tilt to the left or right it will never leave the visible screen? Here is some of my code to the spaceship.
class GameScene: SKScene, SKPhysicsContactDelegate {
var ship = SKSpriteNode(imageNamed:"Spaceship")
let manager = CMMotionManager()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
ship.xScale = 1/5
ship.yScale = 1/5
ship.position = CGPoint(x: self.frame.width/(2), y: self.frame.height/1.5)
self.physicsWorld.contactDelegate = self
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = 0.1
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)! * 15), CGFloat((data?.acceleration.y)! * 15))
}
ship.physicsBody = SKPhysicsBody()
ship.physicsBody?.affectedByGravity = true
self.addChild(ship)
}
I already tried self.view?.bounds and making it into a physics body but my spaceship never comes into contact with it.
All I had to do was meddle around with the size of the screen in the .sks file. I specifically made mine 800x640 because I am testing on an I-Pod 5. Then I added this bit of code:
let edgeFrame = CGRect(origin: CGPoint(x: ((self.view?.frame.minX)! + 200) ,y: (self.view?.frame.minY)!), size: CGSize(width: (self.view?.frame.width)! + 90, height: (self.view?.frame.height)! + 90))
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: edgeFrame)
This made a proper boundary around my screen and did not allow my spaceship to leave the screen. Best of all this was a perfect fit so if you are testing on a different device then just adjust the width and height numbers as needed. Remember to also adjust your .sks screen size.
Credits to 0x141E for hinting me in the right direction.

Getting actual view size in Swift / IOS

Even after reading several posts in frame vs view I still cannot get this working. I open Xcode (7.3), create a new game project for IOS. On the default scene file, right after addChild, I add the following code:
print(self.view!.bounds.width)
print(self.view!.bounds.height)
print(self.frame.size.width)
print(self.frame.size.height)
print(UIScreen.mainScreen().bounds.size.width)
print(UIScreen.mainScreen().bounds.size.height)
I get following results when I run it for iPhone 6s:
667.0
375.0
1024.0
768.0
667.0
375.0
I can guess that first two numbers are Retina Pixel size at x2. I am trying to understand why frame size reports 1024x768 ?
Then I add following code to resize a simple background image to fill the screen:
self.anchorPoint = CGPointMake(0.5,0.5)
let theTexture = SKTexture(imageNamed: "intro_screen_phone")
let theSizeFromBounds = CGSizeMake(self.view!.bounds.width, self.view!.bounds.height)
let theImage = SKSpriteNode(texture: theTexture, color: SKColor.clearColor(), size: theSizeFromBounds)
I get an image smaller than the screen size. Image is displayed even smaller if I choose landscape mode.
I tried multiplying bounds width/height with two, hoping to get actual screen size but then the image gets too big. I also tried frame size which makes the image slightly bigger than the screen.
Main reason for my confusion, besides lack of knowledge is the fact that I've seen this exact example on a lesson working perfectly. Either I am missing something obvious or ?
The frame is given as 1024x768 because it is defined in points, not pixels.
If you want your scene to be the same size as your screen, before the scene is presented in your GameViewController, before:
skView.presentScene(scene)
use this:
scene.size = self.view.frame.size
which will make the scene the exact size of the screen.
Then you could easily make an image fill the scene like so:
func addBackground() {
let bgTexture = SKTexture(imageNamed: "NAME")
let bgSprite = SKSpriteNode(texture: bgTexture, color: SKColor.clearColor(), size: scene.size)
bgSprite.anchorPoint = CGPoint(x: 0, y: 0)
bgSprite.position = self.frame.origin
self.addChild(bgSprite)
}
Also, you may want to read up on the difference between a view's bounds and it's frame.

Why is frame.size 1024x768 in portrait mode in SpriteKit?

When I create a SpriteKit game from scratch using Swift, in the didMoveToView method of my GameScene I write:
print(frame.size.width)
print(frame.size.height)
I get back.
1024.0
768.0
My app is currently running is portrait mode. I thought the frame was the smallest rectangle that could contain the node.
If I add a shape to the screen to confirm what I am reading I add the following code:
anchorPoint = CGPoint(x: 0.5, y: 0.5)
let leftShape = SKShapeNode(rectOfSize: CGSize(width: frame.size.width/8, height: frame.size.height/2))
leftShape.fillColor = UIColor.redColor().colorWithAlphaComponent(0.2)
leftShape.strokeColor = UIColor.clearColor()
leftShape.name = "left"
addChild(leftShape)
I get the following:
None of this makes sense to me. The math doesn't add up. It seems like I missing a fundamental part the sizing in Spritekit or iOS in general.
I am using xcode 7 beta 3 which is Swift 2.0. Although I am using Swift to code this I am not opposed to a suggestion written in Objective-C
I had a similar problem and i solved putting this into gameViewController
scene.size = skView.bounds.size

Resources