Add SKReferenceNode/SKScene to another SKScene in SpriteKit - swift

I would like to add a SKScene to my main GameScene. SKReferenceNode seems to be a good solution.
I have :
- GameScene.sks (main scene)
- Countdown.sks (scene to add to GameScene)
- Countdown.swift (Custom class, how does to init it? SKScene ? SKReferenceNode ? SKNode)
I don't know how to add programmatically my countdown using my class Countdown.
I tried:
let path = Bundle.main.path(forResource: "Countdown", ofType: "sks")
let cd = SKReferenceNode (url: NSURL (fileURLWithPath: path!) as URL) as! Countdown
cd.name = "countdown"
self.addChild(cd)
But I have the following error :
Could not cast value of type 'SKReferenceNode' (0x10d97ad88) to 'LYT.Countdown' (0x10a5709d0
I also tried something more simple like:
let cd=Countdown(scene:self)
self.addChild(cd)
But I don't know how to init the class using the Countdown.sks file.
I know I also have the possibility to create a SKNode class, and init it 100% programmatically, but it really important for me to use the associated .sks file in order to use the Xcode scene editor.

I do that, I don't know if is the best way to do this, but works:
I've 2 file Dragon.swift and sks
I've added a "main" node like DragonNode and other node children of this
Now, the DragonNode is a custom class, set it in sks file:
The DragonNode is a normal SKSpriteNode
class DragonNode: SKSpriteNode, Fly, Fire {
var head: SKSpriteNode!
var body: SKSpriteNode!
var shadow: SKSpriteNode!
var dragonVelocity: CGFloat = 250
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//Example other node from sks file
body = self.childNodeWithName("Body") as! SKSpriteNode
head = body.childNodeWithName("Head") as! SKSpriteNode
shadow = self.childNodeWithName("Shadow") as! SKSpriteNode
shadow.name = "shadow"
}
//Dragon Func
func fireAction () {}
func flyAction () {}
}
Inside the scene, add a SKReferenceNode:
In the SKScene code:
let dragonReference = self.childNodeWithName("DragonReference") as! SKReferenceNode
let dragonNode = dragonReference.getBasedChildNode() as! DragonNode
print(dragonNode)
//Now you can use the Dragon func
dragonNode.flyAction()
getBasedChildNode() is an extension to find your based node (the first one)
extension SKReferenceNode {
func getBasedChildNode () -> SKNode? {
if let child = self.children.first?.children.first {return child}
else {return nil}
}
}

I do a similar thing to Simone above, but instead of extending the reference node, I added my extension to SKNode.
extension SKNode {
func nodeReferenced() -> SKNode? {
if self .isKind(of: SKReferenceNode.self) {
return children.first!.children.first!
}
return nil
}
}
This way there is no need for the cast if the node is not actually a reference node and make this two step process a one liner. My version would change that above code to:
if let dragonNode = childNodeWithName("DragonReference")?.nodeReferenced() as? DragonNode {
print(dragonNode)
dragonNode.fly()
}
This works for me, but Simone's answer seems more straight forward and maybe flexible than mine, so I'd give them the points. I just like clean code and since we almost never actually need that SKReferenceNode, we can ignore it. Also, when enumerating the nodes, it's easy to ask for a referenced node, get one or nothing, without having to see if the node is actually a referenceNode first, then performing the change.

Related

How do I use a RealityKit ARView with ARImageTrackingConfiguration?

