Skip to main content

Camera

A Camera is a device through which the player views the world.

TypeNameInterface Description
VariablescameraType: CameraType

Function: Gets the type of the camera, which can be either CameraType.Ortho or CameraType.Perspective.

VariablesclearColor: Color

Function: The color used to clear this Camera's render target before drawing, when inputTexture is null.

VariablesclearType: CameraClearType

Function: Gets the clear type of the current camera.

VariablesdepthRenderTexture: Texture

Function: Gets the depth render texture associated with the current camera.

Variablesfar: number

Function: The distance of the far clipping plane. Objects farther than this value are not rendered.

Variablesfov: number

Function: The Camera's field of view in degrees. Only effective when is . range: [0.0, 180.0] default: 60.0

VariablesfovType: CameraFovType

Function: Gets the field of view type for the camera.

VariablesinputTexture: Texture | null

Function: The texture blit to the render target as the base color at the start of each frame, before scene objects are rendered on top. Only takes effect when clearType includes color clearing (e.g. CameraClearType.Color, CameraClearType.ColorDepth); ignored otherwise. When null and color clearing is active, clearColor is used instead. Its size is not guaranteed to match renderTexture or the preview viewport.

Variablesnear: number

Function: The distance of the near clipping plane. Objects closer than this value are not rendered.

VariablesorthoHeight: number

Function: The orthographic height of the camera if the camera type is orthographic.

VariablesprojectionMatrix: Matrix4x4f

Function: Get the projection matrix of the camera.

VariablesrenderLayer: LayerSet

Function: Gets the set of layers this Camera will render.

VariablesrenderOrder: number

Function: The sorting order the Camera renders in. Every frame, Cameras render in ascending order determined by their renderOrder properties.

VariablesrenderTexture: Texture

Function: The RenderTexture this camera renders scene objects into each frame. Defaults to the Final Render Output bound by the runtime; can be replaced with a custom RenderTexture via the setter. Its dimensions are not guaranteed to match inputTexture or the preview viewport.

Variablesviewport: Rect

Function: The camera's viewport, default is (0,0,1,1)

Functionsconstructor()

FunctionsgetCameraToWorldMatrix(): Matrix4x4f

Function: Get the matrix that transforms from camera space to world space. Use this to calculate where in the world a specific camera space point is located.

Returns The transformation matrix from camera space to world space.

FunctionsgetLookAt(): Vector3f

Function: Gets the camera's forward direction, also known as the look-at direction. This method returns the normalized vector that the camera is currently looking toward (i.e., the default camera looks in the world-space direction of -Z in camera space, returns (0, 0, -1)). <br/>When constructing a follow-camera with , pass cameraPosition.subtract(targetPosition) as the forward vector — NOT targetPosition.subtract(cameraPosition) — because lookAt aligns the object's positive Z-axis, which is opposite to the camera's view direction.

Returns The forward direction vector of the camera.

FunctionsgetWorldToCameraMatrix(): Matrix4x4f

Function: Retrieves the matrix that transforms coordinates from world space to camera space. <br/>This matrix is commonly known as the "view matrix" in graphics literature. <br/>It can be used to determine the position of game objects in camera space or to specify a custom camera location independent of its transform.

Returns The transformation matrix from world space to camera space.

FunctionsgetWorldToClipMatrix(): Matrix4x4f

Function: Retrieves the transformation matrix that converts coordinates from world space to clip space.

Returns The 4x4 matrix representing the world-to-clip space transformation.

FunctionsScreenPointToRay(screenPoint: Vector2f): Ray

Function: Generates a ray from the screen position in this camera's view space. <br/>The screen coordinate system origin (0, 0) is at the bottom-left, with x increasing rightward and y increasing upward, measured in pixels.

Parameters

screenPoint: - A point in screen space represented as a Vector2f (pixels).

Returns The calculated world space ray as a Ray object.

FunctionsscreenToWorldPoint(screenPoint: Vector3f): Vector3f

Function: Converts a screen space position to a world space position, given an absolute depth.

Parameters

screenPoint: - A point in screen space represented as a Vector3f. <br/>X, Y are screen coordinates in pixels. left-bottom is (0, 0). right-top is (W, H). <br/>Z is the absolute world-unit depth from the camera's <br/>near plane, not a normalized value. Use the Z value from to round-trip correctly.

Returns The corresponding world space position as a Vector3f.

FunctionsviewportPointToRay(viewportPoint: Vector2f): Ray

Function: Generates a ray from the specified position in this camera's viewport space. <br/>Viewport coordinates are normalized: (0, 0) is the bottom-left and (1, 1) is the top-right. Useful for generating rays from UI elements whose positions are in normalized layout space.

