Install
$ agentstack add skill-kyu-n-gdx-claude-skills-libgdx-gdx-ai ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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> extends Location 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 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 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 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, not Steerable. 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 to detect nearby agents: Separation, Alignment, Cohesion, CollisionAvoidance.
PrioritySteering and BlendedSteering
PrioritySteering — try behaviors in order, use first that produces non-zero output:
PrioritySteering 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 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 with three methods: getConnections(node) returns Array>, getIndex(node) returns a unique int index, getNodeCount() returns total node count.
Connection has three methods: getCost(), getFromNode(), getToNode(). For simple weighted edges, use DefaultConnection.
Heuristic and Pathfinding
Heuristic heuristic = (node, end) -> // Manhattan for 4-dir
Math.abs(end.x - node.x) + Math.abs(end.y - node.y);
IndexedAStarPathFinder pathFinder = new IndexedAStarPathFinder<>(graph);
DefaultGraphPath 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 is the root container. E is the blackboard type (your data object shared by all tasks). Call tree.step() each frame.
BehaviorTree 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 for custom logic. Override execute() returning Status:
public class IsEnemyVisible extends LeafTask {
@Override
public Status execute() {
Enemy e = getObject(); // blackboard access
return e.canSeePlayer() ? Status.SUCCEEDED : Status.FAILED;
}
@Override
protected Task copyTo(Task 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 (leaf — pauses for a duration) and Include (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 parser = new BehaviorTreeParser<>();
BehaviorTree 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) or a dedicated data object (BehaviorTree).
Finite State Machines
State Interface
State has four methods: enter(E), update(E), exit(E), onMessage(E, Telegram). Idiomatic pattern: implement as an enum (enum EnemyState implements State).
DefaultStateMachine vs StackStateMachine
DefaultStateMachine — single active state, changeState() replaces it. StackStateMachine — each changeState() pushes current state onto stack; revertToPreviousState() pops. Arbitrary depth — use for interruptible states (stun, cutscene, pause).
DefaultStateMachine fsm = new DefaultStateMachine<>(entity, EnemyState.IDLE);
// or StackStateMachine 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
- Forgetting to set a target on Arrive/Seek — Set via constructor
new Arrive<>(steerable, targetLocation)or via.setTarget(location). Without a target,calculateSteering()throwsNullPointerException. - Claiming NavMesh is built-in — gdx-ai has NO NavMesh classes. Build your own graph from a nav mesh.
- Confusing Sequence and Selector — Sequence = AND (all must succeed). Selector = OR (first success wins).
- Using
Semaphoreinstead ofSemaphoreGuard— The decorator class isSemaphoreGuard. - Not calling
tree.step()each frame — Behavior trees don't update automatically. - Dispatching messages with integer IDs —
MessageManager.dispatchMessage()takesTelegraphobject references as sender/receiver, not integer IDs. - Not updating GdxAI timepiece — Call
GdxAI.getTimepiece().update(deltaTime)each frame, or delayed messages and scheduling won't work. - Mixing Vector2/Vector3 generics — All steering types for one entity must use the same vector type consistently.
- Thinking BehaviorTree needs GL — BT, FSM, pathfinding, and scheduling are all pure logic. They work in headless environments.
- Forgetting
copyTo()in LeafTask — Required for tree cloning. Minimal implementation:return task; - Calling
calculateSteering()without applying the result — Behaviors only compute acceleration. You must integrate it into velocity and position yourself each frame. - Reusing
DefaultGraphPathwithout callingclear()— Old path nodes accumulate alongside new ones. Always calloutPath.clear()before a newsearchNodePathcall.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kyu-n
- Source: kyu-n/gdx-claude-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.