I have a simple function call in my main class to create some instances from my Cube class but I cant seem to get my instances to be added to my scene. I tried returning self inside my Cube class but Swift wont let me do this inside init.
func addCubeLoop() {
for var i = 0; i <= 0; ++i {
cube = Cube(num: i, importedCube: importedCube1)
cubeArray.append(cube)
theScene.rootNode.addChildNode(cubeArray[i])
}
}
class Cube: SCNNode {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(num: Int, importedCube: SCNNode) {
let _scale: Float = 60
let cube: SCNNode = importedCube.copy() as! SCNNode
super.init()
cube.scale = SCNVector3Make(_scale, _scale, _scale)
let node = SCNNode()
node.addChildNode(cube)
node.position = SCNVector3Make(5, 20, 3)
let collisionBox = SCNBox(width: 5.0, height: 5.0, length: 5.0, chamferRadius: 0)
node.physicsBody?.physicsShape = SCNPhysicsShape(geometry: collisionBox, options: nil)
node.physicsBody = SCNPhysicsBody.dynamicBody()
node.physicsBody?.mass = 0.1
node.physicsBody?.restitution = 0.8
node.physicsBody?.damping = 0.5
node.name = "dice" + String(num)
node.physicsBody?.allowsResting = true
}
}
The nodes created in the init of Cube are not added as child nodes of it.
I've simplified the your code below to illustrate the problem.
func addCubeLoop() {
for /* loop */ {
// 1. create cube
cube = Cube(num: i, importedCube: importedCube1)
// 6. add cube to the scene's root node
theScene.rootNode.addChildNode(cubeArray[i])
}
}
class Cube: SCNNode {
init(importedCube: SCNNode) {
// 2. copy importedCube
let cube: SCNNode = importedCube.copy() as! SCNNode
// configure cube
// ...
// 3. create node
let node = SCNNode()
// 4. add cube (the copy) to node
node.addChildNode(cube)
// configure node
// ...
// 5. End of init
}
}
For each run through the loop, this is what happens.
A new Cube instance is created, passing importedCube1
In the Cube initializer, the imported cube argument is copied. The node "cube" is now a copy of the argument.
Still in the initializer, a new node (called "node" is created).
Still in the initializer, "cube" (the copy) is added to "node". At this point, cube is a child node of "node", but the Cube instance itself (which is a node) had no child nodes.
Init completes.
The newly created Cube instance is added to the scene's root node.
At this point there are four relevant nodes:
the root node,
the cube instance node
the node called "node"
the imported copy
The cube instance node is a child of the root node. The imported copy is a child of the "node" node. However, The "node" node doesn't have a parent.
The fix is to make sure that the all nodes are part of the hierarchy by adding the "node" node to self inside the Cube instance initializer.
Related
In an AR app, I want to detect collisions between the user walking around and the walls of an AR node that I construct. In order to do that, I create an invisible cylinder right in front of the user and set it all up to detect collisions.
The walls are all part of a node which is a child of sceneView.scene.rootNode.
The cylinder, I want it to be a child of sceneView.pointOfView so that it would always follow the camera.
However, when I do so, no collisions are detected.
I know that I set it all up correctly, because if instead I set the cylinder node as a child of sceneView.scene.rootNode as well, I do get collisions correctly. In that case, I continuously move that cylinder node to always be in front of the camera in a renderer(updateAtTime ...) function. So I do have a workaround, but I'd prefer it to be a child of pointOfView.
Is it impossible to detect collisions if nodes are children of different root nodes?
Or maybe I'm missing something in my code?
The contactDelegate is set like that:
sceneView.scene.physicsWorld.contactDelegate = self so maybe this only includes sceneView.scene, but will exclude sceneView.pointOfView???
Is that the issue?
Here's what I do:
I have a separate file to create and configure my cylinder node which I call pov:
import Foundation
import SceneKit
func createPOV() -> SCNNode {
let pov = SCNNode()
pov.geometry = SCNCylinder(radius: 0.1, height: 4)
pov.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
pov.opacity = 0.3 // will be set to 0 when it'll work correctly
pov.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
pov.physicsBody?.isAffectedByGravity = false
pov.physicsBody?.mass = 1
pov.physicsBody?.categoryBitMask = BodyType.cameraCategory.rawValue
pov.physicsBody?.collisionBitMask = BodyType.wallsCategory.rawValue
pov.physicsBody?.contactTestBitMask = BodyType.wallsCategory.rawValue
pov.simdPosition = simd_float3(0, -1.5, -0.3) // this position only makes sense when setting as a child of pointOfView, otherwise the position will always be changed by renderer
return pov
}
Now in my viewController.swift file I call this function and set is as a child of either root nodes:
pov = createPOV()
sceneView.pointOfView?.addChildNode(pov!)
(Don't worry right now about not checking and unwrapping).
The above does not detect collisions.
But if instead I add it like so:
sceneView.scene.rootNode.addChildNode(pov!)
then collisions are detected just fine.
But then I need to always move this cylinder to be in front of the camera and I do it like that:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let currentPosition = pointOfView.simdPosition
let currentTransform = pointOfView.simdTransform
let orientation = SCNVector3(-currentTransform.columns.2.x, -currentTransform.columns.2.y, -currentTransform.columns.2.z)
let currentPositionOfCamera = orientation + SCNVector3(currentPosition)
DispatchQueue.main.async {
self.pov?.position = currentPositionOfCamera
}
}
For completeness, here's the code I use to configure the node of walls in ViewController (they're built elsewhere in another function):
node?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: node!, options: nil))
node?.physicsBody?.isAffectedByGravity = false
node?.physicsBody?.mass = 1
node?.physicsBody?.damping = 1.0 // remove linear velocity, needed to stop moving after collision
node?.physicsBody?.angularDamping = 1.0 // remove angular velocity, needed to stop rotating after collision
node?.physicsBody?.velocityFactor = SCNVector3(1.0, 0.0, 1.0) // will allow movement only in X and Z coordinates
node?.physicsBody?.angularVelocityFactor = SCNVector3(0.0, 1.0, 0.0) // will allow rotation only around Y axis
node?.physicsBody?.categoryBitMask = BodyType.wallsCategory.rawValue
node?.physicsBody?.collisionBitMask = BodyType.cameraCategory.rawValue
node?.physicsBody?.contactTestBitMask = BodyType.cameraCategory.rawValue
And here's my physycsWorld(didBegin contact) code:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if contact.nodeA.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue || contact.nodeB.physicsBody?.categoryBitMask == BodyType.wallsCategory.rawValue {
print("Begin COLLISION")
contactBeginLabel.isHidden = false
}
}
So I print something to the console and I also turn on a label on the view so I'll see that collision was detected (and the walls indeed move as a whole when it works).
So Again, it all works fine when the pov node is a child of sceneView.scene.rootNode, but not if it's a child of sceneView.pointOfView.
Am I doing something wrong or is this a limitation of collision detection?
Is there something else I can do to make this work, besides the workaround I already implemented?
Thanks!
Regarding the positioning of your cyliner:
instead to use the render update at time, you better use a position constraint for your cylinder node to move with the point of view. the result will be the same, as if it were a child of the point of view, but collisions will be detected, because you add it to the main rootnode scenegraph.
let constraint = SCNReplicatorConstraint(target: pointOfView) // must be a node
constraint.positionOffset = positionOffset // some SCNVector3
constraint.replicatesOrientation = false
constraint.replicatesScale = false
constraint.replicatesPosition = true
cylinder.constraints = [constraint]
There is also an influence factor you can configure. By default the influence is 100%, the position will immediatly follow.
Using GameplayKit, I'm trying to make an enemy character pursue the player while avoiding obstacles in a SpriteKit game.
However, the agent in agentDidUpdate(_:) has a position and velocity of nan.
Here's what I'm doing now:
Configure the obstacles:
//node is defined here
let obstacle = GKCircleObstacle(radius: Float(node.size.width*0.5))
obstacle.position = vector_float2(x: Float(node.position.x), y: Float(node.position.y))
levelObstacles.append(obstacle)
Configure a GKEntity (etc.) for the player SKSpriteNode, saving a reference to the component and agent for use in #3:
//node is defined here
let entity1 = GKEntity()
heroAgent = GKAgent2D()
heroComponent = GKSKNodeComponent(node: node)
heroAgent.delegate = self // <-- self is a GKAgentDelegate
node.entity = entity1
heroEntity = entity1
if let comp = heroComponent {
entity1.addComponent(comp)
entity1.addComponent(heroAgent)
}
Configure a GKEntity (etc.) for the enemy SKSpriteNode, using the saved hero component and agent to set up the necessary behaviors:
//node is defined here
let entity = GKEntity()
let agent = GKAgent2D()
let avoid = GKGoal(toAvoid: levelObstacles, maxPredictionTime: 5.0)
let pursue = GKGoal(toInterceptAgent: heroAgent, maxPredictionTime: 5.0)
nodeComponent = GKSKNodeComponent(node: node)
agent.behavior = GKBehavior(goals: [avoid, pursue], andWeights: [100.0, 50.0])
agent.delegate = self // <-- self is a GKAgentDelegate
node.entity = entity
if let comp = nodeComponent {
entity.addComponent(comp)
entity.addComponent(agent)
}
nodeEntity = entity
In the SKScene's update(_:) method, call the enemy entity's update(deltaTime:) method:
nodeEntity?.update(deltaTime: currentTime-lastFrameTime)
Print the agent's position and velocity in agentDidUpdate(_:):
func agentDidUpdate(_ agent: GKAgent) {
if let a = agent as? GKAgent2D {
print("agent position: \(a.position), agent velocity: \(a.velocity)") //Prints a position and velocity of "-nan"
}
}
Question: Why are the agent's position and velocity nan inside agentDidUpdate(_:), and how do I solve the problem?
Update: The problem is apparently related to GKGoal(toInterceptAgent:maxPredictionTime:). If I use a different goal, like GKGoal(toFleeAgent:) for example, there are actual values available in agentDidUpdate(_:).
I was able to get this working by setting the player agent's position to the actual SpriteKit node's position inside the SKScene's update(_:) method, like this:
heroAgent.position = vector_float2(x: Float(mainCharacter?.position.x ?? 0.0), y: Float(mainCharacter?.position.y ?? 0.0))
I'm not really sure why that worked, but it did.
For 2 dae objects in the scene, how can one initiate animations for each one of them at different conditions?
Since all objects are part of the SCNScene, I am unable to refer to individual ones based on a condition. They all render properly, but they animate all the same time. Can we put a condition to make specific objects in the scene animate at a time?
Thanks in advance!
let idleScene = SCNScene(named: "art.scnassets/Avatar_1.dae")!
// This node will be parent of all the animation models
let node = SCNNode()
// Add all the child nodes to the parent node
for child in idleScene.rootNode.childNodes {
node.addChildNode(child)
}
// Set up some properties
node.position = SCNVector3(hitTestResult.worldTransform.columns.3.x+0.5,hitTestResult.worldTransform.columns.3.y, hitTestResult.worldTransform.columns.3.z)
node.scale = SCNVector3(0.2, 0.2, 0.2)
// Add the node to the scene
sceneView.scene.rootNode.addChildNode(node)
For another avatar (Avatar_2.dae), how do we add it in the scene but give another reference name.
Also how can we individually play/pause the animations for each avatars?
This one if for scene, but is there one for individual avatars?
sceneView.scene.isPaused = play
By explicitly naming the elements in your SCNScene and then designating them as distinct nodes by using rootNode.childNode(withName:):
let scannerScene = SCNScene(named: "Scanner.scn")
let sectorField: SCNNode = (scannerScene?.rootNode.childNode(withName: "sectorField", recursively: true))!
let scanBeam: SCNNode = (scannerScene?.rootNode.childNode(withName: "scanBeam", recursively: true))!
Once you've done this, the individual nodes can be animated independently:
// start scanBeam
let rotateAction = SCNAction.rotateBy(x: 0, y: CGFloat(2*Float.pi), z: 0, duration: 1.5)
let perpetualRotation = SCNAction.repeatForever(rotateAction)
scanBeam.runAction(perpetualRotation)
To stop a specific animation (as opposed to all animations in the entire scene) simply remove the action
scanBeam.removeAction(forKey: String)
I have a root node sent in from .dae file. It creates the children with camera and lights. I can rotate the individual children nodes if I want.
I am trying to avoid the math because it seems so logical that I can rotate 1 parent node and affect change on the child nodes.
I want to rotate the children node by rotating the root node. Can I just rotate the parent node?
or
Do I have to write math for each point transformed around a arbitrary pivot point (0, 0, 0) for example?
I have read a fair amount. I have tried to rotate the root node, with SCNnode.rootNode.
I have tried creating a pivot point with a new scnnode and adding the scene to that new pivot point.
I have tried Euler angles, rotation....
func set_up_scn() {
// self.scn = self.view as SCNView!
scene.scene = SCNScene(named: "sectional.dae");
group = SCNNode()
group.name = "FurnitureGroup"
for node in self.scene.scene!.rootNode.childNodes {
group.addChildNode(node);
}
scene.scene?.rootNode.addChildNode(group)
scene.autoenablesDefaultLighting = true
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction))
scene.addGestureRecognizer(panGesture)
}
#objc func panGestureAction(sender : UIPanGestureRecognizer) {
var pos : CGPoint = sender.translation(in: self.scene)
var pivotPoint : SCNNode = (self.scene.scene?.rootNode.childNode(withName: "FurnitureGroup", recursively: true))!
pivotPoint.rotation.x = Float(pos.x)
pivotPoint.rotation.y = Float(pos.y)
}
I'm not getting errors. I am not getting rotation either; I am getting rotation if I print out the pivotpoints rotation it shows x is changing. but no visible rotation.
I'm making a simple game application for IOS devices, loosely based from the book 'swift game development'. I have created a protocol which I use as a template for creating a class for each type of in game object. A platform for the player to jump on has the following class based on the protocol
import SpriteKit
class GrassyPlatform: SKSpriteNode, GameSprite {
var textureAtlas:SKTextureAtlas = SKTextureAtlas(named: "Enviroment")
var initialSize: CGSize = CGSize(width:630, height:44)
init() {
super.init(texture: textureAtlas.textureNamed("GrassPlatform1"), color: .clear, size: initialSize)
self.anchorPoint = CGPoint(x: 0.5, y:0.5)
physicsBody = SKPhysicsBody(rectangleOf: initialSize)
physicsBody?.restitution = 0
self.physicsBody?.isDynamic = false
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I'm using the scene editor to place these objects onto the scene, assigning each object the relevant custom class. Like the one above.
When I run the game the objects position (assigned only from the scene editor) is respected, but the zRotation value is ignored. For example setting the platform in the scene editor as so
But this results in the platform appearing at the correct position but with the default zRotation value and not the one assigned in the game scene.
I can adjust the rotation manually through self.zRotation but this defeats the whole point of using the scene.sks for level design.
Is there away to adjust the zRotation through game scene and if so how?
Thanks
Solved the problem was in a piece of code that I never added to the original post because I 'thought' it was irrelevant! boy was I wrong. Another class handles the different scenes. This code I took from the book and was not 100% on its operation when I added it, before getting caught up in something else
class EncounterManager {
let encounterNames:[String] = ["Level1A", "Level1B"] //A array of all the scenes in the level
var encounters:[SKNode] = [] //each scene is a node
var currentEncounterIndex:Int?
var previousEncounterIndex:Int?
init() {
for encounterFileName in encounterNames { //Loop all scenes in the scene array
let encounterNode = SKNode() //create a new node for the encounter/scene
if let encounterScene = SKScene(fileNamed: encounterFileName) { //load the encounter to the skscene
for child in encounterScene.children { //Loop through each child node of the skscene
let copyOfNode = type(of: child).init() //copy the node type and initilize to the encounterNode
copyOfNode.position = child.position //copy the position
copyOfNode.zPosition = child.zPosition //copy of zPosition
copyOfNode.name = child.name //copy the name
encounterNode.addChild(copyOfNode) //add child to encounter node
}
}
encounters.append(encounterNode)
//Save initial sprite positions for this encounter
saveSpritePositions(node: encounterNode)
}
}
This takes a copy of each object node and adds it to the game scene. The origonal author never needed to worry about the rotation of objects. So I added this and it worked
copyOfNode.zRotation = child.zRotation //copy of zRotation
Thanks anyway if you looked.