Playing a local video in a SpriteKit scene - swift

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.

Related

RealityKit – Playing animation from USDZ file in Xcode 13

I'm trying to play animations from a usdz file. So I was given a .dae file and a .scn file both of the same thing. for RealityKit they only accept .usdz files. So I used Xcode's exporter and exported them both to .usdz format. However the animations do not transfer over. I also tried copying the scene graph of the .scn file and pasting it in the .usdz file and when I press the play button in the Bottom center of the viewer in Xcode. I can see the animation play.
However this is wrong because .usdz files can't be edited. so it doesn't save. and hence it doesn't play in the ARview when I run on Xcode. Here's my code for playing the animations. I have tried looking at a bunch of post from both stack overflow and apple developer forum.
bird = try! Entity.load(named: "plane")
bird.name = "bird"
resultAnchor.addChild(bird)
arView.scene.subscribe(to: SceneEvents.AnchoredStateChanged.self) { [self] (event) in
if resultAnchor.isActive {
for entity in resultAnchor.children {
for animation in entity.availableAnimations {
entity.playAnimation(animation.repeat())
}
}
}
}.store(in: &birdAnimations) // Remember to store the cancellable!
I found the structure for the code in a post
Also I guess its important to note that I found a .usdz file online that had an animation. Quick look was able to play it when I rightclicked->Quicklook on the file in finder. But again when I try playing the animation on Xcode it doesn't play.
If you have any questions, need clarity or screenrecordings of what I am doing just ask.
To play animation use DidAddEntity struct instead of AnchoredStateChanged.
import UIKit
import RealityKit
import Combine
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
var subscriptions: [AnyCancellable] = []
override func viewDidLoad() {
super.viewDidLoad()
let model = try! Entity.load(named: "drummer.usdz")
let anchor = AnchorEntity()
anchor.addChild(model)
arView.scene.anchors.append(anchor)
arView.scene.subscribe(to: SceneEvents.DidAddEntity.self) { _ in
if anchor.isActive {
for entity in anchor.children {
for animation in entity.availableAnimations {
entity.playAnimation(animation.repeat())
}
}
}
}.store(in: &subscriptions)
}
}
My issue wasn't with my code. It was the way I was converting the .blend/.dae file to the .usdz.
I first exported it as a .glb in blender and Maya (worked for both). Then used Apple's Reality Converter to export it as a .usdz. that was able to play the animation properly.

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.

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

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.

Scene created in Sprite kit level editor is not working

I'm trying to do this for a while. I have a main scene for a game named PlayScene. I have a pause button there. When player is tapping that button I want to load another scene, named PauseScene. For visualy creating that scene I use Sprite kit level editor. So I have two files PauseScene.swiftand PauseScene.sks. But .sks content ( just a background for now) isn't unarchiving. I don't really know where could I make a mistake.
So this is my transition via pause button, located in PlayScene
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.pause {
var scene = PauseScene(size: self.size)
scene.paused = true
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
And this is what I have in PauseScene.swift:
class PauseScene: SKScene {
override func didMoveToView(view: SKView) {
self.initPauseScene()
}
func initPauseScene() {
}
}
I tried this and it's not working very well. However that tutorial is closest I could get with my problem. It's not crushing and isn't showing any errors, but when I tap pause button it's transitioning to that scene without my background, just standard grey scene. This answer is not working for me. First suggestion show errors and when I tried second and tap the pause – game is crushing.
I don't know, that level editor is made for easier work but for know it just doubles it for me. I even thought that problem is in my background image maybe. But its standard .png. Why my content isn't showing up?
So I tried to copy GameViewController default code for loading GameScene and ended up with crashing. It's not the first time I have that error while trying to unarchive this. What does it mean? I got this when i tried to replace var scene = PauseScene(size: self.size) with a if let scene = PauseScene.unarchiveFromFile("PauseScene") as? PauseScene
It may be transition to a grey scene due to spelling errors or the file not even being in the mainBundle. I have tested this and it works. You need to make sure that you are writing the exact name of the .sks file when loading or else you get a grey screen.
Example
If the file is called GameScreen.sks and the class is called GameScene then if you put the class name when loading the .sks file you will get a grey screen.
let doors = SKTransition.doorwayWithDuration(1.0)
let archeryScene = GameScene(fileNamed: "GameScreen")
self.view?.presentScene(archeryScene, transition: doors)
So if we look at the second line of code, We see that it is loading the .sks file with the name GameScreen opposed to the class name GameScene. We also abort using the file extension because the compiler know by default it is a .sks file we are searching for in the mainBundle.
If you do keep getting a grey screen, Then it may be an error in spelling or ing you GameViewController, In the SKNode Extension, Be sure to change
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")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
to this..
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")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
We just edited this line of code,
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
to this
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKScene
This will allow us to load multiple .sks files without any errors or crashes.

xcode beta 6 sprite kit?

I just update to xCode beta 6. In beta5 everything works but in beta 6 got to change somethings. I add a lot of "!" in my codes. Anyway, My project is little game. And after complete the level(won or lose) I want to call skscene.
The code is where in mainviewController:
if(flag==true){// IF we WON
/* That below lines should call "Won.swift" file But it doesn't */
var scene = Won(fileNamed: "GameScene")
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = SKSceneScaleMode.AspectFill
scene.position = CGPointMake(0, 568)
skView.presentScene(scene)
}
Won.swift file is:
class Won: SKScene {
var audioPlayer = AVAudioPlayer()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Fireworks", ofType: "mp3")!)
// Removed deprecated use of AVAudioSessionDelegate protocol
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound2, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
myLabel.text = "You Win";
myLabel.fontSize = 65;
myLabel.fontColor = UIColor.redColor()
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(myLabel)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
this code does work in beta 5 but doesn't in beta6.
Additionally I re-add uikit, avfoundation, spritekit foundations to project. And also check media files "mp3" or images are exist in bundle resources... The project runs perfectly without any eroor or warnings. But Skscene section doesn't work. I also tried to put breakpoint to trace how it does goes. the process goes on these lines but never goes "Won.swift" file.
Any suggestion?
Thank you.
It sounds like your variable "flag" isn't being properly set to true. Have you tried putting some println()'s in that code to see whether that code is actually being called?
Also, why are you putting it in the mainViewController? Put it in the play scene, so that the scene is controlling when you move to a different scene. Your main view controller should (probably) only fire up the very first scene, and let the scene logic handle transitioning to other scenes.
In fact, that could be your problem - if that "flag" is set to true, and that function is called repeatedly, say, every frame, then that file is getting called up repeatedly. Or, if you're setting flag back to false in there, and that code gets executed, somewhere else, it might be calling your other scene file, and immediately switching back to it so that you never see the "Won" scene.
Could be a lot of things. Start sprinkling some println() and set breakpoints to figure out what exactly is going on.