Parameters

viewportPoint: - A point in viewport space represented as a Vector2f (range [0, 1]).

Returns The calculated world space ray as a Ray object.

FunctionsviewportToWorldPoint(viewPortPoint: Vector3f): Vector3f

Function: Converts a point from view port space to world space. <br/>Viewport coordinates are normalized: (0, 0) is the bottom-left and (1, 1) is the top-right. The Z component specifies the world-unit depth from the camera's near plane.

Parameters

viewPortPoint: - A point in view port space represented as a Vector3f (x, y in [0, 1]; z is depth).

Returns The corresponding point in world space as a Vector3f.

FunctionsworldToScreenPoint(worldPoint: Vector3f): Vector3f

Function: Converts a world space position to a screen space position.

Parameters

worldPoint: - The world space point as a Vector3f.

Returns The corresponding screen space position as a Vector3f. <br/>Screen position is based on the current rendered screen resolution W x H, <br/>ranging from ([0, W], [0, H]) where (0, 0) is the bottom-left and (W, H) is the top-right. <br/>The returned Z component is the distance from the camera's near plane in world units.

FunctionsworldToViewportPoint(worldPoint: Vector3f): Vector3f

Function: Projects a point from world space to viewport space. The returned viewport coordinates use a normalized range where x and y are typically in [0, 1], with (0, 0) at the bottom-left of the viewport and (1, 1) at the top-right. The returned z value represents the distance from the camera along its view direction, in world units.

Parameters

worldPoint: - A point in world space represented as a Vector3f.

Returns The calculated point in viewport space as a Vector3f.

Examples

constructor()

let obj = new APJS.Camera();

getLookAt(): Vector3f

const forward = cam.getLookAt();
const right = forward.cross(new APJS.Vector3f(0, 1, 0)).normalize();

ScreenPointToRay(screenPoint: Vector2f): Ray

// For a default camera at (0, 0, 40) and a default cube at (0, 0, 0)
// with box collider size (8, 8, 8), the following call logs (0, 0, 4).
// The ray to the center of the screen hits the front face of the cube first, and the front face is at z = 4.
const ray = cam.ScreenPointToRay(new APJS.Vector2f(360, 640));
const hits = APJS.Physics3D.raycast(ray, 500, true);
console.log("raycastHit:", hits[0].point);

screenToWorldPoint(screenPoint: Vector3f): Vector3f

// for default camera at (0, 0, 40), the following call returns (0, 0, 10)
// because (360, 640) is the center of the screen with resolution (1280, 720)
// and depth 30 is the depth from the near plane.
const worldPos = cam.screenToWorldPoint(new APJS.Vector3f(360, 640, 30));

viewportPointToRay(viewportPoint: Vector2f): Ray

// For a default camera at (0, 0, 40) and a default cube at (0, 0, 0)
// with box collider size (8, 8, 8), the following call logs (0, 0, 4).
// The ray to the center of the screen hits the front face of the cube first, and the front face is at z = 4.
const ray = cam.viewportPointToRay(new APJS.Vector2f(0.5, 0.5));
const hits = APJS.Physics3D.raycast(ray, 1000, true);
console.log("raycastHit:", hits[0].point);

viewportToWorldPoint(viewPortPoint: Vector3f): Vector3f

// for default camera at (0, 0, 40), the following call returns (0, 0, 30)
const worldPos = cam.viewportToWorldPoint(new APJS.Vector3f(0.5, 0.5, 10));

worldToScreenPoint(worldPoint: Vector3f): Vector3f

// for default camera at (0, 0, 40), the following call returns (360, 640, 30)
// because (0, 0, 10) is the center of the screen with resolution (1280, 720)
// and depth 30 is the depth from the near plane.
const screenPos = cam.worldToScreenPoint(new APJS.Vector3f(0, 0, 10));
// Use screenPoint.x and screenPoint.y to position a 2D UI indicator
const screenPointVec3f = cam.worldToScreenPoint(enemy.getTransform().getWorldPosition());
const screenPos = new APJS.Vector2f(screenPointVec3f.x, screenPointVec3f.y); // pixel coordinates

worldToViewportPoint(worldPoint: Vector3f): Vector3f

// for default camera at (0, 0, 40), the following call returns (0.5, 0.5, 10)
const viewportPos = cam.worldToViewportPoint(new APJS.Vector3f(0, 0, 30));

Use Case

Example 1 — AR plane-world tap-to-place pattern. The placement object lives under AR Plane (so it renders inside the AR Tracking render group, on top of the camera feed).

