Combine DuckHunt with ARKit? — Lesson 2: Boost your app’s ARchitecture

Nomtek Labs
Inborn Experience (UX in AR/VR)
5 min readDec 18, 2018

Story by: Wojciech Trzasko, iOS developer at nomtek

pic by vincentvanzalinge

As you probably remember, quite recently we worked on a small project that combined classic NES game with our reality… Oh, you don’t remember? It’s not a problem, here is a small sneak peek to refresh your memory:

You still don’t remember? Check out our previous lesson where we defined the basics of the Entity-Component-System pattern and VIPER architecture.

So what should we do now?

Currently, we have a knowledge of designing architecture for a real-time application with a usage of ECS pattern, which works well with ARKit experience. We also learned the basics of widely used iOS app architecture called VIPER. So now we need to figure out how to connect those two different worlds.

Why are those two so much different?
On one side we have ECS combined with ARKit, the technology that process image data and based on results provides real-time pieces of information. At the other end, we will be working with classical event-based infrastructure, represented by UIKit and the VIPER architecture.

Let’s start with the VIPER

Now, it’s time for the hardest part. In case of AR experience we operate on real-time pieces of information, not the event-based infrastructure. Now if we look at classical VIPER layers, the presenter and interactor don’t fit much.

Classical VIPER layers

Yes, you are right! We will try to replace those layers with ECS design pattern. To achieve this we need to pass the user’s interactions and calls of the update method to the ECS, but this task is a little bit tricky because of ARKit and SceneKit natures. To build an experience basing on SceneKit, you need to use an object that implements SCNSceneRenderer protocol. For AR scenes Apple provides a default implementation of AR renderer called ARSCNView, which returns game logic and augmented reality callbacks (like updateAtTime, didSimulatePhysicsAtTime or didAddNodeForAnchor) to its delegate. So all callbacks, crucial for our ECS implementation, will be returned by object strictly connected to the view layer. This is a big obstacle and if we don’t want to implement our own renderer, we need to figure out how to handle it.

The easiest way to make it work is to crop view layer from most of its responsibilities and make it as simple as possible. In our DuckHunt demo, we used view controller object only to gather user input and all logic callbacks and then pass them to two additional layers: Scene and Gameplay. Both of them don’t even know about view’s existence. All communication goes through interfaces shared by those layers.

So what are the responsibilities of those two new layers?

Scene is a class that inherits from SCNScene. It loads and holds a graph of 3D objects placed in the world. Also, it allows manipulating the dynamic objects by making them public. Gameplay is the place where all the magic happens. Here we will put our ECS implementation. In general, it holds a reference to our Scene and handles all interactions between objects in the world.

Let’s take a look at the hunt module in our DuckHunt game. HuntScene loads the world that will be displayed around player, from the scene.scn file. Then shares the list of duck spawn points and one additional node through its public interface. We allowed HuntGameplay to use this node for adding geometry for newly spawned ducks entities. Then we moved the logic of managing and determining the order of Systems from ECS pattern to the separate class called EntityManager. So in his final form HuntGameplay takes update callbacks from the View and passes them to the entities, by calling update on EntityManager, then spawns new duck if needed. Finally, it checks the win condition. If it is fulfilled then asks Wireframe to move to the next scene.

New layer connections

So if you take a deeper look at new layers, you will see some similarities to the classic VIPER approach. Just like the VIPER’s View waits for Presenter to give it content to display, the Scene waits for the Gameplay to determine new positions for the objects in the world. The main difference is that we allowed our Gameplay to add new geometry to the scene. Here is probably the first place where we could introduce further improvements. Instead of adding a new geometry for every spawned duck entity, we could create only one and try to reuse it. This could be easily achieved by adding an empty plane to the world and then make it available through Scene interface.

It will definitely allow us to reach a higher level of abstraction, but still could be problematic in richer Gameplays. For example, imagine that in next release our client wants to spawn more enemy types. Reusing the same geometry for every type can be a painful task or will require adding some more advanced algorithms. So it’s up to you which version you will take, which will be more effective for your requirements.

Are we done yet?

To be honest, yes!

And as a reward for your patience, we have a link to the open source version of the project. You can grab it from here: https://github.com/nomtek/DuckHuntAR

So, we showed you our first experiments with preparing architecture for ARKit based experiences. Is it perfect? Hell no!

There is a big room for improvements in this topic. Maybe you already have an idea what to change? Share your experience below in the comments!

Let’s stay in touch, folks!

--

--