logo

Babylon.js Market

AIGoalSeek

ai

AIGoalSeek

1
Unlock

Install with the CLI:

bjs download AIGoalSeek

AIGoalSeek

Steers an entity to push a ball into a chosen goal while veering it away from goals it must not score in.

What it does

Each frame the AIGoalSeekSystem reads the live world positions of the AI, the ball (resolved by ballEntity id or ballTag), the candidate targetGoals, and any avoid goals (by avoidGoals ids or avoidTag), and hands them to a pure core. The core scores each target by priority, pushing-alignment, and proximity, picks the best one, then chooses one of three actions: reposition to the ideal spot behind the ball, approach the ball once near that spot, or push through it when close and well-aligned. Avoid goals add a perpendicular nudge whenever the current push direction points the ball toward a bad goal. The resulting unit XZ vector moves the AI's MeshPrimitive through the renderer adapter, and the same vector is broadcast on the EventBus as aiGoalSeek.steeringComputed so movement blenders and debug overlays read events instead of the component. The first frame the AI enters push range fires a one-shot aiGoalSeek.pushing.started.

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 AIGoalSeek

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 AIGoalSeek
    bjs download AIGoalSeek

    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
{
  "Striker": {
    "tags": ["ai"],
    "components": {
      "MeshPrimitive": { "primitive": "capsule", "radius": 0.5, "height": 1.6, "position": [-6, 0.8, 0] },
      "AIGoalSeek": {
        "ballEntity": "Ball",
        "targetGoals": [{ "entityId": "GoalRight", "priority": 1 }],
        "avoidGoals": ["GoalLeft"],
        "speed": 3
      }
    }
  }
}

Props

  • enabled (boolean, default true) — when false, this AI is skipped each frame; toggle at runtime via aiGoalSeek.enabled.set.
  • ballEntity (string, default 'Ball') — entity id of the ball to push; tried first.
  • ballTag (string | null, default null) — tag fallback when the ball id isn't known up front; the first tagged entity wins.
  • targetGoals ({ entityId, priority? }[], default []) — goals to push toward; priority (default 1) breaks scoring ties.
  • avoidGoals (string[], default []) — explicit entity ids of goals to veer the ball away from.
  • avoidTag (string | null, default null) — tag pulling in additional avoid goals, e.g. "own-goal".
  • speed (number, default 3) — world units/second the AI moves along the force direction.
  • approachDistance (number, default 2) — how far behind the ball the ideal spot sits, world units.
  • pushDistance (number, default 1.5) — AI-to-ball distance at which it stops positioning and starts pushing.
  • pushAlignmentThreshold (number, default 0.7) — minimum dot(AI→ball, ball→goal) required to count as aligned for a push.
  • avoidanceStrength (number, default 2) — how hard the AI veers when its push points at a bad goal.
  • avoidanceAlignmentThreshold (number, default 0.3) — dot above which a bad goal is "in the firing line" and triggers avoidance.
  • weight (number, default 1) — emitted alongside the steering vector; consumers scale the force by it.
  • minForceLength (number, default 0.03) — steering vectors shorter than this are dropped; no force, no event.

Events

  • Emits aiGoalSeek.steeringComputed (AIGoalSeekEvents.STEERING_COMPUTED) every frame a force is produced — { entityId, force: {x,y,z}, weight, mode, chosenTargetId }, where mode is 'idle' | 'reposition' | 'approach' | 'push'.
  • Emits aiGoalSeek.pushing.started (AIGoalSeekEvents.PUSHING_STARTED) once on the frame the AI first enters push mode — { entityId, chosenTargetId }.
  • Listens for aiGoalSeek.enabled.set (AIGoalSeekInputEvents.SET_ENABLED) — { entityId, enabled } — to toggle a specific AI.

Dependencies

MeshPrimitive — supplies the AI's mesh, which the System reads for position and moves along the force direction. The ball and every goal it resolves must also carry a MeshPrimitive so the System can read their world positions; a goal entity with no mesh is skipped that frame.

Notes

  • The whole mechanic runs in the XZ plane — Y is read but never steered, so it sits on flat play areas.
  • force.y is always 0 and weight rides on every event so a downstream blender can mix this steering with others without re-reading the component.
  • The AI only moves while out.hasForce; a frame that produces a sub-minForceLength vector emits nothing and leaves the mesh where it is, which also clears the push edge so the next entry re-fires pushing.started.
  • Targets are re-scored every frame, so the chosen goal can switch mid-chase as positions shift; priority only nudges ties, it doesn't lock a goal in.
  • Live edits to the params (from the tuning panel) are picked up before the core runs, so sliders take effect the same frame.

More like this

MeshPrimitive
AIKick
AIZone
AiPilot
BallPossession
BallPursuit
BallReset
Enemy
EnemySpawner
FreighterChain
Goal
LineOfSight
PlayerInputBridge
PokerAI
PokerAIBill
PokerAICali
PokerAIRandy
PokerAIShark
PokerAISurge
PokerAITilt
Scoreboard
SoccerDirector
WaveDirector

Was this page helpful?

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

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search