Swift & SpriteKit: Full screen image - ios

I'm making a first scene with SpriteKit in swift. I created a background image for 4 inch iPhone with dimensions 640x1136. I placed the image as sprite in centre of the screen, but the image is not full screen, there are edges missing.
Next, I tried to resize the image within app. When I did image.size.height = self.size.height the height got properly resized along full iPhone screen.
But when I did the same with width image.size.width = self.size.width then the picture got extremely stretched width wise.
I printed the dimensions of self.size and it turns out the dimension for my iPhone 5s are 1024, 768. This is total nonsense as the screen can't be wider than its height on iPhone. The app is universal but the only orientation is set to portrait.
Edit: This is the code I use to put the image on the screen
class GameScene: SKScene
{
let menuImage = SKSpriteNode(imageNamed: "menuImage")
override func didMoveToView(view: SKView)
{
/* Setup your scene here */
menuImage.anchorPoint = CGPointMake(0.5, 0.5)
menuImage.size.height = self.size.height
menuImage.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(menuImage)
}

You need to add
scene.size = skView.bounds.size
or for Swift 5
scene.size = self.view.bounds.size
just before skView.presentScene(scene) in your gameViewController file

This is what worked for me to stretch the background image to fit the whole screen in Swift 5.
background.size = CGSize (width: frame.maxX, height: frame.maxY)

Related

Resizing screen for iPhone X using SpriteKit

So, there are tens of questions on Stack Overflow referencing about fitting a SpriteKit scene properly on the iPhone X. I could not find anyone however, where there is the same gap.
Is there anyway I can resize the screen just for the iPhone X? Or do I have to create another scene, JUST for the iPhone X to fit the screen?
Here is what I am getting using .aspectFit:
I want to extend that scene towards just under the notch and just above where the screen corner curves are. I see many games which fit perfectly, but have not found a suitable solution.
Note: I cannot use aspectFill as some parts of the game go out of view.
Any links or answers will be greatly appreciated!
EDIT
resizeFill is too big, everything gets enlarged
I have tried all 4 options for sizing the screen, but none did the job. Anyone got suggestions?
I feel like the solution is going to be done pragmatically because it will need to be relative to those borders.
Add this to your code it will set the Playable Game Area for both the iPhone X and the regular iPhones and then use the gameArea constant to define how far things can move.
//MARK: Playable Game Araa
let gameArea: CGRect
override init(size: CGSize) {
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
//iPhone X
let maxAspectRatio: CGFloat = 19.5/9.0
let playableWidth = size.height / maxAspectRatio
let margin = (size.width - playableWidth) / 2
gameArea = CGRect(x: margin, y: 0, width: playableWidth, height: size.height)
} else {
let maxAspectRatio: CGFloat = 16.0/9.0
let playableWidth = size.height / maxAspectRatio
let margin = (size.width - playableWidth) / 2
gameArea = CGRect(x: margin, y: 0, width: playableWidth, height: size.height)
}
super.init(size: size)
}
We (me and snapbackdev) found a solution, suitable for any phones now or any coming in the future to set the screen to fit the whole view, without stretching or empty spaces.
We ended up doing everything programmatically, which made everything work.
In GameViewController.swift, this code loads the GameScene:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load a GameScene with the size of the view
let scene = GameScene(size: view.frame.size)
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
This may also be done in viewDidLayoutSubviews instead, depending on what you are doing.
The only code needed to setup the scene in GameScene.swift is:
override func didMove(to view: SKView) {
// Set (0, 0) as the centre of the screen
scene?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
// Create the frame
let frameEdge = SKPhysicsBody(edgeLoopFrom: frame)
self.physicsBody = frameEdge
}

How do I place a background image wider than the scene using SpriteKit?

