BoxCollider
Represents a box-shaped collider component used for physics collision detection.
| Type | Name | Interface Description |
|---|---|---|
| Variables | size: Vector3f | • Function: Gets or sets the authored size of the box collider. This is the full width, height, and depth of the box shape in local collider space. Defaults to (8, 8, 8), which is the same as the default cube size. Returns The size vector with x, y, z dimensions of the box collider |
| Functions | constructor() |
Examples
constructor()
let obj = new APJS.BoxCollider();
Use Case
Example 1 — Play a sound effect when a 2D physics object collides — uses CollisionEvent2D.Enter on collider's object emitter
@component()
export class CollisionSoundEffect extends APJS.BasicScriptComponent {
@serializeProperty
sfxPlayer!: APJS.SceneObject;
private sfxAudio!: APJS.AudioComponent;
private collider!: APJS.BoxCollider2D;
private initialized = false;
private collisionCallback!: (event: APJS.IEvent) => void;
onUpdate(dt: number): void {
if (this.initialized) return;
if (!this.sfxPlayer) return;
this.sfxAudio = this.sfxPlayer.getComponent("AudioComponent") as APJS.AudioComponent;
// Get any 2D collider on this object (BoxCollider2D or CircleCollider2D)
this.collider = this.getSceneObject().getComponent("BoxCollider2D") as APJS.BoxCollider2D;
if (!this.sfxAudio || !this.collider) return;
// CRITICAL: enable collision events before registering listener
this.collider.emitCollisionEvent = true;
this.collisionCallback = (event: APJS.IEvent) => {
// Play SFX on each collision
this.sfxAudio.stop();
this.sfxAudio.loopCount = 1;
this.sfxAudio.play();
};
// Use object emitter on the COLLIDER (not global emitter)
const emitter = APJS.EventManager.getObjectEmitter(this.collider);
emitter.on(APJS.CollisionEvent2D.Enter, this.collisionCallback, this);
this.initialized = true;
}
onDestroy(): void {
if (this.collider && this.collisionCallback) {
const emitter = APJS.EventManager.getObjectEmitter(this.collider);
emitter.off(APJS.CollisionEvent2D.Enter, this.collisionCallback, this);
}
}
}
Example 2 — Hoop / basketball-ring trigger scoring — detects a projectile passing through a horizontal hoop using two stacked thin BoxCollider triggers (TopTrigger above, B…
@component()
export class HoopScorer extends APJS.BasicScriptComponent {
@serializeProperty topTrigger!: APJS.SceneObject;
@serializeProperty bottomTrigger!: APJS.SceneObject;
@serializeProperty ball!: APJS.SceneObject;
@serializeProperty scoreText!: APJS.SceneObject;
@serializeProperty mode: number = 0;
@serializeProperty dualWindowSeconds: number = 0.6;
@serializeProperty gravityY: number = -9.81;
// Scene authoring (DSL):
// - TopTrigger: Empty Object @ (0, 1.7, -4) + BoxCollider size (1.5, 0.2, 1.5), isTangible=false.
// - BottomTrigger: Empty Object @ (0, 1.3, -4) + BoxCollider size (1.5, 0.2, 1.5), isTangible=false.
// - RimMarker: Cube scaled (0.2, 0.02, 0.2) at (0, 1.5, -4) -- visual only, no collider.
// - Ball: Sphere with RigidBody + SphereCollider; either drop-tested or fired by ChargeMeterThrow.
// - ScoreText: 2D Text in safe zone, sibling under 2D Camera.
//
// CollisionEvent footgun: pass the COLLIDER component, NOT the SceneObject, into
// APJS.EventManager.getObjectEmitter(...). Passing the SceneObject silently fails.
// Also: collider.emitCollisionEvent must be set to true on init -- default is false.
private inited = false;
private topCol!: APJS.BoxCollider;
private botCol!: APJS.BoxCollider;
private ballName = "";
private scoreTextComp!: APJS.Text;
private score = 0;
private topArmed = false;
private topArmTime = 0;
private now = 0;
private topHandler!: (e: APJS.IEvent) => void;
private bottomHandler!: (e: APJS.IEvent) => void;
private recordStartCb!: () => void;
private initOnce(): void {
this.topCol = this.topTrigger.getComponent("BoxCollider") as APJS.BoxCollider;
this.botCol = this.bottomTrigger.getComponent("BoxCollider") as APJS.BoxCollider;
if (!this.topCol || !this.botCol) return;
this.scoreTextComp = this.scoreText.getComponent("Text") as APJS.Text;
if (this.ball) this.ballName = this.ball.name;
this.topCol.emitCollisionEvent = true;
this.botCol.emitCollisionEvent = true;
this.topHandler = (e: APJS.IEvent) => this.onTopEnter(e);
this.bottomHandler = (e: APJS.IEvent) => this.onBottomEnter(e);
APJS.EventManager.getObjectEmitter(this.topCol).on(APJS.CollisionEvent.Enter, this.topHandler, this);
APJS.EventManager.getObjectEmitter(this.botCol).on(APJS.CollisionEvent.Enter, this.bottomHandler, this);
if (typeof this.gravityY === "number") {
const g = APJS.Physics3D.gravity;
APJS.Physics3D.gravity = new APJS.Vector3f(g.x, this.gravityY, g.z);
console.log("[HoopScorer] gravity set to y=" + this.gravityY);
}
this.recordStartCb = () => this.resetScore();
APJS.EventManager.getGlobalEmitter().on(APJS.EventType.RecordStart, this.recordStartCb, this);
this.refreshScoreText();
console.log("[HoopScorer] init mode=" + this.mode + " ball=" + this.ballName);
this.inited = true;
}
private isBall(other: APJS.SceneObject | null): boolean {
if (!other) return false;
if (this.ballName && other.name === this.ballName) return true;
return !this.ballName;
}
private onTopEnter(event: APJS.IEvent): void {
const infos = event.args[0] as APJS.CollisionInfo[];
for (const info of infos) {
if (!this.isBall(info.otherObject)) continue;
console.log("[HoopScorer] TOP enter @t=" + this.now.toFixed(3));
if (this.mode === 0) {
this.score++;
this.refreshScoreText();
} else {
this.topArmed = true;
this.topArmTime = this.now;
}
return;
}
}
private onBottomEnter(event: APJS.IEvent): void {
const infos = event.args[0] as APJS.CollisionInfo[];
for (const info of infos) {
if (!this.isBall(info.otherObject)) continue;
console.log("[HoopScorer] BOTTOM enter @t=" + this.now.toFixed(3) + " armed=" + this.topArmed);
if (this.mode === 1 && this.topArmed) {
const dt = this.now - this.topArmTime;
if (dt <= this.dualWindowSeconds) {
this.score++;
this.refreshScoreText();
console.log("[HoopScorer] DUAL score! window=" + dt.toFixed(3) + "s");
} else {
console.log("[HoopScorer] DUAL miss \u2014 window expired (" + dt.toFixed(3) + "s)");
}
this.topArmed = false;
}
return;
}
}
private refreshScoreText(): void {
if (this.scoreTextComp) this.scoreTextComp.text = "Hoops: " + this.score;
}
private resetScore(): void {
this.score = 0;
this.topArmed = false;
this.topArmTime = 0;
this.refreshScoreText();
}
onUpdate(dt: number): void {
if (!this.inited) {
if (this.topTrigger && this.bottomTrigger && this.scoreText) {
this.initOnce();
}
return;
}
this.now += dt;
if (this.topArmed && this.now - this.topArmTime > this.dualWindowSeconds) {
this.topArmed = false;
}
}
onDestroy(): void {
if (this.topHandler && this.topCol) {
APJS.EventManager.getObjectEmitter(this.topCol).off(APJS.CollisionEvent.Enter, this.topHandler, this);
}
if (this.bottomHandler && this.botCol) {
APJS.EventManager.getObjectEmitter(this.botCol).off(APJS.CollisionEvent.Enter, this.bottomHandler, this);
}
if (this.recordStartCb) {
APJS.EventManager.getGlobalEmitter().off(APJS.EventType.RecordStart, this.recordStartCb, this);
}
}
}