How do you adapt your nodes position to landscape & portrait - ios

Im trying to make a game board that is universal and can be played on ipad/iphones either in portrait or landscape. The tiles looks fine in portrait but when i switch to landscape it gets cut off. How can i make sure the board will always be centered?
override func didMove(to view: SKView) {
let numRows = 7
let numCols = 7
var counter = 0
let squareSize = CGSize(width: view.bounds.width/7, height: view.bounds.width/7)
for row in 0..<numRows{
for col in 0..<numCols {
if counter == 49 {
break
}
let spriteNode = SKSpriteNode(imageNamed: "piece\(counter)")
spriteNode.size = squareSize
spriteNode.position = CGPoint(x: 0-(frame.width*0.43)+(CGFloat(col)*spriteNode.size.width), y: -CGFloat(row)*spriteNode.size.height + frame.height/3 )
addChild(spriteNode)
counter = counter+1
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "GameScene") {
scene.scaleMode = .resizeFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
It looks like this:

I would change the Scene scale mode from scene.scaleMode = .resizeFill to scene.scaleMode = .aspectFit. In a game the SKView frame size is not required and you can work within your own coordinate space (size property of the scene) e.g. set to 100 x 100. -> Interface Builder setting.
Don't relate anything on the view frame/bounds of the SKView. The code from your scene controller should work then as below, all related to the size of the scene.
override func didMove(to view: SKView) {
let numRows = 7
let numCols = 7
var counter = 0
let squareSize = CGSize(width: size.width/7, height: size.width/7)
for row in 0..<numRows{
for col in 0..<numCols {
if counter == 49 {
break
}
let spriteNode = SKSpriteNode(imageNamed: "piece\(counter)")
spriteNode.size = squareSize
spriteNode.position = CGPoint(x: 0-(size.width*0.43)+(CGFloat(col)*spriteNode.size.width), y: -CGFloat(row)*spriteNode.size.height + size.height/3 )
addChild(spriteNode)
counter = counter+1
}
}
}

Related

SpriteKit - safe area layout Iphone X

So, Currently I have a game that i’ve made in sprite kit and have used this way of fitting everything to the screen size:
buyButton = SKSpriteNode(texture: SKTexture(imageNamed: "BuyButton"), color: .clear, size: CGSize(width: frame.maxX / 2.9, height: frame.maxY / 10))
buyButton.position = CGPoint(x:-frame.maxX + frame.midX*2, y: -frame.maxY + frame.midY*1.655)
addChild(buyButton)
as you can see it uses the frame to calculate the width and height along with the position of the node on the scene, this works with all screen sizes that I have been using from the 6s to the 8 Plus.
but when it comes to the iPhone X there is a problem with it. it seems to stretch everything because of the screen size being bigger and oddly shaped as you can see below compared to the iPhone 8 Plus
I’ve been looking for solutions to this problem but none have really helped or just even make it worse and I couldn’t understand how to programmatically use the safe area layout in my sprite kit project like in this solution here
My Question is
How do I go about getting everything to fit in the iPhone X’s screen so that it fits and isn’t cutting off the score labels and stuff I have in the top right corners?
EDIT 2:
itemDescriptionLabel = UILabel(frame: CGRect(x: frame.maxX - 150, y: frame.maxY - 130 , width: frame.maxX / 0.7, height: frame.maxY / 3.5))
itemDescriptionLabel.font = UIFont(name: "SFCompactRounded-Light", size: 17)
itemDescriptionLabel.text = "This purchase stops all the popup ads that happen after a certain amount of time playing the game from showing up."
itemDescriptionLabel.numberOfLines = 4
itemDescriptionLabel.adjustsFontSizeToFitWidth = true
self.scene?.view?.addSubview(itemDescriptionLabel)
EDIT 3
EDIT 4
let safeAreaInsets = yourSpriteKitView.safeAreaInsets;
buyButton.position = CGPoint(x:safeAreaInsets.left - frame.maxX + frame.midX*2, y: safeAreaInsets.top - frame.maxY + frame.midY*1.655)
EDIT 5
screenWidth = self.view!.bounds.width
screenHeight = self.view!.bounds.height
coinScoreLabel = Score(num: 0,color: UIColor(red:1.0, green: 1.0, blue: 0.0, alpha: 1), size: 50, useFont: "SFCompactRounded-Heavy" ) // UIColor(red:1.00, green:0.81, blue:0.07, alpha:1.0)
coinScoreLabel.name = "coinScoreLabel"
coinScoreLabel.horizontalAlignmentMode = .right
//coinScoreLabel.verticalAlignmentMode = .top
coinScoreLabel.position = CGPoint(x: screenWidth / 2.02, y: inset.top - screenHeight / 2.02)
coinScoreLabel.zPosition = 750
addChild(coinScoreLabel)
I tried it on another SpriteNode that I have such as this one below, which it worked for I have no idea why the yellow label is doing this.
playButton = SKSpriteNode(texture: SKTexture(imageNamed: "GameOverMenuPlayButton"), color: .clear, size: CGSize(width: 120, height: 65))
playButton.position = CGPoint(x: inset.right - frame.midX, y: inset.bottom + frame.minY + playButton.size.height / 1.7)
playButton.zPosition = 1000
deathMenuNode.addChild(playButton)
It came out like this which is perfect:
Interesting question. The problem borns when we try to "transform" our view to SKView to create our game scene. Essentially, we could intercept that moment (using viewWillLayoutSubviews instead of viewDidLoad) and transform the view frame accordling with the safeAreaLayoutGuide layout frame property.
Some code as example:
GameViewController:
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if #available(iOS 11.0, *), let view = self.view {
view.frame = self.view.safeAreaLayoutGuide.layoutFrame
}
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
view.showsDrawCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
override func viewDidLoad() {
super.viewDidLoad()
print("---")
print("∙ \(type(of: self))")
print("---")
}
}
GameScene:
class GameScene: SKScene {
override func didMove(to view: SKView) {
print("---")
print("∙ \(type(of: self))")
print("---")
let labLeftTop = SKLabelNode.init(text: "LEFTTOP")
labLeftTop.horizontalAlignmentMode = .left
labLeftTop.verticalAlignmentMode = .top
let labRightTop = SKLabelNode.init(text: "RIGHTTOP")
labRightTop.horizontalAlignmentMode = .right
labRightTop.verticalAlignmentMode = .top
let labLeftBottom = SKLabelNode.init(text: "LEFTBTM")
labLeftBottom.horizontalAlignmentMode = .left
labLeftBottom.verticalAlignmentMode = .bottom
let labRightBottom = SKLabelNode.init(text: "RIGHTBTM")
labRightBottom.horizontalAlignmentMode = .right
labRightBottom.verticalAlignmentMode = .bottom
self.addChild(labLeftTop)
self.addChild(labRightTop)
self.addChild(labLeftBottom)
self.addChild(labRightBottom)
labLeftTop.position = CGPoint(x:0,y:self.frame.height)
labRightTop.position = CGPoint(x:self.frame.width,y:self.frame.height)
labLeftBottom.position = CGPoint(x:0,y:0)
labRightBottom.position = CGPoint(x:self.frame.width,y:0)
}
}
Other way to do:
Another way to obtain just only the differences between the safe area layout frame and the current view frame without passing through viewWillLayoutSubviews for launching our scene, could be using protocols.
P.S.: Down below, I've report a reference image where you can see the different sizes between:
the main screen height (our view height)
safe area
status bar height (on the top, 44 px for the iPhone X)
GameViewController:
protocol LayoutSubviewDelegate: class {
func safeAreaUpdated()
}
class GameViewController: UIViewController {
weak var layoutSubviewDelegate:LayoutSubviewDelegate?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let _ = self.view {
layoutSubviewDelegate?.safeAreaUpdated()
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("---")
print("∙ \(type(of: self))")
print("---")
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
view.showsPhysics = true
view.showsDrawCount = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
GameScene:
class GameScene: SKScene,LayoutSubviewDelegate {
var labLeftTop:SKLabelNode!
var labRightTop:SKLabelNode!
var labLeftBottom:SKLabelNode!
var labRightBottom:SKLabelNode!
func safeAreaUpdated() {
if let view = self.view {
let top = view.bounds.height-(view.bounds.height-view.safeAreaLayoutGuide.layoutFrame.height)+UIApplication.shared.statusBarFrame.height
let bottom = view.bounds.height - view.safeAreaLayoutGuide.layoutFrame.height-UIApplication.shared.statusBarFrame.height
refreshPositions(top: top, bottom: bottom)
}
}
override func didMove(to view: SKView) {
print("---")
print("∙ \(type(of: self))")
print("---")
if let view = self.view, let controller = view.next, controller is GameViewController {
(controller as! GameViewController).layoutSubviewDelegate = self
}
labLeftTop = SKLabelNode.init(text: "LEFTTOP")
labLeftTop.horizontalAlignmentMode = .left
labLeftTop.verticalAlignmentMode = .top
labRightTop = SKLabelNode.init(text: "RIGHTTOP")
labRightTop.horizontalAlignmentMode = .right
labRightTop.verticalAlignmentMode = .top
labLeftBottom = SKLabelNode.init(text: "LEFTBTM")
labLeftBottom.horizontalAlignmentMode = .left
labLeftBottom.verticalAlignmentMode = .bottom
labRightBottom = SKLabelNode.init(text: "RIGHTBTM")
labRightBottom.horizontalAlignmentMode = .right
labRightBottom.verticalAlignmentMode = .bottom
self.addChild(labLeftTop)
self.addChild(labRightTop)
self.addChild(labLeftBottom)
self.addChild(labRightBottom)
labLeftTop.position = CGPoint(x:0,y:self.frame.height)
labRightTop.position = CGPoint(x:self.frame.width,y:self.frame.height)
labLeftBottom.position = CGPoint(x:0,y:0)
labRightBottom.position = CGPoint(x:self.frame.width,y:0)
}
func refreshPositions(top:CGFloat,bottom:CGFloat){
labLeftTop.position = CGPoint(x:0,y:top)
labRightTop.position = CGPoint(x:self.frame.width,y:top)
labLeftBottom.position = CGPoint(x:0,y:bottom)
labRightBottom.position = CGPoint(x:self.frame.width,y:bottom)
}
}
Output:
Subclassing methods:
Another way to get the correct safe area dimensions without touch your current classes code, could be made using some subclass, so for our GameViewController we set it as GameController and for GameScene we can set it as GenericScene like this example:
GameViewController:
class GameViewController: GameController {
override func viewDidLoad() {
super.viewDidLoad()
guard let view = self.view as! SKView? else { return }
view.ignoresSiblingOrder = true
let scene = GameScene(size:view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
protocol LayoutSubviewDelegate: class {
func safeAreaUpdated()
}
class GameController:UIViewController {
weak var layoutSubviewDelegate:LayoutSubviewDelegate?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let _ = self.view {
layoutSubviewDelegate?.safeAreaUpdated()
}
}
}
GameScene:
class GameScene: GenericScene {
override func safeAreaUpdated() {
super.safeAreaUpdated()
if let view = self.view {
let insets = view.safeAreaInsets
// launch your code to update positions here
}
}
override func didMove(to view: SKView) {
super.didMove(to: view)
// your stuff
}
}
class GenericScene: SKScene, LayoutSubviewDelegate {
func safeAreaUpdated(){}
override func didMove(to view: SKView) {
if let view = self.view, let controller = view.next, controller is GameViewController {
(controller as! GameViewController).layoutSubviewDelegate = self
}
}
}
References:
You can get margins through your view's safeAreaInsets. The insets will be zero on most devices, but non-zero on the iPhone X.
For example replace your line with this:
let safeAreaInsets = yourSpriteKitView.safeAreaInsets;
buyButton.position = CGPoint(x:safeAreaInsets.left - frame.maxX + frame.midX*2, y: safeAreaInsets.top - frame.maxY + frame.midY*1.655)

How To Have Character Slide Back and Forth Automatically and Infinitely?

I'm an ubernoob developing a game using SpriteKit from scratch and im trying to make a character that will slide back and forth off the sides of the screen (in landscape mode) until collision is detected with another node (that I will add later). Think of pong and how the paddle can move side to side except I want that movement to be completely automatic/infinite.
Side Note: I plan on having this character jump when the screen is touched but continue with the back and forth movement. idk if that makes a difference in your approach.
Ok, so this answer isn't perfect but it's working for me right now so:
basically you touch the screen to spawn a boxd, and when the paddle touches the box some stuff happens (it stops moving):
import SpriteKit
// constants!
class GameScene: SKScene, SKPhysicsContactDelegate {
// A little complicated, but basically we want to have a constant speed across all screen sizes
var sliderSpeed: CGFloat { return self.size.width / 3 }
var slider = SKSpriteNode()
var sliderVelocity = CGFloat(0)
var sliderIsContacted = false
let boxMask = UInt32(2)
let sliderMask = UInt32(4)
// For use to contact slider
func spawnBox(at pos: CGPoint) {
let rect = CGRect(origin: CGPoint.zero, size: CGSize(width: 45, height: 45))
let shape = SKShapeNode(rect: rect)
shape.fillColor = .green
shape.position = pos
let pb = SKPhysicsBody(rectangleOf: rect.size)
pb.categoryBitMask = boxMask
pb.contactTestBitMask = sliderMask
shape.physicsBody = pb
addChild(shape)
}
func setupSlider() {
sliderVelocity = sliderSpeed
let rect = CGRect(origin: CGPoint.zero, size: CGSize(width: 45, height: 10))
let shape = SKShapeNode(rect: rect)
shape.fillColor = .blue
let pb = SKPhysicsBody(rectangleOf: rect.size)
pb.categoryBitMask = sliderMask
pb.contactTestBitMask = boxMask
pb.velocity.dx = sliderVelocity // moves our slider to the right!
// A little complicated, but basically we want to have a spritenode, not a shapenode:
slider = SKSpriteNode(texture: view!.texture(from: shape))
slider.physicsBody = pb
addChild(slider)
}
func setupWorld() {
let pb = SKPhysicsBody(edgeLoopFrom: frame)
pb.categoryBitMask = UInt32(0)
self.physicsBody = pb
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector.zero
}
override func didMove(to view: SKView) {
setupSlider()
setupWorld()
}
}
// Game loop:
extension GameScene {
// touchesBegan in iOS:
override func mouseDown(with event: NSEvent) {
let location = event.location(in: self)
spawnBox(at: location)
}
override func update(_ currentTime: TimeInterval) {
let sliderPB = slider.physicsBody!
let halfWidth = slider.size.width/2
// move slider left when it reaches far right border:
if sliderPB.velocity.dx > 0 {
if slider.position.x >= (frame.maxX - halfWidth) {
sliderVelocity = -sliderSpeed
}
}
// move slider right when it reaches far left border:
else {
if slider.position.x <= (frame.minX + halfWidth) {
sliderVelocity = sliderSpeed
}
}
// Keep slider at constant rate:
if sliderIsContacted == false {
sliderPB.velocity.dx = sliderVelocity
}
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask + contact.bodyB.categoryBitMask == sliderMask + boxMask {
sliderIsContacted = true
slider.physicsBody!.velocity.dx = 0 // stop slider
}
}
}
more complicated than it shoudl be, but I"m sdrunk so this is what I got :)
}hope it helps.

SpriteKit world wrong positioning

I'm trying to make my first game using SpriteKit & Swift. I think I understood the concept of World SKNode, but I can't make it work properly.
I add world:SKNode as a child to the GameScene. Then I add 9 child SKSpriteNodes (400px X 400px from starting from 200px X 200px point, because spritesNodes are centered). And they position perfectly, BUT I can't figure out how to get bounds of my world SKNode.
For example, how can I show left down corner of my world?
import SpriteKit
class GameScene: SKScene {
let cityPartSide:Int = 400
let startCoord:Int = 200
var world:SKNode = SKNode()
override func didMoveToView(view: SKView) {
//self.world = SKNode()
self.world.name = "world"
self.addChild(self.world)
let map:[[UInt8]] = [[16, 16, 16],
[16, 16, 16],
[16, 16, 16]]
drawMap(world, map: map)
}
func drawMap(world:SKNode, map:[[UInt8]]) {
let lineSize:Int = map[0].count
var lineCounter = map.count
while lineCounter > 0 {
lineCounter -= 1
var elemCounter = 0
while elemCounter < lineSize {
var sprite:SKSpriteNode = SKSpriteNode()
let currentPartNumb = map[lineCounter][elemCounter]
if currentPartNumb == 0 {
elemCounter += 1
continue
} else {
sprite = SKSpriteNode(imageNamed: "cell")
}
//CHOOSE IMAGE
sprite.position = CGPoint(x: startCoord + elemCounter * cityPartSide, y: startCoord + ((lineSize-1)-lineCounter)*cityPartSide)
world.addChild(sprite)
elemCounter += 1
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
But what I get by running this code is that vertically it is positioned perfectly, but left side of the world is somewhere out of the screen.
Oh, that was quite easy. I needed to add this
scene.size = self.view.frame.size
in GameViewController.swift's viewDidLoad like this
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
scene.size = self.view.frame.size //<---HERE
skView.presentScene(scene)
}
}
//...etc.

Why does SpriteKit crash when transitioning back and forth between scenes

I checked the answer here and on other sites but didn't get a definitive solution.
I have two scenes
Scene 1:
class GameMenuScene: SKScene {
override func didMoveToView(view: SKView) {
// Add background
var background: SKSpriteNode = SKSpriteNode(imageNamed: "Starfield")
background.position = CGPointMake(-20, 0)
background.size = CGSizeMake(self.size.width + 40, self.size.height)
background.anchorPoint = CGPointZero
background.blendMode = SKBlendMode.Replace
self.addChild(background)
// Add game title
gameTitle = SKSpriteNode(imageNamed: "Title")
gameTitle.name = "Game Title"
gameTitle.xScale = scale
gameTitle.yScale = scale
gameTitle.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.height * 0.75)
self.addChild(gameTitle)
// Add scoreboard
scoreboard = SKSpriteNode(imageNamed: "ScoreBoard")
scoreboard.name = "Scoreboard"
scoreboard.xScale = scale
scoreboard.yScale = scale
scoreboard.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.height * 0.50)
self.addChild(scoreboard)
// Add play button
playButton = SKSpriteNode(imageNamed: "PlayButton")
playButton.name = "PlayButton"
playButton.xScale = scale
playButton.yScale = scale
playButton.position = CGPoint(x: CGRectGetMidX(self.frame), y: self.frame.height * 0.25)
self.addChild(playButton)
// Add menu score label
var menuScoreLabel = SKLabelNode()
menuScoreLabel.fontName = fontName
menuScoreLabel.fontSize = scoreFontsize
menuScoreLabel.text = String(score)
menuScoreLabel.position = CGPointMake(scoreboard.position.x - (scoreboard.size.width / 4), scoreboard.position.y - (scoreboard.size.height / 4))
menuScoreLabel.zPosition = 10
self.addChild(menuScoreLabel)
// Add menu top score label
var menuTopScoreLabel = SKLabelNode()
menuTopScoreLabel.fontName = fontName
menuTopScoreLabel.fontSize = scoreFontsize
menuTopScoreLabel.text = String(userDefaults.integerForKey("TopScore"))
menuTopScoreLabel.position = CGPointMake(scoreboard.position.x + (scoreboard.size.width / 4), scoreboard.position.y - (scoreboard.size.height / 4))
menuTopScoreLabel.zPosition = 10
self.addChild(menuTopScoreLabel)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
// Get touch location
var touchLocation = touch.locationInNode(self)
// Check if Play button is presssed
if playButton.containsPoint(touchLocation) == true {
//println("right node touched")
// Transition to GameScene.swift
var transition: SKTransition = SKTransition.fadeWithDuration(1)
// Configure the view.
let scene = GameScene()
let skView = self.view! as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
skView.presentScene(scene, transition: transition)
}
}
}
}
Scene 2:
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Add background
var background: SKSpriteNode = SKSpriteNode(imageNamed: "Starfield")
background.position = CGPointMake(-20, 0)
background.size = CGSizeMake(self.size.width + 40, self.size.height)
background.anchorPoint = CGPointZero
background.blendMode = SKBlendMode.Replace
self.addChild(background)
// Add mainlayer & labelHolderLayer
self.addChild(mainLayer)
self.addChild(labelHolderLayer)
// Add cannon
cannon = SKSpriteNode(imageNamed: "Cannon")
cannon.name = "Cannon"
cannon.position = CGPoint(x: CGRectGetMidX(self.frame), y: 0)
self.addChild(cannon)
// Add score label
scoreLabel.name = "Score Label"
scoreLabel.fontName = fontName
scoreLabel.fontSize = scoreFontsize
scoreLabel.text = String(score)
scoreLabel.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height - scoreFontsize)
scoreLabel.zPosition = 10
self.addChild(scoreLabel)
// Add LivesDisplay
livesDisplay = SKSpriteNode(imageNamed: "Ammo5")
livesDisplay.name = "livesDisplay"
livesDisplay.position = CGPoint(x: self.size.width - livesDisplay.size.width, y: self.size.height - livesDisplay.size.height)
self.addChild(livesDisplay)
// Settings for new game
newGame()
}
func gameIsOver() {
// Set game over flag and stop movement
gameOver = 1
mainLayer.speed = 0
mainLayer.paused = true
// Add game over label
gameOverLabel.fontName = fontName
gameOverLabel.fontSize = gameOverFontsize
gameOverLabel.text = "Game Over!"
gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
gameOverLabel.zPosition = 10
labelHolderLayer.addChild(gameOverLabel)
// Set main menu top score
if score > userDefaults.integerForKey("TopScore") {
userDefaults.setInteger(score, forKey: "TopScore")
userDefaults.synchronize()
}
// Run acton sequence (wait a few seconds before transitioning)
runAction(SKAction.sequence([SKAction.waitForDuration(1), SKAction.runBlock({ () -> Void in
// Transition back to GameMenuScene
var transition: SKTransition = SKTransition.fadeWithDuration(1)
// Configure the view.
let scene = GameMenuScene()
let skView = self.view! as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.size = skView.bounds.size
scene.scaleMode = .AspectFill
skView.presentScene(scene, transition: transition)
})]))
}
}
When I initially launch the app, I can transition from scene 1 to scene 2. when the game is over, the app transitions back to scene 1. BUT when I try to go to scene 2 again, the app crashes. This only happens on a device, on the simulator, i can go back and forth without any problems.
the error I get is that a SKNode is being added that already exists. I know what it means and the error starts when I try to
// Add mainlayer & labelHolderLayer
self.addChild(mainLayer)
self.addChild(labelHolderLayer)
But I don't see why It can add the background with no problem but crash from there on. Also why does it work on the simulator but not on a device? do I really need to check in my didMoveToView if the nodes are already created?
I found out the mistake I was making.
I was instantiating the nodes outside my scene 2 class, making them global. Therefore when I went from scene 1 -> scene 2 -> scene 1 there was not problem but going then to scene 2 again caused a crash because the nodes were globally created.
Solution:
Moving the following code within the class solved the problem.
var mainLayer = SKSpriteNode()
var labelHolderLayer = SKSpriteNode()
var livesDisplay = SKSpriteNode()
var scoreLabel = SKLabelNode()