Can I see an example using a RealityKit ARView with ARImageTrackingConfiguration including the ARSessionDelegate delegate methods?
Here is an example of a RealityKit ARView using ARImageTrackingConfiguration and the ARSessionDelegate delegate methods. I didn't see a complete example of exactly this on Stack Overflow so thought I would ask/answer it myself.
import ARKit
import RealityKit
class ViewController: UIViewController, ARSessionDelegate {
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
// There must be a set of reference images in project's assets
guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else { fatalError("Missing expected asset catalog resources.") }
// Set ARView delegate so we can define delegate methods in this controller
arView.session.delegate = self
// Forgo automatic configuration to do it manually instead
arView.automaticallyConfigureSession = false
// Show statistics if desired
arView.debugOptions = [.showStatistics]
// Disable any unneeded rendering options
arView.renderOptions = [.disableCameraGrain, .disableHDR, .disableMotionBlur, .disableDepthOfField, .disableFaceOcclusions, .disablePersonOcclusion, .disableGroundingShadows, .disableAREnvironmentLighting]
// Instantiate configuration object
let configuration = ARImageTrackingConfiguration()
// Both trackingImages and maximumNumberOfTrackedImages are required
// This example assumes there is only one reference image named "target"
configuration.maximumNumberOfTrackedImages = 1
configuration.trackingImages = referenceImages
// Note that this config option is different than in world tracking, where it is
// configuration.detectionImages
// Run an ARView session with the defined configuration object
arView.session.run(configuration)
}
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
// This example assumes only one reference image of interest
// A for-in loop could work for more targets
// Ensure the first anchor in the list of added anchors can be downcast to an ARImageAnchor
guard let imageAnchor = anchors[0] as? ARImageAnchor else { return }
// If the added anchor is named "target", do something with it
if let imageName = imageAnchor.name, imageName == "target" {
// An example of something to do: Attach a ball marker to the added reference image.
// Create an AnchorEntity, create a virtual object, add object to AnchorEntity
let refImageAnchor = AnchorEntity(anchor: imageAnchor)
let refImageMarker = generateBallMarker(radius: 0.02, color: .systemPink)
refImageMarker.position.y = 0.04
refImageAnchor.addChild(refImageMarker)
// Add new AnchorEntity and its children to ARView's scene's anchor collection
arView.scene.addAnchor(refImageAnchor)
// There is now RealityKit content anchored to the target reference image!
}
}
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
guard let imageAnchor = anchors[0] as? ARImageAnchor else { return }
// Assuming only one reference image. A for-in loop could work for more targets
if let imageName = imageAnchor.name, imageName == "target" {
// If anything needs to be done as the ref image anchor is updated frame-to-frame, do it here
// E.g., to check if the reference image is still being tracked:
// (https://developer.apple.com/documentation/arkit/artrackable/2928210-istracked)
if imageAnchor.isTracked {
print("\(imageName) is tracked and has a valid transform")
} else {
print("The anchor for \(imageName) is not guaranteed to match the movement of its corresponding real-world feature, even if it remains in the visible scene.")
}
}
}
// Convenience method to create colored spheres
func generateBallMarker(radius: Float, color: UIColor) -> ModelEntity {
let ball = ModelEntity(mesh: .generateSphere(radius: radius), materials: [SimpleMaterial(color: color, isMetallic: false)])
return ball
}
}

How to programmatically change text of SKLabelNode?

I added some SKLAbelNode into SKNode, which is placed inside SKScene
I need SKNode for grouping some nodes
I added name "altitude" to my SKLAbelNode
and put this code into SKScene class which was associated with my .sks file
var altitude:SKLabelNode = SKLabelNode()
override func sceneDidLoad()
{
if let alti:SKLabelNode = self.childNode(withName: "altitude") as? SKLabelNode {
altitude = alti
}
}
...
override func didMove (...) {
altitude.text = "000"
}
But always I get Fatal error
UPD: I solved this error, but got a question, why when I point a name of sknode even if it's inside in other node and try to get access to it i must point all tree something like
let altitude= (self.childNode(withName: "sknode") as! SKNode).childNode(withName: "altitude") as! SKLabelNode
altitude.text = "000"

Get rootNode of node

