Room texture isn't fully rendered when I move SCNCamera around SCNBox inside the virtual room using SceneKit - swift

I have tried to create a virtual room using SceneKit and put into it a box.
Also I have created a separate node with camera called ‘cameraOrbitNode’ (camera locates between the wall of room and the box). The main goal is to move ‘cameraOrbitNode’ around the box inside the room. I implemented it via pan gesture recognizer.
import UIKit
import SceneKit
enum WallType {
case left
case right
case top
case bottom
case forward
case back
}
class ViewController: UIViewController {
//MARK: - Outlets
#IBOutlet fileprivate weak var sceneView: SCNView!
//MARK: - Properties
fileprivate let roomLength: CGFloat = 12
fileprivate let cubeLength: CGFloat = 1
fileprivate var cameraOrbitNode: SCNNode?
fileprivate let panModifier: CGFloat = 100
//MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
setupSceneView()
setupRoom()
setupCameraOrbitNode()
setupGestures()
}
func setupSceneView() {
sceneView.scene = SCNScene()
sceneView.autoenablesDefaultLighting = true
}
func setupRoom() {
let roomNode = SCNNode()
roomNode.position = SCNVector3(0, 0, 0)
for wallType in Array<WallType>([.left, .right, .top, .bottom, .forward, .back]) {
roomNode.addChildNode(getWallNodeWith(wallType: wallType))
}
roomNode.addChildNode(getCubeNode())
sceneView.scene?.rootNode.addChildNode(roomNode)
}
func getWallNodeWith(wallType: WallType) -> SCNNode {
let wallGeometrySize = getWallSize()
let wallGeometry = SCNPlane(width: wallGeometrySize.width, height: wallGeometrySize.height)
wallGeometry.firstMaterial?.diffuse.contents = UIColor.darkGray
let wallNode = SCNNode(geometry: wallGeometry)
wallNode.position = getWallPositionWith(wallType: wallType)
wallNode.eulerAngles = getWallEulerAnglesWith(wallType: wallType)
return wallNode
}
func getWallSize() -> CGSize {
return CGSize(width: roomLength, height: roomLength)
}
func getWallPositionWith(wallType: WallType) -> SCNVector3 {
switch wallType {
case .left:
return SCNVector3(-roomLength / 2, 0, 0)
case .right:
return SCNVector3(roomLength / 2, 0, 0)
case .forward:
return SCNVector3(0, 0, -roomLength / 2)
case .back:
return SCNVector3(0, 0, roomLength / 2)
case .top:
return SCNVector3(0, roomLength / 2, 0)
case .bottom:
return SCNVector3(0, -roomLength / 2, 0)
}
}
func getWallEulerAnglesWith(wallType: WallType) -> SCNVector3 {
switch wallType {
case .left, .right:
return SCNVector3(0, CGFloat.pi / 2, 0)
case .forward, .back:
return SCNVector3(0, 0, 0)
case .top, .bottom:
return SCNVector3(CGFloat.pi / 2, 0, 0)
}
}
func getCubeNode() -> SCNNode {
let cubeGeometry = SCNBox(width: cubeLength, height: cubeLength, length: cubeLength, chamferRadius: 0)
cubeGeometry.firstMaterial?.diffuse.contents = UIColor.blue
let cubeNode = SCNNode(geometry: cubeGeometry)
cubeNode.position = SCNVector3(0, 0, 0)
return cubeNode
}
func setupCameraOrbitNode() {
let camera = SCNCamera()
camera.zNear = 0
camera.zFar = Double(roomLength)
let cameraNode = SCNNode()
cameraNode.position = SCNVector3(0, 0, (roomLength + cubeLength) / 4)
cameraNode.camera = camera
cameraOrbitNode = SCNNode()
guard let cameraOrbitNode = cameraOrbitNode else { return }
cameraOrbitNode.addChildNode(cameraNode)
cameraOrbitNode.position = SCNVector3(0, 0, 0)
sceneView.scene?.rootNode.addChildNode(cameraOrbitNode)
}
#objc func actionPan(gesture: UIPanGestureRecognizer) {
let translation = gesture.velocity(in: gesture.view)
cameraOrbitNode?.eulerAngles.y -= Float(translation.x/CGFloat(panModifier)) * .pi / 180
cameraOrbitNode?.eulerAngles.x -= Float(translation.y/CGFloat(panModifier)) * .pi / 180
}
func setupGestures() {
sceneView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(actionPan(gesture:))))
}
}
But in some cases you can see transparent walls of the room (basically room texture isn't fully rendered when I move the camera around the box). I was changing some camera properties (zNear, zFar) but nothing helped. Please see the links on images attached below. Thanks a lot for any help.
https://www.dropbox.com/s/ihxjrsgjfzidoff/IMG_0187.PNG?dl=0
https://www.dropbox.com/s/wd23xepcir3wggk/IMG_0188.PNG?dl=0

set isDoubleSdided to true
node.geometry?.firstMaterial?.isDoubleSided = true

Related

SpriteKit progressive darkness in the Scene

Does anyone know how to bring progressive darkness on a SKScene using SpriteKit?
I tried using a SKLightNode, which works great to have "full" darkness in the scene and some light sources.
I also tried to have a Node in front of everything else where I adjust the alpha to get darker and darker. which works great if there is on light source
But the 2 solutions doesn't work together.
In my example, the blue bar on the buttom control the alpha of the "darkness" node (left = 0 so fully transparent, right = 1 so fully dark), and the white part on the buttom left is to swift on and off the light.
My goal would be to use the bar on the buttom to go from light on to light off, with a gradual transition.
class GameScene: SKScene {
override func didMove(to view: SKView) {
let background = SKSpriteNode(color: .lightGray, size: view.frame.size) //imageNamed: "background")
background.zPosition = 1
background.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
background.lightingBitMask = 0b0001
addChild(background)
let character = SKSpriteNode(color: .blue, size: CGSize(width: 100, height: 100))//imageNamed: "character")
character.zPosition = 2
character.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
character.lightingBitMask = 0b0001
character.shadowCastBitMask = 0b0001
addChild(character)
let lightNode = SKLightNode()
lightNode.name = "SKLightNode"
lightNode.position = CGPoint(x: view.frame.midX, y: view.frame.midY)
lightNode.categoryBitMask = 0b0001
lightNode.lightColor = .white
//lightNode.ambientColor = .white
lightNode.isEnabled = false
addChild(lightNode)
// Control
let elementSize: CGFloat = 40
let lightToggleSize = CGSize(width: elementSize, height: elementSize)
let lightToggle = SKSpriteNode(color: .white, size: lightToggleSize)
lightToggle.name = "LightToggle"
lightToggle.zPosition = 10000
lightToggle.position = CGPoint(x: view.frame.width - (elementSize / 2), y: elementSize / 2)
addChild(lightToggle)
let sideBarSize = CGSize(width: view.frame.width - elementSize, height: elementSize)
let sideBar = SKSpriteNode(color: .blue, size: sideBarSize)
sideBar.name = "SideBar"
sideBar.position = CGPoint(x:(view.frame.width - elementSize) / 2, y: elementSize / 2)
sideBar.zPosition = lightToggle.zPosition
addChild(sideBar)
let darknessNodeSize = CGSize(width: view.frame.width , height: view.frame.height)
let darknessNode = SKSpriteNode(color: .black, size: darknessNodeSize)
darknessNode.name = "DarknessNode"
darknessNode.position = CGPoint(x: view.frame.width / 2, y: view.frame.height / 2)
darknessNode.alpha = 0
darknessNode.zPosition = lightToggle.zPosition - 1
addChild(darknessNode)
}
func handleTouches(_ point: CGPoint) {
let darknessNode = childNode(withName: "DarknessNode") as! SKSpriteNode
let lightNode = childNode(withName: "SKLightNode") as! SKLightNode
let sideBar = childNode(withName: "SideBar")!
let lightToggle = childNode(withName: "LightToggle") as! SKSpriteNode
if lightToggle.contains(point) {
lightNode.isEnabled = !lightNode.isEnabled
lightNode.position = CGPoint(x: lightNode.position.x + 1, y: lightNode.position.y)
} else if sideBar.contains(point) {
darknessNode.alpha = point.x / sideBar.frame.width
} else {
lightNode.position = point
}
}
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {}
override func touchesMoved(_ touches: Set<UITouch>,
with event: UIEvent?) {
handleTouches(touches.first?.location(in: self))
}
override func touchesEnded(_ touches: Set<UITouch>,
with event: UIEvent?) {
handleTouches(touches.first?.location(in: self))
}
}
If anyone is interested by this topic, here is the solution I found: change the alpha of the source.
It works well on a Playground project but on my real one the local light is having a weird flat shape (I posted a question here
This is what it looks like.
And the solution is bellow.
There is a green background and blue box on the scene.
There are 2 white boxes that are switches for the Background light (that simulate the sunlight, which is off screen) and the "local" light (that simulate a light source)
The white bar is to control the alpha of the background light (on the left the alpha is 0 and there is no light and the right the alpha is 1 and the light is full on). In the same time as I change the alpha of the background light, I also change the alpha of the local light for better look.
import Foundation
import SpriteKit
class GameScene: SKScene {
var background: SKSpriteNode!
var object: SKSpriteNode!
var backgroundLight: SKLightNode!
var localLight: SKLightNode!
var backgroundLightSwitch: SKSpriteNode!
var backgroundLightBar: SKSpriteNode!
var localLightSwitch: SKSpriteNode!
var isBackgroundLightOn = false
var isLocalLightOn = false
var selectedElement: SKSpriteNode? = nil
class func newGameScene() -> GameScene {
let scene = GameScene()
scene.scaleMode = .resizeFill
return scene
}
override func didMove(to view: SKView) {
super.didMove(to: view)
let objectSize = CGSize(width: 100, height: 100)
background = createSpriteNode(color: .green, x: view.frame.width / 2, y: view.frame.height / 2, width: view.frame.width, height: view.frame.height, lightMask: 1)
object = createSpriteNode(color: .blue, x: background.position.x, y: background.position.y, width: objectSize.width, height: objectSize.height, lightMask: 1)
backgroundLightSwitch = createSpriteNode(color: .white, x: view.frame.width / 4, y: view.frame.height / 4, width: objectSize.width / 2, height: objectSize.height / 2, lightMask: 2)
backgroundLightBar = createSpriteNode(color: .white, x:view.frame.width * 2 / 3, y: backgroundLightSwitch.position.y, width: view.frame.width / 2, height: objectSize.height / 2, lightMask: 2)
localLightSwitch = createSpriteNode(color: .white, x: backgroundLightSwitch.position.x, y: view.frame.height / 8, width: objectSize.width / 2, height: objectSize.height / 2, lightMask: 2)
let color = NSColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
backgroundLight = createLightNode(color: nil, ambiantColor: color, x: -view.frame.width * 2, y: -view.frame.height * 2)
localLight = createLightNode(color: color, ambiantColor: nil, x:view.frame.width * 2 / 3, y: view.frame.height * 2 / 3)
}
func createSpriteNode(color: NSColor, x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, lightMask: UInt32) -> SKSpriteNode {
let nodePosition = CGPoint(x: x, y: y)
let nodeSize = CGSize(width: width, height: height)
let node = SKSpriteNode(color: color, size: nodeSize)
node.zPosition = 1
node.position = nodePosition
node.lightingBitMask = lightMask
addChild(node)
return node
}
func createLightNode(color: NSColor?, ambiantColor: NSColor? , x: CGFloat, y: CGFloat) -> SKLightNode {
let light = SKLightNode()
light.falloff = 1.5
light.position = CGPoint(x:x, y: y)
light.isEnabled = false
light.categoryBitMask = 1
if color != nil {
light.lightColor = color!
}
if ambiantColor != nil {
light.ambientColor = ambiantColor!
}
addChild(light)
return light
}
func getSelectElement(location: CGPoint) -> SKSpriteNode? {
let elements: [SKSpriteNode] = [backgroundLightSwitch, localLightSwitch, backgroundLightBar]
for element in elements {
if element.contains(location) {
return element
}
}
return nil
}
func switchLight(_ light: SKLightNode, selected: Bool) -> Bool {
light.isEnabled = selected
light.position.x += selected ? 1 : -1
return selected
}
func getAlpha(node: SKSpriteNode, location: CGPoint) -> CGFloat {
if location.x < node.frame.minX {
return 0
} else if location.x > node.frame.maxX {
return 1
} else {
return (location.x - node.frame.minX) / node.frame.width
}
}
func changeAlpha(_ alpha: CGFloat) {
backgroundLight.ambientColor = NSColor(red: 1.0, green: 1.0, blue: 1.0, alpha: alpha)
localLight.lightColor = NSColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1 - alpha)
backgroundLight.position.x -= 1
}
override func mouseDown(with event: NSEvent) {
let location = event.location(in: self)
selectedElement = getSelectElement(location: location)
}
override func mouseDragged(with event: NSEvent) {
if selectedElement == backgroundLightBar {
let location = event.location(in: self)
let newAlpha = getAlpha(node: backgroundLightBar, location: location)
changeAlpha(newAlpha)
}
}
override func mouseUp(with event: NSEvent) {
let location = event.location(in: self)
let newSelectedElement = getSelectElement(location: location)
if selectedElement == newSelectedElement {
if selectedElement == backgroundLightSwitch {
isBackgroundLightOn = switchLight(backgroundLight, selected: !isBackgroundLightOn)
} else if selectedElement == localLightSwitch {
isLocalLightOn = switchLight(localLight, selected: !isLocalLightOn)
}
}
}
}

swift SceneKit decline node move

I create a sphere node, I need the user to be able only to rotate (left / right, up / down) and zoom in / out the node, but default he can move the node from the center (with two fingers) - is possible prohibit the user to move the node from the center? thanks for any help
sceneView.scene = scene
cameraOrbit = SCNNode()
cameraNode = SCNNode()
camera = SCNCamera()
// camera stuff
camera.usesOrthographicProjection = true
camera.orthographicScale = 5
camera.zNear = 1
camera.zFar = 100
cameraNode.position = SCNVector3(x: 0, y: 0, z: 70)
cameraNode.camera = camera
cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraNode)
let sphere = SCNSphere(radius: 2)
sphere.firstMaterial?.diffuse.contents = UIColor.red
let earthNode = SCNNode(geometry: sphere)
earthNode.name = "sphere"
earthNode.geometry?.materials = [blueMaterial]
scene.rootNode.addChildNode(earthNode)
earthNode.rotation = SCNVector4(0, 1, 0, 0)
let lightNode = SCNNode()
let light = SCNLight()
light.type = .ambient
light.intensity = 200
lightNode.light = light
scene.rootNode.addChildNode(lightNode)
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.clear
sceneView.cameraControlConfiguration.allowsTranslation = true
sceneView.cameraControlConfiguration.rotationSensitivity = 0.4
You can put similar code into your UIViewController:
//**************************************************************************
// Gesture Recognizers
// MARK: Gesture Recognizers
//**************************************************************************
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
if(data.isNavigationOff == true) { return } // No panel select if Add, Update, EndWave, or EndGame
if(gameMenuTableView.isHidden == false) { return } // No panel if game menu is showing
let location: CGPoint = recognizer.location(in: gameScene)
if(data.isAirStrikeModeOn == true)
{
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
gameControl.airStrike(position: scenePoint)
}
else
{
let hitResults = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
// May have selected an invalid panel or auto upgrade was on
if(gameControl.selectPanel(vPanel: vHit.node.name!) == false) { return }
return
}
}
}
}
//**************************************************************************
#objc func handlePan(recognizer: UIPanGestureRecognizer)
{
if(data.gameState != .run || data.isGamePaused == true) { return }
currentLocation = recognizer.location(in: gameScene)
switch recognizer.state
{
case UIGestureRecognizer.State.began:
beginLocation = recognizer.location(in: gameScene)
break
case UIGestureRecognizer.State.changed:
if(currentLocation.x > beginLocation.x * 1.1)
{
beginLocation.x = currentLocation.x
gNodes.camera.strafeLeft()
}
if(currentLocation.x < beginLocation.x * 0.9)
{
beginLocation.x = currentLocation.x
gNodes.camera.strafeRight()
}
break
case UIGestureRecognizer.State.ended:
break
default:
break
}
}
Yes, all of that is doable. First, create your own camera class and turn off allowsCameraControl. Then you can implement zoom/strafe/whatever.
Here are some examples that may help, just search for these numbers in the stack search bar and find my answers/examples.
57018359 - this post one tells you how to touch a 2d screen (tap) and translate it to 3d coordinates with you deciding the depth (z), like if you wanted to tap the screen and place an object in 3d space.
57003908 - this post tells you how to select an object with a hitTest (tap). For example, if you showed the front of a house with a door and tap it, then the function would return your door node provided you name the node "door" and took some kind of action when it's touched. Then you could reposition your camera based on that position. You'll want to go iterate through all results because there might be overlapping or plus Z nodes
55129224 - this post gives you quick example of creating a camera class. You can use this to reposition your camera or move it forward and back, etc.
Two finger drag:
func dragBegins(vRecognizer: UIPanGestureRecognizer)
{
if(data.gameState == .run)
{
if(vRecognizer.numberOfTouches == 2) { dragMode = .strafe }
}
}
class Camera
{
var data = Data.sharedInstance
var util = Util.sharedInstance
var gameDefaults = Defaults()
var cameraEye = SCNNode()
var cameraFocus = SCNNode()
var centerX: Int = 100
var strafeDelta: Float = 0.8
var zoomLevel: Int = 35
var zoomLevelMax: Int = 35 // Max number of zoom levels
//********************************************************************
init()
{
cameraEye.name = "Camera Eye"
cameraFocus.name = "Camera Focus"
cameraFocus.isHidden = true
cameraFocus.position = SCNVector3(x: 0, y: 0, z: 0)
cameraEye.camera = SCNCamera()
cameraEye.constraints = []
cameraEye.position = SCNVector3(x: 0, y: 15, z: 0.1)
let vConstraint = SCNLookAtConstraint(target: cameraFocus)
vConstraint.isGimbalLockEnabled = true
cameraEye.constraints = [vConstraint]
}
//********************************************************************
func reset()
{
centerX = 100
cameraFocus.position = SCNVector3(x: 0, y: 0, z: 0)
cameraEye.constraints = []
cameraEye.position = SCNVector3(x: 0, y: 32, z: 0.1)
cameraFocus.position = SCNVector3Make(0, 0, 0)
let vConstraint = SCNLookAtConstraint(target: cameraFocus)
vConstraint.isGimbalLockEnabled = true
cameraEye.constraints = [vConstraint]
}
//********************************************************************
func strafeRight()
{
if(centerX + 1 < 112)
{
centerX += 1
cameraEye.position.x += strafeDelta
cameraFocus.position.x += strafeDelta
}
}
//********************************************************************
func strafeLeft()
{
if(centerX - 1 > 90)
{
centerX -= 1
cameraEye.position.x -= strafeDelta
cameraFocus.position.x -= strafeDelta
}
}
//********************************************************************
}
//********************************************************************
func lerp(start: SCNVector3, end: SCNVector3, percent: Float) -> SCNVector3
{
let v3 = cgVecSub(v1: end, v2: start)
let v4 = cgVecScalarMult(v: v3, s: percent)
return cgVecAdd(v1: start, v2: v4)
}

