Designing complex objects with SpriteKit Scene Editor - ios

Near as I can tell, there isn’t a way to add physics joints in the scene editor. Is that right?
Consider a simple person object with a body, child legs and arms w/ pin joints. If I want to design this person in the scene editor and then programmatically add him to a scene, I’m not getting very far. I’m able to find the nodes in the scene, remove them from their parent and add them as a child at a new position in my scene, but I still have to specify all their joints manually.
Thoughts?

Here is my solution. I'm still hoping there is a way to create physics joints with the scene editor but I haven't found it so...
Step 1) Add all the child nodes to the scene, ensuring that objects are grouped by parent.
Step 2) Define a swift class for your complex node.
class MyNode : SKSpriteNode {
func spawn(parentNode: SKNode, position: CGPoint) {
parentNode.addChild(self) // before physics joints
let arm = self.childNode(withName:"arm")
// note if you didn't add physics bodies in scene file
// do that first
let shoulders = SKPhysicsJointPin.joint(withBodyA:self.physicsBody!, bodyB: arm.physicsBody!, anchor: CGPoint(x:position.x,y:position.y-1))
scene!.physicsWorld.add(shoulders)
// feature of pulling a child from a scene, it's always paused by default.
self.isPaused = false
}
}
Set the class for your body node in your scene.
Step 3) Transfer your node to your game scene at init time.
let tmpScene = SKScene.init(fileNamed: "MyNode.sks")
var myNode = tmpScene.childNamed(withName:"myNode") as! MyNode
myNode.removeFromParent()
myNode.spawn(world, position) // or your own parent and position as needed for your scene

Related

didAddNode vs SceneKit Collision Detection

