Transform
Position, rotation and scale of an object.
| Type | Name | Interface Description |
|---|---|---|
| Variables | localEulerAngles: Vector3f | • Function: Gets the Transform's local euler angles in degrees. Returns The local Euler angles of the Transform. |
| Variables | localMatrix: Matrix4x4f | • Function: Gets the local transformation matrix of the Transform. Returns The local matrix representing the object's position, rotation, and scale relative to its parent. |
| Variables | localPosition: Vector3f | • Function: Get the Transform's position relative to its parent. Returns The local position of the Transform. |
| Variables | localRotation: Quaternionf | • Function: Gets the Transform's rotation relative to its parent. Returns The local rotation as a Quaternionf. |
| Variables | localScale: Vector3f | • Function: Gets the Transform's scale relative to its parent. Returns The local scale of the Transform. |
| Functions | constructor() | |
| Functions | getWorldEulerAngles(): Vector3f | • Function: Returns the Transform's Euler angles relative to the world. <br/>Obtaining the rotation in world space may trigger a matrix transformation operation. Returns The world space Euler angles. |
| Functions | getWorldMatrix(): Matrix4x4f | • Function: Get the world transformation matrix of this object. <br/>Retrieving the world matrix may involve a matrix transformation operation. Returns The world matrix representing the object's position, rotation, and scale in world space. |
| Functions | getWorldPosition(): Vector3f | • Function: Returns the Transform's position relative to the world. <br/>Obtaining the position in world space may trigger a matrix transformation operation. Returns The position of the Transform in world space. |
| Functions | getWorldRotation(): Quaternionf | • Function: Returns the Transform's rotation relative to the world. <br/>Obtaining the rotation in world space may trigger a matrix transformation operation. Returns The rotation of the transform in world space. |
| Functions | getWorldScale(): Vector3f | • Function: Returns the Transform's scale relative to the world. <br/>Obtaining the scale in world space may trigger a matrix transformation operation. Returns The scale of the transform in world space. |
| Functions | setWorldEulerAngles(euler: Vector3f): void | • Function: Sets the Transform's Euler angles relative to the world. <br/> Setting the world space rotation may trigger a matrix transformation operation. Parameters • |
| Functions | setWorldMatrix(matrix: Matrix4x4f): void | • Function: Sets the world transformation matrix for the object. Parameters • |
| Functions | setWorldPosition(worldPosition: Vector3f): void | • Function: Sets the Transform's position in world space. <br/> Modifying the world position may result in a matrix transformation operation. Parameters • |
| Functions | setWorldRotation(worldRotation: Quaternionf): void | • Function: Sets the Transform's rotation relative to the world. <br/> Setting the world space rotation may trigger a matrix transformation operation. Parameters • |
| Functions | setWorldScale(worldScale: Vector3f): void | • Function: Sets the Transform's scale relative to the world. <br/>This may produce lossy results when parent objects are rotated, so use Parameters • |
Examples
constructor()
let obj = new APJS.Transform();
Use Case
Example 1 — Pin one or more 2D screen-space overlays (Profile Photo, Screen Image, 2D Text) to follow 3D world targets every frame.
// Pin one or more 2D screen-space overlays (Profile Photo / Screen Image / 2D Text)
// to follow 3D world targets every frame using the 3D camera's projection.
//
// Each pair is configured by index in the parallel arrays target3DTransform[i] /
// overlay2DTransform[i]. Pair count = min of the two array lengths.
//
// Engine note (verified 2026-04-29 in scratch): when an array of SceneObject refs is
// wired through DSL set_component, the engine coerces each entry to that object's
// first component (Transform / ScreenTransform). This script accepts those component
// arrays directly, so you wire targets with their Transform components and overlays
// with their ScreenTransform components for a 1:1 mapping. We retrieve the owning
// SceneObject via Transform.getSceneObject() to honor enabled state.
//
// Scene authoring (DSL):
// - Camera (3D, perspective): the world camera that frames the gameplay scene.
// - 2D Camera (orthographic): the camera under which UI elements live.
// - Targets: any 3D objects with a Transform (typically tower-block Cubes with
// RigidBody + BoxCollider, but the script doesn't require physics).
// - Overlays: 2D UI elements parented to 2D Camera (Profile Photo, Screen Image,
// 2D Text). The script writes their world position via Transform.setWorldPosition.
//
// DSL wiring of array properties (use explicit type=Transform / ScreenTransform —
// SceneObject refs auto-coerce, but being explicit avoids ambiguity):
// set_component on the WorldToScreenFollower component:
// properties: {
// camera3DObject: { guid: '<3D-cam-SceneObject-guid>' },
// camera2DObject: { guid: '<2D-cam-SceneObject-guid>' },
// target3DTransform: [
// { guid: '<block0-Transform-guid>', type: 'Transform' },
// { guid: '<block1-Transform-guid>', type: 'Transform' },
// ...
// ],
// overlay2DTransform: [
// { guid: '<photo0-ScreenTransform-guid>', type: 'ScreenTransform' },
// { guid: '<photo1-ScreenTransform-guid>', type: 'ScreenTransform' },
// ...
// ]
// }
//
// Verified 2026-04-29 (experiment G11) on a 3-cube static stack at world y =
// 0.80 / 1.60 / 2.40 → screen y = 568 / 662 / 760 (px, origin bottom-left); each
// 0.8u world-y delta maps to ~94 screen-px delta as expected from the 3D camera
// at (0,3,5) with -10° pitch and 60° FOV. Drop test: tower compressed to y =
// 0.60 / 1.39 / 2.19 → screen y = 544 / 637 / 734, all 3 photos lock-step.
@component()
export class WorldToScreenFollower extends APJS.BasicScriptComponent {
@serializeProperty
private camera3DObject!: APJS.SceneObject;
@serializeProperty
private camera2DObject!: APJS.SceneObject;
@serializeProperty
private target3DTransform: APJS.Transform[] = [];
@serializeProperty
private overlay2DTransform: APJS.Transform[] = [];
@serializeProperty
private logEvery: number = 0;
// Hide the 2D overlay when its target SceneObject is disabled (e.g. knocked-down
// block destroyed). Set to false to keep the overlay rendered at the last
// tracked position regardless of target state.
@serializeProperty
private hideWhenTargetDisabled: boolean = true;
private camera3D!: APJS.Camera;
private camera2D!: APJS.Camera;
private inited: boolean = false;
private pairCount: number = 0;
private frame: number = 0;
private initOnce(): void {
if (!this.camera3DObject || !this.camera2DObject) {
return;
}
this.camera3D = this.camera3DObject.getComponent('Camera') as APJS.Camera;
this.camera2D = this.camera2DObject.getComponent('Camera') as APJS.Camera;
if (!this.camera3D || !this.camera2D) {
return;
}
this.pairCount = Math.min(this.target3DTransform.length, this.overlay2DTransform.length);
this.inited = true;
console.log('[W2S] init pairs=' + this.pairCount + ' cam3D=' + this.camera3DObject.name + ' cam2D=' + this.camera2DObject.name);
}
onUpdate(_dt: number): void {
if (!this.inited) {
this.initOnce();
return;
}
this.frame++;
const log = this.logEvery > 0 && this.frame % this.logEvery === 0;
for (let i = 0; i < this.pairCount; i++) {
const tT = this.target3DTransform[i];
const oT = this.overlay2DTransform[i];
if (!tT || !oT) {
continue;
}
const targetObj = tT.getSceneObject();
const overlayObj = oT.getSceneObject();
if (!targetObj || !overlayObj) {
continue;
}
const targetEnabled = targetObj.enabled;
if (this.hideWhenTargetDisabled && overlayObj.enabled !== targetEnabled) {
overlayObj.enabled = targetEnabled;
}
if (!targetEnabled) {
continue;
}
const wp = tT.getWorldPosition();
const screen3 = this.camera3D.worldToScreenPoint(wp);
const wp2 = this.camera2D.screenToWorldPoint(
new APJS.Vector3f(screen3.x, screen3.y, 40)
);
oT.setWorldPosition(wp2);
if (log) {
console.log('[W2S] f=' + this.frame + ' i=' + i + ' ' + targetObj.name + '->' + overlayObj.name + ' world=(' + wp.x.toFixed(2) + ',' + wp.y.toFixed(2) + ',' + wp.z.toFixed(2) + ') screen=(' + screen3.x.toFixed(0) + ',' + screen3.y.toFixed(0) + ')');
}
}
}
}
Example 2 — Camera-follow variant of a 3D lane-switch runner — the player traverses -Z, a chase camera trails on the +Z side and lerps toward player + cameraOffset each f…
@component()
export class CameraFollowRunner3D extends APJS.BasicScriptComponent {
@serializeProperty player: APJS.SceneObject;
@serializeProperty camera: APJS.SceneObject;
@serializeProperty trackTiles: APJS.SceneObject[];
@serializeProperty speed: number = 8; // forward units per second toward -Z
@serializeProperty smoothness: number = 6; // larger = camera catches up faster
@serializeProperty tileLength: number = 14; // world Z length of one track tile
@serializeProperty cameraOffsetY: number = 8; // camera height above player
@serializeProperty cameraOffsetZ: number = 10; // camera distance behind player on the +Z side
private playerTr: APJS.Transform;
private cameraTr: APJS.Transform;
private tileTrs: APJS.Transform[] = [];
private initialized = false;
onUpdate(dt: number): void {
if (!this.initialized) {
if (!this.player || !this.camera || !this.trackTiles || this.trackTiles.length < 2) {
return; // wiring not ready yet — skip the frame
}
this.playerTr = this.player.getComponent("Transform") as APJS.Transform;
this.cameraTr = this.camera.getComponent("Transform") as APJS.Transform;
this.tileTrs = [];
for (let i = 0; i < this.trackTiles.length; i++) {
const t = this.trackTiles[i];
if (t) this.tileTrs.push(t.getComponent("Transform") as APJS.Transform);
}
this.initialized = true;
}
// 1. Advance player forward along -Z. Lane (x) and jump (y) are owned by
// the runner's own input handler; this script only owns the forward axis.
const p = this.playerTr.localPosition;
const newPlayerZ = p.z - this.speed * dt;
this.playerTr.localPosition = new APJS.Vector3f(p.x, p.y, newPlayerZ);
// 2. Lerp camera toward (player + offset). The camera trails on +Z, matching
// the default template Camera at z=30 behind the Player at z=20.
const c = this.cameraTr.localPosition;
const targetX = p.x;
const targetY = this.cameraOffsetY;
const targetZ = newPlayerZ + this.cameraOffsetZ;
const k = Math.min(1, dt * this.smoothness);
this.cameraTr.localPosition = new APJS.Vector3f(
c.x + (targetX - c.x) * k,
c.y + (targetY - c.y) * k,
c.z + (targetZ - c.z) * k
);
// 3. Recycle track tiles. The runner moves toward lower Z, so the front-most
// tile has the smallest z and the back-most tile has the largest z. If the
// back-most tile is more than 1 tileLength behind the player, teleport it
// to frontmost.z - tileLength.
if (this.tileTrs.length >= 2) {
let backIdx = 0;
let backZ = this.tileTrs[0].localPosition.z;
let frontZ = backZ;
for (let i = 1; i < this.tileTrs.length; i++) {
const z = this.tileTrs[i].localPosition.z;
if (z > backZ) { backZ = z; backIdx = i; }
if (z < frontZ) { frontZ = z; }
}
if (backZ - newPlayerZ > this.tileLength) {
const back = this.tileTrs[backIdx].localPosition;
this.tileTrs[backIdx].localPosition = new APJS.Vector3f(back.x, back.y, frontZ - this.tileLength);
}
}
}
}