SKSpriteNode not scalling properly - ios

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

Related

SpriteKit scrolling background image showing line between images

I'm learning how to make games in iOS, so I decided to replicate the first level of Mario Bros using my own assets, just to learn how to make them as well.
The issue I'm having right now is that, when creating the scrolling background, using this formula I found on Hacking with Swift, I keep getting a line in between my images.
I've checked that my images have no border in the AI file. (The images are the ones above)
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var player = SKSpriteNode()
private var bg = SKSpriteNode()
override func didMove(to view: SKView) {
let playerTexture = SKTexture(imageNamed: "player")
player = SKSpriteNode(texture: playerTexture)
player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addBackground()
self.addChild(player)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
// MARK: UTILITY FUNCTIONS
func addBackground() {
let bgTexture = SKTexture(imageNamed: "bg")
for i in 0 ... 1 {
bg = SKSpriteNode(texture: bgTexture)
//bg.position = CGPoint(x: (i == 0 ? bgTexture.size().width : bgTexture.size().width - 1) * CGFloat(i), y: self.frame.midY)
bg.position = CGPoint(x: (bgTexture.size().width * CGFloat(i)) - CGFloat(1 * i), y: 0)
bg.size.height = self.frame.height
//bg.anchorPoint = CGPoint.zero
bg.zPosition = -10
self.addChild(bg)
let moveLeft = SKAction.moveBy(x: -bgTexture.size().width, y: 0, duration: 5)
let moveReset = SKAction.moveBy(x: bgTexture.size().width, y: 0, duration: 0)
let moveLoop = SKAction.sequence([moveLeft, moveReset])
let moveForever = SKAction.repeatForever(moveLoop)
bg.run(moveForever)
}
}
}
Just create a new project, copy-paste it and you should see it running
I also changed my GameScene.sks size to: 2688 x 1242
And one last change I made to make the background appear in full screen was to set the LaunchScreen as stated in this answer which seems to be an issue in Xcode 12
I understand that the formula from Hacking with Swift post is making the images overlap by 1px, I've tried removing the 1 * i part from it, yet results are not different.
Other thing I did was to verify the images were "joining" perfectly together, and they do, the lines you can see in the image below are from the AI canvas, both images join in between "cactuses".
I also tried running it into a device in case it might be a bug in the simulator:
Your image has a thin black border on the left handside and along the top, 1 pixel wide. It's black. Remove that and try again.

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
}

SKSpriteNode showing non integer position when assigned an SKPhysicsBody

I have some very simple Swift code which places an SKSpriteNode on screen at position x:214, y:200. In the update method, I print the current position of said SKSpriteNode. If I omit all of the SKPhysicsBody code, then the position of my SKSpriteNode is shown in the debug window as (214.0, 200.0) which is as I would expect it. If, however, I run the program with the SKPhysicsBody code present, then the first print returns correct with (214.0, 200.0), but subsequent calls return (214.000015258789, 200.0).
This looks to me like something in the physics world is affecting (albeit in a very small way) the position of the SKSpriteNode. What I cannot fathom for the life of me is what that could be. My code is exactly as shown below, nothing more and nothing less. I think I have disabled everything that could possibly have any effect on the position. I am just hoping that I might have missed something silly.
The problem may seem trivial as the values are only fractionally off, however when I attempt to move the sprite smoothly across the screen (incrementing it's x position + 1 on each update) then it will occasionally miss an entire position, going from 289.0 to 289.99, then 291.0.
Any help would be much appreciated. It feels like a bug to me, but as I am fairly new to the physics side of SpriteKit, I would like to be certain.
import SpriteKit
class GameScene: SKScene {
private var playerSprite : SKSpriteNode!
override func didMoveToView(view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)//borderBody
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
playerSprite = SKSpriteNode(color: UIColor.yellowColor(), size: CGSize(width: 100, height: 100))
playerSprite.position = CGPoint(x: 214, y: 200)
playerSprite.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 100, height: 100))
if let playerSpritePhysicsObject = playerSprite.physicsBody {
playerSpritePhysicsObject.usesPreciseCollisionDetection = true
playerSpritePhysicsObject.affectedByGravity = false
playerSpritePhysicsObject.allowsRotation = false
playerSpritePhysicsObject.dynamic = false
playerSpritePhysicsObject.angularVelocity = 0.0
playerSpritePhysicsObject.friction = 0.0
playerSpritePhysicsObject.linearDamping = 0.0
playerSpritePhysicsObject.angularDamping = 0.0
playerSpritePhysicsObject.velocity = CGVectorMake(0, 0)
playerSpritePhysicsObject.restitution = 0.0
}
self.addChild(playerSprite)
}
override func update(currentTime: NSTimeInterval) {
print("playerSprite Position = \(playerSprite.position)")
}
}
Here is the debug window output:
playerSprite Position = (214.0, 200.0)
2017-06-19 20:39:47.682 PhysicsTest[9193:2340365] <SKMetalLayer: 0x14fe6e760>: calling -display has no effect.
playerSprite Position = (214.000015258789, 200.0)
playerSprite Position = (214.000030517578, 200.0)
playerSprite Position = (214.000030517578, 200.0)
playerSprite Position = (214.000030517578, 200.0)
playerSprite Position = (214.000030517578, 200.0)
After some time agonising over this problem, I think I may have found a work around. It's nasty but does the trick. Simply reset the position of the SKSpriteNode in the didFinishUpdate() function which occurs after the didSimulatePhysics() function has complete.
override func didFinishUpdate() {
playerSprite.position.x = 214
}
Having tested further, I found this didn't work for iOS 7.1. In order for it work on older devices, you must place the code in the didSimulatePhysics() function instead:
override func didSimulatePhysics() {
playerSprite.position.x = 214
}

Nodes not appearing

So I'm trying to get a Node in xcode to appear, but it doesnt... here's the code.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let levelLabelNode = SKLabelNode(fontNamed: "Arial")
levelLabelNode.text = "Level"
levelLabelNode.fontSize = 30
levelLabelNode.fontColor = SKColor.green
levelLabelNode.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height*0.75)
self.addChild(levelLabelNode)
}
}
This had me stuck for a while. The coordinate system of the programme (assuming you are running from the default) has point (0, 0) at the centre of the screen. self.frame.size.width refers to the entire width of the screen. Therefore, positioning a node at self.frame.size.width/2 would position a node at the rightmost edge of the screen. If you want to position your node centred horizontally and 3/4 up the screen try:
levelLabelNode.position = CGPoint(x: 0, y: self.frame.height/4)
Hope this helped!
Remember: The coordinate system starts with (0, 0) in the centre and self.frame.size.width is the entire width of the screen.

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.

Resources