What I need
I would like to know how to place a SKSpriteNode as a background image with a SKTexture of 3,240 by 1,920 pixels on a SKScene while I keep it's aspect ratio using SpriteKit on Swift 3.
Why I need it
The idea behind this is to have an image wider than the scene to achieve a parallax effect when the player moves.
What I've tried
The following code place the image but shrink it to fit the scene breaking its aspect ratio horizontally:
let texture = SKTexture(imageNamed: "Background")
let background = SKSpriteNode(texture: texture)
if let view = self.view {
background.size = view.bounds.size
}
background.anchorPoint = CGPoint.zero
addChild(background)
Scene screenshot with its width ratio broken
The following code variant place the image but squish it also breaking its aspect ratio in this case only vertically:
let texture = SKTexture(imageNamed: "Background")
let background = SKSpriteNode(texture: texture)
if let view = self.view {
background.size.height = view.bounds.size.height
}
background.anchorPoint = CGPoint.zero
addChild(background)
Scene screenshot with its height ratio broken
If I leave the background.size property unset no image appears on the scene.
If I try the same code with a SKTexture of 1,080 by 1,920 pixels it works fine.
Also, the scene scaleMode property is set with following line of code:
gameScene.scaleMode = .aspectFill
Thanks
Any kind of help will be very much appreciated.
Thank you very much for your time.
Cheers,
Federico.

SpriteKit Sprites Not Scaling Properly