Background music not working when physicsWorld added

This is my code. The music is not playing for some reason and it started when I added my physicsWorld function. I already checked my sound mp3 file and it seems to be working. I even ran a previous version (same but without the physicsWorld stuff) and the music works perfectly. I'm not sure why this is happening... Your help would be greatly appreciated :)
import UIKit
import QuartzCore
import SceneKit
var ballNode = SCNNode()
var floorNode = SCNNode()
class GameViewController: UIViewController, SCNPhysicsContactDelegate {
let categoryFloor = 2
var scene: SCNScene!
override func viewDidLoad() {
super.viewDidLoad()
scene = SCNScene(named: "art.scnassets/main.scn")!
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .ambient
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
let ball = scene.rootNode.childNode(withName: "ball", recursively: true)!
ballNode = scene.rootNode.childNode(withName: "ball", recursively: true)!
ballNode.physicsBody = SCNPhysicsBody.dynamic()
ballNode.physicsBody!.contactTestBitMask = 1
floorNode = scene.rootNode.childNode(withName: "floor", recursively: true)!
floorNode.physicsBody = SCNPhysicsBody.static()
floorNode.physicsBody!.contactTestBitMask = 1
scene.rootNode.addChildNode(ballNode)
scene.rootNode.addChildNode(floorNode)
let camera = scene.rootNode.childNode(withName: "camera", recursively: true)!
ball.runAction(SCNAction.repeatForever(SCNAction.moveBy(x: 0, y: 0, z: 10, duration: 1)))
camera.runAction(SCNAction.repeatForever(SCNAction.moveBy(x: 0, y: 0, z: 10, duration: 1)))
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.scene?.physicsWorld.contactDelegate = self
scnView.allowsCameraControl = false
scnView.showsStatistics = true
if let source = SCNAudioSource(fileNamed: "art.scnassets/gameMusic.mp3") {
let action = SCNAction.playAudio(source, waitForCompletion: false)
ball.runAction(action)
} else {
print("cannot find file")
}
addSwipeGestureRecognizers()
}
func addSwipeGestureRecognizers() {
let gestureDirections: [UISwipeGestureRecognizerDirection] = [.right, .left, .up, .down]
for gestureDirection in gestureDirections {
let gestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector((handleSwipe)))
gestureRecognizer.direction = gestureDirection
self.view?.addGestureRecognizer(gestureRecognizer)
}
}
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if (contact.nodeA == ballNode || contact.nodeA == floorNode) && (contact.nodeB == ballNode || contact.nodeB == floorNode) {
ballNode.removeFromParentNode()
}
}
#objc func handleSwipe(gesture:UIGestureRecognizer) {
let jumpUpAction = SCNAction.moveBy(x: 0, y: 10, z: 0, duration: 0.2)
let jumpDownAction = SCNAction.moveBy(x: 0, y: -10, z: 0, duration: 0.2)
let up = SCNAction.sequence([jumpUpAction, jumpDownAction])
let right = SCNAction.moveBy(x: -10, y: 0, z: 0, duration: 0.2)
let left = SCNAction.moveBy(x: 10, y: 0, z: 0, duration: 0.2)
let down = SCNAction.moveBy(x: 0, y: 0, z: 0, duration: 0.2)
if let gesture = gesture as? UISwipeGestureRecognizer{
switch gesture.direction {
case .up:
ballNode.runAction(up)
case .right:
ballNode.runAction(right)
case .left:
ballNode.runAction(left)
case .down:
ballNode.runAction(down)
default:
print("No Swipe")
}
}
}
override var shouldAutorotate: Bool {
return false
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Scaling SCNNode and its children

I am making a simple measuring app. Currently I place spheres as SCNNodes around the place and between nodes appears a label that displays the length of the line from node 1 to node 2.
This is how the labels are created:
func addLabel() {
let plane = SCNPlane(width: 0.07, height: 0.02)
plane.cornerRadius = plane.height / 10
let sks = SKScene(size: CGSize(width: plane.width * 10e3, height: plane.height * 10e3))
sks.backgroundColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 0.7)
currentLbl = SKLabelNode(text: "")
currentLbl.fontSize = 110
currentLbl.fontName = "Helvetica"
currentLbl.verticalAlignmentMode = .center
currentLbl.position = CGPoint(x: sks.frame.midX, y: sks.frame.midY)
currentLbl.fontColor = .white
sks.addChild(currentLbl)
let material = SCNMaterial()
material.isDoubleSided = true
material.diffuse.contents = sks
material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
plane.materials = [material]
let node = SCNNode(geometry: plane)
node.constraints = [SCNBillboardConstraint()]
node.position = SCNVector3Make(0, 0, 0)
let (minBound, maxBound) = node.boundingBox
node.pivot = SCNMatrix4MakeTranslation( (maxBound.x + minBound.x)/2, minBound.y, 0.02/2)
lblNodes.append(node)
currentLblNode = node
sceneView.scene.rootNode.addChildNode(node)
}
I would like to apply a mathematical equation to the scale of these label nodes (in my update function) to maintain readability from a couple of metres.
var myNodes: [SCNNode] = []
let s = getMagicScalingNumber()
Say I obtained my scale factor as above and I have an array of SCNNodes, how can I scale all the nodes and their respective children so they stay visually proportional.
If SCNTransformConstraint() is an option for this, I would appreciate an example of how to implement it.
Edit: Just to clarify, I have tried
currentLblNode.scale = SCNVector3Make(s, s, s)
which does not seem to work.
I know this is very late, however I have put together an example which should point you in the right direction.
In your example you are essentially creating a holder SCNNode which contains your labels etc.
You can store these into an array of [SCNNode] and then transform the scale of these like so:
/// Updates The Contents Of Each Node Added To Our NodesAdded Array
///
/// - Parameters:
/// - nodes: [SCNNode]
/// - pointOfView: SCNNode
func updateScaleFromCameraForNodes(_ nodes: [SCNNode], fromPointOfView pointOfView: SCNNode){
nodes.forEach { (node) in
//1. Get The Current Position Of The Node
let positionOfNode = SCNVector3ToGLKVector3(node.worldPosition)
//2. Get The Current Position Of The Camera
let positionOfCamera = SCNVector3ToGLKVector3(pointOfView.worldPosition)
//3. Calculate The Distance From The Node To The Camera
let distanceBetweenNodeAndCamera = GLKVector3Distance(positionOfNode, positionOfCamera)
//4. Animate Their Scaling & Set Their Scale Based On Their Distance From The Camera
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
switch distanceBetweenNodeAndCamera {
case 0 ... 0.5:
node.simdScale = simd_float3(0.25, 0.25, 0.25)
case 0.5 ... 1:
node.simdScale = simd_float3(0.5, 0.5, 0.5)
case 1 ... 1.5:
node.simdScale = simd_float3(1, 1, 1)
case 1.5 ... 2:
node.simdScale = simd_float3(1.5, 1.5, 1.5)
case 2 ... 2.5:
node.simdScale = simd_float3(2, 2, 2)
case 2.5 ... 3:
node.simdScale = simd_float3(2.5, 2.5, 2.5)
default:
print("Default")
}
SCNTransaction.commit()
}
}
Here I am just setting 'random' values to illustrate the concept of scale depending on the distance from the camera.
To put this better into context here is a little demo I have put together:
//--------------------------
// MARK: - ARSCNViewDelegate
//--------------------------
extension ViewController: ARSCNViewDelegate{
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if !nodesAdded.isEmpty, let currentCameraPosition = self.sceneView.pointOfView {
updateScaleFromCameraForNodes(nodesAdded, fromPointOfView: currentCameraPosition)
}
}
}
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView!
var nodesAdded = [SCNNode]()
//-----------------------
// MARK: - View LifeCycle
//-----------------------
override func viewDidLoad() {
super.viewDidLoad()
//1. Generate Our Three Box Nodes
generateBoxNodes()
//2. Run The Session
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
sceneView.delegate = self
}
/// Generates Three SCNNodes With An SCNBox Geometry & Places Them In A Holder Node
func generateBoxNodes(){
//1. Create An SCNNode To Hold All Of Our Content
let holderNode = SCNNode()
//2. Create An Array Of Colours For Each Face
let colours: [UIColor] = [.red, .green, .blue, .purple, .cyan, .black]
//3. Create An SCNNode Wih An SCNBox Geometry
let boxNode = SCNNode()
let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.01)
boxNode.geometry = boxGeometry
//4. Create A Different Material For Each Face
var materials = [SCNMaterial]()
for i in 0..<5{
let faceMaterial = SCNMaterial()
faceMaterial.diffuse.contents = colours[i]
materials.append(faceMaterial)
}
//5. Set The Geometries Materials
boxNode.geometry?.materials = materials
//6. Create Two More Nodes By Cloning The First One
let secondBox = boxNode.flattenedClone()
let thirdBox = boxNode.flattenedClone()
//7. Position Them In A Line & Add To The Scene
boxNode.position = SCNVector3(-0.2, 0, 0)
secondBox.position = SCNVector3(0, 0, 0)
thirdBox.position = SCNVector3(0.2, 0, 0)
holderNode.addChildNode(boxNode)
holderNode.addChildNode(secondBox)
holderNode.addChildNode(thirdBox)
holderNode.position = SCNVector3(0, 0, -1)
self.sceneView.scene.rootNode.addChildNode(holderNode)
nodesAdded.append(holderNode)
}
/// Updates The Contents Of Each Node Added To Our NodesAdded Array
///
/// - Parameters:
/// - nodes: [SCNNode]
/// - pointOfView: SCNNode
func updateScaleFromCameraForNodes(_ nodes: [SCNNode], fromPointOfView pointOfView: SCNNode){
nodes.forEach { (node) in
//1. Get The Current Position Of The Node
let positionOfNode = SCNVector3ToGLKVector3(node.worldPosition)
//2. Get The Current Position Of The Camera
let positionOfCamera = SCNVector3ToGLKVector3(pointOfView.worldPosition)
//3. Calculate The Distance From The Node To The Camera
let distanceBetweenNodeAndCamera = GLKVector3Distance(positionOfNode, positionOfCamera)
//4. Animate Their Scaling & Set Their Scale Based On Their Distance From The Camera
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.5
switch distanceBetweenNodeAndCamera {
case 0 ... 0.5:
node.simdScale = simd_float3(0.25, 0.25, 0.25)
case 0.5 ... 1:
node.simdScale = simd_float3(0.5, 0.5, 0.5)
case 1 ... 1.5:
node.simdScale = simd_float3(1, 1, 1)
case 1.5 ... 2:
node.simdScale = simd_float3(1.5, 1.5, 1.5)
case 2 ... 2.5:
node.simdScale = simd_float3(2, 2, 2)
case 2.5 ... 3:
node.simdScale = simd_float3(2.5, 2.5, 2.5)
default:
print("Default")
}
SCNTransaction.commit()
}
}
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) }
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) }
override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() }
}
Hopefully it helps...

