Articles, Code and Video Tutorials for ARKit, Sprite Kit & SceneKit

How to do Z-Distance Detection with ARKit and SpriteKit

For this example, let’s consider a SpriteKit and ARKit based app where the user will wander around and collect items in AR space. Maybe it’s coins, maybe it’s rainbows, whatever. We’re basically looking to do collision detection but with the user itself, or really the camera of the user. As the user gets close enough to the object in AR space, we want to “collect” the idea (or for the simplicity of this article, let’s just remove it).

We’re going to do most of the work in the update statement of the Scene.swift, which runs every frame  (so all the time really). The only time we don’t want this to run is right away. You’ll get a crash if you run this code immediately, because the AR world isn’t entirely setup yet. So declare a variable in your Scene.swift file to be used as an on-off switch for the rest of what we’ll do in the update statement.

Perfect. You can switch that to true when you’re ready to begin checking each node in the scene to see if it’s within a certain z-distance of the camera. We’ll also check if the node is in the center of the camera. I’ll let you handle when that variable is switched to true.

Next up let’s head to the existing update function in the Scene.swift file. Copy this much in below, and we’ll add more to it in a moment.

So the first thing we do here is return out of this function entirely if allowNodeChecking is false.  That means everything else in the function is ignored. Same thing is true for the guard statement (which we see constantly with our AR-related code).

After we have the sceneView variable we will print out the value of the current z transformation of it. And if you read the code above, that’s a pretty literal description of this… sceneView.session.currentFrame!.camera.transform.columns.3.z

Note that you can switch .z with .x or .y to find out where the camera is on those axises as well. And all these values are relative to the initial starting point of the camera.

So if you were to run this right now, you’d see a lot of small decimal numbers, starting from zero, but changing as you move your device forward and backward. Going forward from the device’s initial position, the numbers will be negative, and backward they will be positive (a little counter-intuitive I know).

You now know where the camera is at on the z axis (“axis” might not be the best term, but for lack of the proper one, let’s go with it). So what we want to do now is check for SKNodes or SKSpriteNodes in front of the camera, that are within a certain range of the cameras z value.

Paste in this code, where I wrote //… to be continued above.

That’s a lot of closing brackets there. Sorry, had to do it! So let’s step through this thing.

At //1 we are iterating through all the nodes at CGPoint.zero. Hopefully you didn’t mess with the scene’s anchor point at all (you’d know if you did), because CGPoint.zero represents the middle of the screen. This is a really easy way to check for nodes that collide with things in the center of the screen (or elsewhere, you are limited to using CGPoint.zero). So for example, if you’re creating a shooter game where something is always fired in the center, this for statement comes in really handy.

At //2 we casting someSprite as the node that was found at the center (assuming one was found). Obviously you could switch out SKSpriteNode with a custom class you’ve written. So if the node isn’t that class, the rest is ignored as we don’t need to do any further testing.

At //3 we are introducing another for statement to iterate through the anchors in the scene.

At //4 we are getting the absolute value of the difference between the cameraZ variable we created earlier and the anchor.transform.columns.3.z. So in other words, for whatever anchor we are currently inspecting in the loop, we want to see how close it is to the cameraZ. We do this by subtracting the two numbers and getting the absolute (or positive) value of the two. Then we see if that amount is less than 0.2 (or whatever range is appropriate for your app). The smaller the value, the closer the user has to get to the node to make it disappear.

At //5 we are creating a new instance variable called someOtherSprite which is whatever sprite is attached to the anchor in question.

At //6 we are checking if someSprite (our original node that we checked was in the center of the screen) is the same one was someOtherSprite.

At //7 the previous if statement was true, and the sprite is removed, or “collected”

Not a bad bit of programming huh! You can take a look at the final result below. Notice the sprite furthest back isn’t removed, but the one closest to us is removed as we get within range.