Swift Sprites animation: xCode doesn't read my atlas folder - swift

I am making a simple animation with a bear moving back and forth. My issue is that my code doesn't recognise my atlas folder (BearImages.atlas) or doesn't recognise the images into it. I don't know what I am doing wrong and I can't figure out. Can you explain to me as I am 5 years old, why xCode doesn't recognise my folder or my images into it?
ScreenShot:
Click to open Image
My code:
import SpriteKit
class GameScene: SKScene {
var bear : SKSpriteNode!
var bearWalkingFrames : [SKTexture]!
override func didMoveToView(view: SKView) {
backgroundColor = (UIColor.blackColor())
let bearAnimatedAtlas = SKTextureAtlas(named: "BearImages")
var walkFrames = [SKTexture]()
print(bearAnimatedAtlas)
let numImages = bearAnimatedAtlas.textureNames.count
for var i=1; i<=numImages/2; i++ {
let bearTextureName = "bear\(i)"
walkFrames.append(bearAnimatedAtlas.textureNamed(bearTextureName))
}
bearWalkingFrames = walkFrames
let firstFrame = bearWalkingFrames[0]
bear = SKSpriteNode(texture: firstFrame)
bear.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
addChild(bear)
walkingBear()
}
func walkingBear() {
//This is our general runAction method to make our bear walk.
bear.runAction(SKAction.repeatActionForever(
SKAction.animateWithTextures(bearWalkingFrames,
timePerFrame: 0.1,
resize: false,
restore: true)),
withKey:"walkingInPlaceBear")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
}
override func update(currentTime: CFTimeInterval) {
}
}
Thank you!

This may not solve your issue, but it is probably the most likely cause...
Click on your project "pierro peguin learning" within the project navigator window on the left
Click on the Build Phases tab
Under the Copy Bundle Resources section, make sure BearImages.atlas is there
If it is not, add it with the plus button

Your code seems find so I presume it must be the image names or you didn't copy the atlas folder into your project (you just referenced it)
1st thing you should do is to use xCode 7 new atlas feature to store your atlas in the asset catalogue instead of just copying an atlas folder into the project. Try that first and see if it makes a difference.
Apple also recommends this approach for performance and better organisation.
If that doesnt work....
1) What formats are your images in? They normally should be in PNG format.
2) Can you show the content of the atlas folder?
3) Are you not seeing any animations at all or do you get the white square with the red X?
Generally, and not specific to your question, why are you creating 2 arrays for the images?
Just use the bearWalkingFrames array and append the images directly. Seems complicated to create a second array (walkFrames) append the images and than set bearWalkingFrames to that array.

Related

Playing a local video in a SpriteKit scene