The sprites in my SpriteKit game, though properly scaled, are being rendered extremely small in my scenes. I've been following a few different tutorials using similarly sized sprites and theirs are scaled fine in the simulator. Below are some code snippets and screenshots. For example, the pause button on the top right of the screen is crazy small, yet the actual resolutions of the assets are standard in size.
GameScene.swift
private let pauseTex = SKTexture(imageNamed: "PauseButton")
...
private func setupPause() {
pauseBtn.texture = pauseTex
pauseBtn.size = pauseTex.size()
pauseBtn.anchorPoint = CGPoint(x: 1.0, y: 1.0)
pauseBtn.position = CGPoint(x: size.width, y: size.height)
pauseBtn.zPosition = Layer.Foreground.rawValue
worldNode.addChild(pauseBtn)Btn)
...
}
GameVC.swift
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
...
if let skView = self.view as? SKView {
if skView.scene == nil {
let aspectRatio = view.bounds.size.height / view.bounds.size.width
let scene = MenuScene(size: CGSize(width: 750, height: 750 * aspectRatio))
scene.scaleMode = .aspectFill
skView.ignoresSiblingOrder = true
...
}
}
}
Basically what has happened here is Mike got confused with scene size vs screen size. He was thinking that since the sprite was 32 pixels, it should be taking up 10% of the iPhone SE Screen since it has a 1x width of 320 (Natively it is 640 [2x]) But in reality, his scene is 750 pixels wide, so it was only showing at 5%.
He switched his scene size to be 375x667 (iPhone 6 non retina resolution) to properly use the retina graphics, and now everything should be aligning up for him.
The only issue he is going to come across with now, is cropping on iPad devices. He just needs to work in safe zones for this.
The size of your assets will not always reflect the desired size on-screen due to resolution discrepancies between different devices (this is true if the SKScene's scaleMode is set to resizeFill). If you want a specific size for your sprite, you need to set the SKNode's size property to your desired value.
What this would look like in your method:
private func setupPause() {
pauseBtn.anchorPoint = CGPoint(x: 1.0, y: 1.0)
// Set size property (arbitrarily selected below)
pauseBtn.size = CGSize(width: 50, height: 50)
pauseBtn.position = CGPoint(x: size.width, y: size.height)
pauseBtn.zPosition = Layer.Foreground.rawValue
worldNode.addChild(pauseBtn)
}

Universal game position

Explanation
I'm building a game in a iPhone 5s, but now I want to make it universal, so it can run in all iPhones (or at least 4s ahead) and all iPads (or at least iPad 2 ahead).
By now, I pretty much created those 3 images (1x, 2x and 3x). So there's a 50x50 square (#1x), a 100x100 square (#2x) and a 150x150 square (#3x).
This is the sample project (download here) I'm testing in:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scene!.scaleMode = SKSceneScaleMode.ResizeFill //usually at GameViewController, not GameScene
let square = SKSpriteNode(imageNamed: "square")
square.anchorPoint = CGPointZero
square.position = CGPoint(x: self.frame.width / 1.095, y: self.frame.height / 1.1875) //superior right on iPhone 5/5s
addChild(square)
}
}
and these are the images:
Testing
When I run on each device, this is happens:
iPhone 4s - wrong position
iPhone 5/5s - right position (it was set up here)
iPhone 6/6s - wrong position
iPhone 6+/6s+ - wrong position
iPad 2 - wrong position
iPad Air/iPad Air 2 - wrong position
iPad Pro - wrong position
iPad Retina - wrong position
You can see better what happens by clicking on the image below.
Question
Basically, my question is: how can I make this universal? I mean, how can I make the square be positioned at the same relative place on the devices above?
Attempts
Michael Austin's attempt (download here)
import SpriteKit
// MARK: Screen Dimensions
let screenWidth = CGFloat(1024)
let screenHeight = CGFloat(768)
// MARK: Node Sizes
let square = SKSpriteNode(imageNamed: "square")
let nodeConstantWidth = screenWidth/square.size.width * 0.088
let nodeConstantHeight = screenHeight/square.size.height * 0.158
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scene!.scaleMode = SKSceneScaleMode.Fill //usually at GameViewController, not GameScene
square.xScale = nodeConstantWidth
square.yScale = nodeConstantHeight
square.anchorPoint = CGPointZero
square.position = CGPoint(x: screenWidth / 1.5, y: screenHeight / 1.5)
addChild(square)
}
}
Timmy Sorensen's attempt (download here)
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scene!.scaleMode = SKSceneScaleMode.ResizeFill //usually at GameViewController, not GameScene
let square = SKSpriteNode(imageNamed: "square")
square.anchorPoint = CGPointZero
square.position = CGPoint(x: self.frame.width / 1.095, y: self.frame.height / 1.1875) //superior right on iPhone 5/5s
addChild(square)
}
}
the square isn't positioning at the same place on every device, only on the device where position was set up. I made a little comparison below.
Try mathematically setting the size. Use UIScreen.mainScreen().bounds.width and UIScreen.mainScreen().bounds.height to find the device size. Use a little math to set up a proportion between your .sks scene and the device's size. Then use node.setScale to set the scale of your Sprite
Here is some sample code. Declare these as universal constants:
// MARK: Screen Dimensions
let screenSize = UIScreen.mainScreen().bounds
let screenWidth = screenSize.size.width
let screenHeight = screenSize.size.height
// MARK: Node Sizes
let node = SKSpriteNode(imageNamed: "image")
let nodeConstantWidth = screenWidth/node.size.width*0.3
If you were to move the declaration of nodeConstantWidth to GameScene, It will continually scale itself each time the scene is rendered. To avoid this, we just declare it once universally. Mathematically, the equation above will set the node's width to 30% of the screen's width on any device, and the height will be calculated accordingly. Change the '0.3' to any number to adjust this. Then in GameScene:
node.setScale(nodeConstantWidth)
This is how I placed my objects
scoreLbl.position = CGPoint(x: scene!.frame.width / 2, y: scene!.frame.height / 1.3)
airDefense.positon = CGPoint(x: frame.size.width / 2, y: frame.size.height / 5
This takes the screen and divides it out so its always in the same place no matter how big the screen is.
It is the same as using
CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
to place something in the middle of the screen.

SKSpriteNode not scalling properly

I'm developing a small game using Swift & SpriteKit. When I add a SKSpriteNode for Restart button, it doesn't scale properly.
The size of Restart button is 100px in height and width. If I don't set scale , it covers the whole screen and make the screen appear white. I figured out that if I setScale to 0.005 only than it appears on screen, but not in proper size.
import Foundation
import SpriteKit
class EndScene: SKScene {
var restartBtn = SKSpriteNode()
override func didMoveToView(view: SKView) {
background()
restartGame()
}
func restartGame() {
restartBtn = SKSpriteNode(imageNamed: "restartBtn")
restartBtn.setScale(0.005)
restartBtn.position = CGPoint(x: self.size.width / 2, y: self.size.height / 4)
restartBtn.zPosition = 1
self.addChild(restartBtn)
}
func background() {
let bkg = SKSpriteNode(imageNamed: "Background")
bkg.size = self.frame.size
bkg.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
bkg.zPosition = -2
self.addChild(bkg)
}
}
Here is the output of this code,
Restart Button Output
UPDATE
I put scene!.scaleMode = .AspectFill right inside didMoveToView function and it helped in rendering the shape of the SpriteNode properly. But still I have to setScale(0.001) to make the size of Restart button fit in the screen. Can anyone assist me what line of code I'm still missing?
Instead of using .setScale try using restartBtn.size = CGSize(Width: 50, Height: 50)
This uses the Sprite Kit's resize formula.
I ran into this as well. It happened when I forgot to specify the size of the scene. Instead of calling initWithSize I just wrote init and the scene started to scale down all nodes by x1000. Nodes were visible, but they required a scale of 0.001

Resources