In this article I would like to answer the questions:
what topics will be covered in the next articles about Crimson Native?
In the life of all Unity Developers, sooner or later there will be a moment when they will be forced to use functionality that is only available from the native platform the game is developed for. It's not a big deal if there are already ready solutions (whether created by the Unity team or other developers) and they address our needs. However, the difficulty arises when the functionality we need is not yet created and shared by anyone, or the solution proposed by someone is not sufficiently matched to our requirements. At such a moment, if you don't have experience in this area, you may end up with a serious headache and stress about whether you will manage to deliver the given functionality on time.
Examples of functionalities that we may need to use:
Continuing with the system themes example - in September 2019, there was an iOS update to version 13 where one of the new features was the addition of dark mode to the entire system. Due to the freshness of this functionality, no solutions were available, so if any company wanted to have an app that supported themes, they were forced to implement the functionality themselves. The ability to quickly implement native plugins for a company developing games and mobile applications using the Unity engine is crucial from the business point of view e.g. implementing the aforementioned system themes could help the application to be distinguished by the Apple team, which really translates to its reach and, of course, earnings.
Some may wonder why am I even writing about this topic? Writing plugins for native platforms isn't rocket science, plus you can find plenty of ready-made solutions that you just pay a few dollars for and integrate into your application and you're done. So here I will introduce you to our reason why we created Crimson Native - but first I will describe its very concept. Crimson Native is a pattern we adopted for creating functionalities that integrate two levels - one of them is Unity level (C#), the second is native platform level (it can be Android with Java or iOS with Objective-C). At its core are several assumptions, i.e.:
When creating a new plugin using this approach, we can distinguish 4 main stages:
Below you can see a class diagram showing the implementation of one of the plugins we implemented, specifically the logic responsible for detecting light/dark mode. A diagram with a better resolution is available here.
The diagram is divided into 3 main layers:
Over the years we have been forced to use plugins created by other developers many times. The plugins we integrated were those available for a fee, e.g. on the Asset Store, as well as free ones, such as implementations of advertising networks like Google Mobile Ads (short: AdMob), Facebook Audience Network (short: FAN), or IronSource. Those who worked with third-party code know that it has its pros, but also its cons - let's list some of them.
Looking at the above pros and cons you can come to a conclusion that the positive aspects behind the use of ready-made solutions are more significant - so why have we decided to use our own solution?
At the turn of 2019 and 2020, we decided to implement our own ads mediation system, which was supposed to work analogously to commercially available mediators such as AdMob or IronSource. One of the ad providers we used was FAN, although after some time we noticed that the latest available FAN Unity SDK was almost a year behind the native version, and moreover, it didn't have all the functionality offered by the version dedicated to a specific platform. Taking all of this into account - we decided to prepare our own implementation of FAN for Unity - for both Android and iOS platforms.
When we started the implementation we already had some experience in creating simple plugins, but our knowledge was not sufficient for our needs. The first and main problem was creating and managing objects from the native side, not just on static dependencies as before. In the case of Android - it was a simple matter because of the ready-made solution to facilitate the management of Java objects from within Unity (see: AndroidJavaObject). All we had to do was to add some logic responsible for managing the objects on the native side, communicating with Unity, and finally making sure that the Java objects are not being cleaned up by the garbage collector until we release them. And voila - Android was quite quick. It took us much longer to implement a reasonable solution for the iOS platform. There were several reasons:
The biggest problems we encountered were those related to object lifecycle management on the native side. Objective-C, or more specifically its compiler, has a mechanism called ARC - Automatic Reference Counting - whose default behavior conflicted with what we wanted to achieve (more specifically, manual memory release). Another same non-trivial aspect was the issue of mapping objects created on the native side with those available on the Unity/C# level. In the end, we managed to achieve our goal - we finished implementing our own version of the FAN plugin for Unity, and an additional positive side effect was that we developed a new convention for creating native plugins. A more detailed description of the whole implementation and its progress will be included in the following articles.
It's been a while since we finished the first version of Crimson Native and we managed to migrate all our older plugins to the new Crimson Native system. It took some time, but looking at how much easier it is now to maintain our entire native infrastructure - it was worth it. At this point, in addition to the above-mentioned FAN SDK, we have implemented features such as:
Looking into the future I can say that the plans are ambitious - for sure we will implement even more functionalities available from the native level using Crimson Native - at this point their creation is much more enjoyable than a few years ago.
In this article, I wanted to give you an overview of the genesis of Crimson Native and why we want to share this knowledge at all. In future articles, I'd like to focus on technical implementation details for both platforms, i.e. Android and iOS - probably a separate article for each platform, as I'd like to showcase larger pieces of code. However, before getting into the world of native implementations, I will also show the Unity/C# side, which is where most of the implementation is. I will show you also real-world examples of how this has all been used and integrated into our applications. At the very end of the series, we'll get off the strictly technical tone again by going into the post-mortem - the lessons we've learned along the way, ideas for improving the whole process, and other lessons we've learned.