In the article
GameDev and the candelabrum we described the process of porting the game Marriage for Android. The game came out quite successful and almost immediately after the release we began to plan a cross-platform version with a further eye on the online. Unity3d was chosen as the platform. The development process took about six months.
Interesting? Come under the cat!
The task
So, the platform is chosen, the performers, too, everything seems simple and clear, no visible problems can be seen on the horizon.
Development can be divided into 2 parts - backend (hereinafter - logic) and frontend (GUI with platform features, hereinafter - client). Unity3d was chosen as the platform (this is how it will be later in the text, so that they don’t talk about rebranding and the lack of “3d” units themselves). The logic has already been written and perfectly functioned. The only problem was that the language in which the logic was written was java, and only C # or JS was suitable for the chosen platform. Accordingly, the main task was to port the existing code into a valid C # code, while maintaining one-to-one functionality.
The client part has already been implemented, but like the logic - it was rigidly tied to the implementation of the GUI in Android. Accordingly, the client had to write from scratch, according to a technically competent TK in the form of the phrase “so that it was the same as in the Android version” :)
Porting logic
The task was to do it as quickly as possible with minimal rewriting. But I still had to face a number of problems.
')
In C #, there is no extensible enum type, and in our Preference this type was used very widely: suits and card ranks, the cards themselves, conventions, rates and whists, game states. In some cases, we managed to get along with the standard enum in C #. But in most cases, enums were more than just enumerated types: various data were attached to them and there were auxiliary methods. All this did not want to lose in the ported code. As a result, a minimal Enum class for C # was made. All of the original enums became separate classes with the same data and functionality preserved, the usual enums from C # were attached to them, which specified a sequence (the Enum.ordinal () method) and the name (Enum.name ()) of the enumerated type, as well as used in switch..case.
The heavy legacy of Delphi also made itself felt when porting to C #. Most of the logic was written in internal procedures with common variables. In Java, this was solved relatively simply by inner classes. In C #, internal classes, although they exist, are similar to static classes in Java, that is, they cannot use the fields and methods of the container class. I had to add additional links.
Despite the richer choice of arrays in C #, there was no similar Java functionality. The problem is that an array of the form int [] [] cannot be created by a single statement like new int [10] [20]. It was not possible to switch to rectangular arrays everywhere, in some cases sub-arrays were used separately. I had to write helper methods that completely create a multidimensional array.
There were other unpleasant inconveniences:
- switch..case, requiring a break even in the most recent condition, including default;
- bulk boolean to bool;
- final clearance, or readonly / sealed replacement;
- no unsigned right shift (>>>) due to unsigned types;
- the goto statement, which in Java controls transitions in nested loops, and in C # retains its ancient purpose.
But all this was revealed by the compiler. But it was necessary to cut the signed Java type byte and change it to the C # sbyte analogue without any help.
Client implementation
All graphic content was completely (with small fixes) borrowed from the Android version. NGUI was chosen as the GUI framework - there were no options in principle: working with atlases, drawing optimization with minimization of DrawCalls, imputed message system using the unity3d message system. No wonder the main developer of this framework is now sawing a standard GUI for unity3d.
All content was sorted into logical groups and compiled into atlases based on the limit of 2048x2048 points on the atlas:
Atlas of city icons (there is still space for at least 6 cities).
The atlas of the main controls used on all “screens”.
Such an organization of graphic content allowed to reduce the number of DrawCalls in peak to 15 (for example, when opening a scrollable betting panel and opening a menu with visual settings).
One of the problems was the need to “do everything the same as on Android,” namely, adjusting the controls to the screen resolution. Usually all controls are “glued” to the edges of the screen and move apart / move together with them, in this case this option was unacceptable. As a result, a solution was written, working approximately as follows:
- set the reference resolution (1280x800 was chosen);
- place the controls how they should look right;
- we hang on controls helpers for proportional scaling / repositioning;
- profit
When running at any resolution, the screen height is always considered equal to 800 virtual points, and the width varies depending on the aspect. Helpers at the start consider the ratio between the old aspect and the new one; according to the results, repositioning and zooming are performed.
Reference resolution (1280x800).
A “wider” aspect (1136x640) - bands are visible at the edges.
The same resolution (1136x640), the result of working out the helpers - it is clear that the controls are located proportionally wider, and the background is scaled.
As a result, everything was done on the reference resolution + tested on the narrowest aspect (4: 3).
It was also necessary to implement non-standard behavior of controls, which, thanks to the unified NGUI message system, turned out to be relatively simple. So, LongPress buttons, a crawl line with clipping, and a Grid with animated repositioning of elements were implemented. We also had to patch a couple of places of standard components for adaptation to the project (UIPopup had the opportunity to show the pop-up part in the specified parent, but not in the current one - problems with clipping pop-up content with UIPanel components were resolved; small fixes).
All the client's work was divided into “screens” with a garter of local logic of behavior on a specific “screen”: the main menu screen, the “classroom” settings screen, the city selection screen in the “tournament”, the game screen itself, etc. This made it possible to reduce the load on the graphic part in terms of memory consumption at any one time — when loading the next “screen”, all loaded objects were destroyed and the engine could clean up the freed memory or use it to load new resources. Between the scenes, only a few non-killed objects “roll”, providing access to user data in terms of settings, recently selected items, etc.
By the start of work on the client side, the API of interaction with logic was not approved, so it was decided to divide the entire game mechanics into isolated modules (managing a set of each user's cards, managing the “flying” of cards, managing stakes, managing text messages for players and the information panel , management of game settings, local state, etc.) with access to them from anywhere in the game process through singletones. At the same time, something was written about the type of tests right inside the client, pulling all the available methods to verify the correctness of the work of this whole enterprise. All animations and other things work in the same stream, through soroutine, there is nothing special here.
External systems
This includes the implementation of IAP functionality, banner advertising, Tapjoy-client for getting in-game currency for free for certain actions of the advertising platform and Google Analytics.
Soom.la was originally chosen as an IAP framework: a cross-platform API, support for two main mobile platforms, seamless integration without crutches around the start-up activation patch in Android, and this is all for free. It is enough to compare the horrors of integration of the prime31 division, when they forcibly ground the manifestos exclusively for their loved ones + had different APIs on different platforms to understand that this framework is simply brilliant. In the future (if there is a need for a winphone version and soomla does not receive full support), the transition to
Unibill is possible .
As a banner provider, admob spoke, and the client for it was a product from
http://www.neatplug.com . The reason is the same - easy integration, unified API, support for two main mobile platforms, the cost of the universal version for 2 platforms is $ 56 (versus $ 50 for each platform for prime31 and the jambs described above for IAP). One not quite adequate behavior of the framework was found with absolute positioning of banners at different DPIs, so as a result it was decided to restrict ourselves to the standard binding to the screen boundaries.
As a client, Tapjoy was a standard framework from the site itself. The code was slightly finished for correct work in the editor, general integration did not cause any problems, it was enough 4-5 starts of the build in the emulator to check the event triggering by logs.
GoogleAnalytics self-made, adapted from the version for Web sites. The result was a cross-platform solution via WWW, which does not require native versions of libraries for different platforms for $ 50.
Conjugation of logic and client
After 4 months, the first version of the logic appeared and it could be started to dock with the client. The logic itself was developed as an independent application, spinning in an infinite loop, which allows queuing commands and pulling subscriber methods in case of outgoing commands. As a result, the logic in unity3d was wrapped in code that controls its start in a separate thread and provides for the transit of commands in both directions (do not forget that data types, method signatures, etc. are completely incompatible, since both systems were developed in isolation from each other and two different performers). Oddly enough, everything grew together surprisingly simple and the management teams lay down well on the client modules.
Issues related to the target execution platform
With the work of logic in a separate thread, there was one not very good story. As you know, iOS does not allow the use of any non-native code, therefore, when exporting a project, unity3d runs managed assemblies through AOT, generating output libraries that are already linked with system + any glue-wrappers are added to obj-c to dock all of this of good. Moreover, a separate build is made for the emulator, in which there is a JIT and everything will work as it works in the editor - this is important, because errors that occur on real hardware can never occur either in the editor or in the emulator.
Those. in fact, when running on a real hardware - no debugging of the project, just by touch on the logs and the jaw.
So back to the problem with the flow. It seems everything worked, and then suddenly it began to fall, dropping the application completely. According to xcode logs, it turned out that the crash occurs deep in Mono runtime after calling Thread.Sleep () or Thread.Current.IsAlive if the thread was interrupted (but not every time). Those. got crash on calling standard methods with a sort of standard attempt to interrupt the stream. The funny thing is that on the JIT version of the build (for example, Android, standalone, webplayer), everything worked out as it should. At random, we identified the above methods, leading to crash. As a result, it was decided to abandon the forced interruption of the flow, and instead signal the logic that it would be time to end, and then wait for the flow to close. The crashes disappeared and never returned.
The second problem was the incorrect calculation of the behavior of AI when using the type of double exactly after processing the AOT and when running on real hardware. It was necessary to find out through the collection of a wild number of logs containing the calculations used in the analysis of the game strategy. The size of the log of each test game exceeded 40MB of plain-text. It was all necessary to somehow collect inside the application running on real hardware as well as extract it outside. At first there was an attempt to collect logs via GoogleDocs, but the speed and the allowed volume were depressing, so a server was written in node.js quickly, receiving data via post-requests and pasting them into a local text file. From the application side, everything was implemented as a caching queue of log entries + discarding overflow via a standard WWW to a self-made http-server. It turned out quickly and relatively simple.
Testing
The entire testing process took place using the
https://testflightapp.com platform, both for intermediate Android builds and for all stages of development for iOS. The UIID-numbers of testers' devices were collected (automatically, via the client's GUI after registration), the provision-profile was updated, and builds were collected with installation permissions for the listed remote devices. After assembling the ipa-build, it was simply poured through the admin panel on the test-light and access was given with email alerts for all testers. It was enough for them to enter the client and press the install button for the build, everything happened automatically. There was no additional integration with the test-flight services, since this would entail yet another integration with the native-libraries, and there would be practically no profit.
Want to do well - do it yourself!
We did not order reviews to promote the Android version of the game, but for iOS, we decided to order one review on a popular resource. It’s too early to talk about its financial side, but I want to share my impressions about the quality of the review. After payment a letter was received confirming the order and wishes for the review. The review itself for approval came on time, but it would be better not to open it. Not only that the one who wrote the review did not even bother to press as many as three (!) Buttons on the main screen and did not even try to understand how the game works (I agree, this is not a kerchief solitaire), but he didn’t know how to write at all. In addition to technical blunders, the article was full of phrases like "you need to score as many points as possible, which are calculated from the points." The copy-paste of Wikipedia and our HELP under the sauce of the C grade from the eighth grade obviously did not suit us and we wrote a review ourselves. It is worth noting that after the criticism we received the second version of the review, but it was also very far from what can be shown to users.
Results
Unity3d is currently the best platform for creating cross-platform games. We didn’t have any particular problems with creating a GUI, although the integration of various plug-ins took much longer than originally thought. Also, do not update the plugins or Unity3d itself, if everything works and you do not have a couple of extra days for a pleasant stay. And do not forget that there will not be guaranteed cross-platform, we must be ready to debug logic on all platforms.
For Android, we have so far decided not to use the cross-platform version, since devices based on 2.2 and 2.3 (which use 4% and 10% of users, respectively) simply do not pull it, falling out on OutOfMemory. We hope that soon these devices will become a thing of the past, thus simplifying the development of the online version.