Slow Skscene transition - ios

I have two scenes: Home and Play. The transition to play scene is really slow compared to the transition to home scene. I think this is because there's more happing in my play scene. Is there any way I can preload play scene ? Or make the transition more seamless?
I'm interested in the answer in this forum Preload a Scene to prevent lag?
but I have no idea where to begin. Where do I place part A and B of the answer
Here is what I'm using
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches
let location = touch.first!.locationInNode(self)
let node = self.nodeAtPoint(location)
if (node.name == "Balloon") {
let sceneAction = SKAction.runBlock({
let secondScene = Scene2(size: self.size)
let transition = SKTransition.fadeWithDuration(1)
secondScene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(secondScene, transition: transition)
})
PlayButton.runAction(SKAction.sequence([sound,SKAction.animateWithTextures(array, timePerFrame: 0.1),sceneAction,SKAction.waitForDuration(1)]))
}
Any other solution is fine.
Thanks

I do this and works:
in didView of HomeScene put preloadGameScene()
And always in home scene:
fileprivate var nextScene: SKScene?
func preloadGameScene () {
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
self.nextScene = GameScene(fileNamed:"yoursksfilename")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("SCENE LOADED")
let loading = self.childNodeWithName("Loading") as? SKLabelNode
self.playButton?.hidden = false
self.playButton?.alpha = 0
//loading?.hidden = true
self.playButton?.runAction(SKAction.fadeAlphaTo(1.0, duration: 0.4))
loading!.runAction(SKAction.fadeAlphaTo(0.0, duration: 0.4))
})
})
}
goToGameScene() is called by a button personal SKButton class:
func goToGameScene () {
guard let nextScene = nextScene else {return}
let transition = SKTransition.crossFadeWithDuration(0.5)
nextScene.scaleMode = .AspectFill
scene!.view?.presentScene(nextScene, transition: transition)
}
UPDATE SWIFT 3
fileprivate var nextScene: SKScene?
func preloadGameScene () {
let qualityOfServiceClass = DispatchQoS.QoSClass.background
let backgroundQueue = DispatchQueue.global(qos: qualityOfServiceClass)
backgroundQueue.async(execute: {
self.nextScene = GameScene(fileNamed:"yoursksfilename")
DispatchQueue.main.async(execute: { () -> Void in
print("SCENE LOADED")
let loading = self.childNode(withName: "Loading") as? SKLabelNode
self.playButton?.isHidden = false
self.playButton?.alpha = 0
//loading?.hidden = true
self.playButton?.run(SKAction.fadeAlpha(to: 1.0, duration: 0.4))
loading?.run(SKAction.fadeAlpha(to: 0.0, duration: 0.4))
})
})
}

Related

How can I get a call to aUIView.presentScene(aSKScene, transition: aSKTransition) to act in unison with aUIView.addSubview(aUITextField!)?

How can I get a call to aUIView.presentScene(aSKScene, transition: aSKTransition) to act in unison with aUIView.addSubview.
Without the transition, there is zero problem .. with the SKTransition the UITextField happens 1st and then the SKTransition – not in unision.
Like, the UITextField is planted in the view of the ViewController and sits around waiting for the SKTransition to catch up
Code
func showScene(theSceneName: String) {
if let ourScene = SKScene(fileNamed: theSceneName) {
addTextFieldToVC(toSceneName: theSceneName)
// NB: theSceneName is passed by Reference so we can
// return here before we call presentScene(...)
addGamePiecesToScene(toScene: theSceneName)
if let theView = self.view as! SKView? {
theView.ignoresSiblingOrder = true
theView.showsFPS = true
theView.showsNodeCount = true
// Finally, present the scene
let theTransition = SKTransition.doorway(withDuration: 2.0)
theView.presentScene(ourScene, transition: theTransition)
}
} // if let ourScene
} // showScene
func addTextFieldToVC(toSceneName: String) {
if (toSceneName == "GameScene") {
if let theView = self.view as! SKView? {
aUITextField = UITextField(frame:CGRectMake(x: aXValue, y: aYValue))
theView.addSubview(aUITextField!)
}
}
}
func addGamePiecesToScene(toScene: SKScene) {
myRoom = SKSpriteNode(texture: SKTexture(imageNamed: roomImg))
myRoom!.zPosition = roomZposition
// etc with .size, .position
toScene.addChild(myRoom!)
}
As the above shows, I add the UITextField 1st and add the SKSpriteNode images 2nd.
Yet they are not in sync with the SKTransition. They appear in sync just without the SKTransition.
FWIW, I have tried this sequence within showScene, but no changes:
theView.presentScene(theSceneName, transition: theTransition)
DispatchQueue.main.async {
addTextFieldToVC(toSceneName: theSceneName)
}
I have also started to experiment with Completion Handlers:
override func viewDidLoad() {
super.viewDidLoad()
setupScene()
// Completion Handler for showScene()
showModifiedScene {
if thisSceneName == "GameScene" { // a global var
addTextFieldToVC(toSceneName: thisSceneName!)
}
}
} // viewDidLoad
// modified for callback option??
func showScene(theSceneName: String) {
if let ourScene = SKScene(fileNamed: theSceneName) {
addGamePiecesToScene(toScene: ourScene)
showModifiedScene()
} // if let ourScene
} // showScene
func showModifiedScene(completionBlock: () -> Void) {
if thisSceneName == "GameScene" { // a global var
if let ourScene = SKScene(fileNamed: thisSceneName!) {
if let theView = self.view as! SKView? {
theView.ignoresSiblingOrder = true
theView.showsFPS = true
theView.showsNodeCount = true
let theTransition = SKTransition.doorway(withDuration: 2.0)
theView.presentScene(ourScene, transition: theTransition)
}
}
completionBlock()
}
} // showModifiedScene
Again, without the transition, there is zero problem .. with the SKTransition the UITextField happens 1st and then the SKTransition – not in unision.
I am still chugging with this .. but I figure it’s time to call for a few reinforcements! I'll still work away while I am waiting for the calvary.
Try adding the text view, then wrapping the call to presentScene() in a call to DispatchQueue.main.async().
That way the text field will be added to your view before you begin presenting that view.

