Get the FREE Ultimate OpenClaw Setup Guide →

libgdx-gdx-ai

npx machina-cli add skill kyu-n/gdx-claude-skills/libgdx-gdx-ai --openclaw
Files (1)
SKILL.md
11.4 KB

libGDX gdx-ai Extension

Reference for steering behaviors, A* pathfinding, behavior trees, finite state machines, and scheduling in the gdx-ai extension.

Dependency

// In core module only — pure Java, no native dependencies
implementation "com.badlogicgames.gdx:gdx-ai:$aiVersion"

Works on all backends including headless. No platform-specific jars needed.

Steering Behaviors

Steerable Interface

Steerable<T extends Vector<T>> extends Location<T> and Limiter. Your entity implements it, providing: getPosition(), getOrientation(), setOrientation(), newLocation(), vectorToAngle(), angleToVector(), getLinearVelocity(), getAngularVelocity(), getBoundingRadius(), isTagged()/setTagged(), and all Limiter getters/setters for max linear/angular speed/acceleration.

vectorToAngle/angleToVector convention: vectorToAngle(v) = atan2(-v.x, v.y), angleToVector(out, a) sets out.x = -sin(a), out.y = cos(a). Getting this wrong causes steering to point in the wrong direction.

SteeringAcceleration — The Output Container

SteeringAcceleration<T> has public fields T linear and float angular. Create ONE per entity, reuse each frame: new SteeringAcceleration<>(new Vector2()).

calculateSteering() only fills the output container — it does NOT move the entity. You must apply the acceleration to velocity and position yourself each frame.

Common Setup Pattern

// 1. Create behavior — target via constructor or setter
Arrive<Vector2> arrive = new Arrive<>(steerable, targetLocation)
    .setArrivalTolerance(0.5f)        // stop within this distance
    .setDecelerationRadius(3f)        // start slowing here
    .setTimeToTarget(0.1f);           // response speed (lower = snappier)
// Or set target separately:
// Arrive<Vector2> arrive = new Arrive<>(steerable)
//     .setTarget(targetLocation)
//     ...;

// 2. Each frame — calculate then apply
arrive.calculateSteering(steeringOutput);
linearVelocity.mulAdd(steeringOutput.linear, deltaTime);
if (linearVelocity.len2() > maxLinearSpeed * maxLinearSpeed)
    linearVelocity.setLength(maxLinearSpeed);
position.mulAdd(linearVelocity, deltaTime);

Target is Location<T>, not Steerable<T>. Any Location implementation works. Passing another Steerable works because Steerable extends Location.

Individual Behaviors

