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).
Related
I am following this question How to create a main menu for a spritekit game made with swift in xcode? step by step.
I have created one SKScene and one Cocoa Touch Class and in Cocoa Touch Class I inserted this :
import SpriteKit
class MenuScene: SKScene {
var playButton = SKSpriteNode()
let playButtonTex = SKTexture(imageNamed: "play")
override func didMove(to view: SKView) {
playButton = SKSpriteNode(texture: playButtonTex)
playButton.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(playButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == playButton {
if let view = view {
let transition:SKTransition = SKTransition.fade(withDuration: 1)
let scene:SKScene = GameScene(size: self.size)
self.view?.presentScene(scene, transition: transition)
}
}
}
}
}
Now in GameViewController File I am trying to change from if let scene = GameScene.unarchiveFromFile("GameScene") as? to if let scene = MenuScene.unarchiveFromFile("MenuScene") as? Menu Scene.
However I keep getting this error. Please Help!
Here Is my code
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene.level(levelNum: 18) {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the sc ene
view.presentScene(scene)
// view.showsPhysics = true
}
view.ignoresSiblingOrder = false
view.showsFPS = true
view.showsNodeCount = true
}
}
I have a main menu scene where when a button is pressed it should transition to another scene. It appears to transition properly but when it's done transitioning the 2nd scene is blank. I put breakpoints in the 2nd scene's didMove function and it appears to be running properly. I know the 2nd scene works properly when directly opened from the GameViewController. I'm not sure why it's blank when transitioning from another scene. Here is my current code:
class StartScene: SKScene {
var playButton = SKSpriteNode()
let playButtonTex = SKTexture(imageNamed: "BlueRoundedButton")
override func didMove(to view: SKView) {
playButton = SKSpriteNode(texture: playButtonTex)
playButton.size = CGSize(width: 200, height: 50)
playButton.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(playButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == playButton {
if let view = view {
let transition:SKTransition = SKTransition.fade(withDuration: 1)
let scene:SKScene = PoolScene(size: self.size)
self.view?.presentScene(scene, transition: transition)
}
}
}
}
}
This is the scene transitioning to:
class PoolScene: SKScene, SKPhysicsContactDelegate {
var ballPoke : SKSpriteNode?
var ballBlue : SKSpriteNode?
var ballOrange : SKSpriteNode?
override func didMove(to view: SKView) {
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
physicsWorld.contactDelegate = self
ballBlue = childNode(withName: blueBallName) as? SKSpriteNode
ballOrange = childNode(withName: orangeBallName) as? SKSpriteNode
ballPoke = childNode(withName: pokeBallName) as? SKSpriteNode
ballBlue?.physicsBody?.categoryBitMask = blueBallCategory
ballOrange?.physicsBody?.categoryBitMask = orangeBallCategory
ballPoke?.physicsBody?.categoryBitMask = pokeBallCategory
borderBody.categoryBitMask = borderCategory
}
You are calling the wrong init method to load your PoolScene. The way you do it is for when you are not using the Xcode visual level editor.
Like your GameViewController, you need to init scenes with a reference to the corresponding .sks file.
if node == playButton {
if let view = view {
let transition = SKTransition.fade(withDuration: 1)
if let scene = PoolScene(fileNamed: "PoolScene") {
view.presentScene(scene, transition: transition)
}
}
}
or in a slightly cleaner way
if node == playButton {
let transition = SKTransition.fade(withDuration: 1)
if let scene = PoolScene(fileNamed: "PoolScene") {
view?.presentScene(scene, transition: transition)
}
}
"fileNamed:..." is the name of the .sks file for your PoolScene, which you usually give the exact same name as the .swift file. Use guard or if let because it will return an optional.
As a small tip, make use of swifts type inference to make your code a bit nicer like in my example.
Hope this helps
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.
I have a button on my first view controller that leads to the second view controller. When the second view controller appears, a ball image is supposed to appear and it's code is supposed to run. I seem to be having a little bit of trouble figuring out why the image isn't appearing. Any help would be greatly appreciated!
import UIKit
import SpriteKit
class GameViewController: UIViewController {
#IBOutlet weak var startButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if let scene = gamePlay(fileNamed:"gamePlay") {
// 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
skView.presentScene(scene)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
import SceneKit
import SpriteKit
class gamePlayController: UIViewController {
}
class gamePlay: SKScene, SKPhysicsContactDelegate {
let skyColor = SKColor(red: 0, green: 191, blue: 255, alpha: 1)
var blueBall:SKSpriteNode!
override func didMoveToView(view: SKView) {
self.backgroundColor = skyColor
self.physicsWorld.gravity = CGVectorMake(0.0, -5.0)
self.physicsWorld.contactDelegate = self
blueBall = SKSpriteNode( imageNamed: "ball")
blueBall.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
blueBall.physicsBody = SKPhysicsBody(circleOfRadius: blueBall.size.width / 1.5 )
blueBall.physicsBody!.dynamic = true
blueBall.physicsBody!.allowsRotation = false
self.addChild(blueBall)
}
override func touchesBegan(touches: Set<UITouch> , withEvent event: UIEvent?) {
self.blueBall.physicsBody?.velocity = CGVectorMake(35, 0)
self.blueBall.physicsBody?.applyImpulse(CGVectorMake(4, 10))
}
}
Looks like position is wrong. Try this code
blueBall.position = CGPoint(x: CGRectGetMidX(self.bounds), y: CGRectGetMidY(self.bounds))
I fixed the issue. I needed to write my backgroundImage as an SKSpriteNode and set the zPosition accordingly.
I got a Image with 2 faces, Up and Down ( Imagine a Card ).
Whenever you tap the Image, it changes it image.
I used a Bool to get the expression. However, after the Scene is presented again, I want to show the image which was Up/Down in the last Scene. Note that this is only 1 Scene, and it presents itself again.
How can I pass a Bool value between the Scene?
//Update:
My Code:
....
var FaceUp = true
....
override func didMoveToView(view: SKView) {
TTexture = SKTexture(imageNamed: "TCard.png")
TTexture.filteringMode = .Nearest
T2Texture = SKTexture(imageNamed: "T2Card.png")
T2Texture.filteringMode = .Nearest
Card = SKSpriteNode(texture: TTexture)
Card.position = CGPoint(x: Rate.position.x - (TransparentLayer.size.width / 3), y: Rate.position.y)
Card.size = CGSize(width: 50, height: 50)
Card.name = "Card"
Card.zPosition = 100
self.addChild(Card)
flip(FaceUp)
}
func flip(state : Bool) {
FaceUp = state
if (!state) {
Card.texture = TTexture //Error
Player.texture = T1
Player.runAction(RunAnimation1)
FaceUp = true
} else {
Card.texture = T2Texture //Error
Player.texture = T2
Player.runAction(RunAnimation2)
FaceUp = false
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let touch:UITouch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let Node:SKNode = self.nodeAtPoint(touchLocation)
if Node.name == "Card" {
let Scale1 = SKAction.scaleTo(0.7, duration: 0.1)
let Scale2 = SKAction.scaleTo(1, duration: 0.1)
let Scaling = SKAction.sequence([Scale1, Scale2])
Card.runAction(Scaling)
self.flip(FaceUp)
}
}
func Restart(){
self.saveHighScore()
LastScore = Score
self.addLeaderboardScore(self.Score)
let Transition = SKTransition.fadeWithDuration(1.8)
let Scene = GameScene(size: self.frame.size)
Scene.LastScore = Score
Scene.FaceUp = FaceUp
scene?.scaleMode = SKSceneScaleMode.AspectFill
self.scene?.view?.presentScene(Scene, transition: Transition)
}
You can define a function in GameScene like the following.
func setCardState(state : Bool) {
FaceUp = state
if (!state) {
Card.texture = Texture2
Player.texture = T2
} else {
Card.texture = Texture1
Player.texture = T1
}
}
And call it from didMoveToView in the new scene.
override func didMoveToView(view: SKView) {
TTexture = SKTexture(imageNamed: "TCard.png")
TTexture.filteringMode = .Nearest
T2Texture = SKTexture(imageNamed: "T2Card.png")
T2Texture.filteringMode = .Nearest
Card = SKSpriteNode(texture: TTexture)
Card.position = CGPoint(x: Rate.position.x - (TransparentLayer.size.width / 3), y: Rate.position.y)
Card.size = CGSize(width: 50, height: 50)
Card.name = "Card"
Card.zPosition = 100
self.addChild(Card)
setCardState(FaceUp) // added line.
}
Also set the old FaceUp value in the new scene in Restart function.
func Restart(){
self.saveHighScore()
LastScore = Score
self.addLeaderboardScore(self.Score)
let Transition = SKTransition.fadeWithDuration(1.8)
let Scene = GameScene(size: self.frame.size)
Scene.LastScore = Score
scene?.scaleMode = SKSceneScaleMode.AspectFill
Scene.FaceUp = FaceUp //changed line.
self.scene?.view?.presentScene(Scene, transition: Transition)
}