From AS3 to TypeScript

Last week, I decided to port over one of my favorite AS3 projects, F*Rogue, to TypeScript. I decided to call this new project RogueTS (project source code), and it was my first attempt at using TypeScript for a real JavaScript project. After a few hours of hacking around, I decided to port over the entire AS3 codebase in two nights. I figured I would put together some notes on how I did it and talk about the similarities and differences between ActionScript and TypeScript.

Code Organization

Perhaps the biggest advantage of languages like C#, Java, and AS3 is their ability to cleanly organize your code into packages, classes, and interfaces. Trying to do this in JavaScript is kind of a mess. I love the module design pattern, but I use it more out of necessity than anything else. TypeScript builds upon this pattern by allowing me to organize my code into clean-looking models and classes that mirrored what I had originally done in AS3.

In F*Rogue, I had set up a package structure com.gamecook.frogue.*, but in RogueTS I decided to go with something a little simpler rogue.*. One of the biggest reasons for doing this is that the compiler doesn’t allow you to “import” a package and its classes, so whenever I want to use a custom class or a type I need to write it out in full like rogue.geom.Point. Of course, if you are in the same module level, you don’t need to do the full path, but you’ll see I ended up with lots of geom.Point references everywhere, which I would have easily been able to resolve in other languages by just importing rogue.geom.*. Either way, it wasn’t a deal breaker but something to think of as you are setting up your modules. Here is an example of how you set one up in TypeScript:

module rogue.geom {
    ...
}

Likewise, you can add classes into your modules. Classes are similar to what you would expect in other languages. Here is an example:

class Rectangle {
    x: number;
    y: number;
    width: number;
    height: number;
 
    constructor (x: number, y: number, width: number, height: number) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
}

Notice how this class has a constructor defined? This is the default method that gets called on instantiation. Another thing to keep in mind is that, if you want this class to be accessible to other modules, you will need to use the export keyword. Here is an example:

module rogue.geom {
 
    export class Rectangle {
        ...
    }
}

Now I can create a new Rectangle class by simply writing the following:

new rogue.geom.Rectangle(0, 0, 20, 20);

Outside of that, everything else should be straightforward to pick up. Class properties don’t use var, so simply state the property name and type. There is no need to preface a method name with the keyword function. Just declare it and add your code. Lastly, inside of your classes you will have to use the this keyword to access properties and methods as needed just like you would in JavaScript. In the end, the TypeScript compiler will automatically generate a JavaScript class for you. My Rectangle class looks something like this in JavaScript:

var rogue;
(function (rogue) {
    (function (geom) {
var Rectangle = (function () {
            function Rectangle(x, y, width, height) {
            }
            return Rectangle;
        })();
        geom.Rectangle = Rectangle;        
    })(rogue.geom || (rogue.geom = {}));
    var geom = rogue.geom;
 
})(rogue || (rogue = {}));

As you can see, it’s readable, so if you ever drop TypeScript, you can modify this JavaScript class as needed.

Once I wrapped my head around modules and classes, it was easy to port over my AS3 classes from F*Rogue one class at a time. I would simply copy the class’s code over, replace all of the stuff I didn’t need like private/public/protected var function keywords and other miscellaneous things, and end up with a TypeScript-ready class.

Interfaces made the transition over as well. Here is what a simple interface looks like in TypeScript:

module rogue.renderer {
    export interface IMapRenderer {
        renderMap(selection: map.ISelectTiles): void;
        renderTile(j: number, i: number, currentTile: string, tileId: number): void;
        clearMap(): void;
        renderPlayer(j: number, i: number, tileType: string): void;
    }
}

Now with all of my classes and interfaces ported over, the only thing I needed to do was clean up the variable types.

Typing

Typing took a moment to wrap my head around, not because it was difficult to figure out, but because it took some time trying to match up the TypeScript equivalent of the AS3 types. So, here were my general rules while doing the port:

  • String becomes string
  • Boolean becomes bool
  • Int, uint, etc. all become number
  • Array becomes any[], which I’ll talk about in more detail in a minute

Once you make these changes, you should be good to go. Also, don’t forget you will need to use full module paths to classes, such as geom.Point for class types, which gets wordy fast. Notice I don’t really use rogue.geom.Point since my geom module is inside of the rogue module.

So, the only place I cut corners was with arrays. In TypeScript, the any type any is similar to * in AS3, which tells the compiler to basically ignore typing. I started going back and adding in more specific array types, such as string[], number[], and geom.Point[], which is the equivalent of vectors in AS3. For the most part, it’s not needed unless you really want to be as strict as possible with your TypeScript code, which I hope to enforce later on as I continue to clean up the framework.

TypeScript-Specific Additions

The last thing I wanted to highlight was event listener callbacks. TypeScript offers a shortcut to write them out that took some getting used to. Here is some simple code showing off how I connected up the keyboard events/bindings in the game:

window.addEventListener("keydown", event => this.keydown(event) , false);

Once you understand how the => denotes where the returned event will be sent to, it’s easy to create new event listeners with their corresponding callbacks. Even the main onLoad() function for your TypeScript project uses this:

window.onload = () => {
    var canvas = <htmlcanvaselement>document.getElementById('display');
    var rogueTS = new rogue.Game(canvas);
};
</htmlcanvaselement>

As you can see in this case, the onLoad callback is being sent to an anonymous function containing the necessary setup to run RogueTS.

Conclusion

I love digging into new languages to figure things out, and working with TypeScript was a real treat! I really like how seamless it is to work with TypeScript classes in VisualStudio and how it just auto-generates the JS files I need. Finally having structure, type checking, and better error handling really made me quickly fall in love with TypeScript. If you come from an AS3 or even an AS1/2 background you are going to feel right at home. I would say the same for C# and Java developers with the exception that TypeScript doesn’t contain all of the code libraries that make those languages so easy and fast to develop in. That being said, I see a real potential to port over older AS3 code directly to TypeScript and hope that more developers take advantage of the language for their JavaScript projects. If you are looking for a more detailed explanation of how TypeScript works, I suggest you check out Jesse Warden’s excellent post on it. I also wanted to thank Richard Davey for pointing me in the right direction and answering my numerous questions while I ramped up.

Subscribe To My Mailing List

Want to learn how to make a game? Not sure where to start? Even if you are a seasoned game maker there is still a lot you can learn from my mailing list. I'll be covering tips and tricks for how to build, release and market games each month.

Simply sign up for my mailing list and also get access to a 50% off discount code for my eBooks and other content. I promise to not spam your inbox!

Join Now