How I Modernized My Flash Game

This post is going to talk about how I took Frog Fractions, a Flash game, and moved it over to a modern platform. I ended up doing a partially-automated port to Unity via Haxe, and I'll talk about how that went, but there should be a bunch of interest here for anyone trying to modernize a Flash code base. All this will necessarily include structural spoilers for Frog Fractions: Game of the Decade Edition and its DLC, Hop's Iconic Cap, so if you haven't played Frog Fractions while wearing a hat, maybe you should do that first!

After Frog Fractions 2 failed to make me rich, I did contract work and sketches/prototypes for about a year. Then my wife got pregnant, and I decided it was time to get a real job so I could support a family. I tweeted about looking for work just prior to GDC 2018, in hopes of meeting with some folks there. I did end up interviewing at a few places, but the most important result was that I stumbled into funding for my next project.

The project: hide the next Frog Fractions game inside of Frog Fractions 1 and sell it on Steam.

Frog Fractions is around 13k lines of Actionscript 3, built using the Flex compiler. I estimated that including the remaster's new content it would about triple, to ~40k lines of code. (This turned out to be pretty close to right -- it's 28k lines of C# and 3k lines of cutscene/dialog tree scripting, on top of the original game.)

I considered a number of paths forward:

  • Continue building the game in AS3 and ship using Adobe AIR. This was the easiest method, but had several significant disadvantages:
    • Frog Fractions was software-rendered and built for 640x480. At any higher resolution it gets very crunchy-looking. That limitation was acceptable for a browser game in 2012 but probably wouldn't be for a Steam game in 2020.
    • Flex was already throwing memory errors as I pushed past 10k lines on the original code base, unless I did a full rebuild. Would it choke entirely on 40k? (Experts say no, I just needed to tweak some compiler flags.)
    • Flash was in its death throes. Was AIR even going to be around in 2020? (Turns out it's alive and well! Only the browser plugin was really going away.)
    • I wanted to maybe put this on consoles someday. Folks have shipped entire games in Scaleform -- The Banner Saga, Road Not Taken -- but it was unclear whether Scaleform or anything like it would still be around in 2020. (As it turns out, Autodesk stopped selling Scaleform in 2018 and any discussion I've seen of replacements focus on the UI design end of things -- its actual selling point.)
  • Embed a Flash VM in my game and intercept its I/O code. There are open source Flash VMs out there, but none of them seemed approachable/ready enough for this project. Nowadays I hear good things about Ruffle. Maybe that's good?
  • Port the game to C#/Unity. This'd be a ton of work to do manually, and pretty boring work to boot.
  • Ship two executables: boot the game in Flash, and transition over to Unity when the player reaches the new content. I was imagining "somehow" coaxing two executables to draw into the same window. I bet this is doable, but maybe not from Unity, and it's also exactly the sort of thing that's super fragile and breaks horribly in the next Windows update. (Put a gun to my head today and ask me to do this, and my approach would probably be to open a TCP connection between the two processes and transmit video over it. Flash has good video playback support and I bet there's a Unity plugin that does video encoding.)
  • Port the game to Haxe and OpenFL. There's a tool, as3hx, to convert AS3 code to the very similar Haxe, and OpenFL is trying to be an escape pod for the Flash API. I tooled around with OpenFL for about a week, coming to the conclusion that it wasn't mature enough at the time.
I want to stress here that core to all of these approaches is that Frog Fractions is, at its heart, a program that I wrote that happened to compile to a SWF. I can't speak to how feasible any these ideas are if your game is at its heart a Flash animation timeline with scripting added for interactivity.

I asked some folks for advice about how to move forward, and the most helpful was Lars Doucet who among other things mentioned that Haxe has a C# backend and could target Unity. He also suggested that whatever I do, to first try porting a small project using my intended workflow.

(Note to self: add Lars Doucet to the credits. I hadn't realized how influential he was on the project until I was revisiting my correspondence to write this post.)

A week of experiments later, I had Frog Infarctions (a game I'd written for Sos Sosowski's 0-hour game jam) running in the Unity editor. The result wasn't perfect, but it was done enough to see all the way to the finish line, so I moved forward with the main project.

Here's the workflow. Do these things once:

  • Use as3hx to convert the code from AS3 to Haxe.
  • Manually clean up the code as3hx produces as needed.
Then do these things, iterated:
  • Port the Haxe code base over to use Unity's API rather than Flash's.
  • Use Haxe's C# target to compile the Haxe code to C#. If you tell Haxe where to find UnityEngine.dll, it'll pull APIs out of there and you can call them from your hx code. You can do the same with Assembly-Csharp.dll the C# code you've written for the project.
  • Build and test in Unity.
The first week was a real slog, cleaning up expressions that as3hx flagged as ambiguous, fixing compiler errors that it introduced, and stubbing out I/O calls. Haxe's error recovery isn't great, so there were always about 30 errors each build attempt no matter how many I fixed, so I had no idea how close I was to finished until it compiled cleanly. Also, I'm not certain about this, but it seems like as3hx is a search-and-replace type of translator, not a true to-AST-and-back-again transpiler, because it seemingly introduced errors like putting code on the wrong side of braces when my flow control used an idiosyncratic brace style.

After this was done, I had a Haxe code base that successfully compiled to the C# target. I never touched the AS3 code again after this. (Except to export art assets as described below.) I considered throwing away the Haxe too at this point and just working in the C#, but while as3hx's output is pretty one-to-one, preserving brace style, code order and comments, Haxe's C# output is very clearly not meant for human consumption. So from here on, except when I was building entirely new scenes, I was editing the Haxe code and compiling to C# every time I ran it.

At this point this code compiled and ran in the Unity engine, but with no I/O whatsoever. The next step was to reimplement all the I/O code using the Unity API. This took about four months, including asset conversion, largely because Frog Fractions has many different kinds of assets:

  • Vector art drawn in the Flash editor. I rendered these out at 4k quality by making an AIR build of the original game that did nothing but render animation frames and write them out to PNG files. Then I arranged them into sprite sheets using TexturePacker.
  • Vector art using Flash's Shape API. In Flash it takes just a few lines of code to e.g. draw a rounded rectangle and apply Photoshop-style filters, like a bevel or drop shadow. The frog tongue is drawn as a thick line with rounded end-caps. In Unity, this sort of thing is harder. I ended up doing different things in different cases -- e.g. rendering out nine-slices of each style of text container, and drawing the tongue using a solid quad plus a scaled circle for the caps.
  • SVG art sourced from OpenClipArt.org. Since I'd kept track of the artist usernames so I could credit them, it wasn't difficult to go back and pull down higher resolution versions of this art.
  • Same as above with photographs sourced from Flickr. I used Photoshop's "preserve details" upscaling to make up the difference where necessary, and built a pipeline to break down a 4k full-screen image into manageable-sized power-of-two textures.
  • Hand-drawn raster art. In some cases artists had originally provided me with high-res art and I'd scaled it down for the game. In others, I paid the original artists to paint over their old art in 4k. The hardest part of this was convincing them that I didn't want the better art they could make with 8 years of additional experience -- I wanted to stick to the original game as closely as possible!
  • The cavern maze was its own can of weird worms. In the Flash original they were huge sprawling images that the game sliced up into screen-sized chunks and ran through a Marching-Cubes-like algorithm to build a collision mesh. Since I didn't want to upscale those enormous images to 4k resolution, instead I used the same Marching-Cubes-like algorithm but at a higher resolution, to generate a mesh to display as well. Both meshes are included in the final game; the images are gone.
  • A number of fullscreen effects which had to be reimplemented as shaders.
  • A short video clip of a waving flag. Back when I made Unboxing Story in 2015, Unity's video playback API was terrible. By 2018 it wasn't terrible any more. Yay!
  • Audio was easier. I had .wav files of all the sound effects and could use them directly.
  • The original music was 64kbps mono MP3s -- which has saved me probably a few thousand dollars in Amazon S3 hosting costs over the years -- but I already had high-quality stereo renders of all the music from back when I created the OST.
Apart from the fullscreen shaders and movie playback, everything you see in the remaster -- and most of what you see in the new story -- is rendered via calls to Graphics.DrawMesh, rather than being objects in Unity's scene graph. This raises the question, was Unity the right choice for this project?

Back when Craig Timpany and I were choosing an engine for Glittermitten Grove, we went with Unity basically because it had good cross-platform support, because FNA wasn't ready for prime time, and because building your own engine is a great way to never ship a video game. In the end, while I was frustrated by Unity's poor 2D support, it allowed us to ship on Mac and Linux almost trivially, and that was a huge win in my book. (Unity's 2D support has allegedly gotten better since 2015, but I haven't really looked at it. Graphics.DrawMesh still works great. Heck, maybe FNA has a functional asset pipeline now!)

For this project, I chose Unity basically out of inertia -- I didn't want the extra burden of learning a new engine on top of doing the wild R&D project described above -- and because it supported all the platforms I cared about eventually publishing on, including potentially consoles.

Oh, and then I built the additional game! I've discussed that process in another post, but the part that pertains to this post is that I built the transition scene in Haxe and then the rest of the new game in C#. I also made extensive changes to the main menu inside of the Haxe code base. So it seems totally feasible to, after porting to Haxe, continue shipping updates to a code base by developing in Haxe.

I did have to go back and do some optimizations. The original AS3 code was written deliberately ignoring efficiency, because I was trying to escape a "pristine code" mindset. E.g., I can't believe that I got away with allocating a Vector2 to the heap on every arithmetic operation! The result worked fine on 2012 PCs, but for the worst-performing stuff, being double-transpiled took a significant toll and I had to rewrite some code in native C# to get things working well on modern PCs.

It's also worth noting that if I wasn't planning on significantly expanding the scope of the original game, if I were literally just trying to save the game from dying alongside Flash, then OpenFL, AIR, or Ruffle seem like they'd be the smarter option. I didn't spoil the hat DLC in this post for my health -- it provides vital context for this particular decision.

Shipping this game felt like a goodbye. I loved working in Flash. At the time, it was the best way to get your games in front of people with zero friction, in a way that seemed like they'd live forever -- SWFs from the 90s still work flawlessly in the latest Flash player, decades later. Seeing the world try to transition over to HTML5 when it clearly wasn't good enough yet was agonizing to watch. (And frankly, it's getting worse rather than better. My bet is that we're never again going to see the browser as a serviceable game platform as long as the owners of the two most popular browsers also own phone app stores.) I made this remaster partly to sell a new game, but also out of a genuine desire to preserve my corner of game history. Thanks for revisiting it with me!