My animated SKSprite nodes atlas won't change during touchBegan

I created a sprite with an animated texture atlas. I then want that animation to change based on the direction the sprite is heading. I try to do so with "self.player!.texture = firstFrametexture(ofatlas)"
Here is where I build the player (put inside didMove but the animation starts without having to move?)
func buildPlayer() {
let playerAnimatedAtlas = SKTextureAtlas(named: "animation")
var walkFrames: [SKTexture] = []
let numImages = playerAnimatedAtlas.textureNames.count
for i in 1...numImages {
let playerTextureName = "player\(i)"
walkFrames.append(playerAnimatedAtlas.textureNamed(playerTextureName))
}
walkingPlayer = walkFrames
let firstFrameTexture = walkingPlayer[0]
player! = SKSpriteNode(texture: firstFrameTexture)
player!.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(player!)
}
Here is where I animate the player
func animatePlayer() {
player!.run(SKAction.repeatForever(
SKAction.animate(with: walkingPlayer,
timePerFrame: 0.5,
resize: false,
restore: true)),
withKey:"walkingInPlacePlayer")
}
Here is where I try to change the animation. Everything works, the if statement are correctly executed. Just nothing changes
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let location = touches.first?.location(in: self) {
let horizontalAction = SKAction.move(to: location, duration: 1.0)
horizontalAction.timingMode = SKActionTimingMode.easeOut
player?.run(horizontalAction)
let playerAnimatedAtlas = SKTextureAtlas(named: "animation")
let lplayerAnimatedAtlas = SKTextureAtlas(named: "animationleft")
var walkFrames: [SKTexture] = []
var lwalkFrames: [SKTexture] = []
let numImages = playerAnimatedAtlas.textureNames.count
for i in 1...numImages {
let playerTextureName = "player\(i)"
let playerLeftTextureName = "lplayer\(i)"
walkFrames.append(playerAnimatedAtlas.textureNamed(playerTextureName))
lwalkFrames.append(lplayerAnimatedAtlas.textureNamed(playerLeftTextureName))
}
walkingPlayer = walkFrames
lwalkingPlayer = lwalkFrames
let firstFrameTexture = walkingPlayer[0]
let leftFrameTexture = lwalkingPlayer[0]
if location.x > player!.position.x {
self.player!.texture = firstFrameTexture
print("rightsuccess")
} else if location.x < player!.position.x {
self.player!.texture = leftFrameTexture
print("leftsuccess")
}
}
}

How can I create an onscreen controller that works in multiple scenes in SpriteKit?

Working on a game in SpriteKit to learn. Its a platformer with an onscreen controller. I have this all working using touchesBegan and touchesEnded to know when the player is pushing the buttons or not. This works fine, however when i want to load the next scene for 'level 2' i need to implement the controller all over again. I could do a lot of copy and pasting but I feel this will lead to a lot of duplication of code. Every tutorial I've ever read said to try to adhere to the DRY principle.
Im sorry if this is simple, but I have <6 months programming experience and am trying to learn and improve. Im assuming I would need to create a separate class for the onscreen controller so it can be reused, but Im a little lost on where to start.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = (touch.location(in: playerCamera))
print("LocationX: \(location.x), LocationY: \(location.y)")
let objects = nodes(at: location)
print("\(objects)")
if rightButton.frame.contains(location) {
rightButtonPressed = true
playerFacingRight = true
playerFacingLeft = false
thePlayer.xScale = 1
let animation = SKAction(named: "running")!
let loopingAnimation = SKAction.repeatForever(animation)
thePlayer.run(loopingAnimation, withKey: "moveRight")
moveRight()
} else if leftButton.frame.contains(location) {
leftButtonPressed = true
playerFacingLeft = true
playerFacingRight = false
thePlayer.xScale = -1
let leftAnimation = SKAction(named: "running")!
let leftLoopingAnimation = SKAction.repeatForever(leftAnimation)
thePlayer.run(leftLoopingAnimation, withKey: "moveLeft")
moveLeft()
} else if upButton.frame.contains(location) {
upButtonPressed = true
print("upButton is pressed")
if playerAndButtonContact == true {
print("contact - player + button + upButtonPressed=true")
print("\(movingPlatform.position)")
button.texture = SKTexture(imageNamed: "switchGreen")
let moveRight = SKAction.moveTo(x: -150, duration: 3)
if movingPlatform.position == CGPoint(x: -355, y: movingPlatform.position.y) {
movingPlatform.run(moveRight)
thePlayer.run(moveRight)
button.run(moveRight)
}
}
if playerAndDoorSwitchContact == true {
let switchPressed = SKAction.run{
self.switchPressedSound()
self.doorSwitch.texture = SKTexture(imageNamed: "switchGreen")
self.door.texture = SKTexture(imageNamed: "DoorUnlocked")
}
let wait = SKAction.wait(forDuration: 2)
let doorOpen = SKAction.run {
let doorOpen = SKSpriteNode(imageNamed: "DoorOpen")
doorOpen.alpha = 0
doorOpen.position = self.door.position
doorOpen.size = self.door.size
doorOpen.size = self.door.size
self.door.zPosition = -2
doorOpen.zPosition = -1
let fadeIn = SKAction.fadeIn(withDuration: 0.5)
let start = SKAction.run {
self.addChild(doorOpen)
doorOpen.run(fadeIn)
}
let sound = SKAction.run {
self.doorOpeningSound()
}
let opening = SKAction.group([sound, start])
self.door.run(opening)
}
let sequence = SKAction.sequence([switchPressed, wait, doorOpen])
self.doorSwitch.run(sequence)
}
if playerAndDoorContact == true {
self.view?.presentScene(level1, transition: transition)
}
} else if downButton.frame.contains(location) {
}
else if shoot.frame.contains(location) {
shoot()
} else if jumpButton.frame.contains(location) {
self.pressed = true
let timerAction = SKAction.wait(forDuration: 0.05)
let update = SKAction.run {
if(self.force < Constants.maximumJumpForce) {
self.force += 2.0
} else {
self.jump(force: Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
}
let sequence = SKAction.sequence([timerAction, update])
let repeat_seq = SKAction.repeatForever(sequence)
self.run(repeat_seq, withKey: "repeatAction")
}
}
}

how to add a delay when you tap when firing a bullet on my game

