I'm using an SKScene as an overlay over my Scene Kit scene, but when I add a child node to the SKScene it doesn't render to the screen. Here is my code(truncated):
import SceneKit
import SpriteKit
class GameViewController {
var scene = SCNScene()
var overlay = SKScene()
var expand = SKSpriteNode(imageNamed: "expand")
var trash = SKSpriteNode(imageNamed: "trash")
var palette = SKSpriteNode(imageNamed: "palette")
override func viewDidLoad() {
super.viewDidLoad()
// set up scene
let sceneView = view as! SCNView
sceneView.backgroundColor = SKColor.blackColor()
sceneView.autoenablesDefaultLighting = true
sceneView.showsStatistics = false
setupScene()
sceneView.scene = scene
sceneView.pointOfView = cameraNode
sceneView.overlaySKScene = overlay
setupEnvironment()
setupHud()
}
func setupHud() {
var sizeOfModifiers = (expand.frame.size.width + trash.frame.size.width + palette.frame.size.width) / 2
trash.anchorPoint = CGPointMake(0, 0)
palette.anchorPoint = CGPointMake(0, 0)
expand.anchorPoint = CGPointMake(0, 0)
trash.position = CGPointMake((view.frame.width / 2) - sizeOfModifiers, 0)
palette.position = CGPointMake(trash.position.x + trash.frame.size.width, 0)
expand.position = CGPointMake(palette.position.x + trash.frame.size.width, 0)
overlay.addChild(trash)
overlay.addChild(palette)
overlay.addChild(expand)
}
}
From what I see here, the code is written correctly. The images are loading, but it doesn't get rendered over the 3d elements in the scene.
I asked this question prematurely. From my research, the SKScene needs to be a separate class. I made a schoolboy error.
Related
I'm investigating iOS and trying to understand is it possible to change the texture of the model using any other picture?
Let's say I have model.obj with related texture green.png which is applied to this model so the appearance of the product is green. Is it possible to choose any other image, for example, blue.png, and apply it programmatically in runtime to 3d model and make the appearance of the product blue?
I have one working example
override func viewDidLoad() {
super.viewDidLoad()
let node = SCNNode()
let geometry = SCNSphere(radius: 0.2)
node.position = SCNVector3(0, 0, -1)
sceneView.backgroundColor = .black
sceneView.scene = SCNScene()
node.geometry = self.geometry
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
But when I try apply image to uploaded 3d model it's appearance doesn't change, here is a code.
override func viewDidLoad() {
super.viewDidLoad()
let tempScene = SCNScene(named: "art.scnassets/California_chair_1.obj")!
let node = tempScene.rootNode
node.position = SCNVector3(0, 0, -1)
sceneView.scene = SCNScene()
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
/**/
let material = SCNMaterial()
material.isDoubleSided = false
material.diffuse.contents = image
node.geometry?.materials = [material]
/**/
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
How to apply image to any 3d model created by designer?
Many thanks for any help!
For retrieving a model from SCNScene, you may use subscript .childNodes[0] several times to get to geometry and its corresponding materials in hierarchy.
import ARKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView!
let node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene(named: "art.scnassets/California_chair.scn")!
sceneView.autoenablesDefaultLighting = true
node = sceneView.scene.rootNode.childNode(withName: "firstChair",
recursively: true)
let green = UIColor.green
node?.childNodes[0].geometry?.firstMaterial?.diffuse.contents = green
}
#IBAction func changeTexture(_ sender: UIButton) {
let blue = UIImage(named: "art.scnassets/blueTexture.png")
node.geometry?.firstMaterial?.diffuse.contents = blue
}
}
Consider that SCNGeometry may be nested inside deep hierarchy:
node?.childNodes[0].childNodes[0].childNodes[0].geometry.firstMaterial?.diffuse
In Xcode's Scene graph such a nested hierarchy looks like this:
Also always check your node's size (scale), to find out if your camera is inside 3D model or not.
P.S.
In case you use obj models – use their corresponding mtl textures:
sceneView.scene = SCNScene(named: "art.scnassets/file.obj")!
let obj = sceneView.scene.rootNode.childNode(withName: "default",
recursively: true)
obj?.geometry?.firstMaterial?.diffuse.contents = UIImage(named:
"art.scnassets/file.mtl")
I'm having a difficult time trying to figure out how to get my SceneKit camera to orbit around a specific node in my game.
If I have a single node (a ship) and a camera in my scene everything works fine. If I add an additional node (a planet) the cameras pivot point appears to change from my ship to a space between my ship and planet.
Things I've tried:
Setting a lookat constraint on my camera (set to the ship)
Settingcamera position to my ship (it will move but the pivot point
still seems to be between the two objects)
Changing the cameras pivot point
example:
class TestSceneViewController: UIViewController, SCNSceneRendererDelegate {
var scnView: SCNView = SCNView()
var scnScene: SCNScene!
var cameraNode: SCNNode!
var ship: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
setupCamera()
...
func setupView() {
// scnView = self.view as! SCNView
// retrieve the SCNView
scnView = SCNView(frame: view.frame)
scnView.showsStatistics = true
view.addSubview(scnView)
scnView.allowsCameraControl = true
scnView.defaultCameraController.interactionMode = .orbitTurntable
scnView.defaultCameraController.inertiaEnabled = true
scnView.delegate = self
scnView.isPlaying = true
scnView.loops = true
}
func setupScene () {
scnScene = SCNScene()
scnView.scene = scnScene
let ships = SCNScene(named: "art.scnassets/simpleshuttle3.scn")
ship = ships!.rootNode.childNode(withName: "ship", recursively: true)
ship?.position = SCNVector3(x: 0, y: 0, z: 0)
scnScene.rootNode.addChildNode(ship!)
let planets = SCNScene(named: "art.scnassets/sphere.scn")!
if let planet = planets.rootNode.childNode(withName: "Ball", recursively: true){
planet.position = SCNVector3(x: 0, y: 0, z: 40)
scnScene.rootNode.addChildNode(planet)
}
}
func setupCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: ship.position.x, y: ship.position.y, z: 80)
cameraNode.camera?.motionBlurIntensity = 1.0
cameraNode.camera?.automaticallyAdjustsZRange = true
scnScene.rootNode.addChildNode(cameraNode)
}
You are enabling the manual camera control.
scnView.allowsCameraControl = true
If you want to use e.g. SCNLookAtConstraint you have to disable that. Otherwise you have a conflict. The camera is not supposed to point at a certain position while simultaneously being rotated by the user.
If you want to stay with the default camera controller you can create an additional SCNNode as the parent for your planet and the ships. This parent node can now be moved so that the pivot point is at your desired position.
Somebody told me in one of my other questions that SpriteKit was easier than UI. I searched online on how to get started with SpriteKit, and I got this: https://www.raywenderlich.com/145318/spritekit-swift-3-tutorial-beginners. I put the images in and everything, I put this code in:
import SpriteKit
class GameScene: SKScene {
// 1
let player = SKSpriteNode(imageNamed: "player")
override func didMove(to view: SKView) {
// 2
backgroundColor = SKColor.white
// 3
player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
// 4
addChild(player)
}
}
(the code they told me to put in), and when I run it, I just see a blank screen. On the tutorial, it had a ninja, but mine is just a blank screen.
Can anyone help with this?
if the screen is white try this :
import SpriteKit
class GameScene: SKScene {
// 1
var player = SKSpriteNode()
override func didMove(to view: SKView) {
// 2
backgroundColor = SKColor.white
// 3
let image = UIImage(named: "player")
let texture = SKTexture(image: image!)
player = SKSpriteNode(texture: texture)
player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
// 4
addChild(player)
}
}
If the screen not white : make sure the scene presented correctly .
If you have the GameScene.sks :
In GameViewController :
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
let scene = SKScene(fileNamed: "GameScene")
scene.scaleMode = .aspectFill
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
If you don't have GameScene.sks File
In GameViewController :
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
let scene = GameScene(size : view.frame.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
I am trying to implement a scene which consists of a ball falling from a height and bouncing off the floor, i would also like the camera to follow the ball throughout its journey. Bellow is an attempt however the ball does not seem to be affected by gravity as println near the bottom of the code always outputs 100. however in reality the ball is on screen for a brief moment quickly falls off screen. What have i done wrong?
class obj {
var sn_node: SCNNode
init() {
let geom = SCNSphere(radius: 2.0)
sn_node = SCNNode(geometry: geom)
sn_node.position = SCNVector3Make(0, 100, 0)
sn_node.physicsBody = SCNPhysicsBody.dynamicBody()
}
}
var obj = obj()
class SceneKitController: NSViewController,SCNSceneRendererDelegate {
var _scene:SCNScene!
var _cameraNode:SCNNode!
var _floorNode:SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
let sceneView = view as SCNView
sceneView.backgroundColor = NSColor.blackColor()
setupScene()
sceneView.scene = _scene
sceneView.delegate = self
sceneView.jitteringEnabled = true
sceneView.autoenablesDefaultLighting = true
var newPos = obj.sn_node.position
newPos.z += 10
_cameraNode.position = newPos
sceneView.pointOfView = _cameraNode
}
func setupScene() {
_scene = SCNScene()
setupEnviroment()
setupSceneElements()
setupInitial()
}
func setupEnviroment() {
//create main camera
let camera = SCNCamera()
_cameraNode = SCNNode()
_cameraNode.camera = camera
//floor
var floor = SCNFloor()
floor.reflectivity = 0;
_floorNode = SCNNode(geometry: floor)
_floorNode.physicsBody = SCNPhysicsBody.staticBody()
_scene.rootNode.addChildNode(_floorNode)
}
func setupSceneElements() {
}
func setupInitial() {
_scene.rootNode.addChildNode(obj.sn_node)
}
func renderer(aRenderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: NSTimeInterval){
var newPos = obj.sn_node.position
println(newPos.y)
}
}
obj.sn_node.presentatioNode.position will give you what you want.
Hello I have a SCNScene with a sprite kit overlay. I also have a subview that contains just a UISlider. The Slider controls the value of one of the SKLabel nodes in the sprite kit overlay. However when I am moving the slider the label does not update. It only updates once I touch outside of the slider. So I am guessing that the SCNScene and the overlay don't update while my touches are in the subview and only update once I touch back to the main view. How can I fix this?
Thanks!
My project is in swift, but if you only know objective-c ill still gratefully take your answer and translate on my own.
SceneKit Code:
class MyMarbleScene: UIViewController {
var scene: SCNScene!
var sceneView: SCNView!
var hud : MyMarbleSpriteKit!
var cameraNode: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
scene = SCNScene()
sceneView = SCNView()
sceneView.frame = self.view.frame
sceneView.autoresizingMask = UIViewAutoresizing.allZeros
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = false
sceneView.allowsCameraControl = false
sceneView.backgroundColor = UIColor.blackColor()
self.view = sceneView
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 20, z: 60)
hud = MyMarbleSpriteKit(size: self.view.frame.size, game: self)
sceneView.overlaySKScene = hud
let frame = CGRectMake(10.0, self.view.frame.height*0.5, 180.0, 10.0)
let slider = UISlider(frame: frame)
slider.addTarget(self, action: "sliderValueChanged:", forControlEvents: UIControlEvents.ValueChanged)
slider.backgroundColor = UIColor.clearColor()
slider.minimumValue = 1.0
slider.maximumValue = 10.0
slider.continuous = true
slider.value = 5
self.view?.addSubview(slider)
}
func sliderValueChanged(sender: UISlider) {
var currentValue = Int(sender.value)
println("\(currentValue)")
self.hud.updateLabel(currentValue)
}
}
SpriteKit code
class MyMarbleSpriteKit: SKScene {
var myScene: MyMarbleScene!
var SensLabelTwo = SKLabelNode()
override func didMoveToView(view: SKView) {
}
init(size: CGSize, game:MyMarbleScene) {
super.init(size: size)
myScene = game
let SensLabel = SKLabelNode()
SensLabel.text = "Marble Sensitivity:"
SensLabel.fontName = "AvenirNext-HeavyIta"
SensLabel.fontSize = 40
SensLabel.fontColor = UIColor.whiteColor()
SensLabel.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Top
SensLabel.position = CGPoint(x: 170, y: size.height-20)
self.addChild(SensLabel)
SensLabelTwo.text = "5"
SensLabelTwo.fontName = "AvenirNext-HeavyIta"
SensLabelTwo.fontSize = 60
SensLabelTwo.fontColor = UIColor.whiteColor()
SensLabelTwo.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Top
SensLabelTwo.position = CGPoint(x: 100, y: size.height-80)
self.addChild(SensLabelTwo)
}
func updateLabel(text: Int){
println("helllo")
SensLabelTwo.text = String(format:"%i", text)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
the 2D scene is not automatically refreshed when it's modified.
You will have to wait for the 3D scene to be redisplayed first. This happens when 3D animations or actions are running or when playing is set to YES. Alternatively, you can manually request a refresh with -[SCNView setNeedsDisplay].