Back to Blog

Flash to Phaser: The Build Process.

Converting a classic Flash game to modern HTML5 is part port, part archaeology. The .swf files are sealed containers, the game logic is buried in ActionScript you have to reverse-engineer by eye, and the art needs several passes before a modern renderer can use it.

When I decided to port classic Neopets Flash titles to Phaser 3 for Grundo's Café, I found out quickly that "port" was the wrong word. The Flash files are sealed containers. The game engines are buried in ActionScript I had to reverse-engineer by eye. The art needs several passes before a modern renderer can consume it. Here is how the build process actually worked, start to finish.

Adobe Flash logo pointing to the Phaser logo, representing a Flash to Phaser migration.

Step One: Read the Docs. Really Read Them.

Phaser 3 has some of the most complete documentation of any open-source game framework I've worked with. Before writing a single line of game code, I read the docs properly. Three things that matter most:

  • The Scene lifecycle (preload, create, update) controls when assets are ready and when your game code can safely run.
  • The Loader system has specific rules about when assets are available. Violating them produces silent failures.
  • Phaser lets you pick Arcade Physics or Matter Physics. The choice shapes collision logic and feel from the start.

Phaser gives you a lot of power, but the texture atlas system, the animation manager, and the input pipeline all have nuances that bite if you guess. Hasee Bounce's see-saw physics required a close read of how Arcade physics handles rotation and velocity. Skimming would have cost me days.

Step Two: Crack Open the .swf with FFDEC.

JPEXS Free Flash Decompiler (FFDEC) is the tool that made this whole project possible. A .swf file is a compiled binary. FFDEC lets you open it like a cabinet: every sprite, every sound, every frame, every class, every script.

The extraction workflow for each game:

  1. Open the .swf in FFDEC and navigate the symbol tree.
  2. Export all image assets as individual PNGs: characters, backgrounds, UI elements, and items.
  3. Export audio as .mp3 files.
  4. Read the ActionScript 2 or 3 source to understand game logic: scoring rules, spawn rates, physics constants, animation frame ordering.

The ActionScript is where you find the real design decisions. Spawn rate for the doughnut fruits in Hasee Bounce. The point values per item. The timing windows for the bonus letter system. None of that is documented anywhere because it was never meant to be read. FFDEC let me reverse-engineer the original designer's intent directly from the compiled output.

The trickiest part was animated sprites. Flash stores animations as individual frames in a timeline. FFDEC exports those frames as a numbered sequence of PNGs. I had to figure out frame counts, timing, and grouping by reading the symbol metadata, then build the sprite atlas manually for Phaser to consume.

Step Three: Verify Every Pixel with GIMP.

Once the assets were out, I loaded them into GIMP. This was a verification step. The questions I needed to answer:

  • Are the transparent regions correct, or is the export leaving garbage pixels at the edges?
  • Do the hitbox regions match what's visually on screen? A character sprite with a 10px invisible border will behave wrong in physics if you don't account for it.
  • Are the frame sequences in the right order? Phaser's atlas system needs consistency or animations will glitch.
  • Are the asset dimensions what the game code expects? If a background image is 2px too wide or a sprite is slightly off-axis, you will chase that bug for a long time.

GIMP's pixel canvas view was the ground truth. When something looked off in the game, the answer was almost always traceable back to something I'd overlooked here:

  • A sprite aligning wrong
  • An animation popping between frames
  • A background seaming

Step Four: Build the Scaling Code.

Flash games ran in a fixed browser window, typically 550×400 pixels. A 4K monitor will letterbox that to a tiny stamp. A phone at 375px wide will cut it off entirely. Phaser gives you a canvas, but making that canvas look right across every screen size is your problem.

My solution was a standalone scaling utility wired into every game page. The logic:

  • Read the native canvas dimensions from HTML attributes (set by Phaser at init).
  • Calculate the ratio against window.innerWidth and window.innerHeight.
  • Take the smaller scale factor so the game always fits entirely in the viewport.
  • Apply via setProperty with the 'important' flag to win against Phaser's own inline styles.
The game is what players see. The scaling code is what makes it playable at all.

Phaser sets inline styles on the canvas at initialization. Standard CSS rules will lose to those. The 'important' flag is the only reliable way to guarantee the scale values stick.

Fullscreen gets its own toggle: the game container switches to position: fixed with inset: 0 and margin: auto. An overlay div catches clicks outside so players can exit without an extra button. On resize and orientation change, the scaler recalculates. About 120 lines total, handles everything from a 27-inch monitor to a 4-year-old phone in landscape.

Step Five: Connect It to the Internet.

A standalone game running in a browser is fine. A game that reports your score, checks your account, grants trophies, and lets you compete on a leaderboard is what makes players come back. Three integration points, each an async operation inside a game loop that can't pause:

  • Score submission on game over
  • Session validation at launch (is this user logged in? are they allowed to play?)
  • Live ops hooks for special events (double points, seasonal item spawns)

The approach I settled on:

  • Score submission is fire-and-forget, with a localStorage fallback that retries on next page load if the API call fails.
  • Session validation happens before the game boots. The page checks auth before loading Phaser, so the game never starts in an invalid state.
  • Live ops flags come in as JavaScript globals set server-side by the page template. The game reads them at preload time with no additional network calls.

What All of This Taught Me.

Every game in my library went through this same pipeline. The lessons stack up fast.

Test with real players. I knew the games because I built them. My friends found bugs in the first five minutes that I had never seen, because they played differently. Get people in front of it early.

Design within the constraints. Players wanted these games fast. Three weeks on a particle effect for a game shipping in a weekend is the wrong trade. The discipline of knowing what to skip is a skill. You learn it by shipping things that are 80% done and watching what players actually notice.

Mobile takes real thought. Flash games were built for desktop. Most of my players are on mobile. Every game needed touch controls rethought from scratch. Hasee Bounce has a tap-anywhere control scheme. Ultimate Bullseye has drag-to-aim. Neither existed in the original.

Geometry will humble you. Getting arrow trajectories right in Ultimate Bullseye required brushing up on arctangents and projectile math. I spent a full weekend watching arrows launch in wrong directions before I found the angle calculation was off by a sign. The math is always hiding somewhere underneath the game feel.

If you want to see any of these running in the wild, they're playable right here in the Arcade.