JavaScript Basics
HELIX uses PuerTS, Tencent's V8-based JavaScript/TypeScript runtime for Unreal Engine. If you're a web developer, you'll feel right at home.
Setting Up
- In your Package folder, create a
Scriptsdirectory - Add
.jsor.tsfiles inside it - Register your script entry point in
package.jsonunder the"scripts"field
{
"name": "my-package",
"version": "1.0.0",
"scripts": {
"main": "Scripts/Main.js"
}
}
For TypeScript, PuerTS compiles .ts files automatically. Add a tsconfig.json to your Package root for custom compiler options.
Accessing UE APIs
PuerTS provides bindings to Unreal Engine classes:
const UE = require("ue");
const { Actor, GameplayStatics, KismetSystemLibrary, Vector } = UE;
// Get all actors of a class
const players = GameplayStatics.GetAllActorsOfClass(
world,
UE.HELIXCharacter.StaticClass()
);
// Spawn an actor
const location = new Vector(100, 200, 0);
const rotation = new UE.Rotator(0, 0, 0);
const actor = GameplayStatics.BeginDeferredActorSpawnFromClass(
world,
UE.MyBlueprintClass.StaticClass(),
new UE.Transform(rotation, location, new Vector(1, 1, 1))
);
GameplayStatics.FinishSpawningActor(actor, actor.GetTransform());
Binding to Blueprint Classes
Create a JavaScript class that extends a Blueprint class:
const UE = require("ue");
class MyCharacter extends UE.HELIXCharacter {
constructor() {
super();
this.health = 100;
this.inventory = [];
}
ReceiveBeginPlay() {
console.log("Character spawned with health:", this.health);
}
ReceiveTick(deltaTime) {
// Called every frame
}
OnPlayerInteract(otherPlayer) {
console.log(`${otherPlayer.GetName()} interacted with us`);
}
}
module.exports = MyCharacter;
Using npm Packages
One of PuerTS's biggest strengths is npm access. Install packages into your Package folder:
cd MyPackage
npm init -y
npm install lodash uuid
Then use them in your scripts:
const _ = require("lodash");
const { v4: uuidv4 } = require("uuid");
const playerId = uuidv4();
const sorted = _.sortBy(players, ["score"]);
Not every npm package works out of the box. Packages that rely on Node.js-specific APIs (filesystem, network, child processes) won't work unless HELIX provides equivalent APIs. Pure logic packages (lodash, uuid, date-fns) work great.
Async/Await
PuerTS supports modern async patterns. This is huge for things like HTTP requests, database queries, and sequential game logic:
// Database queries use the Helix.Database module
Helix.server(async () => {
await Helix.Database.connect({
host: "localhost",
database: "gamedb",
username: "root",
password: "pass",
dialect: "mysql"
});
// Use your ORM models for queries
const player = await db.Player.findOne({
where: { id: playerId }
});
console.log("Loaded player:", player.dataValues);
});
// Async game logic with standard JS timers
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function gameStartSequence() {
console.log("Starting countdown...");
await delay(3000);
console.log("Spawning players...");
// Use your game's player management logic
console.log("Game started!");
}
Module System
PuerTS supports both CommonJS and ES module patterns:
// CommonJS (require/module.exports)
const Utils = require("./Utils");
module.exports = { MyClass };
// ES Modules (import/export) -- requires .mjs or tsconfig setting
import { formatTime } from "./Utils.mjs";
export class MyClass { }
Organizing your code
MyPackage/
Scripts/
Main.js -- entry point
Systems/
Combat.js
Inventory.js
Utils/
Math.js
Formatting.js
TypeScript
Add a tsconfig.json in your Package root:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "./Scripts",
"rootDir": "./TypeScript"
},
"include": ["TypeScript/**/*"]
}
// TypeScript gives you type safety with UE classes
import * as UE from "ue";
interface PlayerData {
id: string;
name: string;
score: number;
}
class ScoreManager {
private scores: Map<string, PlayerData> = new Map();
addScore(playerId: string, points: number): void {
const data = this.scores.get(playerId);
if (data) {
data.score += points;
}
}
getLeaderboard(): PlayerData[] {
return [...this.scores.values()].sort((a, b) => b.score - a.score);
}
}
Tips
- Use
console.log(),console.warn(), andconsole.error()-- they all go to the Output Log. - TypeScript is strongly recommended for larger projects. The type checking catches entire categories of bugs at compile time.
- PuerTS supports source maps, so stack traces point to your original
.tsfiles. - Use
async/awaitinstead of callback chains. It keeps your game logic readable.