SpriteKit Lagging on iPhone 5 but not on iPhone 6

I have been working on my first SpriteKit game that perfectly works on iPhone 6 but not on iPhone 5 (iOS 8.2 on both, written in Swift). I explain my problem: there is a simple ball (a SkShapeNode) that bounces when tapping on the screen. The move is very smooth on iPhone 6 but it has some lagging on iPhone 5 but NOT the first time. The FPS is 60 all the time. I have simplified my project a maximum and I have tried everything, I really need your help so much, I am a little bit desperate. I am ready to try anything, any help would be very appreciated. Thank you so much in advance!
Here is the video (smooth the first time then lagging):
https://www.youtube.com/watch?v=xNxp2uGIJew
Here is the repository if you want to test it:
https://github.com/jonmars8/SpriteKit-Lag-iPhone5
Here is the code in the GameScene: (empty but it was the menu before simplification)
import SpriteKit
class GameScene: SKScene {
var viewController:GameViewController?
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
viewController?.presentPlayScene()
}
}
Here is the code in the PlayScene:
import SpriteKit
let PlayerCategory: UInt32 = 1 << 2
let ObstacleCategory: UInt32 = 1 << 3
class PlayScene: SKScene, SKPhysicsContactDelegate {
let world = SKNode()
var player : SKShapeNode!
var firsttap = true
var viewController : GameViewController?
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVectorMake(0.0, -9.8)
physicsWorld.contactDelegate = self
self.createPlayer()
let obstacle = SKSpriteNode(color: SKColor.whiteColor(), size: CGSizeMake(100.0, 38.0))
obstacle.position = CGPointMake(frame.size.width / 2.0, CGRectGetMidY(frame) * 0.75 + 400)
let body = SKPhysicsBody(rectangleOfSize: obstacle.size)
body.categoryBitMask = ObstacleCategory
body.contactTestBitMask = PlayerCategory
body.collisionBitMask = PlayerCategory
body.linearDamping = 0.0
body.angularDamping = 0.0
body.dynamic = false
obstacle.physicsBody = body
self.addChild(world)
self.world.addChild(obstacle)
self.world.addChild(player)
}
func createPlayer() {
self.player = SKShapeNode(circleOfRadius: 13)
player.fillColor = SKColor.greenColor()
player.strokeColor = SKColor.blackColor()
player.lineWidth = 1
let body = SKPhysicsBody(circleOfRadius:13)
body.dynamic = false
body.linearDamping = 0.0
body.angularDamping = 0.0
body.allowsRotation = false
body.affectedByGravity = true
body.categoryBitMask = PlayerCategory
body.contactTestBitMask = ObstacleCategory
body.collisionBitMask = ObstacleCategory
player.physicsBody = body
player.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame) * 0.75)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
if firsttap {
player.physicsBody?.dynamic = true
firsttap = false
}
let touchLoc = touch.locationInNode(self)
var right = true
if touchLoc.x > CGRectGetMidX(frame) {
right = false
}
player.physicsBody?.velocity = right ?
CGVectorMake(CGFloat(-65), CGFloat(650)) :
CGVectorMake(CGFloat(65), CGFloat(650))
}
}
override func update(currentTime: NSTimeInterval) {
var pos = -player.position.y + CGRectGetMidY(frame)
if pos < world.position.y {
world.position = CGPointMake(world.position.x, pos)
}
}
func didBeginContact(contact: SKPhysicsContact) {
viewController?.presentGameScene()
}
}
Here is the code in the gameViewController:
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData!)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
if file == "GameScene"{
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
archiver.finishDecoding()
return scene
}
else {
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as PlayScene
archiver.finishDecoding()
return scene
}
} else {
return nil
}
}
}
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.presentGameScene()
}
func presentPlayScene() {
if let scene = PlayScene.unarchiveFromFile("PlayScene") as? PlayScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
//reference to self
scene.viewController = self
skView.presentScene(scene, transition: SKTransition.crossFadeWithDuration(0.5))
}
}
func presentGameScene() {
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.showsQuadCount = true
skView.showsDrawCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
//reference to self
scene.viewController = self
skView.presentScene(scene, transition: SKTransition.crossFadeWithDuration(0.5))
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
} else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Note: on iPhone 6 there is a very small lagging when quickly tapping on the screen at the very first beginning of the game.
I have replaced the SKShapeNode by SKSpriteNode and it hasn't changed anything.
Please help me, thank you so much in advance!
I posted my view controller code over here. I do not observe performance difference between iPhone 5 and iPhone 6. Can you try my code and let me know if the lag is still there?
import UIKit
import SpriteKit
class GameViewController: UIViewController
{
var playScene: PlayScene?
override func viewDidLoad() {
}
override func viewDidAppear(animated: Bool) {
let view = self.view as SKView
playScene = PlayScene(size: view.bounds.size)
playScene!.viewController = self
view.showsFPS = true
view.showsNodeCount = true
view.presentScene(playScene!)
}
func presentGameScene()
{
let view = self.view as SKView
playScene = PlayScene(size: view.bounds.size)
playScene!.viewController = self
view.presentScene(playScene!)
}
}
Maybe you can try to apply impulse instead of changing velocity directly:
if(right){
player.physicsBody?.velocity = CGVectorMake(0,0)
player.physicsBody?.applyImpulse(CGVectorMake(-3, 15))
}else{
player.physicsBody?.velocity = CGVectorMake(0,0)
player.physicsBody?.applyImpulse(CGVectorMake(3, 15))
}
Also I can't produce that lag with your code(but only tested on iPhone 6)...Try to experiment with update vs didSimulatePhysics though. And try to isolate the part which causing the lag. Delete the code from update method to see what causing the lag (ball bouncing or moving the world node or something else).

Resources