In an ARKit project, when the user taps the screen, I want to get the rootNode of the element, that the user want to interact with.
Gesture & Hit test
func screenTapped(_ sender: UITapGestureRecognizer) {
let hitTestResult = sceneView.hitTest(touchLocation)
if let result = hitTestResult.first {
guard let rootNode = getRoot(for: result.node) else {return}
...
}
Recursive function to get the root node
func getRoot(for node: SCNNode) -> SCNNode? {
if let node = node.parent {
return getRoot(for: node)
}
else {
return node
}
}
But it seems odd to me that Swift doesn't offer something by default, while offering recursive methods for child nodes.
Is there an alternative/better approach to this?
Should I write this function as extension for SCNNode?
Isn't it equivalent to sceneView.scene.rootNode?

Load a spritekit scene from another bundle?

I am making a SpriteKit framework using swift 4.2 and want to include some .sks files for scenes and actions. I have tried to load the scene from the bundle using the code below:
class func newGameScene() -> GameScene {
guard let gameScenePath = Bundle(for: self).path(forResource: "GameScene", ofType: "sks") else { assert(false) }
guard let gameSceneData = FileManager.default.contents(atPath: gameScenePath) else { assert(false) }
let gameSceneCoder = NSKeyedUnarchiver(forReadingWith: gameSceneData)
guard let scene = GameScene(coder: gameSceneCoder) else { assert(false) }
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
return scene
}
I load the scene and present it. (This code is mostly from Apple's template for SpriteKit as Im testing this issue.)
guard let view = view else {
return nil
}
let scene = GameScene.newGameScene()
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
return nil
The GameScene.sks and the code is unchanged from Apples template in this case. This code and the .sks assets are in the dynamic framework and imported into another project.
When having the framework load the scene into a view I pass it, it shows the fps and node count but not the "Hello, World!" text.
In the code below, also copied from the template, a break point shows that these are not called when mousing down.
#if os(OSX)
// Mouse-based event handling
extension GameScene {
override func mouseDown(with event: NSEvent) {
if let label = self.label {
label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
}
self.makeSpinny(at: event.location(in: self), color: SKColor.green)
}
override func mouseDragged(with event: NSEvent) {
self.makeSpinny(at: event.location(in: self), color: SKColor.blue)
}
override func mouseUp(with event: NSEvent) {
self.makeSpinny(at: event.location(in: self), color: SKColor.red)
}
}
#endif
I know it must have to do with how SpritKit loads the scene but cannot find a solution. I have to use an NSKeyedUnarchiver becuase SpritKit's built in file initializer:
GameScene(fileNamed: "GameScene")
Only loads from the Main Bundle.
Now in the above I assumed that the file can be loaded by using a coder but Tomato made the point that sks most likely was not saved using a coder. In that case, It may be impossible to load an sks file from another bundle in sprite-kit using the provided api from apple. The answer may not include coders.
I have compiled the above discussion/solution into a single extension function on SKScene. Hope this helps someone!
import SpriteKit
extension SKScene {
static func fromBundle(fileName: String, bundle: Bundle?) -> SKScene? {
guard let bundle = bundle else { return nil }
guard let path = bundle.path(forResource: fileName, ofType: "sks") else { return nil }
if let data = FileManager.default.contents(atPath: path) {
return NSKeyedUnarchiver.unarchiveObject(with: data) as? SKScene
}
return nil
}
}
Just as I thought let gameSceneCoder = NSKeyedUnarchiver(forReadingWith: gameSceneData) was not creating a proper coder for you.
Just do
guard let scene = NSKeyedUnarchiver.unarchiveObject(with: gameSceneData) as? SKScene
else{
assert(false)
}
This will unarchive the file properly for you.
Note, if you want to use GameScene, make sure GameScene is set in the custom class of the SKS file

SpriteKit add one node's children to another

In my sprite kit project I have a scene .sks file which contains the ground physicsBody and background graphics. The scene will be loaded from this file. It also contains an empty node called container. I have another .sks file for each level which contain blocks and enemies. When my scene is initialised I load a level from one of my level .sks files (casting the SKScene to an SKNode) and add into the empty "container" node. I use this code:
import SpriteKit
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(SKNode.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as SKNode
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
class GameScene: SKScene {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.childNodeWithName("//container")?.addChild(SKNode.unarchiveFromFile("level1")!)
}
override 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
}
}
}
However this means that there is an empty unused node in the node hierarchy:
Scene
-background
-ground
-container
--level scene(cast to SKNode) <- empty node here
---block
---enemy
so my question is: how do I add one node's children to another?
-I cannot directly set the children property (not assignable)
-I cannot replace the container node as I loose all its properties
Just iterate over the children array - you'll need to cast from AnyObject back to SKNode to add the node to the new parent:
for node in oldParentNode.children {
node.removeFromParent()
newParentNode.addChild(node as AnyObject)
}
This should do the trick (untested), just add them one by one:
for (CCNode* node in [oneNode.children copy])
{
[node removeFromParent];
[anotherNode addChild:node];
}
The copy is needed to avoid mutating the children array while enumerating it. Alternatively you could also use the reverseObjectEnumerator. Removing the node must be done so addChild: won't complain about the node already having a parent.