Most-used: Seek (move toward target), Flee (move away), Arrive (decelerate to stop at target), Wander (random-looking movement), Pursue/Evade (predict moving target's future position).

Also available: Face, LookWhereYouGoing, ReachOrientation, FollowPath, FollowFlowField, RaycastObstacleAvoidance, Hide (extends Arrive — uses Proximity to find obstacles but is an individual behavior).

Group Behaviors

Require a Proximity<T> to detect nearby agents: Separation, Alignment, Cohesion, CollisionAvoidance.

PrioritySteering and BlendedSteering

PrioritySteering — try behaviors in order, use first that produces non-zero output:

PrioritySteering<Vector2> priority = new PrioritySteering<>(steerable, 0.001f);
//                                                          epsilon ↑ threshold
// below this magnitude, output considered "zero" → try next behavior
priority.add(obstacleAvoidance);  // highest priority
priority.add(arrive);             // fallback

BlendedSteering — weighted sum of multiple behaviors:

BlendedSteering<Vector2> blended = new BlendedSteering<>(steerable);
blended.add(arrive, 0.7f);         // 70% arrive
blended.add(separation, 0.3f);     // 30% separation

Limiter Interface

Steerable extends Limiter (caps linear/angular speed/acceleration). Behaviors read limits from the owner. Override per-behavior via behavior.setLimiter(customLimiter).

Pathfinding

Graph Setup

Implement IndexedGraph<N> with three methods: getConnections(node) returns Array<Connection<N>>, getIndex(node) returns a unique int index, getNodeCount() returns total node count.

Connection<N> has three methods: getCost(), getFromNode(), getToNode(). For simple weighted edges, use DefaultConnection<N>.

Heuristic and Pathfinding

Heuristic<TileNode> heuristic = (node, end) ->                       // Manhattan for 4-dir
    Math.abs(end.x - node.x) + Math.abs(end.y - node.y);
IndexedAStarPathFinder<TileNode> pathFinder = new IndexedAStarPathFinder<>(graph);
DefaultGraphPath<TileNode> outPath = new DefaultGraphPath<>();
boolean found = pathFinder.searchNodePath(startNode, endNode, heuristic, outPath);
outPath.clear();                                                      // MUST clear before reuse

HierarchicalPathFinder — for large maps; searches at multiple abstraction levels. Use when standard A* is too slow.

NavMesh

NavMesh is NOT included in gdx-ai. Use a third-party library or build your own graph from a nav mesh. gdx-ai pathfinding works with any graph you implement — tile grids, waypoint graphs, or nav mesh triangulations — but provides no mesh generation or triangle-based nav classes.

Behavior Trees

BehaviorTree and Task

BehaviorTree<E> is the root container. E is the blackboard type (your data object shared by all tasks). Call tree.step() each frame.

BehaviorTree<Enemy> tree = parser.parse(Gdx.files.internal("ai/enemy.tree"), enemy);
// In render/update:
tree.step();

No GL context needed. BehaviorTree is pure logic — works in headless, server, or testing environments.

LeafTask

Extend LeafTask<E> for custom logic. Override execute() returning Status:

public class IsEnemyVisible extends LeafTask<Enemy> {
    @Override
    public Status execute() {
        Enemy e = getObject();  // blackboard access
        return e.canSeePlayer() ? Status.SUCCEEDED : Status.FAILED;
    }

    @Override
    protected Task<Enemy> copyTo(Task<Enemy> task) { return task; }
}

Status values: RUNNING (continue next frame), SUCCEEDED, FAILED.

Branch Tasks

Sequence = AND (run children in order, fail on first failure). Selector = OR (run children in order, succeed on first success). Also: Parallel (run all simultaneously), RandomSelector, RandomSequence.

Decorator Tasks

AlwaysFail, AlwaysSucceed, Invert (flip SUCCEEDED/FAILED), Repeat (N times or indefinitely), UntilFail, UntilSuccess, SemaphoreGuard (limits concurrent subtree access).

Note: The class is SemaphoreGuard, not Semaphore. Also available: Wait<E> (leaf — pauses for a duration) and Include<E> (decorator — includes an external subtree).

Text Format (.tree files)

BehaviorTreeParser loads .tree files. Indentation defines hierarchy. Without import, task names must be fully-qualified. With import com.mygame.ai.tasks.*, unqualified names work.

import com.mygame.ai.tasks.*
selector
  sequence
    isEnemyVisible
    attack damage:20
  wander

Task attributes use @TaskAttribute annotation on fields. Set in .tree as taskName attrName:value.

BehaviorTreeParser<Enemy> parser = new BehaviorTreeParser<>();
BehaviorTree<Enemy> tree = parser.parse(Gdx.files.internal("ai/enemy.tree"), enemy);

Blackboard Pattern

E is your blackboard — all tasks access it via getObject(). Can be the entity itself (BehaviorTree<Enemy>) or a dedicated data object (BehaviorTree<AIContext>).

Finite State Machines

State Interface

State<E> has four methods: enter(E), update(E), exit(E), onMessage(E, Telegram). Idiomatic pattern: implement as an enum (enum EnemyState implements State<Enemy>).

DefaultStateMachine vs StackStateMachine

DefaultStateMachine<E, S> — single active state, changeState() replaces it. StackStateMachine<E, S> — each changeState() pushes current state onto stack; revertToPreviousState() pops. Arbitrary depth — use for interruptible states (stun, cutscene, pause).

DefaultStateMachine<Enemy, EnemyState> fsm = new DefaultStateMachine<>(entity, EnemyState.IDLE);
// or StackStateMachine<Enemy, EnemyState> fsm = new StackStateMachine<>(entity, EnemyState.IDLE);

Update each frame: fsm.update();

MessageManager

Inter-entity communication with optional delay. Entities implement Telegraph with handleMessage(Telegram) (typically delegates to fsm.handleMessage(telegram)).

Sending messages — Telegraph references, NOT integer IDs:

MessageManager dispatcher = MessageManager.getInstance();
dispatcher.dispatchMessage(sender, receiver, MSG_SPOTTED);         // immediate
dispatcher.dispatchMessage(sender, receiver, MSG_DAMAGE, 25f);     // with extra data
dispatcher.dispatchMessage(2.0f, sender, receiver, MSG_BACKUP);    // delayed 2s
dispatcher.addListener(receiver, MSG_SPOTTED);                     // register for type

You MUST update each frame for delayed messages: GdxAI.getTimepiece().update(deltaTime); then MessageManager.getInstance().update();

Scheduling

LoadBalancingScheduler distributes AI updates across frames. Entities implement Schedulable (method run(long nanoTimeToRun)). Add via scheduler.addWithAutomaticPhasing(entity, framesPerRun). Call scheduler.run(nanoBudget) each frame.

Common Mistakes

  1. Forgetting to set a target on Arrive/Seek — Set via constructor new Arrive<>(steerable, targetLocation) or via .setTarget(location). Without a target, calculateSteering() throws NullPointerException.
  2. Claiming NavMesh is built-in — gdx-ai has NO NavMesh classes. Build your own graph from a nav mesh.
  3. Confusing Sequence and Selector — Sequence = AND (all must succeed). Selector = OR (first success wins).
  4. Using Semaphore instead of SemaphoreGuard — The decorator class is SemaphoreGuard.
  5. Not calling tree.step() each frame — Behavior trees don't update automatically.
  6. Dispatching messages with integer IDsMessageManager.dispatchMessage() takes Telegraph object references as sender/receiver, not integer IDs.
  7. Not updating GdxAI timepiece — Call GdxAI.getTimepiece().update(deltaTime) each frame, or delayed messages and scheduling won't work.
  8. Mixing Vector2/Vector3 generics — All steering types for one entity must use the same vector type consistently.
  9. Thinking BehaviorTree needs GL — BT, FSM, pathfinding, and scheduling are all pure logic. They work in headless environments.
  10. Forgetting copyTo() in LeafTask — Required for tree cloning. Minimal implementation: return task;
  11. Calling calculateSteering() without applying the result — Behaviors only compute acceleration. You must integrate it into velocity and position yourself each frame.
  12. Reusing DefaultGraphPath without calling clear() — Old path nodes accumulate alongside new ones. Always call outPath.clear() before a new searchNodePath call.

Source

git clone https://github.com/kyu-n/gdx-claude-skills/blob/master/skills/libgdx-gdx-ai/SKILL.mdView on GitHub

Overview

This skill provides practical guidance for leveraging the gdx-ai extension in libGDX projects. It covers steering behaviors, A* pathfinding, behavior trees, finite state machines, and the LoadBalancingScheduler. Use it to diagnose common issues like steering output not applying, incorrect graph setups, behavior trees not updating, or FSM messages not delivering.

How This Skill Works

Implement Steerable on your entities and compute SteeringAcceleration each frame with calculateSteering, then apply the resulting linear and angular velocity to update position. Use behaviors such as Arrive, Seek, Wander, Pursue, and Evade, and combine them with PrioritySteering or BlendedSteering. For trees and states, rely on BehaviorTree, BehaviorTreeParser, DefaultStateMachine, and MessageManager to drive decisions and messaging.

When to Use It

  • When steering output is not applied to the entity
  • When the A* pathfinding graph seems misconfigured or wrong
  • When a behavior tree stops updating or does not execute
  • When FSM messages are not delivered or handled correctly
  • When debugging LoadBalancingScheduler or scheduling of tasks in gdx-ai

Quick Start

  1. Step 1: Add the dependency in core: implementation \"com.badlogicgames.gdx:gdx-ai:$aiVersion\"
  2. Step 2: Implement Steerable for your entity and create a steering behavior (e.g., Arrive or Seek)
  3. Step 3: In your game loop, call calculateSteering on the behavior and apply the output to velocity and position using deltaTime

Best Practices

  • Always implement Steerable and honor the vectorToAngle/angleToVector convention to ensure correct steering directions
  • Create and reuse a single SteeringAcceleration per entity to minimize allocations
  • Keep target as a Location, not a Steerable, for Arrive and Seek patterns
  • Prefer PrioritySteering for high-priority outputs like obstacle avoidance; use BlendedSteering for combining outputs that don’t conflict
  • Log, visualize, and verify graphs, heuristics, and BT/FSM structures to confirm correct setup and behavior

Example Use Cases

  • A guard NPC uses Seek to pursue a target and Wander when idle for natural movement
  • A drone uses Pursue and Evade with Proximity groups for coordinated combat avoidance
  • An AI agent navigates with IndexedAStarPathFinder on a Graph to reach waypoints
  • An enemy uses BehaviorTree with LeafTask, Sequence, and Selector loaded from a .tree file
  • A unit runs a DefaultStateMachine with MessageManager to respond to in-game events

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers