SpriteKit Swift Node Count issues - ios

The simulator shows the node count, that there are 2 nodes. But why is that? Since there is only one var(ball). Is there a way to create a circle without having it be double the nodes.
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 50)
override func didMoveToView(view: SKView) {
ball.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
ball.fillColor = SKColor.blackColor()
self.addChild(ball)
}

This is happening because SKShapeNode fillColor is set. Obviously, when either fillColor or strokeColor are set, each of them require additional node and additional draw pass . By default there is already strokeColor set (one node, one draw pass) and because of fillColor is set additionally, two nodes (and two drawing passes) are required for shape to be rendered.
SKShapeNode is not a performant solution in many cases, because of obvious reasons. One SKShapeNode requires at least one draw call. There are some interesting post related to this topic, for example this one or this one.
But still IMO, SKShapeNode should be avoided if it's not the only solution. Just because of performance reasons. And in your case, if you for example want to have many different kind of balls, the performant solution would be to use texture atlas and SKSpriteNode for representing those textures. This way you can render as much balls as you like in single draw pass.
Note that draw passes are much more important than number of nodes when it comes to performance. SpriteKit can render hundreds of nodes at 60fps if amount of drawing passes required for scene rendering is low.

Related

ARKit - SCNNode continuous movement

I'm trying to create an application in which the user stacks different geometric shapes. In a .scn file, which is loaded inside the ARSCNView, I insert a static plane and then at each tap of the user, the app inserts a dynamic SCNNode.
The first node is being inserted a few inches above the plane, to replicate a falling object. And then, each other node is being dropped on top of another.
This is the main idea of the application; the problem appears after adding 3 or 4 nodes, they appear to slide of each other, almost jiggle, and the whole structure collapses.
This is my node I'm inserting:
let dimension: CGFloat = 0.075
let cube = SCNBox(width: dimension, height: dimension, length: dimension, chamferRadius: 0.0)
let node = SCNNode(geometry: cube)
node.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.dynamic, shape: nil)
node.physicsBody?.mass = 2.0
node.physicsBody?.friction = 1.0
node.physicsBody?.restitution = 0.01
node.physicsBody?.damping = 0.0
node.physicsBody?.angularDamping = 0.0
node.physicsBody?.rollingFriction = 1.0
node.physicsBody?.allowsResting = true
let insertionYOffset = 0.3
node.position = SCNVector3(hitResult.worldCoordinates.x, hitResult.worldCoordinates.y + Float(insertionYOffset), hitResult.worldCoordinates.z)
I've tried to play with the values and these are the best ones, but they aren't enough to create a stable structure of blocks.
As a requirement, I need to keep the blocks dynamic, they need to be affected by gravity, wind, etc.
The problem most likely has something to do with a factor called Dynamic which will allow it to continuously move or may have to do with the objects hitting each other to fix that problem all you have to do is change the collision mask to two different numbers.
I see two points that may be the flaws of your simulation:
Your objects are stacked. When two objects collides there's a small amount of error added in order to keep the physics in a stable phase and prevent weird behaviors. So when you stack objects you add error amounts and the simulation becomes inaccurate. I suggest making objects static over time or depending of the position, or reduce gravity by a bit.
Most of the physics engines works better with objects sized with the unit size (most of the cases, it's 1 like in Box2D or PhysX). Objects that are very small or very big tend to act less natural. 0.075 sounds a bit small for a simulation. I don't see a obvious solution, maybe look for a way to change the reference size.

Most optimal method of implementing tappable shapes?

I'm building an app that provides an editable canvas similarly to photoshop or illustrator (currently using SpriteKit). I'm encountering some performance issues when displaying a large number of nodes (1000+) as you might imagine.
Currently, I've got an SKScene with SKShapeNodes on it. They presently do not have any images and are simply filled with a color. Their shape paths (CGPaths) vary from circles, to bezier paths. Each shape currently has an SKPhysicsBody with the same path as the rendered shape that is used to detect taps.
The performance issues can be described by:
slowness when adding 1000 nodes (circles), uses about 0.1mb of memory per node
slowness when moving 1000 nodes (circles)
slowness when generating a texture from 1000 nodes (circles)
Disabling PhysicsBodies doesn't substantially improve performance, but does improve CPU load (jumps from constant 60% to 1% or so)
Most users will not be working with 1000 nodes, but I'd like to implement the optimal solution.
What I'd like is two have two layers:
A render layer on which I'd like to be able to render CGPaths with strokes and fills (preferably choosing the stroke end cap style among other little things)
An interaction layer on which I'd like to be able to detect taps inside CGPaths and stroke CGPath's with a color to indicate highlighting.
How can I accomplish this or a similar solution that will improve the speed at which I can render 1000 circles?
Don't use SKShapeNode, it needs 1 draw call per shapenode. You can create a shape, then "cast" it to a SpriteNode before adding it to the scene:
func shapeToSprite(_ shape: SKShapeNode) -> SKSpriteNode {
let sprite = SKSpriteNode(texture: SKView().texture(from: shape))
sprite.physicsBody = shape.physicsBody // Or create a new PB from alpha mask (may be slower, IDK)
shape.physicsBody = nil
return sprite
}
override func didMove(to view: SKView) {
let shape = SKShapeNode(circleOfRadius: 60)
addChild(shapeToSprite(shape))
}
This would suck though if you needed to constantly edit the size / shape of your shape in a way that isn't doable via scaling or SKWarpGeometry
To detect taps like this, you just need to either 1, Set a name for the SpriteNode then look for it in touchesBegan, or 2, subclass a SpriteNode and override it's touchesBegan.
Also, if you need to just have a static image, you can bit-blit thousands of nodes onto 1 texture.
Sprite Kit: A Lot of sprites (1000+) with Bit Blitting
http://www.sdkboy.com/2014/01/spritekit-bit-blitting-by-drawing-to-a-texture-for-performance-optimization/
So if you don't need every node to be touchable at all times, you can alternate between having physicsBodys and not, or switch layers from bitblitted to active.

SpriteKit : enumerateBodiesAtPoint not showing correct bodies

I have a few non-completed-circles rotating constantly and a user going from circle to circle.
I removed all gravity, forces etc from my scene
Image A
Image B
Problem : I am trying to do a hit detection where I just check where the user is, and if there are SKNode's bodies at this point in the physics world of my scene. If it's a hit with the shape, the user can continue (image A), but fails if he is outside (image B)
Although the shapes are pretty complex, the scene.showPhysics seem to match my shapes precisely. (see image A and B)
let updatedOrigin = user.calculateAccumulatedFrame().origin
user.scene?.physicsWorld.enumerateBodiesAtPoint(updatedOrigin, usingBlock: { (body, stop) in
print("🍄 Shape contains \(body.node!.name)")
})
which prints
🍄 Shape contains Optional("User")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("circle")
🍄 Shape contains Optional("Scene")
It prints the user and scene correctly, but also prints all the circle's shapes around, when there should only be one at this point, or none. The nodes are there, but the bodies physics should not hit.
Any ideas why it shows a hit for all those circles when it should only match 1 or none? Thanks!
Edit : additional info
- I had similar results when using user.physicsBody?.allContactedBodies()
- I am using a CGPath to create the PhysicsBody of my rotating node
I created a simple test project with a scene containing 3 arcs with physics bodies and 3 rectangle shape-nodes that identify the bounding box for each arc. I then added a touch handler that places a small circle at the tap point and a label that identifies the nodes returned by enumerateBodiesAtPoint with the touch location as the parameter. The figure below shows the results of tapping at various locations in the scene.
From the test results, it's not obvious how enumerateBodiesAtPoint determines if a physics body contains the specified point or not. It is clear, though, that the method is not consistent with its documentation. I suggest that you avoid using it in your app.
Alternatively, you can use SpriteKit's built-in contact detection:
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
}
func didBeginContact(contact: SKPhysicsContact) {
// Handle contacts between physics bodies here
}
}
You can also test if a point is within a CGPath using CGPathContainsPoint. Here, you will need to iterate over the paths you used to create the arc-shaped physics bodies. The below figure shows the result of my test project that uses CGPathContainsPoint instead of enumerateBodiesAtPoint. You may need to convert the test point, with convertPoint, to the appropriate coordinate space prior passing it to CGPathContainsPoint.

SpriteKit physics bodies not colliding when connected with joints

I am evaluating the iOS SpriteKit physics engine, and for a test I have created a simple scene with Xcode that contains two circle-shaped nodes:
Both nodes have physics bodies of circular shape and 5 kg of mass.
I am connecting both nodes with a SKPhysicsJointSpring. Here is the entire setup (in viewDidLoad()):
let path = NSBundle.mainBundle().pathForResource("MyScene", ofType: "sks")!
let scene = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as! SKScene
let border = SKPhysicsBody(edgeLoopFromRect: scene.frame)
border.restitution = 0;
border.friction = 0;
scene.physicsBody = border
let player = scene.childNodeWithName("player")! // large ball
let tail1 = scene.childNodeWithName("tail1")! // smaller ball
player.physicsBody!.usesPreciseCollisionDetection = true
tail1.physicsBody!.usesPreciseCollisionDetection = true
let spring1 = SKPhysicsJointSpring.jointWithBodyA(player.physicsBody!, bodyB: tail1.physicsBody!, anchorA: player.position, anchorB: tail1.position)
spring1.damping = 1
spring1.frequency = 3
scene.physicsWorld.addJoint(spring1)
spriteKitView.presentScene(scene)
Note: The gravity is set to (0,0) in the scene editor.
When I move the bigger node (say, by capturing touches with touchesBegan() and setting the node's position to the touch position), the other follows according to spring parameters.
However, if I move the node fast enough, so that the spring forces become extreme, both nodes overlap when the spring is contracting. I am expecting them to collide with each other since their collisionBitMask is by default set to -1 (all bits set). I have enabled usesPreciseCollisionDetection but the effect is still visible.
When I add an edge loop around the scene they do collide with that edge as expected. Same with additional nodes that have a physics body (but no joints attached).
I have the impression that the presence of the spring somehow makes the engine ignore collisions between nodes that are connected with joints.
Did anyone else observe this, too?
Did I forget anything? Or is this working as intended?
Attempting to answer my own question.
I found out that other joints (such as SKPhysicsJointLimit) disable collision between the joined nodes as well. My only conclusion is that all SpriteKit joints somehow consider two objects as one, where collisions are deemed undesirable. Though in my app I would prefer to have collisions enabled. Maybe I should file an enhancement at Apple developer feedback.
So far my workarounds include:
Create spring behaviour by adding a SKFieldNode.springField plus a SKFieldNode.dragField as children to the sprite node. Pro: as children these fields automatically follow their parent position; Con: this spends 2 precious bits of fieldBitMask which puts a natural limit to the number of such nodes.
Manually apply some spring + drag force at every frame. Pro: does not waste bits of fieldBitMask, Con: compute spring + drag forces manually.
So I tend to favor option 2.
To summarize: If you want spring behaviour between 2 nodes while maintaining collisions, implement forces manually and do not use a joint.

How to Render Many SpriteKit Nodes at Once?

I am using SpriteKit to render a large (20 x 20) dot grid that looks like this:
I'd like to highlight rows or columns based on user input. For example, I'd like to change rows 1-10 to a red color, or columns 5-15 to a blue color.
What is the most performant way to do this?
I've tried:
Naming each GridNode based on the column it's in (e.g. #"column-4). Then use enumerateChildNodesWithName: with the string as #"column-n", changing the color of each node (by changing SKShapeNode setFillColor:) in the enumerate block.
Giving all the columns a parent node associated with that column. Then telling the parent node to change its alpha (thus changing the alpha of all its children).
Making arrays for the different columns, then looping through each node and changing its color or alpha.
I've tried making the GridDot class an SKEffectNode with shouldRasterize: set to YES. I've tried both an SKShapeNode and a SKSpriteNode as its child. I've also tried taking away the SKEffectNode parent and just render an SKSpriteNode.
Each of these options makes my whole app lag and makes my framerate drop to ~10 FPS. What is the correct way to change the color/alpha of many nodes (without dropping frames)?
At its heart, the issue is rendering this many nodes, yes?
When I faced similar performance problems while using SKShapeNode I came up with this solution:
Create SKShapeNode with required path and color.
Use SKView's method textureFromNode:crop: to convert SKShapeNode to an SKTexture
Repeat steps 1,2 to create all required textures for a node.
Create SKSpriteNode from a texture
Use created SKSpriteNode in your scene instead of SKShapeNode
Change node's texture when needed using SKSpriteNode's texture property
If you have a limited set of collors for your dots, I think this aproach will fit fine for your task.
In contrast to #amobi's statement, 400 nodes is not a lot. For instance, I have a scene with ~400 nodes and a render time of 9.8ms and 9 draw calls.
If you have 400 draw calls though, you should try to reduce that number. To determine the amount of draw calls needed for each frame rendered, implement (some of) the following code. It is actually taken from my own SpriteKit app's ViewController class which contains the SpriteKit scene.
skView.showsFPS = YES;
skView.showsNodeCount = YES;
skView.showsDrawCount = YES;
Proposed solution
I recommend using SKView's ignoresSiblingOrder. This way, SKSpriteNodes with equal zPosition are drawn in one draw call, which (for as many nodes/draw you appear to have) is horribly efficient. Set this in the -viewDidLoad method of the SKView's ViewController.
skView.ignoresSiblingOrder = YES;
I see no reason to burden the GPU with SKEffectNodes in this scenario. They are usually a great way to tank your frame rate.
Final thoughts
Basic performance issues mean you have a CPU or a GPU bottleneck. It is difficult to guess which you're suffering from with the current information. You could launch the Profiler, but Xcode itself also provides valuable information when you are running your app in an attached device. FPS in the Simulator is not representative for device performance.

Resources