How to make shape not to go to GameOver? -SpriteKit

I am creating a game where there is a square shape and every time the player taps on the square, it goes to GameOver scene. All I want to do is when the square shape is tapped, it will be allotted different position of the screen to be tapped on the squares.
Here is my code:
let touch:UITouch = touches.first!
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name
{
//The first ball that shows up
if name == "startball"
{
print("Touched", terminator: "")
addBall(ballSize)
self.addChild(score)
}
else if name == "shape"{
scoreCount += 1
addBall(ballSize)
audioPlayer.play()
}
}
else {
let scene = GameOver(size: self.size)
scene.setMyScore(0)
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
scene.size = skView.bounds.size
scene.setMessage("You Lost!")
scene.setEndGameMode(va)
gameTimer.invalidate()
shownTimer.invalidate()
print(" timers invalidated ", terminator: "")
ran = true
skView.presentScene(scene, transition: SKTransition.crossFade(withDuration: 0.25))
}
if firstTouch {
shownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(GameScene.decTimer), userInfo: nil, repeats: true)
gameTimer = Timer.scheduledTimer(timeInterval: TIME_INCREMENT, target:self, selector: Selector("endGame"), userInfo: nil, repeats: false)
firstTouch = false
}
if touchCount > 5 {
for touch: AnyObject in touches {
let skView = self.view! as SKView
skView.ignoresSiblingOrder = true
var scene: Congratz!
scene = Congratz(size: skView.bounds.size)
scene.scaleMode = .aspectFill
skView.presentScene(scene, transition: SKTransition.doorsOpenHorizontal(withDuration: 1.0))
}
}
touchCount += 1
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
}
func endGame(){
shownTimer.invalidate()
gameTimer.invalidate()
let scene = GameOver(size: self.size)
scene.setMyScore(scoreCount)
if let skView = self.view {
skView.ignoresSiblingOrder = false
scene.scaleMode = .resizeFill
scene.size = skView.bounds.size
scene.setMessage("Times up")
skView.presentScene(scene, transition: SKTransition.crossFade(withDuration: 0.25))
}
}
override func addBall(_ size: Int) {
// Add the ball
let currentBall = SKShapeNode(circleOfRadius: CGFloat(size))
let viewMidX = view!.bounds.midX
let viewMidY = view!.bounds.midY
currentBall.fillColor = pickColor()
currentBall.position = randomBallPosition()
if scoreCount != 0{
if scoreCount == 1{
self.addChild(score)
self.addChild(timeLeftLabel)
self.childNode(withName: "welcome")?.removeFromParent()
}
self.childNode(withName: "ball")?.run(getSmaller)
self.childNode(withName: "ball")?.removeFromParent()
}
currentBall.name = "ball"
self.addChild(currentBall)
}
func addSquare(_ size: Int) {
// Add the square
let shape = SKShapeNode(rectOf: CGSize(width:CGFloat(size), height:CGFloat(size)))
shape.path = UIBezierPath(roundedRect: CGRect(x: 64, y: 64, width: 160, height: 160), cornerRadius: 50).cgPath
shape.fillColor = pickColor()
shape.position = randomBallPosition()
shape.name = "shape"
self.addChild(shape)
}
func randomBallPosition() -> CGPoint {
let xPosition = CGFloat(arc4random_uniform(UInt32((view?.bounds.maxX)! + 1)))
let yPosition = CGFloat(arc4random_uniform(UInt32((view?.bounds.maxY)! + 1)))
return CGPoint(x: xPosition, y: yPosition)
}
What I would suggest would be to make something like an enum for the different shapes, so you can keep track of what shape you're using.
enum GameShape: Int {
case circle = 0
case square = 1
}
Then create a GameShape property at the top of your GameScene:
var currentShape: GameShape = .circle
Then you could create some sort of updateShape method, which you could call in your touchesBegan method instead of just addBall
func updateShape(shapeSize: CGSize) {
switch currentShape {
case .circle:
addCircle(shapeSize)
case .square:
addSquare(shapeSize)
default:
break
}
// However you want to setup the condition for changing shape
if (condition) {
currentShape = .square
}
}
func addBall(_ size: CGSize) {
// Add the ball
}
func addSquare(_ size: CGSize) {
// Add the square
}
Now in your touchesBegan method, instead of calling addBall(size, you could call updateShape:
override func touchesBegan(_ touches: Set<UITouch>!, with event: UIEvent?) {
// Setup your code for detecting position for shape origin
updateShape(shapeSize)
}
EDIT - Your code is a mess. You really should take the time to make sure it's properly formatted when you submit it, otherwise it's really hard to help you. Indentation helps to see where a closure begins and ends. From what I can tell, it looks like you have two or more functions nested within your addBall method. This is not good. I tried my best to clean it up for you. You'll still need to write the code to make the shape a square, but I've lead you in the right direction to start to make that happen:
func addBall(_ size: CGSize) {
// Add the ball
let currentBall = SKShapeNode(circleOfRadius: CGFloat(size))
let viewMidX = view!.bounds.midX
let viewMidY = view!.bounds.midY
currentBall.fillColor = pickColor()
shape.path = UIBezierPath(roundedRect: CGRect(x: 64, y: 64, width: 160, height: 160), cornerRadius: 50).cgPath
shape.fillColor = pickColor()
currentBall.position = randomBallPosition()
shape.position = randomBallPosition()
self.addChild(shape)
if scoreCount != 0{
if scoreCount == 1{
self.addChild(score)
self.addChild(timeLeftLabel)
self.childNode(withName: "welcome")?.removeFromParent()
}
self.childNode(withName: "ball")?.run(getSmaller)
self.childNode(withName: "ball")?.removeFromParent()
}
currentBall.name = "ball"
shape.name = "ball"
self.addChild(currentBall)
}
func addSquare(_ size: CGSize) {
// Add the square
}
func randomBallPosition() -> CGPoint {
let xPosition = CGFloat(arc4random_uniform(UInt32((view?.bounds.maxX)! + 1)))
let yPosition = CGFloat(arc4random_uniform(UInt32((view?.bounds.maxY)! + 1)))
return CGPoint(x: xPosition, y: yPosition)
}
Now the above code assumes you have some property named shape because from what I could tell you never explicitly instantiated that, but you begin manipulating it.