// AR plane-world tap-to-place + InteractableObject co-existence.
// Tap projection uses the DeviceTracker-backed AR Camera and intersects the Y=0 XOZ ground plane.
// The cube center is placed at the hit XZ plus PLACEMENT_Y, so the cube rests on the AR Plane.
// Tap is recognized on Touch.Ended only when the finger stayed within TAP_MOVE_THRESHOLD.

const GROUND_Y = 0;
const PLACEMENT_Y = 16;
const RAY_EPSILON = 0.0001;
const TAP_MOVE_THRESHOLD = 0.02;

@component()
export class TapPlaceController extends APJS.BasicScriptComponent {
@serializeProperty
placementCube!: APJS.SceneObject;

@serializeProperty
scoreText!: APJS.SceneObject;

private touchCallback!: (event: APJS.IEvent) => void;
private cubeTransform!: APJS.Transform;
private scoreTextComp!: APJS.Text;
private placementCamera!: APJS.Camera;
private hasPlacementCamera = false;
private placedCount = 0;
private touchActive = false;
private touchMoved = false;
private touchStartX = 0;
private touchStartY = 0;
private startCubePos: APJS.Vector3f | null = null;

// RecordStart: reset placedCount + return cube to its cached start position + reset score
// text + clear transient touch state. AR plane tracking is managed by DeviceTracker /
// ARCamera and resets automatically. See GameState §"RecordStart / RecordEnd Lifecycle".
private onRecordStart = (_event: APJS.IEvent) => {
this.placedCount = 0;
if (this.cubeTransform && this.startCubePos) {
this.cubeTransform.setWorldPosition(this.startCubePos);
}
if (this.scoreTextComp) {
this.scoreTextComp.text = "Score: 0";
}
this.touchActive = false;
this.touchMoved = false;
};

onStart(): void {
if (this.placementCube) {
this.cubeTransform = this.placementCube.getComponent("Transform") as APJS.Transform;
if (this.cubeTransform) {
const p = this.cubeTransform.getWorldPosition();
this.startCubePos = new APJS.Vector3f(p.x, p.y, p.z);
}
}
if (this.scoreText) {
this.scoreTextComp = this.scoreText.getComponent("Text") as APJS.Text;
}
this.resolvePlacementCamera();

this.touchCallback = (event: APJS.IEvent) => {
const t = event.args[0] as APJS.TouchData;
if (t.phase === APJS.TouchPhase.Began) {
this.touchActive = true;
this.touchMoved = false;
this.touchStartX = t.position.x;
this.touchStartY = t.position.y;
return;
}
if (!this.touchActive) return;
if (t.phase === APJS.TouchPhase.Moved) {
this.touchMoved = this.touchMoved || this.touchDistanceSq(t.position.x, t.position.y) > TAP_MOVE_THRESHOLD * TAP_MOVE_THRESHOLD;
return;
}
if (t.phase === APJS.TouchPhase.Ended) {
const isTap = !this.touchMoved && this.touchDistanceSq(t.position.x, t.position.y) <= TAP_MOVE_THRESHOLD * TAP_MOVE_THRESHOLD;
this.touchActive = false;
this.touchMoved = false;
if (isTap) {
this.handleTap(t.position.x, t.position.y);
}
return;
}
if (t.phase === APJS.TouchPhase.Canceled) {
this.touchActive = false;
this.touchMoved = false;
}
};
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.Touch, this.touchCallback, this);
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.onRecordStart);
}

private touchDistanceSq(nx: number, ny: number): number {
const dx = nx - this.touchStartX;
const dy = ny - this.touchStartY;
return dx * dx + dy * dy;
}

private resolvePlacementCamera(): boolean {
if (this.hasPlacementCamera) {
return true;
}
const sceneObjects = this.getSceneObject().scene.getAllSceneObjects();
let fallbackCamera!: APJS.Camera;
for (let i = 0; i < sceneObjects.length; i += 1) {
const camera = sceneObjects[i].getComponent("Camera") as APJS.Camera;
if (!camera || camera.cameraType !== APJS.CameraType.Perspective) {
continue;
}
if (!fallbackCamera) {
fallbackCamera = camera;
}
if (sceneObjects[i].name === "AR Camera" || sceneObjects[i].getComponent("DeviceTracker")) {
this.placementCamera = camera;
this.hasPlacementCamera = true;
return true;
}
}
if (fallbackCamera) {
this.placementCamera = fallbackCamera;
this.hasPlacementCamera = true;
return true;
}
return false;
}

