Spritekit Animation not working - swift

I'm trying to make a game where the main user is animated and i converted the gif to png and took each frame and put them all into a folder. However, right now I'm simply trying to get the first frame to appear, but nothing appears. Sorry if i worded this weird my English isn't very good.
Right now I think just the first frame, "trumpyhappy1.png" should show when I try to run it, but when I run it nothing shows up.

For anyone that stumbles upon this and is still wondering why their animations are blank. I found that programmatically adding the actions would cause this before viewDidAppear got called. However, setting the animation in the scene file directly I was able to get around the blank animations

So basically in spritekit to animate a spritenode you should be doing this
override func didMove() {
let mainGuy = SKSpriteNode()
self.addChild(mainGuy)
// Give your size and position and everything
// Then to animate that spriteNode with frames
let textureAtlas = SKTextureAtlas(named: "trump")
let frames: [SKTexture] = []
for i in 0...textureAtlas.textureNames.count {
var name = "trumphappy\(i).png"
frames.append(SKTexture(named: name))
}
// Then to create the animation add this code
let animation = SKAction.animate(with: frames, timePerFrame: ) // Whatever time you want your animation to spend on each frame
}
That should work, hope it works!

Related

SpriteKit - change animation speed dynamically

I am new to the SpriteKit framework and cannot figure out how to do the following:
In the middle of the scene I have a sprite to which I apply an .animateWithTextures SKAction. Now I simply want to increase the speed of that animation, or decrease its duration for the same effect.
I made the SKAction a property of my GameScene class so I can access its properties from everywhere, but neither changing its speed nor its duration affects the animation.
I read several threads, the closest to my problem being this one:
How to change duration of executed SpriteKit action
which is from seven years ago, and none of the answers is working for me.
Here is basically what I do:
class GameScene: SKScene {
var thePlayer: SKSpriteNode = SKSpriteNode()
var playerAnimation: SKAction?
...
...
func setInitialAnimation() {
let walkAnimation = SKAction.repeatForever(SKAction(named: "PlayerWalk", duration: 1)!)
self.playerAnimation = walkAnimation
self.thePlayer.run(walkAnimation)
}
func changeAnimationDuration(to duration: CGFloat) {
self.playerAnimation?.duration = duration
}
}
The animation starts when the setInitialAnimation method is called, but changing the action's duration has no effect. The only thing that works so far is removing the old action from the player sprite and running a new one.
Should it not be possible to change the properties of a running SKAction or is that some kind of fire and forget mechanism?
Play around with the timePerFrame to get the correct animation speed.
var frames = [SKTexture]()
for i in 1...10 {
frames.append(SKTexture(imageNamed: "YourTextureAnimationFrame\(i)"))
}
let animate = SKAction.animate(with: frames, timePerFrame: 0.025)
//timePerFrame setting the speed of the animation.
node.run(animate)

How to zoom paused ARFaceTrackingSession

I think the title is self-explanatory. I am starting a session like this:
let configuration = ARFaceTrackingConfiguration()
sceneView.session.run(configuration)
Then I add some nodes to the scene.
Then I pause the scene:
self.sceneView.session.pause()
Then, I want to programmatically zoom into the face so that the screen is filled with the face.
But I cannot figure out how. I have tried
let cameraNode = sceneView.pointOfView!
faceNode.simdScale = cameraNode.simdWorldFront * 1
But that does not seem to take any effect.
I can do it with CGAffinTransform on the SceneView itself but that approach seems wrong.

SpriteKit Memory leaks changing Scenes containing SKTileMapNodes

