logo

Babylon.js Market

Goal

gameplay

Goal

1
Unlock

Install with the CLI:

bjs download Goal

Goal

An axis-aligned scoring volume that fires once when a tracked ball enters it.

What it does

GoalSystem watches one ball entity per goal. Each frame it samples that ball's world position through the renderer adapter and feeds it to a pure core (Goal.core), which runs an inclusive AABB containment test against center ± size/2. Scoring fires only on the rising edge — not inside last frame, inside this frame, cooldown clear — so a ball resting in the net can't re-trigger, and a cooldown tail keeps a few frames ignored after each score. On a score the System emits goal.scored (the full payload) and a companion goal.score.added so a Score system tallies points without a direct reference. With resetBallOnScore set, it teleports the ball to resetPosition and emits goal.ball.reset. It resolves the ball's mesh handle from meshprimitive.created (sweeping existing MeshPrimitive handles at init) and clears its counter on the goal.reset input event.

Use it in a scene

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Fastest path

bjs download scene Goal

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add Goal
    bjs download Goal

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Net": {
    "components": {
      "Goal": {
        "ballEntity": "Ball",
        "ownerEntity": "Player",
        "center": [0, 1, 8],
        "size": [4, 3, 1],
        "points": 1,
        "cooldown": 2,
        "resetBallOnScore": true,
        "resetPosition": [0, 2, 0]
      }
    }
  }
}

Props

  • ballEntity (string, default "") — id of the entity whose mesh this goal watches. Empty = nothing tracked.
  • ownerEntity (string, default "") — id of the scoring player, echoed back on events for Score routing.
  • center ([x,y,z], default [0, 1, 0]) — world-space center of the trigger volume.
  • size ([x,y,z], default [2, 2, 2]) — full width/height/depth; half-extents are size/2.
  • points (number, default 1) — points awarded per crossing.
  • cooldown (number, default 2) — seconds the goal ignores new entries after a score.
  • resetBallOnScore (boolean, default true) — teleport the ball to resetPosition after scoring.
  • resetPosition ([x,y,z], default [0, 2, 0]) — world point the ball is moved to on reset.

Events

  • Emits goal.scored (GoalEvents.SCORED) on the scoring frame — { goalEntityId, triggerEntityId, ownerEntity, points, totalScores }.
  • Emits goal.score.added (GoalEvents.SCORE_ADDED) alongside it — { ownerEntity, points }, the shape a Score system consumes.
  • Emits goal.ball.reset (GoalEvents.BALL_RESET) after teleporting the ball — { entityId, position: { x, y, z } }.
  • Listens for goal.reset (GoalInputEvents.RESET) — { entityId? }; clears the score counter and cooldown on every goal, or just the matching one when entityId is set.
  • Listens for meshprimitive.created{ entityId, handle }; how it learns the ball's mesh handle.

Dependencies

MeshPrimitive — the ball's mesh, named by ballEntity. The System reads that entity's world position through the renderer to test containment, and writes back to it via setMeshPosition on reset. The goal entity itself needs no mesh — center/size define the volume in world space.

Notes

  • The volume is invisible: nothing renders for the goal itself. Give the goal entity a MeshPrimitive (or place real net geometry at center) if you want players to see where it is.
  • The panel writes live edits straight into the core, so the System never pushes comp.params back in during onUpdate — runtime tuning of center/size/cooldown survives the frame.
  • resetPosition is reused as a single tuple inside the core; the System reads it on the scoring frame and copies the components out, so there's no stale-reference trap.
  • A score still emits even with resetBallOnScore off; the reset and goal.ball.reset are skipped, and a ball left inside the volume waits out the cooldown before it can score again.

More like this

BallReset
MeshPrimitive
Score
SoccerDirector
AIGoalSeek
AIKick
AIZone
BallPossession
BallPursuit
GameConfig
MissionDirector
PlayerInputBridge
PokerBetting
PokerHandEval
PokerMultiplayer
PokerTableDirector
RespawnTimer
Scoreboard
TurnPacer
WaveDirector

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search