Working on taking around 23 ".mov" files - short animations - that will piece together to make a children's book, with a choose-your-own-adventure style to it.
I feel like I have a decent grasp on what needs to be done on a macro sense, present the video in a scene, once the video finishes create x number of buttons that will launch the user into the next chapter/scene. Furtherdown the road will add some simple animations and music/sound as well.
However, I am having some issues with playing the videos, I was successful in creating a scene with a screenshot as the background which had a button to launch the next scene, but for adding the video, I am both lost and having a hard time finding resources on the topic.
Apple Dev's solution is to use:
let sample = SKVideoNode(fileNamed: "sample.mov")
sample.position = CGPoint(x: frame.midX,
y: frame.midY)
addChild(sample)
sample.play()
I tried this, albeit probably improperly, in my GameScene.swift file.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var label : SKVideoNode?
override func didMove(to view: SKView) {
let sample = SKVideoNode(fileNamed: "V1.mov")
sample.position = CGPoint(x: frame.midX,
y: frame.midY)
addChild(sample)
sample.play()
}
}
I was hoping this would be - and who knows, maybe it still is, my golden egg of a solution, cause then the rest would really be just a copy and paste session with multiple scenes, but I was not able to get the video to load.
Do I need to further connect the SKVideoNode to the "V1.mov" file?
Furthermore, I think some of my confusion stems from whether or not I implement the video programatically or through the .sks / storyboard files, is one easier than the other?
Also am aware that this question may be more-so to do with something very rudimentary that I've brushed over in my learning process, and if that is the case, I apologize for my over zealousness :p
Appreciate any advice you folks can give would be greatly appreciated:)
UPDATE:
Okay, so here is the code I have so far which works, it is the skeleton framework for the book I am making, it's a choose your own adventure, so each scene has multiple options to take you to other scenes:
import SpriteKit
import Foundation
import SceneKit
class v1Scene: SKScene {
var trashButtonNode:SKSpriteNode!
var bikeButtonNode:SKSpriteNode!
var backButtonNode:SKSpriteNode!
override func didMove(to view: SKView) {
trashButtonNode = (self.childNode(withName: "trashButton") as? SKSpriteNode)
bikeButtonNode = (self.childNode(withName: "bikeButton") as? SKSpriteNode)
backButtonNode = (self.childNode(withName: "backButton") as? SKSpriteNode)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let v2Scene = v2Scene(fileNamed: "v2Scene")
let v3Scene = v3Scene(fileNamed: "v3Scene")
let MainMenu = MainMenu(fileNamed: "MainMenu")
if let location = touch?.location(in: self) {
let nodesArray = self.nodes(at: location)
if nodesArray.first?.name == "trashButton" {
let transistion = SKTransition.fade(withDuration: 2)
self.view?.presentScene(v2Scene!, transition: transistion)
} else if nodesArray.first?.name == "bikeButton" {
let transistion = SKTransition.fade(withDuration: 2)
self.view?.presentScene(v3Scene!, transition: transistion)
} else if nodesArray.first?.name == "backButton" {
let transistion = SKTransition.fade(withDuration: 2)
self.view?.presentScene(MainMenu!, transition: transistion)
}
}
}
}
What I am trying to achieve is that when the "trashButton" is clicked, v2Scene is launched and a video automatically plays. Each video is a "page" in a book, so need to assign each video to a scene.
The files are in a folder in my project and the Assets.xcasstes folder, although, the .mov files do not seem to work in that folder.
Thanks,
Owen
Because the video files are in their own folder, you won't be able to use the SKVideoNode init that takes a file name. You must use the init that takes a URL as an argument.
To get the URL for a video file, you must call the Bundle class's url function, supplying the file name, extension, and the name of the path to the folder where your video files are.
The following code shows what you need to do to create a video node from a file in the app bundle, using the V1.mov file from your question as the example:
let bundle = Bundle.main
if let videoURL = bundle.url(forResource: "V1",
withExtension: "mov",
subdirectory "FolderName" {
sample = SKVideoNode(url: videoURL)
}
FolderName is the folder where the video files are.
Make sure the folder with the video files is in the app target's Copy Bundle Resources build phase. The folder must be in this build phase for Xcode to copy it to the app bundle when it builds the project. Take the following steps to view the build phases for a target:
Open the project editor by selecting the project file on the left side of the project window.
Select the target on the left side of the project editor.
Click the Build Phases button at the top of the project editor.

Is it possible to change the ARKit scene's object when clicking on a button Swift

I am building and ARkit project for the first time, and what I need to do is to, first of all, I have to display a 3D Sphere in the ARSCNView. and then when I click on a button, the sphere should disappear and display a 3D cube at its place.
I was thinking about it ad that my code :
#IBOutlet weak var sceneView: ARSCNView!
var objectNode: SCNNode?
var objectScene: SCNScene?
objectScene = SCNScene(named: "sphere.dae")
objectNode = objectScene!.rootNode
sceneView.scene.rootNode.addChildNode(objectNode!)
and here is the code for the button:
#IBAction func cubeButtonClicked(_ sender: UIButton) {
sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
node.removeFromParentNode()
}
objectScene = SCNScene(named: "cube.dae")
objecteNode = objectScene!.rootNode
sceneView.scene.rootNode.addChildNode(objecteNode!)
}
and I am facing this error
[SceneKit] Error: removing the root node of a scene from its scene is not allowed
Am I doing something wrong?
Actually the error tells exactly what's causing it.
Editted --- try one of the two following ways
Use the following code to avoid removing pointOfView which is your SCNCamera.
sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
if node == sceneView.pointOfView {return}
node.removeFromParentNode()
}
If you only have one node to remove, which in your case is sphereNode, there's no need to use enumerate method. Simply hold a reference to that node, and remove if when you'd like to.
sphereNode.removeFromParentNode()
What I was missing in my above code is that I had to make the deletion and add two synchronize tasks. Since the Delete function is into a closure( asynchronous task). so the add function will be executed before the deletion.
And by then the error will be gone.

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: How do I remove objects once they leave the screen?

