How to detect the current scene in SpriteKit project - ios

In my SpriteKit app I've 3 different scenes and I want to set a specific settings for each scene.
I mean I want set visible an AdBanner in MainPage and SomeInfoScene but not in GameScene. How can I do this?
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let mainPage = SKScene(fileNamed: "MainPage")!
mainPage.name = "MainPage"
let someInfoPage = SKScene(fileNamed: "SomeInfoScene")!
someInfoPage.name = "SomeInfoScene"
let gameScene = SKScene(fileNamed: "GameScene")!
gameScene.name = "GameScene"
view.presentScene(mainPage)
if let currentScene = view.scene {
if currentScene.name == mainPage.name {
print("MainPage")
adBannerView.isHidden = false
}
if currentScene.name == someInfoPage.name {
print("SomeInfoScene")
adBannerView.isHidden = false
}
if currentScene.name == gameScene.name {
print("GameScene")
adBannerView.isHidden = true
}
}
}
}

This is tracked by scene property of the SKView class. For instance:
if let view = self.view as? SKView {
if let currentScene = view.scene {
print("Current scene is: \(currentScene)")
else {
print("Current scene is nil")
}
}
By the way, your first line really should read:
if let view = self.view as? SKView { ...
That's the standard/idiomatic way to try a downcast in an if statement.
Update. You also need to set the name property if you are planning to use that in your game code (in the *.sks file or directly inside your code). For instance:
let scene = SKScene(fileNamed: "MainPage")!
scene.name = "MainPage"
view.presentScene(scene)

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.

ARKit : Handle tap to show / hide a node

I am new to ARKit , and i am trying an example to create a SCNBox on tap location. What i am trying to do is on initial touch i create a box and on the second tap on the created box it should be removed from the scene. I am doing the hit test. but it keeps on adding the box. I know this is a simple task, but i am unable to do it
#objc func handleTap(sender: UITapGestureRecognizer) {
print("hande tapp")
guard let _ = sceneView.session.currentFrame
else { return }
guard let scnView = sceneView else { return }
let touchLocation = sender.location(in: scnView)
let hitTestResult = scnView.hitTest(touchLocation, types: [ .featurePoint])
guard let pointOfView = sceneView.pointOfView else {return}
print("point \(pointOfView.name)")
if hitTestResult.count > 0 {
print("Hit")
if let _ = pointOfView as? ARBox {
print("Box Available")
}
else {
print("Adding box")
let transform = hitTestResult.first?.worldTransform.columns.3
let xPosition = transform?.x
let yPosition = transform?.y
let zPosition = transform?.z
let position = SCNVector3(xPosition!,yPosition!,zPosition!)
basketCount = basketCount + 1
let newBasket = ARBox(position: position)
newBasket.name = "basket\(basketCount)"
self.sceneView.scene.rootNode.addChildNode(newBasket)
boxNodes.append(newBasket)
}
}
}
pointOfView of a sceneView, is the rootnode of your scene, which is one used to render your whole scene. For generic cases, it usually is an empty node with lights/ camera. I don't think you should cast it as ARBox/ or any type of SCNNode(s) for that matter.
What you probably can try is the logic below (hitResults are the results of your hitTest):
if hitResults.count > 0 {
if let node = hitResults.first?.node as SCNNode? (or ARBox) {
// node.removeFromParentNode()
// or make the node opaque if you don't want to remove
else {
// add node.

Load .scn file as SCNNode LOSS animations

When I load Horse.scn to a SCNNode, the animations are lost.
func addHorse(for parentNode:SCNNode, postion:SCNVector3){
guard let virtualObjectScene = SCNScene(named: "Horse.scn") else {
return
}
let wrapperNode = SCNNode()
for child in virtualObjectScene.rootNode.childNodes {
child.geometry?.firstMaterial?.lightingModel = .physicallyBased
child.movabilityHint = .movable
wrapperNode.addChildNode(child)
}
wrapperNode.scale = SCNVector3(0.001,0.001,0.001)
wrapperNode.position = postion;
parentNode.addChildNode(wrapperNode)
}
If I load scene from Horse.dae file, everything is fine. Setting rootNode 's playing property to YES doesn't work.

SCNNode shared between two view controllers disappears

I have a very large (500mb) .dae file which takes a few seconds to load, so I load it once in the background and reuse it in two identical view controllers. The first controller shows the model fine, but when I modally present the next view controller (which also shows the model fine) and dismiss it, the model is gone, removed from the first view controller's scene. Does SceneKit try to dispose of Nodes when a SCNView is cleaned up? I'm not sure how else I could explain what is happening, because the two view controllers are two instances of the same class being used as child view controllers.
// CarLoader.swift
class CarLoader {
private static let instance = CarLoader();
var storedCar: SCNNode = SCNNode();
var loaded: Bool = false;
var onReady:(car: SCNNode)->() = { (car: SCNNode) in } ;
private init(){}
class func load( onLoad: ()->() ){
if CarLoader.instance.loaded {
onLoad();
return;
}
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
let scene = SCNScene(named: "Scenes.scnassets/export_test6.dae");
dispatch_async(dispatch_get_main_queue()) { // 2
let nodeArray = scene!.rootNode.childNodes;
let centeringNode = SCNNode();
for childNode in nodeArray {
centeringNode.addChildNode(childNode as SCNNode)
}
centeringNode.eulerAngles.x = -Float(M_PI/2);
var min = SCNVector3(), max = SCNVector3();
let _ = centeringNode.getBoundingBoxMin(&min, max: &max);
let w = CGFloat(abs(max.x - min.x));
let h = CGFloat(abs(max.y - min.y));
//let l = CGFloat(abs(max.z - min.z));
centeringNode.position.x = -min.x + -Float(w/2);
centeringNode.position.y = -min.y/2 + -Float(h/2); // it's still a little low in y, but we want it lower
centeringNode.position.z = 0;
CarLoader.instance.storedCar = centeringNode;
CarLoader.instance.loaded = true;
CarLoader.instance.onReady(car: centeringNode);
onLoad();
}
}
}
class func cachedModel( onReady: (car: SCNNode) -> ()) {
CarLoader.instance.onReady = onReady;
if CarLoader.instance.loaded {
CarLoader.instance.onReady(car: CarLoader.instance.storedCar);
}
else {
// callback is called when ready
}
}
}
I ended up working around it by naming it and checking if it's there duringViewDidAppear.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
// the car disappears in the Explorer when it reappears after the settings menu has been up
if self.carRootNode.childNodeWithName(CarLoader.CarModelNodeName, recursively: false) == nil {
self.insertCarModel();
}
}

SpriteKit reference nodes from level editor

I'm using the scene editor in SpriteKit to place color sprites and assign them textures using the Attributes Inspector. My problem is trying to figure out how to reference those sprites from my GameScene file. For example, I'd like to know when a sprite is a certain distance from my main character.
Edit - code added
I'm adding the code because for some reason, appzYourLife's answer worked great in a simple test project, but not in my code. I was able to use Ron Myschuk's answer which I also included in the code below for reference. (Though, as I look at it now I think the array of tuples was overkill on my part.) As you can see, I have a Satellite class with some simple animations. There's a LevelManager class that replaces the nodes from the scene editor with the correct objects. And finally, everything gets added to the world node in GameScene.swift.
Satellite Class
func spawn(parentNode:SKNode, position: CGPoint, size: CGSize = CGSize(width: 50, height: 50)) {
parentNode.addChild(self)
createAnimations()
self.size = size
self.position = position
self.name = "satellite"
self.runAction(satAnimation)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = PhysicsCategory.satellite.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.laser.rawValue
self.physicsBody?.collisionBitMask = 0
}
func createAnimations() {
let flyFrames:[SKTexture] = [textureAtlas.textureNamed("sat1.png"),
textureAtlas.textureNamed("sat2.png")]
let flyAction = SKAction.animateWithTextures(flyFrames, timePerFrame: 0.14)
satAnimation = SKAction.repeatActionForever(flyAction)
let warningFrames:[SKTexture] = [textureAtlas.textureNamed("sat8.png"),
textureAtlas.textureNamed("sat1.png")]
let warningAction = SKAction.animateWithTextures(warningFrames, timePerFrame: 0.14)
warningAnimation = SKAction.repeatActionForever(warningAction)
}
func warning() {
self.runAction(warningAnimation)
}
Level Manager Class
import SpriteKit
class LevelManager
{
let levelNames:[String] = ["Level1"]
var levels:[SKNode] = []
init()
{
for levelFileName in levelNames {
let level = SKNode()
if let levelScene = SKScene(fileNamed: levelFileName) {
for node in levelScene.children {
switch node.name! {
case "satellite":
let satellite = Satellite()
satellite.spawn(level, position: node.position)
default: print("Name error: \(node.name)")
}
}
}
levels.append(level)
}
}
func addLevelsToWorld(world: SKNode)
{
for index in 0...levels.count - 1 {
levels[index].position = CGPoint(x: -2000, y: index * 1000)
world.addChild(levels[index])
}
}
}
GameScene.swift - didMoveToView
world = SKNode()
world.name = "world"
addChild(world)
physicsWorld.contactDelegate = self
levelManager.addLevelsToWorld(self.world)
levelManager.levels[0].position = CGPoint(x:0, y: 0)
//This does not find the satellite nodes
let satellites = children.flatMap { $0 as? Satellite }
//This does work
self.enumerateChildNodesWithName("//*") {
node, stop in
if (node.name == "satellite") {
self.satTuple.0 = node.position
self.satTuple.1 = (node as? SKSpriteNode)!
self.currentSatellite.append(self.satTuple)
}
}
The Obstacle class
First of all you should create an Obstacle class like this.
class Obstacle: SKSpriteNode { }
Now into the scene editor associate the Obstacle class to your obstacles images
The Player class
Do the same for Player, create a class
class Player: SKSpriteNode { }
and associate it to your player sprite.
Checking for collisions
Now into GameScene.swift change the updated method like this
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let obstacles = children.flatMap { $0 as? Obstacle }
let player = childNodeWithName("player") as! Player
let obstacleNearSprite = obstacles.contains { (obstacle) -> Bool in
let distance = hypotf(Float(player.position.x) - Float(obstacle.position.x), Float(player.position.y) - Float(obstacle.position.y))
return distance < 100
}
if obstacleNearSprite {
print("Oh boy!")
}
}
What does it do?
The first line retrieves all your obstacles into the scene.
the second line retrieves the player (and does crash if it's not present).
Next it put into the obstacleNearSprite constant the true value if there is at least one Obstacle at no more then 100 points from Player.
And finally use the obstacleNearSprite to print something.
Optimizations
The updated method gets called 60 times per second. We put these 2 lines into it
let obstacles = children.flatMap { $0 as? Obstacle }
let player = childNodeWithName("player") as! Player
in order to retrieves the sprites we need. With the modern hardware it is not a problem but you should save references to Obstacle and Player instead then searching for them in every frame.
Build a nice game ;)
you will have to loop through the children of the scene and assign them to local objects to use in your code
assuming your objects in your SKS file were named Obstacle1, Obstacle2, Obstacle3
Once in local objects you can check and do whatever you want with them
let obstacle1 = SKSpriteNode()
let obstacle2 = SKSpriteNode()
let obstacle3 = SKSpriteNode()
let obstacle3Location = CGPointZero
func setUpScene() {
self.enumerateChildNodesWithName("//*") {
node, stop in
if (node.name == "Obstacle1") {
self.obstacle1 = node
}
else if (node.name == "Obstacle2") {
self.obstacle2 = node
}
else if (node.name == "Obstacle3") {
self.obstacle3Location = node.position
}
}
}

Resources