Skip to main content

Prefab

Prefabs allow you to create reusable SceneObject instances

TypeNameInterface Description
Functionsconstructor()

Functionsinstantiate(parent: SceneObject): SceneObject | null

Function: Instantiates a scene object.

Parameters

parent: - The parent scene object of the instantiated scene object.

Returns The instantiated scene object, or null if instantiation fails.

Examples

constructor()

let obj = new APJS.Prefab();

Use Case

Example 1 — Object pool from a Prefab asset. Pre-instantiate N copies via APJS.Prefab.instantiate() into a hidden pool, then acquire/release with setEnabledInHierarchy.

@component()
export class PrefabPool extends APJS.BasicScriptComponent {
@serializeProperty
bullet!: APJS.Prefab;

@serializeProperty
poolSize: number = 20;

private pool: APJS.SceneObject[] = [];
private poolReady = false;

onUpdate(_dt: number): void {
// Lazy init — @serializeProperty fields are null in onStart, so pool
// construction has to wait for the first onUpdate after wiring lands.
if (!this.poolReady && this.bullet) {
const host = this.getSceneObject();
for (let i = 0; i < this.poolSize; i++) {
const obj = this.bullet.instantiate(host);
if (!obj) continue; // engine refused — keep going, log if any
obj.setEnabledInHierarchy(false);
this.pool.push(obj);
}
console.log("[PrefabPool] pool ready: " + this.pool.length + "/" + this.poolSize);
this.poolReady = true;
}
}

/** Acquire a hidden instance from the pool. Returns undefined when exhausted. */
acquire(): APJS.SceneObject | undefined {
if (!this.poolReady) return undefined;
for (const obj of this.pool) {
if (!obj.enabled) {
obj.setEnabledInHierarchy(true);
return obj;
}
}
return undefined;
}

/** Return an instance to the pool. */
release(obj: APJS.SceneObject): void {
obj.setEnabledInHierarchy(false);
}

/** Convenience for the common case — acquire and place. */
spawnAt(x: number, y: number): APJS.SceneObject | undefined {
const obj = this.acquire();
if (!obj) return undefined;
const st = obj.getComponent("ScreenTransform") as APJS.ScreenTransform;
if (st) {
st.anchoredPosition = new APJS.Vector2f(x, y);
} else {
const tr = obj.getComponent("Transform") as APJS.Transform;
if (tr) tr.localPosition = new APJS.Vector3f(x, y, 0);
}
return obj;
}
}

Example 2 — Spawn instances of a Prefab asset at runtime via APJS.Prefab.instantiate().

@component()
export class RuntimePrefabSpawn extends APJS.BasicScriptComponent {
@serializeProperty
bullet!: APJS.Prefab;

private touchCallback!: (e: APJS.IEvent) => void;
private spawnCount = 0;
private spawned: APJS.SceneObject[] = [];

// RecordStart: remove every spawned object + reset spawnCount. See GameState SKILL.
private onRecordStart = (_event: APJS.IEvent) => {
const scene = this.getSceneObject().scene;
for (const s of this.spawned) {
scene.removeSceneObject(s);
}
this.spawned = [];
this.spawnCount = 0;
};

onStart(): void {
// Touch callback must use a stable bound reference so onDestroy can detach it.
this.touchCallback = (e: APJS.IEvent) => {
const t = e.args[0] as APJS.TouchData;
if (t.phase !== APJS.TouchPhase.Began) return;
this.spawnAtTouch(t);
};
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.Touch, this.touchCallback, this);
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.onRecordStart);
}

private spawnAtTouch(t: APJS.TouchData): void {
// @serializeProperty fields are null until MCP wires them after compile.
// This callback runs after onStart, so by the time the user taps the field
// is normally populated — but check anyway in case the user taps too early.
if (!this.bullet) {
console.log("[RuntimePrefabSpawn] bullet not wired yet — ignoring tap");
return;
}
const obj = this.bullet.instantiate(this.getSceneObject());
if (!obj) {
console.log("[RuntimePrefabSpawn] instantiate returned null");
return;
}
// Place the spawned instance at the tap position. 720x1280 portrait → world
// mapping is scene-specific; this example assumes a 2D screen with
// ScreenTransform on the spawn.
const st = obj.getComponent("ScreenTransform") as APJS.ScreenTransform;
if (st) {
st.anchoredPosition = new APJS.Vector2f(
t.position.x * 720 - 360,
(1.0 - t.position.y) * 1280 - 640,
);
} else {
// 3D scene fallback — push the spawn in front of the camera origin.
const tr = obj.getComponent("Transform") as APJS.Transform;
if (tr) tr.localPosition = new APJS.Vector3f(0, 0, 0);
}
this.spawnCount++;
this.spawned.push(obj);
console.log("[RuntimePrefabSpawn] spawned #" + this.spawnCount + " name=" + obj.name);
}

onDestroy(): void {
if (this.touchCallback) {
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.Touch, this.touchCallback, this);
}
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.RecordStart, this.onRecordStart);
}
}
Copyright © 2026 TikTok. All rights reserved.
About TikTokHelp CenterCareersContactLegalTerms of ServicePrivacy PolicyCookies