After publishing two games for iOS and experiencing the pain of being locked into that platform, I decided I would try a more cross-platform option the next time around. I also wanted to be able to do essentially all of the development on my iPad, so that ruled out options like Unity, but made web technologies a great choice. I opted for the Phaser Game Framework and successfully published Word Fall for Windows and macOS on itch.io.
The next step was to publish Word Fall to the iOS App Store where I hope to reach a wider audience for this fast-paced word game. I naively thought I would be able to add a WKWebView to my root ViewController, point it at my index.html file and click publish. I was wrong, oh so wrong.
Time For A New Adventure
I assume these all work, but my very brief attempt to use PhoneGap proved it was going to take some figuring out (Cocoon.io claims to be "1 click" and this Phaser-specific tutorial supports that claim). If I was going to spend time figuring something out, I'd rather spend it figuring out how to do it myself (e.g. I'm not on a deadline so I indulged myself).
Aside from ease (and presumably speed) of conversion, one key point for using these services is that they imbed a web server in your app in order to avoid restrictions on accessing local files as the result of cross-origin security restrictions. Indeed, when I discussed my naive approach to converting my game in the Phaser Game Makers Facebook group, I was pointed to the lack of an imbedded web server as contributing to my challenges.
With all of that said, my previous experience developing iOS apps and a few references to this WWDC talk made me question the need for this imbedded web server. There are just a few minutes dedicated to using a WKWebView to serve content from local files as (or as a part of) your app, but the whole talk is structured by contrasting this with serving content available on the web. This convinced me there should be a way to show rich web content using local assets and WKWebView without having to use a third party service.
Wiring It Up Right
There are numerous references out there (including Apple's Official Documentation) which suggest using WKWebView's "loadHTMLString" method to present your content. This works well if everything you're presenting is contained in that String. If you have scripts, images, audio or any other content saved in other files you'll run into cross-origin security challenges. This doesn't make a lot of sense to me since iOS apps are sand boxed and can't write to their Bundle after compile time, but I guess that shows what I know.
If you are presenting content which is too complex to easily include in a single HTML string, you'll need to load your HTML file using WKWebView's "loadFileURL allowingReadAccessTo:" method. Setting the folder to which you are allowing read access to be your project's main bundle (use "Bundle.main.bundleURL") will prevent trouble with cross-origin security measures. Once again I had hoped I could now just drop my entire Phaser project into my main bundle and publish my game. No such luck of course.
Timing Is Everything
What About Images
So, in order to simplify the incorporation of Phaser as much as possible, I decided I would use the basic "Add A Sprite" example from the Phaser.io site and try to recreate it in iOS. To support that, I downloaded the mushroom image used in that example from the GitHub site where it is stored. That is the image you'll see me using for the remainder of this article.
Adding Phaser should be, and is, as easy as copying the phaser.min.js file to the project's bundle and importing it with a <script> tag. The challenge comes from getting an image to render onto the stage. I spent days trying to figure out why I could get the image to render using an <img> tag in my html document, but I couldn't get that same image to display in Phaser's canvas, even if I did all of the work in my html file. Nothing seemed to work. Then I tried moving everything into that image's "onload" function.
Once Again, Timing Is King
Once again, before an image can be added to Phaser's cache and then on to the canvas, it needs to be loaded. Inside the image's "onload" function, the image is added to Phaser's cache and then to the game object as a sprite. Normally, an image would be loaded using Phaser's built-in Loader object, but since I am not controlling the timing of when the image is loaded, I can't do it inside the "preload" function. That means I either need to add the image directly to Phaser's cache myself (as I am doing) or I could instantiate my own instance of a Phaser.Loader, add the image to that loader's queue and start it. That second option is certainly the more "Phaser" way to do it and it might make more sense if I was loading more than one image.
As a side note, I had to specify Phaser's renderer as Phaser.CANVAS vice Phaser.AUTO because in AUTO Phaser defaults to WebGL if it is available (and it is for almost all iPhones out there today), but this doesn't seem to work using WebGL (i.e. the mushroom doesn't show up in the canvas area). Figuring that out may have to be a project for another day.
Animate It! (Remember The Timing)
The final piece I'll talk about today is animating the image. Once again, I thought I'd just add an update method and change the image's position each frame and be done. No. It still doesn't work that way - and for the same reason. The update method will be called by Phaser before the image is loaded, so there will be no sprite to animate and the game crashes because it "can't find variable". A quick solution to this is to check if the sprite exists before changing its position. A better solution is to wait for the "onload" function to be called before instantiating the game object, then loading the image into Phaser using the built-in loader object and then animating via the update function since we now know the sprite will exist before the update function is called.
That's All For Now
The next steps are to repeat this process for the audio assets and validate touch actions are working properly. If I can do that, I should be able to submit Word Fall to the App Store. The beauty here is that this is very nearly a natural Phaser implementation with a little boilerplate code added in Xcode, so if you start your Phaser project with this in mind, preparing it for the App Store should really just be a matter of dropping your project in and wiring it up to the boiler plate.