I am building a small demo where two objects can collide with each other. Basically an object will be placed on a plane. I have the following code for adding physics body to the plane.
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if anchor is ARPlaneAnchor {
let plane = SCNPlane(width: 0.5, height: 0.5)
let material = SCNMaterial()
material.isDoubleSided = true
material.diffuse.contents = UIImage(named: "overlay_grid")
plane.firstMaterial = material
let planeNode = SCNNode(geometry: plane)
planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
planeNode.physicsBody?.categoryBitMask = BodyType.plane.rawValue
planeNode.eulerAngles.x = .pi/2
node.addChildNode(planeNode)
}
Even though the plane gets added it does not participate in any physical collisions. If I try to place objects on it, it goes right through it. But if I change the last line to the following it works:
// node.addChildNode(planeNode) // INSTEAD OF THIS
planeNode.position = SCNVector3(anchor.transform.columns.3.x, anchor.transform.columns.3.y, anchor.transform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(planeNode) // THIS WORKS
My understanding is that all the collision related stuff is maintained by SceneView and in order to participate in collisions I need to add it to the SceneView hierarchy instead of the ARSCNView hierarchy.
QUESTION:
// node.addChildNode(planeNode) // WHY THIS DOES NOT WORK
planeNode.position = SCNVector3(anchor.transform.columns.3.x, anchor.transform.columns.3.y, anchor.transform.columns.3.z)
self.sceneView.scene.rootNode.addChildNode(planeNode) // WHY THIS WORKS
static physics bodies are so named because they aren’t supposed to move (relative to the global/world/scene coordinate space). Many optimizations in the inner workings of a physics engine depend on this, so changing the position of a node with an attached static physics body is likely to cause incorrect behavior.
ARKit continually moves the ARPlaneAnchors that result from plane detection — the longer it looks at a real-world planar surface, from more different angles, the better it knows the position and size of that plane.
When you add a child node to the ARSCNView-managed node in renderer(_:didAdd:for:), the child node’s position may not change... but that position is relative to its parent node, and ARKit automatically moves the parent node to match the ARPlaneAnchor it goes with. So the child node moves relative to the world whenever ARKit updates the plane anchor. If you have a static physics body on that node, you get weirdness.
When you directly add a node as a child of the scene’s rootNode and set its position based on the initial of a plane anchor, that node stays still — you’re the only one setting its world-space position, and you’re doing so exactly once. So it’s safe to give it a static physics body.
(Note that if you want “static” physics body behavior for something that can change over time, it is possible to delete the physics body and re-create it at a new position. Just don’t do so too often or you’re probably defeating other optimizations in the physics engine.)

iOS ARKit - move objects along path

I am trying to move implement bouncing balls using ARKit. I want the balls coming from one end of screen bouncing and moving out of screen.
Can anyone please recommend best approach or point to sample code to implement this?
Can I use UIBezierPath to create a path and move SCNNode along the path. If yes, how can I move the node along path.
Create a scene and a ball Node. Use force direction for ball Node and animate the ball Node with Physics. This is just an example.
// Create a new scene
guard let scene = SCNScene(named: "BouncingBalls.scn", inDirectory: "art.scnassets") else { return }
sceneView.scene = scene
// Add physics bodies
guard let ballsNode = scene.rootNode.childNode(withName: "balls", recursively: true) else { return }
let forceDirection = SCNVector3Make(0, 3, 0)
for ballNode in ballsNode.childNodes {
let physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
physicsBody.applyForce(forceDirection, asImpulse: true)
ballNode.physicsBody = physicsBody
}
Try this in View didLoad. Hope this works.
The best way to get what you want is to use a predefined animation or dynamics that was made in such 3D apps as Autodesk Maya, Autodesk 3dsMax, or Maxon Cinema4D.
After producing a ball animation (or dynamics, it's up to you) you need to bake this animation (baking is a process of keyframe generation for every frame instead of using interpolated curve) and export this scene as a .dae file format. ARKit/SceneKit supports not only .dae animations but also brand-new format .usdz.
SceneKit API (as well as other Apple frameworks' APIs) is not designed for 3D animation.

How to dynamically create annotations for 3D object using scenekit - ARKit in iOS 11?

I am working on creating annotations using overlaySKScene something similar to this(https://sketchfab.com/models/1144d7be20434e8387a2f0e311eca9b1#). I followed https://github.com/halmueller/ImmersiveInterfaces/tree/master/Tracking%20Overlay to create the overlay.
But in the provided example, they are creating only one annotation and it is static. I want to create multiple annotations dynamically based on the number of child nodes we have and also should be able to position annotation on top of respective child node. How to achieve this?
I am adding overlay like below,
sceneView.overlaySKScene = InformationOverlayScene(size: sceneView.frame.size)
where InformationOverlayScene is the SKScene in which i have added two childnodes to create one annotation.
Create an array with the annotation sprites that is mapped to the childnodes array, and then do something like the following:
func renderer(_ aRenderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
let scnView = self.view as! SCNView
//for each character
for var i in 0...inBattleChars.count-1 {
let healthbarpos = scnView.projectPoint(inBattleChars[i].position)
battleSKO.healthbars[i].position = CGPoint(x: CGFloat(healthbarpos.x), y: (scnView.bounds.size.height-10)-CGFloat(healthbarpos.y))
}
}
Before every frame is rendered this updates the position of an SKSprite (in healthBars) for each SCNNode in inBattleChars. The key part is where projectPoint is used to get the SK overlay scene's 2D position based on the SCNNode in the 3D scene.
To prevent the annotations of non-visible nodes from showing up (such as childnodes on the back side of the parent object) use the SCNRenderer’s nodesInsideFrustum(of:) method.
You can add a SKScene or a CALayer as a material property.
You could create a SCNPlane with a specific width and height and add a SpriteKit scene as the material.
You can find an example here.
Then you just position the plane where you want it to be and create and delete the annotations as you need them.

Unexpected physicsBody in SpriteKit scene

I'm implementing a mass-spring system (many small physics bodies joined together with SKPhysicsJointSpring instances) with SpriteKit. Some of the particles would get snagged while traversing the center of the scene.
There seems to be a small, static body in the middle of the scene and I don't know why it's there.
Here's an easy way to see what I'm talking about:
In XCode 8, create a brand new project with the "Game" template.
In GameViewController.viewDidLoad(), add view.showsPhysics = true
If you run the project, you should see a little dot in the middle, which is the errant body:
Anyone know how to get rid of it?
Edit: I tried to manually create the scene object:
In GameViewController.viewDidLoad(), I replaced this:
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
view.presentScene(scene)
}
with this:
let scene = GameScene(size: view.frame.size)
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
view.presentScene(scene)
but that didn't fix it.
Anyways, I decided to make an answer because comments are not suitable due to lot of info I want to share. Also my answer, sadly, doesn't answer the question but it gives some useful info about this unidentified, obviously capable of flying (physics body) object :)
So this is the code how to grab it (and modify it???):
//you can use self.frame here...I just copied Alessandro's code
self.physicsWorld.enumerateBodies(in:(label?.frame)!) { body, stop in
if let node = body.node {
print("Type of this node: \(type(of:node))")
print("Frame of this node: \(node.frame))")
}else{
print("This body's node property is nil")
body.affectedByGravity = true
body.isDynamic = true
body.applyImpulse(CGVector(dx: 0.003, dy: 0.0003))
}
print("Area covered by this node physicsBody: \(body.area)")
}
So if you put a break point inside of that else statement, you can scan this physics body completely and get all the info about it, like that its node property is set to nil or that its isDynamic property is set to false. But you can change that, and like in my code, set for example isDynamics to true. This makes it moveable. So if you apply some forces to it, it will move.
Still, like I said in comments, I don't have an idea why it is there and what it represents or what is its purpose.
Also, for those who are wondering how it is possible that one physics body doesn't have a node associated with it ( body.node equals nil) but is still visible on screen when showsPhysics is set to true, there is a reasonable explanation. Physics world is separated from the node tree. So we can remove a sprite from a node tree, but that doesn't mean that its physics body will be removed instantly. It may happen that physics engine haven't finished simulation... So you probably wonder, how this might happen?
Let say you have three SKSpriteNode objects intersecting at the same time (say A contacts B and A contacts C at the same time). SpriteKit can process only one contact at time. And say that you are removing A from a parent when it is contacting with B. Then, there is a contact between A and C also, so didBegin:(_ contact) will be called twice. And if you remove A from its parent in first didBegin(_ contact) call, in the next didBegin(_ contact) call, bodyA.node will be nil (bodyA is a physics body of sprite A), but its physics body will remain visible until engine finishes what needed. This is because node tree and a physics world are separated.
About the "hello world" xCode game template , it seems a little physicsBody associated to the GameScene node.
With some code I've found this:
class GameScene: SKScene {
private var label : SKLabelNode?
private var spinnyNode : SKShapeNode?
override func didMove(to view: SKView) {
...
// End part of this function:
if let b = physicsWorld.body(in: (label?.frame)!) {
if let node = b.node {
print("Type of this node: \(type(of:node))")
print("Frame of this node: \(node.frame))")
}
print("Area covered by this node physicsBody: \(b.area)")
}
}
}
With a breakpoint to the last brace, you can see two bodies (maybe an array of bodies), one of them is the physicsBody to the left debugArea (array with index 1) with the same hex address as my body b in my code : 0x79794f90, a little rectangle body with area 4.444
Printing description of ((PKPhysicsBody *)0x79794f90):
<SKPhysicsBody> type:<Rectangle> representedObject:[<SKScene> name:'(null)' frame:{{-375, -667}, {750, 1334}} anchor:{0.5, 0.5}]
(lldb)
I had a similar problem. I have a game with two sprite nodes joined together (SKPhysicsJointFixed.joint) moving around an SKEditor created scene.
As per my design, this node-pair would impact a third sprite node and be propelled smoothly away from the third sprite node, EXCEPT when the impact was in the center of the scene. For the center of the scene impact, the node-pair would compress together while be propelled away from the third sprite node, presenting a poor graphical image.
After significant time debugging my code, I found this post. Kudos for the explanations and code. I can’t answer the “why” question but for the “particles would get snagged while traversing the center of the scene” question my suggested solution is to clear the collisionBitMask instead of moving the body.
BTW categoryBitMask is 0 when loaded.
//moves the problem offscreen unless it hits another node
//body.affectedByGravity = true
//body.isDynamic = true
//body.applyImpulse(CGVector(dx: 0.003, dy: 0.0003))
//
// collisionBitMask loads with 4294967295 = default value to collide with all categories
//
body.collisionBitMask = 0

iOS spriteKit child nodes position with respect to view coordinates

I have a child node added to another node. I want to get the child nodes position with respect to the views coordinates and not the parent nodes coordinates
Get the child node's position with respect to its parent, then use the convertPoint:fromNode: or convertPoint:toNode: method to convert from the parent node's coordinate system to the scene's coordinate system. (Remember, SKScene inherits from SKNode and is the root of the node hierarchy, so you can use it with either of those methods.) If you then need to work in the UIKit view coordinate space, use the scene's convertPointToView: method.
If by 'view coordinates' you mean the scene coordinates, then check out this swift solution I use:
extension SKNode {
var positionInScene:CGPoint? {
if let scene = scene, let parent = parent {
return parent.convert(position, to:scene)
} else {
return nil
}
}
}
Then you can get the scene position in the same manner you get the regular position. Here is an example:
let positionInParent = childNode.position
let positionInScene = childNode.positionInScene? //optional return type
Note that the positionInScene property is optional for the same reason that the scene property of SKNode is optional, the node might not be added to a scene. In this case, you get nil. You could even reverse the process and add a setter, so you could position every node in the scene coordinates no matter how deeply it was buried.

Resources