I have been working on a game that has different levels, that you access through menus. The levels are 3D and use SceneKit, and the menus are 2D and use SpriteKit.
The game starts in level 1 which is an UiViewController, and when you beat it, the menu appears. If you press a button (that says "Level 1") in the menu, you will be directed back to level 1, but for some reason when loading level 1 again on the iPhone, it crashes, but the simulator is working fine.
I have a simple example code here, which does pretty much the same thing and crashes only on the device:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
func ajrj() {
presentViewController(GameViewController(), animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view = SCNView()
var jarrarar = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(GameViewController.ajrj), userInfo: nil, repeats: true)
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor.darkGrayColor()
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNodeWithName("ship", recursively: true)!
// animate the 3d object
ship.runAction(SCNAction.repeatActionForever(SCNAction.rotateByX(0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.blackColor()
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
func handleTap(gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.locationInView(scnView)
let hitResults = scnView.hitTest(p, options: nil)
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result: AnyObject! = hitResults[0]
// get its material
let material = result.node!.geometry!.firstMaterial!
// highlight it
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
// on completion - unhighlight
SCNTransaction.setCompletionBlock {
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
material.emission.contents = UIColor.blackColor()
SCNTransaction.commit()
}
material.emission.contents = UIColor.redColor()
SCNTransaction.commit()
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return .AllButUpsideDown
} else {
return .All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
So is it a bug? Should i just trust the simulator? Is this a bad way of presenting a view controller? Why is it crashing on the device?
UIViewController does not have an initializer with no arguments. You should use this one:
public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
and provide it the name of the xib file.
Simulator can handle this situation - it guesses the xib, but the device not.
This smells like a memory leak to me because it gets progressively slower as the program runs. It looks like you instantiate the scene/view each time you present it.
The timer has a string reference to the scene. It appears to fire presentViewController() on the previous VC. Each time through (every 2 seconds? Why?) you'll consume another VC and scene.
I don't see where the stack ever gets popped.
What does the Leaks Instrument show?
Related
Here is a simple SceneKit project with two boxes. I’ve added a CIBloom filter to one of them. When I rotate the scene, the ”glow” effect is rendered behind the other box?
I saw someone else had this issue and solved it by setting writesToDepthBuffer to false but in my case it’s important to keep all 3D data (I want to rotate around the scene).
I’ve provided a couple of images and the code.
I would really appreciate any help!
Here is the code I used:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let box = SCNNode(geometry: SCNBox(width: 8.0, height: 8.0, length: 2.0, chamferRadius: 0.0))
box.geometry?.firstMaterial?.diffuse.contents = UIColor.green
box.position.z = -4
scene.rootNode.addChildNode(box)
let box2 = SCNNode(geometry: SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0))
box2.geometry?.firstMaterial?.diffuse.contents = UIColor.red
scene.rootNode.addChildNode(box2)
let bloomFilter = CIFilter(name:"CIBloom")!
bloomFilter.setValue(10.0, forKey: "inputIntensity")
bloomFilter.setValue(100.0, forKey: "inputRadius")
box2.filters = [bloomFilter]
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
scnView.autoenablesDefaultLighting = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
Ive been trying to move a cube forward when the user long presses the screen. I can't get it to move smoothly. Is there any way I can move it only forward (in the negative direction) and update the position on each frame the scene?
Here is what I have so far:
var carLocation = SCNVector3(x:0, y: 0, z:-0.01)
func setupScene() {
sceneView = self.view as? SCNView
scene = SCNScene(named: "art.scnassets/Map.scn")
sceneView.scene = scene
let holdRecognizer = UILongPressGestureRecognizer()
holdRecognizer.addTarget(self, action: #selector(GameViewController.hold(recognizer:)))
sceneView.addGestureRecognizer(holdRecognizer)
}
#objc func hold(recognizer: UILongPressGestureRecognizer) {
print("Held down!")
let position = recognizer.location(in: sceneView)
carLocation = SCNVector3(x:0, y: 0, z:-0.01)
carNode.physicsBody?.velocity += carLocation
}
I put a count in your print, just so you could see that LongPress is going to repeat itself... 'a lot', which is probably not what you want long term. So, assuming you created something like:
let p = SCNPhysicsBody(type: .dynamic, shape: nil)
p.isAffectedByGravity = false
shipNode.physicsBody = p
Then this will move your object forward and backward smoothly.
#objc func hold(recognizer: UILongPressGestureRecognizer) {
print("Held down! \(count)")
count += 1
shipNode.physicsBody?.velocity = SCNVector3Make(0.0, 0, 0.51)
}
#objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
shipNode.physicsBody?.velocity = SCNVector3Make(0.0, 0, -0.51)
}
Unless you have a ton of objects in there, I can't see any reason for it to be slow. Set scnView.showsStatistics = true to see your FPS. Hopefully, you are not trying to run scenekit in the simulator?
I am trying to display a 3D model when a picture is tapped (See here). The picture gets tapped, it segues to 3DFloorPlanViewController (which is embedded in a navigation controller), the camera works but there is no 3D object displayed (See here).
I've tried this exact same code without a navigation controller and it works perfectly so I'm confused as to why it's not working when embedded in a navigation controller.
Storyboard picture
My 3D objects are located in a folder called "3D Objects" (See here). I've tried both named: "3D Objects/paperPlane.scn" and named: "paperPlane.scn" in the addPaperPlane function and neither worked.
Here's the code to 3DFloorPlanViewController:
import UIKit
import ARKit
import SceneKit
class _DFloorPlanViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet weak var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
addPaperPlane()
//addCar()
configureLighting()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
func addPaperPlane(x: Float = 0, y: Float = 0, z: Float = -0.5) {
guard let paperPlaneScene = SCNScene(named: "3D Objects/paperPlane.scn"), let paperPlaneNode = paperPlaneScene.rootNode.childNode(withName: "paperPlane", recursively: true) else { return }
paperPlaneNode.position = SCNVector3(x, y, z)
sceneView.scene.rootNode.addChildNode(paperPlaneNode)
}
func addCar(x: Float = 0, y: Float = 0, z: Float = -0.5) {
guard let carScene = SCNScene(named: "car.dae") else { return }
let carNode = SCNNode()
let carSceneChildNodes = carScene.rootNode.childNodes
for childNode in carSceneChildNodes {
carNode.addChildNode(childNode)
}
carNode.position = SCNVector3(x, y, z)
carNode.scale = SCNVector3(0.5, 0.5, 0.5)
sceneView.scene.rootNode.addChildNode(carNode)
}
func configureLighting() {
sceneView.autoenablesDefaultLighting = true
sceneView.automaticallyUpdatesLighting = true
}
}
At the moment there is no scene set to your scene view
let scene = SCNScene()
self.sceneView.scene = scene
in general try this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Set the view's delegate
self.sceneView.delegate = self
self.sceneView.showsStatistics = true
self.sceneView.autoenablesDefaultLighting = true
let scene = SCNScene()
self.sceneView.scene = scene
}
Below is the function that loads my object. Whenever I run the app, the object loads perfectly but when I try getting close or moving around, it moves along with the camera. I tried loading using hit test, it works but then collisions do not work when I use hit results and world positioning.
func addBackboard() {
guard let bucketScene = SCNScene(named:"art.scnassets/BucketBlue.scn") else {
return
}
guard let bucketNode = bucketScene.rootNode.childNode(withName: "tube", recursively: false) else {
return
}
bucketNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
bucketNode.position = SCNVector3(x: 0, y: -3.5, z: -5)
let physicsShape = SCNPhysicsShape(node: bucketNode, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
let physicsBody = SCNPhysicsBody(type: .static, shape: physicsShape)
bucketNode.physicsBody = physicsBody
sceneView.scene.rootNode.addChildNode(bucketNode)
}
I found some Apple documentation that might help. Summary: "To track the static positions and orientations of real or virtual objects relative to the camera, create anchor objects and use the add(anchor:) method to add them to your AR session."
And then, did you implement the ARSCNViewDelegate methods?
I suppose your code should look like the following one:
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
//.................................
func addBackboard() {
sceneView.delegate = self
guard let scene = SCNScene(named:"art.scnassets/BucketBlue.scn") else {
return
}
sceneView.scene = scene
let bucketNode = SCNNode()
bucketNode.geometry = SCNTube()
bucketNode.scale = SCNVector3(0.5, 0.5, 0.5)
bucketNode.position = SCNVector3(x: 0, y: -3.5, z: -5)
let physicsShape = SCNPhysicsShape(
node: bucketNode,
options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
let physicsBody = SCNPhysicsBody(type: .static, shape: physicsShape)
bucketNode.physicsBody = physicsBody
scene.rootNode.addChildNode(bucketNode)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
}
I am trying to access the childnode: boxNode, and then rotate the node using the input from a pan gesture recognizer. How should I access this node so I can manipulate it? Should I declare the cube node in a global scope and modify that way? I am new to Swift so I apologize if this code looks sloppy. I would like to add rotation actions inside of my panGesture() function. Thanks!
import UIKit
import SceneKit
class GameViewController: UIViewController {
var scnView: SCNView!
var scnScene: SCNScene!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
}
// override var shouldAutorotate: Bool {
// return true
// }
//
// override var prefersStatusBarHidden: Bool {
// return true
// }
func setupView() {
scnView = self.view as! SCNView
}
func setupScene() {
scnScene = SCNScene()
scnView.scene = scnScene
scnView.backgroundColor = UIColor.blueColor()
initCube()
initLight()
initCamera()
}
func initCube () {
var cubeGeometry:SCNGeometry
cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let boxNode = SCNNode(geometry: cubeGeometry)
scnScene.rootNode.addChildNode(boxNode)
}
func initLight () {
let light = SCNLight()
light.type = SCNLightTypeAmbient
let lightNode = SCNNode()
lightNode.light = light
light.castsShadow = false
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
scnScene.rootNode.addChildNode(lightNode)
}
func initCamera () {
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 5.0)
scnScene.rootNode.addChildNode(cameraNode)
}
func initRecognizer () {
let panTestRecognizer = UIPanGestureRecognizer(target: self, action: #selector(GameViewController.panGesture(_:)))
scnView.addGestureRecognizer(panTestRecognizer)
}
//TAKE GESTURE INPUT AND ROTATE CUBE ACCORDINGLY
func panGesture(sender: UIPanGestureRecognizer) {
//let translation = sender.translationInView(sender.view!)
}
}
First of all you should update your Xcode. It looks like this is an older Swift version you are using. SCNLightTypeAmbient is .ambient in Swift 3.
So the way you should go is by giving your nodes names to identify them:
myChildNode.name = "FooChildBar"
and then you can call on your parent node
let theNodeYouAreLookingFor = parentNode.childNode(withName: "FooChildBar", recursively: true)
You can also use this on your scene's root node
let theNodeYouAreLookingFor = scene.rootNode.childNode(withName: "FooChildBar", recursively: true)
which will look at all nodes in your scene and return the one that you gave the name.