The main benefit to using TypeScript is it provides much more information for tools while writing code. For example Construct does provide an autocomplete for JavaScript code, but since JavaScript is dynamically typed, it doesn't know what properties are really available and so it tends to list everything in the autocomplete box. On the other hand TypeScript provides enough information for tools like VS Code to show an exact autocomplete list while writing code, which is much more helpful. This also allows a range of other tools, such as navigating source code, refactoring, and identifying errors.
As TypeScript is an extension of JavaScript, it is essentially the full JavaScript language, but with additional features on top. This guide assumes some familiarity with JavaScript. It also does not attempt to teach TypeScript in full, as it's already well documented. This guide covers the details about using TypeScript that are specific to Construct. Here are some additional links to help you get started with JavaScript and TypeScript:
Installation
To use TypeScript in Construct, you will need a TypeScript-compatible code editor. This guide uses Visual Studio Code, or VS Code for short. It's a free download and a professional coding tool used widely in the industry. This is then used as an external code editor for the code in your Construct project.
Install VS Code using the link above if you don't already have it. You'll also need to install TypeScript support, which you can do by following these steps:
- Install Node.js if you don't already have it
- In a terminal, run the command
npm install -g typescript
You can check the TypeScript compiler, or tsc
for short, is installed by running tsc --version
in the terminal. It should print the version installed.
For more details see TypeScript in Visual Studio Code.
Setting up a Construct project
To use TypeScript with a Construct project, you must first save the project as a folder. This means it's made up of individual files in a folder, rather than everything being contained within a single .c3p file. To do this, choose Menu►Project►Save as►Save as project folder... and choose a folder to save the project to. If you're starting from scratch, you can also create a new project and then save it to a folder.
For more details on folder-based projects, see the manual section on Saving projects.
Next, right-click on the Scripts folder in the Project Bar, and select TypeScript►Set up TypeScript. This only needs to be done once per project and performs the initial setup for using TypeScript in your project. It does three things:
- It creates a TypeScript (.ts) file copy of every JavaScript (.js) file in your project. The TypeScript files aren't shown in the Project Bar, but they'll appear in your project folder. If the project doesn't have any JavaScript files, Construct creates the initial two default script files main.js and importsForEvents.js, and then creates TypeScript copies of those.
- A file named tsconfig.json is created in the project folder. This is a configuration file for TypeScript. This also does not appear in the Project Bar, as it's just for the external code editor.
- A subfolder named ts-defs is created with lots of .d.ts files. These are TypeScript definition files, which tells TypeScript about all of Construct's built-in APIs, as well as some types that are specific to your project. This also doesn't appear in the Project Bar.
If you run Set up TypeScript again, it won't overwrite any existing tsconfig.json or .ts files. This means it's safe to run again later on, for example if you add more .js files and want a quick way to make .ts copies of them.
Adding types
Now your project is ready for working with TypeScript! Start up VS Code, choose Open folder... and select your project folder (or the scripts subfolder if you prefer to see only your code). Open one of the TypeScript (.ts) files and you can start writing TypeScript.
The first thing that will happen is some errors will appear. While TypeScript is an extension of JavaScript, by default it requires types to be specified, since that is largely the point of using TypeScript. You will have to add types to fix the errors. Once you have fixed all the errors in all TypeScript files, it means your code is fully annotated with type information. If you're starting from scratch there's only a couple of updates, but if you already have a large amount of JavaScript code, there could be a significant amount of work to do to add types.
For example the default main.js code file includes this function:
async function OnBeforeProjectStart(runtime)
{
// ...
}
TypeScript will identify the runtime
parameter as an error, because it does not have a required type annotation. Construct's runtime interface type is IRuntime. So the parameter must be marked as having the type IRuntime
, as shown below.
async function OnBeforeProjectStart(runtime: IRuntime)
{
// ...
}
In many cases TypeScript can automatically infer the types of things so they don't necessarily always need to be updated. However there are several places like function parameters that will need type annotations to be added. Familiarity with Construct's built-in class names is useful as they are sometimes needed as types, such as IRuntime
above - all the class names are included in the reference in the scripting section of the manual.
Other TypeScript changes
This guide does not contain an exhaustive list of all changes you'll need to make, but here are some common ones that you're likely to run in to, and some advice specific to Construct.
Instance types
When using TypeScript, Construct generates a special class representing an instance for every object type and family in the project. This includes type definitions for things like the instance variables, behaviors and effects specific to that object. These classes are all in the InstanceType
namespace with the name of the object. For example InstanceType.Player
is the type for an instance of the Player object type.
Optional types
Many of Construct methods, such as objectType.getFirstInstance()
, can return null
(in this case, if no instances exist at all). This means the method's return type can optionally be null
. TypeScript will show an error if you try to use something that could be null
. An example of this is shown below.
const playerInst = runtime.objects.Player.getFirstInstance();
playerInst.x += 10; // Error: 'playerInst' is possibly 'null'
If you know for sure that there is always an instance of the object and so it will never return null
, you can add an exclamation mark !
after the expression to tell TypeScript you know it won't be null
.
// Note '!' added to line below
const playerInst = runtime.objects.Player.getFirstInstance()!;
playerInst.x += 10; // OK
This is known as the non-null assertion operator.
Subclassing
If your project uses subclassing to customize the instance class for Construct objects, then you'll find some Construct APIs still return instances of the default type. For example instances of a Monster sprite object will be typed as the default InstanceType.Monster instead of a custom MonsterInstance class, e.g.:
const inst = runtime.objects.Monster.getFirstInstance()!;
// 'inst' is of type InstanceType.Monster - so it won't have any of
// the properties or methods of the custom MonsterInstance class
To solve this, the methods available on IObjectType
are in fact generic, so you can make them return the correct type. This means adding the <Type>
generic syntax like so:
const inst = runtime.objects.Monster.getFirstInstance<MonsterInstance>()!;
// 'inst' is now of type MonsterInstance and so can use the properties
// and methods of the custom class
Object literals
Sometimes it's useful to write an object literal, which the Ghost Shooter Code example does for sharing global variables from a module, similar to this:
const Globals = {
score: 0,
playerInstance: null
};
In this case, TypeScript will correctly infer the type of score
as number, but it will infer the type of playerInstance
as null. The type null means the variable can only ever have the value null
and assigning anything else to it will be an error! Due to the syntax of object literals, which already use a colon, it's not always obvious at first how to add a specific type to this property. The solution is to use the generic-style syntax <Type>
like so:
const Globals = {
score: 0,
playerInstance: <InstanceType.Player | null> null
};
Class properties
Normally in JavaScript, class properties are added in the constructor.
class MyClass {
constructor()
{
this.prop1 = "hello";
this.prop2 = 123;
}
}
TypeScript does not infer the class properties from the constructor, so it will show an error for prop1
and prop2
, e.g. Property 'prop1' does not exist on type 'MyClass'. Instead you must declare the class properties, and their types, at the class-level like so:
class MyClass {
prop1: string;
prop2: number;
constructor()
{
this.prop1 = "hello";
this.prop2 = 123;
}
}
Note JavaScript does allow class property definitions in a similar way, with the feature known as class fields. Using this feature in JavaScript should make it easier to switch to TypeScript, as you can then just add type annotations to the existing class fields.
Imports
Typically when importing other JavaScript files in your project, you'd write a relative import for another .js file like so:
import Globals from "./globals.js";
How do you write the import for TypeScript? The answer is: exactly the same way! Even though the import ends with .js, TypeScript knows the file is really generated from the .ts file, and so everything just works. Don't try to change it, as otherwise it won't work after it's compiled to JavaScript.
Example
The Spell Caster TypeScript example demonstrates the Spell Caster Code JavaScript example but updated to use TypeScript. In particular, this commit shows the list of changes that were necessary to add types to the existing JavaScript code.
Workflow
Once you are up and running, you will likely want to make repeated changes to your TypeScript code, and easily be able to preview the result in Construct. To make this process work smoothly, use the following two settings:
- In VS Code, press Ctrl + Shift + B, and then select tsc: watch. This enables a mode where VS Code will automatically compile your .ts files to .js whenever you save the file. Note this must be done once per session.
- In Construct, right-click on the Scripts folder in the Project Bar and choose Auto reload all on preview. (Construct remembers this setting across sessions.)
The workflow then goes like this:
- You make a change to a TypeScript file and save the change
- TypeScript then automatically compiles the .ts file to .js (or reports errors if you made a mistake)
- Then preview the project in Construct, at which point the .js file is re-loaded from the project folder
Sometimes you may make changes to the project that affect the TypeScript definition files Construct generated for you. Alternatively when updating to new versions of Construct, the TypeScript definition files could change too. To make sure the TypeScript definition files are up-to-date, right-click on the Scripts folder in the Project Bar, and select TypeScript►Update TypeScript definitions. This essentially does only step 3 from the Set up TypeScript steps.
Conclusion
TypeScript is a great way to enhance the coding experience in Construct, using the same industry-standard tooling and languages as used by many professionals. It requires using a folder-based project and an external editor like VS Code, but in turn it brings useful features such as precise autocomplete, error checking, and source code navigation.
See the Spell Caster TypeScript example on GitHub for a sample of a JavaScript project converted to TypeScript. There is also excellent official TypeScript documentation available which is a great place to go to learn more about TypeScript.