Kalin wrote:
Yay!
Was the translation hard? Tedious? Do you have any advice for others trying to convert Flash games to JS?
It was fairly straightforward. For background, DROD was written purely in AS3, I did not use any frameworks, though it used the same rendering principles as Flixel (so instead of using Display Tree, all the rendering was done in bitmaps).
I converted it to TypeScript, using Pixi.js for rendering (which is Display Tree based and by default is GPU-accelerated).
So the first trouble was to figure out how to utilize Pixi. I had essentially two options.
A) Rewrite all the rendering code to use Display Tree (I investigated pixi-tilemap for faster rendering of a huge tilemap).
B) Figure out how to do bitmap rendering with Pixi.
To explain what bitmap rendering and display tree are:
Display Tree rendering is that every bit of graphic you have is a special object (Sprite, Container) that can be assembled into a tree structure. Then there is some built-in code
(in case of AS3) or a library
(in case of Pixi) that takes that tree and renders it
(in CPU in AS3 and in GPU in Pixi). If you want to draw another something, you need to add a new sprite somewhere in the tree. If you want to no longer draw something, you either need to remove it or mark it visible=false. When used smart it can be great, as you can cache leaves that don't change often. It also does all the matrix translations for you (ie, calculating the position, scale, rotation of a sprite inside a container that itself is transformed, inside a container that itself is transformed, etc).
By Bitmap Rendering I mean a case where you have a bitmap data in memory, and draw into it, and then you somehow display that bitmap onto screen. Flash DROD usually worked with 3 bitmaps at a time: under layer
(HUD, all the static tiles, which only changed when something in the room changed), active layer
(all the active elements and bunch of effects, it had some complicated rules for full and partial redrawing. It would redraw completely during the movement animation, and then it would only redraw the specific monster tile that animated) and effects layer (for things like mimic placement or invisibility range).
Phew, that's a lot of words. After some investigation I figured out it's trivially simple to do Bitmap Rendering in Pixi. All you need to do is create a new Canvas, draw in it and then do
new PIXI.Texture(new PIXI.BaseTexture(canvas)), and when you later need to reupload new data to the texture you just call
texture.baseTexture.update() and it's synchronously updated.
After deciding on the details, it was then time to convert the code. Thank goodness AS3 was based on ECMA Script, so the syntax is almost the same as TS (since AS3 also has type annotations). There are a couple of important differences though, that were annoying:
- In AS3 you don't need to use
this.<member> to access member fields/methods of a class, in TS you have to. Fixing those probably took the most time of all.
- Same case with calling static members.
- In AS3 every variable that holds a reference is also by default nullable (and there is no way around it), while in TS I prefer to work with strict null checks, so nullability has to be explicitly stated.
- In AS3 there are static constructors, in TS there are not. Initially I fixed this by adding a new initialize() method and calling it right below the class export, but when working on KDDL2-4 it turned out to be a problem because I was experiencing some race condition in this and similar code, so all the initialization calls were moved to a single class that deals with bootstrapping. You can see the code
here if you're curious.
- Checking if a value is an instance of a class uses
is in AS3 but
instanceof in TS (maybe
instanceof was also available in AS3 but
is was shorter so I used that)
- In TS you cast by using
as keyword, in AS3 you (I?) cast by using
ClassName(<variable>)
- AS3 has
internal access modifier, as well as custom ones, TS has neither
- AS3 uses
var before member name that is a field, TS does not
Yea, I think those were the main sticking points.
So let me now explain how I got around
transcompiling all the code manually:
(note, when I say Grep I really mean "
Use IntelliJ's Search and Replace in Files using both Match Case and Words options"
)
1. Grep the whole codebase for
retrocamel_int (a custom namespace I used) and
internal and replaced them with
public
2. Grep for
public var,
public static var,
private var and
private static var and replace it with versions without
var
3. Grep for
Number and replace it with
number. Ditto for
String and
Boolean, as TS uses different casing.
4. Grep for
uint and
int and replace them with
number, as TS does not have those types
5. THen I'd chose a file that has no references to other files, change its extension to 'ts' and just work on it.
6. Remove all imports and the package (since in AS3 every class is wrapped in a package)
7. Add
export before
class
8. I'd go through every field, search in the file and prefix it with
this. to save me some manual labor.
9. And then just kept mashing F2 which is "
Go to next error in current file"
until there were none.
10. If I encountered something I couldn't fix at the moment I'd comment it out and mark with @todo to come back to it later
At the begnning I mostly dealt with non-gfx code so it was mostly tedious code hacking. Then when I would encounter rendering code it would all be commented out. Then at some point I
had to deal with the rendering, did it mostly without issues.
Once I had all the game logic and rendering code done, I did the minimal amount of work required to test it - this involved doing the preloader screen, initialization screen and title screen.
Then some more work, adding audio, some more work, and the first alpha version was released.
That's the whole story!
____________________________
My website