i need help on this issue, in my game when a node is tapped a bullet is fired, but the problem is that i can tap continually and a lot of bullets fire, i would like to add some kind of delay to the shot.
here is my touches began code
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in:self)
if player1.contains(pointOfTouch) {
fireBullet1()
}
if player2.contains(pointOfTouch) {
fireBullet2()
}
if player3.contains(pointOfTouch) {
fireBullet3()
}
}
}
}
func fireBullet1() {
let bullet = SKSpriteNode(imageNamed: "b")
bullet.position = player1.position
bullet.zPosition = 1
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet.run(bulletSequence)
}
func fireBullet2 () {
let bullet2 = SKSpriteNode(imageNamed: "b")
bullet2.position = player2.position
bullet2.zPosition = 1
self.addChild(bullet2)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet2.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet2.run(bulletSequence)
}
func fireBullet3() {
let bullet3 = SKSpriteNode(imageNamed: "b")
bullet3.position = player3.position
bullet3.zPosition = 1
self.addChild(bullet3)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet3.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([moveBullet, deleteBullet])
bullet3.run(bulletSequence)
}
Declare flags to disable multiple firing immediatly
let minFireDelay = 0.5
var allowsFire1 = true
var allowsFire2 = true
var allowsFire3 = true
Update touches began
for touch: AnyObject in touches {
let pointOfTouch = touch.location(in:self)
if allowsFire1 && player1.contains(pointOfTouch) {
fireBullet1()
// disable firing temporarily
allowsFire1 = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + minFireDelay) {
allowsFire1 = true
}
}
if allowsFire2 && player2.contains(pointOfTouch) {
fireBullet2()
// disable firing temporarily
allowsFire2 = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + minFireDelay) {
allowsFire2 = true
}
}
if allowsFire3 && player3.contains(pointOfTouch) {
fireBullet3()
// disable firing temporarily
allowsFire3 = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + minFireDelay) {
allowsFire3 = true
}
}
}
Look up Timer (NSTimer in Swift 2 and Objective-C.)
The idea is as follows:
Have a gunXEnabled Bool for each player's gun. Set each Bool to true initially.
Have your fireBullet1() method check gun1Enabled. If false, do nothing.
If gun1Enabled == true, fire the gun, set gun1Enabled = false, and start a timer that will re-enable the gun once it fires:
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) {
(timer) -> Void) in
gun1Enabled = true
}
I gave you the unfamiliar part, creating the timer. See if you can work out the rest, and if not, post your code with info about what's not working.

Add a delay to a function in SpriteKit

I would like to be able to only allow the player to shoot a missile every 0.7 seconds. How do I do this? Here is my code. I have tried other methods I found on this site but they do not work.
func fireTorpedo() {
if !self.player.isPaused
{
if isSoundEffect == true {
self.run(SKAction.playSoundFileNamed("Rocket", waitForCompletion: false))
}
let torpedoNode = SKSpriteNode(imageNamed: "Rocket")
torpedoNode.position = player.position
torpedoNode.position.y += 5
torpedoNode.physicsBody = SKPhysicsBody(circleOfRadius: torpedoNode.size.width / 2)
torpedoNode.physicsBody?.isDynamic = true
torpedoNode.physicsBody?.categoryBitMask = photonTorpedoCategory
torpedoNode.physicsBody?.contactTestBitMask = alienCategory
torpedoNode.physicsBody?.collisionBitMask = 0
torpedoNode.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(torpedoNode)
let animationDuration:TimeInterval = 0.5
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: player.position.x, y: self.frame.size.height + 10), duration: animationDuration))
actionArray.append(SKAction.removeFromParent())
torpedoNode.run(SKAction.sequence(actionArray))
}
}
I like to do that like this:
class GameScene:SKScene {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if action(forKey: "shooting") == nil {
let wait = SKAction.wait(forDuration: 0.7)
run(wait, withKey: "shooting")
print("shot fired")
}
}
}
While there is a key "shooting" found on a scene, you can't shoot anymore. In real game, this would be likely the part of a node (an action should be run on a node).
First, add a flag at class-level that indicates whether the player can fire a torpedo.
var canFireTorpedo = true
Then, at the end of the fireTorpedo method, set canFireTorpedo to false, then set it to true again after 0.7 seconds:
canFireTorpedo = false
player.run(SKAction.sequence([
SKAction.wait(forDuration: 0.7),
SKAction.run { [weak self] in self?.canFireTorpedo = true }]))
After that, check canFireTorpedo at the start of the method:
func fireTorpedo() {
if canFireTorpedo {
// put everything in the method here, including the parts I added
}
}
Hopefully this code is self-explanatory.
To launch every 0.7 seconds you can do:
let actionKey = "repeatLaunchMethod"
let launchMethod = SKAction.afterDelay(0.7, runBlock: {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.fireTorpedo()
})
// do this if you want to repeat this action forever
self.player.run(SKAction.repeatForever(launchMethod), withKey: actionKey)
// do this if you want to repeat this action only n times
// self.player.run(SKAction.repeat(launchMethod, count: 5), withKey: actionKey)
To stop this action you can do:
if self.player.action(forKey: actionKey) != nil {
self.player.removeAction(forKey: actionKey)
}
Extension used to render the code more readable:
extension SKAction {
/**
* Performs an action after the specified delay.
*/
class func afterDelay(_ delay: TimeInterval, performAction action: SKAction) -> SKAction {
return SKAction.sequence([SKAction.wait(forDuration: delay), action])
}
/**
* Performs a block after the specified delay.
*/
class func afterDelay(_ delay: TimeInterval, runBlock block: #escaping () -> Void) -> SKAction {
return SKAction.afterDelay(delay, performAction: SKAction.run(block))
}
}

Resources