跳到正文
cccskills
免费在 GitHub 公开

game-state-machines-and-ai

Use when adding character/enemy/game-flow states or NPC behaviour - explicit transition tables instead of flag soup, hierarchical and pushdown machines, behaviour trees per engine, navmesh pathfinding, AI tick budgets, and table-driven tests.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md6.2 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Game State Machines and AI

Overview

Character controllers, enemies, menus and match flow are all state machines whether or not the code admits it. When they are written as a pile of booleans (isJumping, isAttacking, isStunned), illegal combinations become reachable and every new state touches every branch. Make the states and the legal transitions explicit, and test the table.

Core principle: One current state, an explicit list of (state, event) → next state, and enter/exit hooks. Anything not in the table cannot happen.

Flags vs a table

// ❌ flag soup: can be jumping AND stunned AND attacking; every rule checks every flag
if (input.attack && !this.isStunned && !this.isDead && (!this.isJumping || this.canAirAttack)) { ... }

// ✅ explicit states and transitions
type State = "idle" | "run" | "jump" | "attack" | "stunned" | "dead";
type Event = "move" | "stop" | "jump" | "land" | "attack" | "attackEnd" | "hit" | "recover" | "die";

const transitions: Record<State, Partial<Record<Event, State>>> = {
  idle:    { move: "run", jump: "jump", attack: "attack", hit: "stunned", die: "dead" },
  run:     { stop: "idle", jump: "jump", attack: "attack", hit: "stunned", die: "dead" },
  jump:    { land: "idle", hit: "stunned", die: "dead" },
  attack:  { attackEnd: "idle", hit: "stunned", die: "dead" },
  stunned: { recover: "idle", die: "dead" },
  dead:    {},
};

export class Fsm {
  constructor(public state: State, private hooks: Hooks) {}
  send(e: Event): boolean {
    const next = transitions[this.state][e];
    if (!next) return false;
    this.hooks.exit?.(this.state); this.state = next; this.hooks.enter?.(next);
    return true;
  }
}

The same shape in C# is an enum plus a Dictionary<(State, Event), State> built once; in GDScript a Dictionary of dictionaries or one node per state (godot-gdscript-patterns); in Unreal a UENUM with a switch, or StateTree for larger designs.

Testing the table

it.each([
  ["idle", "jump", "jump"], ["jump", "attack", "jump"],   // no air attack in this design
  ["stunned", "move", "stunned"], ["dead", "recover", "dead"],
  ["attack", "hit", "stunned"],
])("%s + %s → %s", (from, ev, to) => {
  const fsm = new Fsm(from as State, {});
  fsm.send(ev as Event);
  expect(fsm.state).toBe(to);
});

Cover every row that matters to the design plus the rejections ("dead ignores everything"). A new state lands with its rows in this test.

Bigger machines

  • Hierarchical: shared transitions (hit → stunned, die → dead) live on a parent "alive" state instead of being copied into every child.
  • Pushdown (a stack): pause and nested menus — push paused, pop back to exactly where play was.
  • Concurrent: movement and weapon as two machines rather than the product of their states.
  • Timed states: a state that ends after 0.4 s counts seconds from enter, on the simulation clock (game-loop-and-time).

Behaviour trees and their alternatives

Reach for a behaviour tree when an NPC has many prioritised goals with conditions (patrol, investigate, chase, flee, heal). Use the one the repository already has:

EngineBuilt in / common
UnrealBehavior Trees + Blackboard + EQS; StateTree for state-based logic
UnityBehavior package (com.unity.behavior); third-party trees if already present
GodotAddons such as LimboAI or Beehave; a node-based state machine for simple NPCs
BevyCrates such as big-brain (utility AI) or a hand-rolled tree
WebA small hand-rolled tree, or the repository's library

Rules either way: leaf actions are small and testable alone, the tree reads a blackboard instead of reaching into scene objects, conditions are cheap, and running actions can be aborted cleanly when a higher-priority branch wins. Utility AI (score each option, pick the best) suits needs-driven NPCs; keep its curves in data (data-driven-gameplay-design).

Navigation

EngineNavmesh and agents
UnityAI Navigation package: NavMeshSurface (bake), NavMeshAgent.SetDestination
GodotNavigationRegion2D/3D + NavigationAgent2D/3D; query only after the navigation map has synced (wait one physics frame after load)
UnrealNavMeshBoundsVolume, AAIController::MoveToLocation/MoveToActor, async path queries
Webrecast-navigation (WASM) or grid A* for 2D
BevyCrates such as vleue_navigator or oxidized_navigation
  • Do not re-path every frame: re-path when the target moved more than a threshold or on a timer, and cap path requests per frame (time-slicing).
  • Grid A* for tile games: precompute the grid, reuse open/closed buffers, and early-out on unreachable targets.
  • Avoidance (RVO) and path following are separate concerns from path finding.

AI budget

  • Perception (line-of-sight, hearing) at 5–10 Hz, staggered across agents so they do not all think on the same frame.
  • Far or off-screen agents think less often (level of detail for AI).
  • Measure the AI system's frame cost against its slice of the budget (game-performance-budget).
  • Seed any randomness in decisions from the simulation's RNG, so a bug report with a seed reproduces.

Common Mistakes

  • Transition logic spread across Update branches instead of one table.
  • State entry effects (play animation, start timer) duplicated at every place that changes the state — put them in enter.
  • An animation state machine (Animator, AnimationTree, Anim Blueprint) used as the gameplay authority — it presents the gameplay state, it does not own it.
  • Every agent calling the pathfinder every frame.

Red Flags

  • Three or more booleans describing what a character is "doing".
  • A new state added with no test rows.
  • An if (state == X) check outside the state machine that decides a transition.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1182026年10月21日 更新

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1182026年10月21日 更新

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1182026年10月21日 更新

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1182026年10月21日 更新

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1182026年10月21日 更新

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

中文简介正在准备中。当前显示原文说明。

makifbaysal/tasktrooper1182026年10月21日 更新

查看 makifbaysal 的全部技能

报告此技能的问题