private handleTap(nx: number, ny: number): void {
if (!this.cubeTransform || !this.resolvePlacementCamera()) {
return;
}
const ray = this.placementCamera.viewportPointToRay(new APJS.Vector2f(nx, 1 - ny));
if (ray.direction.y > -RAY_EPSILON && ray.direction.y < RAY_EPSILON) {
return;
}
const t = (GROUND_Y - ray.origin.y) / ray.direction.y;
if (t <= RAY_EPSILON) {
return;
}
const hitX = ray.origin.x + ray.direction.x * t;
const hitZ = ray.origin.z + ray.direction.z * t;
const worldPoint = new APJS.Vector3f(hitX, PLACEMENT_Y, hitZ);
this.cubeTransform.setWorldPosition(worldPoint);
this.placedCount += 1;
if (this.scoreTextComp) {
this.scoreTextComp.text = "Score: " + this.placedCount;
}
}

onDestroy(): void {
if (this.touchCallback) {
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.Touch, this.touchCallback, this);
}
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.RecordStart, this.onRecordStart);
}
}

Example 2 — Virtual joystick using ScreenTransform: detect tap on joystick top via worldToScreenPoint radius check, drag with clamped distance, output normalized direction.

@component()
export class JoystickInput extends APJS.BasicScriptComponent {
@serializeProperty
private uiCameraObject: APJS.SceneObject;
@serializeProperty
private joystickBase: APJS.SceneObject;
@serializeProperty
private joystickTop: APJS.SceneObject;

private uiCamera: APJS.Camera;
private baseTransform: APJS.ScreenTransform;
private topTransform: APJS.ScreenTransform;
private maxDistance: number = 100;
private active: boolean = false;
public direction: APJS.Vector2f = new APJS.Vector2f(0, 0);

onStart(): void {
this.uiCamera = this.uiCameraObject.getComponent("Camera") as APJS.Camera;
this.baseTransform = this.joystickBase.getComponent("ScreenTransform") as APJS.ScreenTransform;
this.topTransform = this.joystickTop.getComponent("ScreenTransform") as APJS.ScreenTransform;
const size = this.baseTransform.sizeDelta;
const topSize = this.topTransform.sizeDelta;
this.maxDistance = Math.min(size.x, size.y) / 2 - Math.min(topSize.x, topSize.y) / 2;
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.Touch, this.onTouch);
}

private isInRadius(vp: APJS.Vector2f, st: APJS.ScreenTransform, radius: number): boolean {
const worldPos = st.getWorldPosition();
const screenPos = this.uiCamera.worldToScreenPoint(worldPos);
const origin = this.uiCamera.worldToScreenPoint(new APJS.Vector3f(0, 0, 0));
const sw = origin.x * 2;
const sh = origin.y * 2;
const dx = vp.x * sw - screenPos.x;
const dy = vp.y * sh - screenPos.y;
return Math.sqrt(dx * dx + dy * dy) <= radius;
}

private onTouch = (event: APJS.IEvent) => {
const t = event.args[0] as APJS.TouchData;
const vp = new APJS.Vector2f(t.position.x, 1 - t.position.y);
if (t.phase === APJS.TouchPhase.Began) {
this.active = this.isInRadius(vp, this.topTransform, this.maxDistance);
} else if (t.phase === APJS.TouchPhase.Moved && this.active) {
const baseWorld = this.baseTransform.getWorldPosition();
const baseScreen = this.uiCamera.worldToScreenPoint(baseWorld);
const origin = this.uiCamera.worldToScreenPoint(new APJS.Vector3f(0, 0, 0));
const sw = origin.x * 2;
const sh = origin.y * 2;
const dx = vp.x * sw - baseScreen.x;
const dy = vp.y * sh - baseScreen.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const clamped = Math.min(dist, this.maxDistance);
if (dist > 0) {
this.direction.set(dx / dist, dy / dist);
}
const topWorld = this.topTransform.getWorldPosition();
const topScreen = this.uiCamera.worldToScreenPoint(topWorld);
const newScreen = new APJS.Vector3f(baseScreen.x + this.direction.x * clamped, baseScreen.y + this.direction.y * clamped, topScreen.z);
this.topTransform.setWorldPosition(this.uiCamera.screenToWorldPoint(newScreen));
} else if (t.phase === APJS.TouchPhase.Ended && this.active) {
this.active = false;
this.direction.set(0, 0);
const baseWorld = this.baseTransform.getWorldPosition();
const topWorld = this.topTransform.getWorldPosition();
this.topTransform.setWorldPosition(new APJS.Vector3f(baseWorld.x, baseWorld.y, topWorld.z));
}
};

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