I am trying to create a simple 2d platform game using Swift, SpriteKit, and SKTileMaps. But every time i change between scenes containing SKTileMaps I see a lot of memory leaks in the Xcode Instruments.
I have recreated the problem as simple as I can. I am using a .sks file to create the scene and this file only contains 1 tileMap filled with some tiles.
The code in the view controller for presenting the scene:
if let view = self.view as! SKView? {
let scene = LoadingScene(size: CGSize(width: 2048, height: 1536))
scene.scaleMode = .aspectFill
view.presentScene(scene)
The code for the scene:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let scene = GameScene(fileNamed: "WorldScene") else{fatalError("Could not open world scene")}
view?.presentScene(scene)
}
}
I am choosing GameScene as the custom class int the .sks scene file.
This will result in a lot of small memory leaks each time I change the scene:
picture of memory leak in Instruments
This is the leak from only one scene change. Am I doing something wrong or is this a SpriteKit bug?
Edit1
The SKCTileMapNode::_ensureChunkForTileIndex(unsigned int) leaks happen each time i load the tile map, while the rest only appear when changing a scene
Edit2
Changed the GameViewController to skip the LoadingScene and go straight to GameScene. The memory leak is still there:
if let view = self.view as! SKView? {
guard let scene = GameScene(fileNamed: "WorldScene") else{fatalError("Could not open world scene")}
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
I have come across this same issue and after looking into it, I believe it is a problem that is inherent to all SKTileMapNodes. It is a bug in SpriteKit.
When you use an SKTileMapNode with ANY tiles filled (not a blank tile map), then the memory for the tile map will persist even when loading subsequent scenes. If you keep loading levels with SKTileMapNodes, then the memory will keep increasing until the game ultimately crashes. I have tested this with different games I have coded and using the code of others as well.
Interestingly, if the tile map has all blank tiles (even if it has an SKTileSet assigned), then no memory leak will occur.
From the best that I can guess, when an SKTileMapNode has any tile in it besides blank tiles, the entire SKTileSet that it is using, is being kept in memory and never gets removed.
From my experiments, you can prevent this problem by swapping the SKTileSet with a blank tileset in didMove. You cannot do it anywhere else except within didMove (or a function called by didMove).
So my solution has been to extract the tile set into individual sprites and then "nullify" the tileset on the tilemap. You can do so with the following code:
extension SKTileMapNode {
func extractSprites(to curScene: SKScene) {
for col in 0..<numberOfColumns {
for row in 0..<numberOfRows {
if tileDefinition(atColumn: col, row: row) != nil {
let tileDef = tileDefinition(atColumn: col, row: row)
let newSprite = SKSpriteNode(texture: tileDef!.textures.first!)
curScene.addChild(newSprite)
let tempPos = centerOfTile(atColumn: col, row: row)
newSprite.position = convert(tempPos, to: curScene)
}
}
}
eraseTileSet()
}
func eraseTileSet() {
let blankGroup: [SKTileGroup] = []
let blankTileSet = SKTileSet(tileGroups: blankGroup)
tileSet = blankTileSet
}
}
Basically within didMove, you will need to call extractSprites on each SKTileMapNode. This will simply create SKSpriteNodes from each tile and put them into the scene. Then the SKTileSet will be "switched" to a blank one. Magically the memory leak will disappear.
This is a simplified solution which you will need to expand upon. This only puts the sprites there, but doesn't define how they behave. Sorry but this is the only solution I have found and I believe your problem is a major bug in SpriteKit.

Spritekit- Scene transition Delay

I have a main screen which has a button, when this button is pressed it should transition immediately to another scene, but it doesn't. It actually takes a few seconds. Is there a way that I could load all the nodes in that scene beforehand? (Example: in the games load screen)
This is my code:
let pressButton = SKAction.setTexture(SKTexture(imageNamed: "playButtonP.png"))
let buttonPressed = SKAction.waitForDuration(0.15)
let buttonNormal = SKAction.setTexture(SKTexture(imageNamed: "playButton.png"))
let gameTrans = SKAction.runBlock(){
let doors = SKTransition.doorsOpenHorizontalWithDuration(0)
let levelerScene = LevelerScene(fileNamed: "LevelerScene")
self.view?.presentScene(levelerScene, transition: doors)
}
playButton.runAction(SKAction.sequence([pressButton,buttonPressed,buttonNormal,gameTrans]))
You could preload the SKTextures you're using in LevelerScene before presenting the scene. Then, once the loading has finished you would then present the scene. Here's an example from the Apple's Documentation, translated to Swift:
SKTexture.preloadTextures(arrayOfYourTextures) {
if let scene = GameScene(fileNamed: "GameScene") {
let skView = self.view as! SKView
skView.presentScene(scene)
}
}
In your case you have a couple of options:
1.
Keep an array of the textures, which you need to use in LevelerScene, that you preload in GameScene:
class LevelerScene : SKScene {
// You need to keep a strong reference to your textures to keep
// them in memory after they've been loaded.
let textures = [SKTexture(imageNamed: "Tex1"), SKTexture(imageNamed: "Tex1")]
// You could now reference the texture you want using the array.
//...
}
Now in GameScene, when the user presses the button:
if let view = self.view {
let leveler = LevelerScene(fileNamed: "LevelerScene")
SKTexture.preloadTextures(leveler.textures) {
// Done loading!
view.presentScene(leveler)
}
}
There's no way you can get around having to wait a little bit, but taking this approach the main thread won't get blocked and you'll be able to interact with GameScene whilst LevelerScene is loading.
You could also use this approach to make a loading SKScene for LevelerScene. GameScene would take you to the loading scene, which would load the textures and then move you to LevelerScene once it was complete.
It's important to note that because the reference to the textures is in LevelerScene, once LevelerScene is deinit-ed the textures will be removed from memory. Therefore, if you want to go back to LevelerScene you'll need to load the textures again.
2. You could use SKTexture.preloadTextures in GameViewController before any SKScenes have been presented. You'd need to keep a strong reference to these textures (perhaps in a singleton) which you could then reference in LevelerScene (or anywhere else you needed them in the app).
With this approach, because the SKTextures are stored outside of a scene, they won't be removed from memory when you transition to the next scene. This means you won't have to load the textures again if you leave and then go back to a scene. However, if you've got a lot of textures taking up a lot of memory you could run into some memory issues.
For more information see Preloading Textures Into Memory from Working with Sprites.
Hope that helps!

Swift - Stopping an animation after the last image has appeared?

I'm trying to learn animation in swift. I have an explosion composed of 77 images, and I've stumbled upon a couple issues.
1) I'm trying to make the animation automatically stop once 77.png has appeared. Here is what I have so far. Obviously, it is currently in a continuous animation loop.
2) There is ~1 second delay for the animation to start. However, after it has animated once, and I click animate again, it is instant from then on. How can I make the first animation instant as well?
#IBOutlet var explosionSequence: UIImageView
var imgListArray :NSMutableArray = []
for countValue in 1...77 {
var strImageName : String = "\(countValue).png"
var image = UIImage(named:strImageName)
imgListArray .addObject(image)
}
explosionSequence.animationImages = imgListArray as [AnyObject];
explosionSequence.startAnimating()
//i want to stop animation here after all 77 .pngs have appeared
thank you in advance!!
You can use UIImageView method animationRepeatCount to limit your animation loop to 1.
The default value is 0, which specifies to repeat the animation
indefinitely:
explosionSequence.animationRepeatCount = 1
You can also use animationDuration to adjust the time of you animation.