I have a rock sprite that it is falling and every time it goes out of the screen I want to reset it back to the top and have it fall again. It should be a continuous cycle. Here's my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
func addRock(){
var rock = self.childNode(withName: "rock")
rock?.physicsBody?.affectedByGravity = true
//self.addChild(rock!)
}
override func sceneDidLoad() {
//bRock = self.childNode(withName: "rock")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//addRock()
var rock = self.childNode(withName: "rock")
rock?.physicsBody?.affectedByGravity = true
/*if (Int((rock?.position.y)!) < Int((self.view?.scene?.view?.bounds.minY)!)){
print("out of screen")
rock?.removeFromParent()
addRock()
}*/
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
var rock = self.childNode(withName: "rock")
//rock?.physicsBody?.affectedByGravity = true
if (!intersects(rock!)){
print("out of screen")
rock?.removeFromParent()
addRock()
}
}
}
I have the rock coming on the screen and then falling. Once it leaves the screen, it does not reset and I get an error. I tried placing the reset code in both the touchesBegan and update functions but neither work. If someone could guide me to the correct path, that would be greatly appreciated.
The problem you're having is that you're removing the rock node from the scene but the addRock method doesn't actually add a new rock, it just finds the existing node if there is one and sets a property on its physicsBody. Instead of removing the node, you should just change its position.
Assuming your scene has the default anchor point of (0,0), you can reset the position like this:
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if let rock = self.childNode(withName: "rock") {
if !intersects(rock!) {
print("out of screen")
rock.position.y = size.height/2 // divided by 2 as discussed in comments
}
}
That will reset the rock's position to the top of the scene and it should resume falling from there.
Three ways:
the same rock is newly located at the right place after leaving the screen, so you just need to set the position of it in (misnamed) addRock (resetRock should be better): rock.position = CGPoint(...). But beware that you also need to reset its velocity, etc, as it was previously moved by physical laws...
create a new rock each time one leaves the screen in addRock: let rock = SKSpriteNode(...)... (and all the initializing code for it. Much simpler as all its physical parameters will be initialized with right values by default.
create a clone of an initial rock. I suppose you have a model of it in your sks file, then don't name it rock but something like (rockModel), and just clone it in addRock with giving it the right name rock. Don't forget to remove the model from the scene at the beginning. This is the usual way to do it.
You could also think about using a series of move actions in a sequence to control the rock's behaviour. Given the move actions are already defined, in your defined sequence, pass in sequence[(falling, hide, backtotop, unhide)] - Repeat Forever.
As long as the rocks don't actually interact with anything and are just for the backdrop/background, hiding and unhiding them is a good way to give the affect they are falling.

EXEC_BAD_ACCESS when adding sprite to view

I'm playing around with Sprite Kit in Swift on 10.9 using XB6. I have some code in my Scene that adds a sprite at the location of a mouse click. It loads the sprite thus:
let location = theEvent.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.position = location
sprite.setScale(0.5)
self.addChild(sprite)
This code runs fine for a while; I click and a sprite appears where I expect it to. But if I keep clicking, eventually that second line will result in a:
EXEC_BAD_ACCESS (code=EXC_I386_GPFLT)
(I wish they let you copy the error...). Sometimes it takes 5 clicks, other times 20, there's no apparent pattern to it. Googling the error, its obviously something that's happening deep in the bowels of SK or Swift.
Is anyone else seeing this?
Is this in the touchesBegan function? If so, did you use a loop?
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch in touches{
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.position = location
sprite.setScale(0.5)
self.addChild(sprite)
}
}
OK this turned out to be an error in the beta builds. Replacing my Xcode with the 6.0 release and then copying the 10.10 SDK from 6.1B fixes this problem.