Skip to main content

HandInfo

APJS Script API reference for the HandInfo interface.

TypeNameInterface Description
Variablesaction: HandAction

Function: action

VariablesID: number

Function: ID

Variablesrect: Rect

Function: Bounding rectangle of the detected hand in normalized coordinates [0.0, 1.0], relative to the camera input image. x and y represent the bottom-left corner; width and height extend rightward and upward.

Variablesrotation: number

Function: rot_angle

FunctionsgetHandType(): string

Function: getHandType

Use Case

Example 1 — AR goalkeeper game — track hand position via AlgorithmManager, check distance to incoming ball objects, catch/deflect balls that come near the hand

@component()
export class HandCatchGame extends APJS.BasicScriptComponent {
@serializeProperty
balls: APJS.SceneObject[] = [];
@serializeProperty
scoreDisplay!: APJS.SceneObject;
@serializeProperty
handIndicator!: APJS.SceneObject;

private score = 0;
private scoreText!: APJS.Text;
private catchRadius = 80; // pixels
private handST!: APJS.ScreenTransform;
private ballStartPos: APJS.Vector2f[] = [];

// RecordStart: reset score + restore every ball (visible=true + cached startPos). Hand
// tracking state managed by AR system. See GameState §"RecordStart / RecordEnd Lifecycle".
private onRecordStart = (_event: APJS.IEvent) => {
this.score = 0;
if (this.scoreText) this.scoreText.text = "Catches: 0";
for (let i = 0; i < this.balls.length; i++) {
const ball = this.balls[i];
if (!ball) continue;
ball.visible = true;
const ballST = ball.getComponent("ScreenTransform") as APJS.ScreenTransform;
if (ballST && this.ballStartPos[i]) {
ballST.anchoredPosition = new APJS.Vector2f(this.ballStartPos[i].x, this.ballStartPos[i].y);
}
}
};

onStart(): void {
this.scoreText = this.scoreDisplay.getComponent("Text") as APJS.Text;
this.scoreText.text = "Catches: 0";
this.handST = this.handIndicator.getComponent("ScreenTransform") as APJS.ScreenTransform;
for (const ball of this.balls) {
const st = ball ? ball.getComponent("ScreenTransform") as APJS.ScreenTransform : null;
if (st) this.ballStartPos.push(new APJS.Vector2f(st.anchoredPosition.x, st.anchoredPosition.y));
else this.ballStartPos.push(new APJS.Vector2f(0, 0));
}
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.onRecordStart);
}

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

onUpdate(dt: number): void {
const result = APJS.AlgorithmManager.getResult();
if (result.getHandCount() === 0) {
this.handIndicator.visible = false;
return;
}

this.handIndicator.visible = true;
const hand = result.getHandInfo(0);

// Convert hand rect center (0-1, bottom-left origin, y-up) to ScreenTransform coords
const handCenterX = hand.rect.x + hand.rect.width / 2;
const handCenterY = hand.rect.y + hand.rect.height / 2;
const handScreenX = (handCenterX - 0.5) * 720;
const handScreenY = (handCenterY - 0.5) * 1280;

// Move hand indicator
this.handST.anchoredPosition = new APJS.Vector2f(handScreenX, handScreenY);

// Check distance to each active ball
for (const ball of this.balls) {
if (!ball.visible) continue;
const ballST = ball.getComponent("ScreenTransform") as APJS.ScreenTransform;
if (!ballST) continue;

const ballPos = ballST.anchoredPosition;
const dx = ballPos.x - handScreenX;
const dy = ballPos.y - handScreenY;
const dist = Math.sqrt(dx * dx + dy * dy);

if (dist < this.catchRadius) {
ball.visible = false; // caught!
this.score++;
this.scoreText.text = "Catches: " + this.score;
console.log("Caught ball! Score: " + this.score);
}
}
}
}

Example 2 — Rock-Paper-Scissors game using hand gesture recognition — Fist=Rock, HandOpen=Paper, BigV=Scissors. Computer picks randomly, compare to determine winner.

@component()
export class RockPaperScissors extends APJS.BasicScriptComponent {
@serializeProperty
resultText!: APJS.SceneObject;
@serializeProperty
scoreText!: APJS.SceneObject;
@serializeProperty
gestureText!: APJS.SceneObject;

private wins = 0;
private losses = 0;
private lastGesture = -1;
private holdTime = 0;
private holdThreshold = 1.0;
private cooldown = 0;
private choices = ["Rock", "Paper", "Scissors"];

// RecordStart: reset wins/losses + gesture state. Hand tracking auto-managed.
// See GameState §"RecordStart / RecordEnd Lifecycle".
private onRecordStart = (_event: APJS.IEvent) => {
this.wins = 0;
this.losses = 0;
this.lastGesture = -1;
this.holdTime = 0;
this.cooldown = 0;
if (this.resultText) {
const rt = this.resultText.getComponent("Text") as APJS.Text;
if (rt) rt.text = "Show your hand!";
}
if (this.scoreText) {
const st = this.scoreText.getComponent("Text") as APJS.Text;
if (st) st.text = "W:0 L:0";
}
if (this.gestureText) {
const gt = this.gestureText.getComponent("Text") as APJS.Text;
if (gt) gt.text = "";
}
};

onStart(): void {
const rt = this.resultText.getComponent("Text") as APJS.Text;
rt.text = "Show your hand!";
const st = this.scoreText.getComponent("Text") as APJS.Text;
st.text = "W:0 L:0";
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.onRecordStart);
}

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

private gestureToChoice(action: APJS.HandAction): number {
if (action === APJS.HandAction.Fist) return 0; // Rock
if (action === APJS.HandAction.HandOpen || action === APJS.HandAction.PlamUp) return 1; // Paper
if (action === APJS.HandAction.BigV || action === APJS.HandAction.DoubleFingerUp) return 2; // Scissors
return -1;
}

onUpdate(dt: number): void {
if (this.cooldown > 0) {
this.cooldown -= dt;
return;
}

const result = APJS.AlgorithmManager.getResult();
if (result.getHandCount() === 0) {
this.holdTime = 0;
this.lastGesture = -1;
return;
}

const hand = result.getHandInfo(0);
const choice = this.gestureToChoice(hand.action);
const gt = this.gestureText.getComponent("Text") as APJS.Text;

if (choice < 0) {
gt.text = "Show: Rock/Paper/Scissors";
this.holdTime = 0;
return;
}

gt.text = "You: " + this.choices[choice];

if (choice === this.lastGesture) {
this.holdTime += dt;
} else {
this.lastGesture = choice;
this.holdTime = 0;
}

if (this.holdTime >= this.holdThreshold) {
// Play round
const computer = Math.floor(Math.random() * 3);
const rt = this.resultText.getComponent("Text") as APJS.Text;
const st = this.scoreText.getComponent("Text") as APJS.Text;

if (choice === computer) {
rt.text = "Draw! Both " + this.choices[choice];
} else if ((choice + 1) % 3 === computer) {
this.losses++;
rt.text = "Lose! " + this.choices[computer] + " beats " + this.choices[choice];
} else {
this.wins++;
rt.text = "Win! " + this.choices[choice] + " beats " + this.choices[computer];
}
st.text = "W:" + this.wins + " L:" + this.losses;

this.holdTime = 0;
this.cooldown = 2.0; // wait before next round
}
}
}
Copyright © 2026 TikTok. All rights reserved.
About TikTokHelp CenterCareersContactLegalTerms of ServicePrivacy PolicyCookies