Vector3f
APJS Script API reference for the Vector3f class.
| Type | Name | Interface Description |
|---|---|---|
| Variables | x: number | • Function: Represents the x-coordinate in a 3-dimensional vector. |
| Variables | y: number | • Function: Represents the y-coordinate in a 3-dimensional vector. |
| Variables | z: number | • Function: Represents the z-coordinate in a 3-dimensional vector. |
| Functions | constructor() | |
| Functions | constructor(x?: number, y?: number, z?: number) | Parameters • • • |
| Functions | add(other: Vector3f): this | • Function: Adds the components of Parameters • Returns This vector with updated components after addition. |
| Functions | angleTo(other: Vector3f): number | • Function: Returns the angle in radians between the current vector and another specified vector. Parameters • Returns The angle in radians between the two vectors. |
| Functions | clampLength(length: number): this | • Function: Clamps the length of this vector to the specified Parameters • Returns This vector with its length clamped to the specified value. |
| Functions | clone(): Vector3f | • Function: Creates and returns a new Vector3f with the same component values. Use this before calling mutating math methods when the original value must be preserved. Returns A new Vector3f that is a clone of this vector. |
| Functions | cross(other: Vector3f): this | • Function: Computes the cross product with Parameters • Returns This vector after computing the cross product. |
| Functions | distance(other: Vector3f): number | • Function: Calculates and returns the Euclidean distance between the current vector and another vector. Parameters • Returns The distance between the two vectors. |
| Functions | divide(value: number | Vector3f): this | • Function: Divides this vector in place by Parameters • Returns This vector after division. |
| Functions | dot(other: Vector3f): number | • Function: Returns the dot product of the vector and another vector. Parameters • Returns The dot product of the two vectors. |
| Functions | equals(other: Vector3f): boolean | • Function: Determines if this vector is equal to the specified vector. Parameters • Returns A boolean indicating whether the vectors are equal. |
| Functions | inverse(): this | • Function: Replaces each component with its reciprocal in place. Returns This vector with inverted x, y, and z values. |
| Functions | magnitude(): number | • Function: Calculates and returns the magnitude (length) of the vector. Returns The magnitude of the vector. |
| Functions | multiply(value: number | Vector3f): this | • Function: Multiplies this vector in place. If Parameters • Returns This vector after multiplication. |
| Functions | multiplyScalar(scalar: number): this | • Function: Multiplies all components of this vector by Parameters • Returns This vector with updated components. |
| Functions | normalize(): this | • Function: Normalizes this vector in place so its magnitude becomes 1. If the magnitude is zero, the vector is left unchanged. Returns This vector after normalization. |
| Functions | project(other: Vector3f): this | • Function: Projects this vector onto Parameters • Returns This vector after projection. |
| Functions | projectOnPlane(normal: Vector3f): this | • Function: Projects the current vector onto the plane defined by the given normal vector. Parameters • Returns This vector after being projected onto the specified plane. |
| Functions | reflect(normal: Vector3f): this | • Function: Reflects this vector across the plane defined by the normal Parameters • Returns This vector after reflection. |
| Functions | set(x: number, y: number, z: number): this | • Function: Sets the three-dimensional coordinates of this vector in place. Parameters • • • Returns This instance with updated coordinates. |
| Functions | sqrMagnitude(): number | • Function: Returns the squared magnitude (length) of the vector. Returns The squared magnitude of the vector. |
| Functions | subtract(other: Vector3f): this | • Function: Subtracts the components of Parameters • Returns This vector after subtraction. |
| Functions | toString(): string | • Function: Returns a string representation of the vector. Returns A string in the format "Vector3f(x, y, z)" where x, y, and z are fixed to 5 decimal places. |
| Static Functions | compareApproximately(vec1: Vector3f, vec2: Vector3f, dist: number): boolean | • Function: Approximately compares two vectors by the value of each component with a specified tolerance. Parameters • • • Returns True if the vectors are approximately equal within the given tolerance, false otherwise. |
| Static Functions | lerp(vecA: Vector3f, vecB: Vector3f, t: number): Vector3f | • Function: Linearly interpolates between the two vectors Parameters • • • Returns A new |
| Static Functions | max(vecA: Vector3f, vecB: Vector3f): Vector3f | • Function: Returns a new vector containing the largest value of each component from the two input vectors. Parameters • • Returns A new Vector3f instance with components set to the maximum values from vecA and vecB. |
| Static Functions | min(vecA: Vector3f, vecB: Vector3f): Vector3f | • Function: Returns a new vector containing the smallest value of each component from the two input vectors. Parameters • • Returns A new Vector3f instance with components set to the minimum values from vecA and vecB. |
Examples
constructor()
let obj = new APJS.Vector3f();
constructor(x?: number, y?: number, z?: number)
let obj = new APJS.Vector3f();
compareApproximately(vec1: Vector3f, vec2: Vector3f, dist: number): boolean
let a = new Vector3f(0.0000001, 0.0000001, 0.0000001);
let b = new Vector3f(0.0000000, 0.0000000, 0.0);
Vector3f.compareApproximately(a, b, 0.0001); // true
Use Case
Example 1 — 3D bowling-lane timing game — the ball auto-ping-pongs laterally in the ready state, touch Began locks the current lane line, hold duration maps linearly to lau…
@component()
export class BowlingGameManager extends APJS.BasicScriptComponent {
@serializeProperty ball: APJS.SceneObject;
@serializeProperty pins: APJS.SceneObject[] = [];
@serializeProperty scoreTextObject: APJS.SceneObject;
@serializeProperty resetButtonObject: APJS.SceneObject;
@serializeProperty minLaunchSpeed: number = 30;
@serializeProperty maxLaunchSpeed: number = 60;
@serializeProperty maxChargeSeconds: number = 1.4;
@serializeProperty settleVelocityThreshold: number = 0.12;
@serializeProperty settleAngularThreshold: number = 0.28;
@serializeProperty pinSettleFrames: number = 14;
@serializeProperty ballSettleFrames: number = 10;
@serializeProperty roundTimeoutSeconds: number = 5.8;
@serializeProperty fallenDotThreshold: number = 0.6;
@serializeProperty ballStartXClamp: number = 3.1;
@serializeProperty pinActivationZ: number = -1.5;
@serializeProperty aimPingPongSpeed: number = 2.2;
private static readonly STATE_READY = 0;
private static readonly STATE_CHARGING = 1;
private static readonly STATE_ROLLING = 2;
private static readonly STATE_SETTLING = 3;
private static readonly STATE_RESULT = 4;
private static readonly BALL_SPIN_FACTOR = 1.35;
private static readonly PIN_GRAVITY_DELAY_FRAMES = 3;
private static readonly PIN_FAST_SLEEP_FRAMES = 8;
private static readonly PIN_SLEEP_CLEARANCE_Z = 0.28;
private initialized = false;
private currentState = BowlingGameManager.STATE_READY;
private chargeSeconds = 0;
private roundElapsed = 0;
private ballStillFrames = 0;
private touchActive = false;
private score = 0;
private pinsActive = false;
private pinResetFramesRemaining = 0;
private pinGravityDelayFramesRemaining = 0;
private aimDirection = 1;
private lockedAimX = 0;
private debugApproachLogged = false;
private debugPastLogged = false;
private debugResultLogged = false;
private ballRigidBody!: APJS.RigidBody;
private ballTransform!: APJS.Transform;
private scoreText!: APJS.Text;
private resetButtonImage!: APJS.Image;
private ballStartPosition!: APJS.Vector3f;
private ballStartRotation!: APJS.Quaternionf;
private pinRigidBodies: APJS.RigidBody[] = [];
private pinTransforms: APJS.Transform[] = [];
private pinStartPositions: APJS.Vector3f[] = [];
private pinStartRotations: APJS.Quaternionf[] = [];
private pinLowMotionFrames: number[] = [];
private pinCounted: boolean[] = [];
private pinAssistApplied: boolean[] = [];
private touchCallback = (event: APJS.IEvent) => {
this.onTouch(event);
};
private onRecordStart = (_event: APJS.IEvent) => {
this.resetRound();
};
private logBallAndHeadPin(label: string): void {
if (this.pinRigidBodies.length === 0) {
console.log(label + " pins=0");
return;
}
const ballPos = this.ballTransform.getWorldPosition();
const headPos = this.pinTransforms[0].getWorldPosition();
const headRb = this.pinRigidBodies[0];
const headVel = headRb.velocity;
console.log(
label +
" pins=" + this.pinRigidBodies.length +
" ball=(" + ballPos.x.toFixed(3) + "," + ballPos.y.toFixed(3) + "," + ballPos.z.toFixed(3) + ")" +
" head=(" + headPos.x.toFixed(3) + "," + headPos.y.toFixed(3) + "," + headPos.z.toFixed(3) + ")" +
" headVel=(" + headVel.x.toFixed(3) + "," + headVel.y.toFixed(3) + "," + headVel.z.toFixed(3) + ")" +
" headStatic=" + headRb.static +
" headGravity=" + headRb.useGravity +
" headCounted=" + this.pinCounted[0]
);
}
private initOnce(): void {
if (!this.ball || this.pins.length === 0 || !this.scoreTextObject || !this.resetButtonObject) return;
this.ballRigidBody = this.ball.getComponent("RigidBody") as APJS.RigidBody;
this.ballTransform = this.ball.getTransform();
this.scoreText = this.scoreTextObject.getComponent("Text") as APJS.Text;
this.resetButtonImage = this.resetButtonObject.getComponent("Image") as APJS.Image;
if (!this.ballRigidBody || !this.ballTransform || !this.scoreText || !this.resetButtonImage) return;
const ballWorld = this.ballTransform.getWorldPosition();
this.ballStartPosition = new APJS.Vector3f(ballWorld.x, ballWorld.y, ballWorld.z);
this.ballStartRotation = this.ballTransform.getWorldRotation().clone();
this.lockedAimX = this.ballStartPosition.x;
this.pinRigidBodies = [];
this.pinTransforms = [];
this.pinStartPositions = [];
this.pinStartRotations = [];
this.pinLowMotionFrames = [];
this.pinCounted = [];
this.pinAssistApplied = [];
for (let i = 0; i < this.pins.length; i++) {
const pin = this.pins[i];
if (!pin) continue;
const rb = pin.getComponent("RigidBody") as APJS.RigidBody;
const tf = pin.getTransform();
if (!rb || !tf) continue;
this.pinRigidBodies.push(rb);
this.pinTransforms.push(tf);
const pos = tf.getWorldPosition();
this.pinStartPositions.push(new APJS.Vector3f(pos.x, pos.y, pos.z));
this.pinStartRotations.push(tf.getWorldRotation().clone());
this.pinLowMotionFrames.push(0);
this.pinCounted.push(false);
this.pinAssistApplied.push(false);
}
console.log("init pins=" + this.pinRigidBodies.length);
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.Touch, this.touchCallback, this);
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.onRecordStart, this);
this.initialized = true;
this.resetRound();
}
private resetRigidBody(rb: APJS.RigidBody, position: APJS.Vector3f, rotation: APJS.Quaternionf, useGravity: boolean): void {
rb.static = false;
rb.velocity = new APJS.Vector3f(0, 0, 0);
rb.angularVelocity = new APJS.Vector3f(0, 0, 0);
rb.position = new APJS.Vector3f(position.x, position.y, position.z);
rb.rotation = rotation.clone();
rb.useGravity = useGravity;
}
private freezePin(index: number): void {
const rb = this.pinRigidBodies[index];
rb.velocity = new APJS.Vector3f(0, 0, 0);
rb.angularVelocity = new APJS.Vector3f(0, 0, 0);
rb.useGravity = false;
rb.static = true;
}
private forcePinsToStartPose(): void {
for (let i = 0; i < this.pinRigidBodies.length; i++) {
const rb = this.pinRigidBodies[i];
const tf = this.pinTransforms[i];
const startPosition = this.pinStartPositions[i];
const startRotation = this.pinStartRotations[i];
rb.static = false;
rb.velocity = new APJS.Vector3f(0, 0, 0);
rb.angularVelocity = new APJS.Vector3f(0, 0, 0);
rb.useGravity = false;
rb.position = new APJS.Vector3f(startPosition.x, startPosition.y, startPosition.z);
rb.rotation = startRotation.clone();
tf.setWorldPosition(new APJS.Vector3f(startPosition.x, startPosition.y, startPosition.z));
tf.setWorldRotation(startRotation.clone());
rb.static = true;
}
}
private setPinsActive(active: boolean): void {
this.pinsActive = active;
if (active) {
this.pinResetFramesRemaining = 0;
this.pinGravityDelayFramesRemaining = BowlingGameManager.PIN_GRAVITY_DELAY_FRAMES;
for (let i = 0; i < this.pinRigidBodies.length; i++) {
const rb = this.pinRigidBodies[i];
rb.static = false;
rb.velocity = new APJS.Vector3f(0, 0, 0);
rb.angularVelocity = new APJS.Vector3f(0, 0, 0);
rb.useGravity = false;
}
return;
}
this.pinGravityDelayFramesRemaining = 0;
this.pinResetFramesRemaining = 3;
this.forcePinsToStartPose();
}
private updatePinGravityDelay(): void {
if (!this.pinsActive || this.pinGravityDelayFramesRemaining <= 0) return;
this.pinGravityDelayFramesRemaining -= 1;
if (this.pinGravityDelayFramesRemaining > 0) return;
for (let i = 0; i < this.pinRigidBodies.length; i++) {
const rb = this.pinRigidBodies[i];
if (!rb.static) {
rb.useGravity = true;
}
}
}
private resetRound(): void {
if (!this.initialized) return;
this.currentState = BowlingGameManager.STATE_READY;
this.chargeSeconds = 0;
this.roundElapsed = 0;
this.ballStillFrames = 0;
this.touchActive = false;
this.score = 0;
this.aimDirection = 1;
this.lockedAimX = this.ballStartPosition.x;
this.debugApproachLogged = false;
this.debugPastLogged = false;
this.debugResultLogged = false;
this.resetRigidBody(this.ballRigidBody, this.ballStartPosition, this.ballStartRotation, false);
this.setPinsActive(false);
for (let i = 0; i < this.pinRigidBodies.length; i++) {
this.pinLowMotionFrames[i] = 0;
this.pinCounted[i] = false;
this.pinAssistApplied[i] = false;
}
this.updateHud();
}
private updateHud(): void {
if (!this.scoreText) return;
if (this.currentState === BowlingGameManager.STATE_READY) {
this.scoreText.text = "Tap and hold";
return;
}
if (this.currentState === BowlingGameManager.STATE_CHARGING) {
const power = Math.min(100, Math.round((this.chargeSeconds / this.maxChargeSeconds) * 100));
this.scoreText.text = "Power: " + power + "%";
return;
}
if (this.currentState === BowlingGameManager.STATE_RESULT) {
this.scoreText.text = "Final: " + this.score + "/10";
return;
}
if (this.currentState === BowlingGameManager.STATE_ROLLING || this.currentState === BowlingGameManager.STATE_SETTLING) {
this.scoreText.text = "Score: " + this.score + "/10";
return;
}
}
private isBallIdle(): boolean {
const v = this.ballRigidBody.velocity;
const a = this.ballRigidBody.angularVelocity;
const v2 = v.x * v.x + v.y * v.y + v.z * v.z;
const a2 = a.x * a.x + a.y * a.y + a.z * a.z;
return v2 < this.settleVelocityThreshold * this.settleVelocityThreshold && a2 < this.settleAngularThreshold * this.settleAngularThreshold;
}
private startCharging(): void {
this.touchActive = true;
this.chargeSeconds = 0;
this.currentState = BowlingGameManager.STATE_CHARGING;
this.updateHud();
}
private onTouch(event: APJS.IEvent): void {
if (!this.initialized) return;
const touch = event.args[0] as APJS.TouchData;
if (touch.phase === APJS.TouchPhase.Began && APJS.TouchUtils.isScreenPointOnImage(touch.position, this.resetButtonImage)) {
this.resetRound();
return;
}
if (this.currentState === BowlingGameManager.STATE_ROLLING || this.currentState === BowlingGameManager.STATE_SETTLING) {
return;
}
if (touch.phase === APJS.TouchPhase.Began) {
if (this.currentState === BowlingGameManager.STATE_RESULT) {
return;
}
if (this.currentState === BowlingGameManager.STATE_READY) {
this.lockedAimX = this.ballRigidBody.position.x;
this.startCharging();
return;
}
}
if ((touch.phase === APJS.TouchPhase.Ended || touch.phase === APJS.TouchPhase.Canceled) && this.touchActive && this.currentState === BowlingGameManager.STATE_CHARGING) {
this.touchActive = false;
this.launchBall();
}
}
private launchBall(): void {
const ratio = Math.max(0, Math.min(1, this.chargeSeconds / this.maxChargeSeconds));
const speed = this.minLaunchSpeed + (this.maxLaunchSpeed - this.minLaunchSpeed) * ratio;
const spinSpeed = speed * BowlingGameManager.BALL_SPIN_FACTOR;
if (!this.pinsActive) {
this.setPinsActive(true);
}
this.ballRigidBody.velocity = new APJS.Vector3f(0, 0, -speed);
this.ballRigidBody.angularVelocity = new APJS.Vector3f(spinSpeed, 0, 0);
this.ballRigidBody.useGravity = true;
this.logBallAndHeadPin("launch speed=" + speed.toFixed(3) + " aimX=" + this.lockedAimX.toFixed(3));
this.currentState = BowlingGameManager.STATE_ROLLING;
this.roundElapsed = 0;
this.ballStillFrames = 0;
this.updateHud();
}
private isPinTilted(index: number): boolean {
const up = this.pinTransforms[index].getWorldRotation().multiplyVector(new APJS.Vector3f(0, 0, -1));
return up.y < this.fallenDotThreshold;
}
private maybeAssistPins(): void {
for (let i = 0; i < this.pinRigidBodies.length; i++) {
if (this.pinAssistApplied[i] || this.pinCounted[i] || this.isPinTilted(i)) continue;
const rb = this.pinRigidBodies[i];
if (rb.static) continue;
const velocity = rb.velocity;
const horizontalSpeed2 = velocity.x * velocity.x + velocity.z * velocity.z;
if (horizontalSpeed2 < 0.08) continue;
const horizontalSpeed = Math.sqrt(horizontalSpeed2);
const invLen = 1 / horizontalSpeed;
const dirX = velocity.x * invLen;
const dirZ = velocity.z * invLen;
const liftBias = 0.18;
const impulse = Math.max(3.6, Math.min(7.2, horizontalSpeed * 4.8));
const pinPos = this.pinTransforms[i].getWorldPosition();
const pushForce = new APJS.Vector3f(dirX * impulse * 0.35, impulse * liftBias, dirZ * impulse * 0.55);
const pushPoint = new APJS.Vector3f(pinPos.x, pinPos.y + 0.18, pinPos.z);
rb.addForceAt(pushForce, pushPoint, false, APJS.ForceMode3D.Impulse);
rb.addTorque(new APJS.Vector3f(dirZ * impulse * 0.22, 0, -dirX * impulse * 0.22), APJS.ForceMode3D.Impulse);
this.pinAssistApplied[i] = true;
}
}
private updatePinScoreState(): void {
let counted = 0;
for (let i = 0; i < this.pinRigidBodies.length; i++) {
const rb = this.pinRigidBodies[i];
const isTilted = this.isPinTilted(i);
const v = rb.velocity;
const a = rb.angularVelocity;
const v2 = v.x * v.x + v.y * v.y + v.z * v.z;
const a2 = a.x * a.x + a.y * a.y + a.z * a.z;
const isLowMotion = v2 < this.settleVelocityThreshold * this.settleVelocityThreshold && a2 < this.settleAngularThreshold * this.settleAngularThreshold;
if (isLowMotion) {
this.pinLowMotionFrames[i] += 1;
} else {
this.pinLowMotionFrames[i] = 0;
}
if (!this.pinCounted[i] && isTilted) {
this.pinCounted[i] = true;
}
if (this.pinCounted[i]) counted++;
}
if (counted !== this.score) {
this.score = counted;
this.updateHud();
}
}
private freezeQuietPinsAfterBallPass(ballZ: number): void {
for (let i = 0; i < this.pinRigidBodies.length; i++) {
const rb = this.pinRigidBodies[i];
if (rb.static) continue;
if (this.pinLowMotionFrames[i] < BowlingGameManager.PIN_FAST_SLEEP_FRAMES) continue;
if (this.pinCounted[i]) {
this.freezePin(i);
continue;
}
const pinZ = this.pinTransforms[i].getWorldPosition().z;
const ballPassedPin = ballZ <= pinZ - BowlingGameManager.PIN_SLEEP_CLEARANCE_Z;
if (ballPassedPin && !this.isPinTilted(i)) {
this.freezePin(i);
}
}
}
private freezeQuietPinsNow(): void {
for (let i = 0; i < this.pinRigidBodies.length; i++) {
const rb = this.pinRigidBodies[i];
if (rb.static) continue;
if (this.pinLowMotionFrames[i] >= BowlingGameManager.PIN_FAST_SLEEP_FRAMES) {
this.freezePin(i);
}
}
}
private updateAimPingPong(dt: number): void {
const minX = this.ballStartPosition.x - this.ballStartXClamp;
const maxX = this.ballStartPosition.x + this.ballStartXClamp;
let targetX = this.ballRigidBody.position.x + this.aimPingPongSpeed * this.aimDirection * dt;
if (targetX >= maxX) {
targetX = maxX;
this.aimDirection = -1;
} else if (targetX <= minX) {
targetX = minX;
this.aimDirection = 1;
}
this.resetRigidBody(this.ballRigidBody, new APJS.Vector3f(targetX, this.ballStartPosition.y, this.ballStartPosition.z), this.ballStartRotation, false);
}
private updateRollingState(dt: number): void {
this.roundElapsed += dt;
const ballPos = this.ballTransform.getWorldPosition();
if (!this.debugApproachLogged && ballPos.z <= -1.1) {
this.debugApproachLogged = true;
this.logBallAndHeadPin("approach");
}
if (!this.debugPastLogged && ballPos.z <= -1.95) {
this.debugPastLogged = true;
this.logBallAndHeadPin("past");
}
if (!this.pinsActive && ballPos.z <= this.pinActivationZ) {
this.setPinsActive(true);
}
this.updatePinGravityDelay();
//this.maybeAssistPins();
if (this.pinsActive) {
this.updatePinScoreState();
this.freezeQuietPinsAfterBallPass(ballPos.z);
}
if (this.isBallIdle()) {
this.ballStillFrames += 1;
} else {
this.ballStillFrames = 0;
}
if (this.ballStillFrames >= this.ballSettleFrames || ballPos.z < -2.8 || this.roundElapsed >= this.roundTimeoutSeconds) {
if (!this.pinsActive) {
this.setPinsActive(true);
}
this.currentState = BowlingGameManager.STATE_SETTLING;
this.ballStillFrames = 0;
this.updateHud();
}
}
private updateSettlingState(dt: number): void {
this.roundElapsed += dt;
this.updatePinGravityDelay();
//this.maybeAssistPins();
this.updatePinScoreState();
this.freezeQuietPinsNow();
let allPinsQuiet = true;
for (let i = 0; i < this.pinRigidBodies.length; i++) {
if (this.pinLowMotionFrames[i] < this.pinSettleFrames) {
allPinsQuiet = false;
break;
}
}
if (!this.debugResultLogged && (allPinsQuiet || this.roundElapsed >= this.roundTimeoutSeconds + 1.5)) {
this.debugResultLogged = true;
this.logBallAndHeadPin("result");
}
if (allPinsQuiet || this.roundElapsed >= this.roundTimeoutSeconds + 1.5) {
this.currentState = BowlingGameManager.STATE_RESULT;
this.updatePinScoreState();
this.updateHud();
}
}
private updateResultState(): void {
this.updatePinScoreState();
this.freezeQuietPinsNow();
}
onUpdate(dt: number): void {
if (!this.initialized) {
this.initOnce();
return;
}
if (this.pinResetFramesRemaining > 0) {
this.forcePinsToStartPose();
this.pinResetFramesRemaining -= 1;
}
if (this.currentState === BowlingGameManager.STATE_READY) {
this.updateAimPingPong(dt);
if (this.pinsActive) {
this.setPinsActive(false);
}
return;
}
if (this.currentState === BowlingGameManager.STATE_CHARGING) {
this.chargeSeconds += dt;
if (this.chargeSeconds > this.maxChargeSeconds) {
this.chargeSeconds = this.maxChargeSeconds;
}
this.resetRigidBody(this.ballRigidBody, new APJS.Vector3f(this.lockedAimX, this.ballStartPosition.y, this.ballStartPosition.z), this.ballStartRotation, false);
if (this.pinsActive) {
this.setPinsActive(false);
}
this.updateHud();
return;
}
if (this.currentState === BowlingGameManager.STATE_ROLLING) {
this.updateRollingState(dt);
return;
}
if (this.currentState === BowlingGameManager.STATE_SETTLING) {
this.updateSettlingState(dt);
return;
}
if (this.currentState === BowlingGameManager.STATE_RESULT) {
this.updateResultState();
}
}
onDestroy(): void {
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.Touch, this.touchCallback, this);
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.RecordStart, this.onRecordStart, this);
}
}
Example 2 — 3D physics: tap anywhere to apply an upward impulse to a RigidBody object.
@component()
export class TapImpulse extends APJS.BasicScriptComponent {
@serializeProperty
impulseStrength: number = 50;
private rb: APJS.RigidBody;
private inited = false;
private startPos!: APJS.Vector3f;
private startRot!: APJS.Quaternionf;
private startGravityOn = true;
private onTouch = (event: APJS.IEvent) => {
const touch = event.args[0] as APJS.TouchData;
if (touch.phase === APJS.TouchPhase.Began && this.rb) {
// Impulse: one-shot force, object falls back under gravity
this.rb.addForce(
new APJS.Vector3f(0, this.impulseStrength, 0),
APJS.ForceMode3D.Impulse
);
}
};
// RecordStart: reset per Physics3D §"RecordStart Reset for 3D Physics" — full 6-step
// single-body block (velocity → angularVelocity → position → rotation → useGravity →
// accumulators). See GameState §"RecordStart / RecordEnd Lifecycle".
private onRecordStart = (_event: APJS.IEvent) => {
if (!this.inited) return;
this.rb.velocity = new APJS.Vector3f(0, 0, 0);
this.rb.angularVelocity = new APJS.Vector3f(0, 0, 0);
this.rb.position = new APJS.Vector3f(this.startPos.x, this.startPos.y, this.startPos.z);
this.rb.rotation = this.startRot;
this.rb.useGravity = this.startGravityOn;
};
onUpdate(dt: number): void {
if (!this.inited) {
const obj = this.getSceneObject();
if (!obj) return;
this.rb = obj.getComponent("RigidBody") as APJS.RigidBody;
if (!this.rb) return;
this.startPos = new APJS.Vector3f(this.rb.position.x, this.rb.position.y, this.rb.position.z);
this.startRot = this.rb.rotation;
this.startGravityOn = this.rb.useGravity;
this.inited = true;
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.Touch, this.onTouch);
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.onRecordStart);
}
}
onDestroy(): void {
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.Touch, this.onTouch);
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.RecordStart, this.onRecordStart);
}
}