AISystem handles behavior decisions in HarmonyOS games rather than directly carrying business rules. It solves the problems that arise when NPC decision-making gets mixed into BattleSystem, becoming hard to extend, hard to test, and hard to control. Keywords: HarmonyOS, game AI, behavior-driven architecture.
Technical Specifications at a Glance
| Parameter | Details |
|---|---|
| Domain | HarmonyOS game architecture |
| Core Language | TypeScript / ArkTS-style pseudocode |
| Architectural Roles | Store, System, AISystem, Engine, UI |
| Decision Protocol | Action Dispatch / Command style |
| GitHub Stars | Not provided in the source |
| Core Dependencies | State container, scheduling engine, behavior enum, validator |
AI Visual Insight: This image serves as the article’s architectural illustration. It emphasizes AISystem as an independent layer embedded in the game architecture. Visually, it typically corresponds to the starting point of the chain from state input to behavior decision to rule execution, making it well suited to express the structural idea of decoupling the AI decision layer from the business rule layer.
AI Visual Insight: The animated image is better suited to showing how AI behavior switches dynamically at runtime, such as transitions between attack, escape, and defense. It demonstrates that AISystem is not static configuration, but a decision system that generates actions in real time based on the current context.
This article identifies a critical architectural misconception
In HarmonyOS games, many teams first split the architecture into Store, System, Engine, and UI. The player input path is usually clear: input enters the System, the System modifies state, and the UI refreshes in response.
But once you add NPCs, auto-battle, or dynamic difficulty, the problem appears immediately: where should AI logic live? If you stuff it directly into BattleSystem, rules and decisions become tightly coupled, and the system starts to lose its boundaries.
class BattleSystem {
update(store: GameStore) {
this.handlePlayer(store) // Handle player behavior
if (store.enemyHp < 30) {
this.escape() // Write AI decisions directly inside the rule system
} else {
this.attack()
}
}
}
The issue with this code is not whether it runs. The issue is confused responsibility: the System should define rules, but it also ends up deciding behavior.
The right approach is to fully separate rule execution from behavior decision-making
The System answers the question, “What can be done?” For example, it handles damage calculation, skill cooldowns, collision resolution, and state write-back. AISystem answers the question, “What should be done now?” For example, it decides whether to attack, retreat, or choose a skill.
The value of this split is immediate: decisions become replaceable, rules become reusable, and scheduling becomes testable. You no longer need to copy the same if-else decision tree into every business system.
The responsibility boundary between System and AISystem must remain stable
- System: defines rules for attack, movement, damage taken, cooldowns, and more.
- AISystem: outputs an Action based on context.
- Engine: schedules execution order.
- UI: consumes state only and does not participate in AI decision-making.
type Action = { type: 'ATTACK' | 'ESCAPE' | 'DEFEND' }
class AISystem {
decide(store: GameStore): Action {
if (store.enemyHp < 30) {
return { type: 'ESCAPE' } // Choose retreat when HP is low
}
return { type: 'ATTACK' } // Attack by default
}
}
This code only produces a decision result and does not directly modify state, so it is naturally easier to test and reuse.
The minimum viable AISystem model should stay simple
The minimal model has only one main path: read state, produce an Action, and pass it to the System for execution. The most important point here is not a complex algorithm, but a clear boundary.
const action = aiSystem.decide(store) // Let AI make the behavior decision first
battleSystem.execute(store, action) // Then let the rule system execute the action
Once this flow is established, AI evolves from a “logic patch” into a true behavior-driven layer.
A behavior model scales better than scattered if-else branches
As behaviors increase, a single chain of conditional statements quickly gets out of control. A more robust approach is to split the logic into three parts: behavior enum, behavior selector, and behavior executor.
enum ActionType {
ATTACK,
DEFEND,
ESCAPE
}
function decide(store: GameStore): ActionType {
if (store.enemyHp < 30) return ActionType.ESCAPE // The decision layer only selects an action
return ActionType.ATTACK
}
function execute(store: GameStore, action: ActionType) {
switch (action) {
case ActionType.ATTACK:
// Execute attack rules
break
case ActionType.ESCAPE:
// Execute retreat rules
break
}
}
The core benefit of this structure is simple: adding new behaviors does not break the existing rule structure.
AISystem can evolve progressively through three patterns
The most basic pattern is rule-driven AI. It is simple, controllable, and suitable for lightweight NPCs. The downside is that the behavior pattern is fixed and can become rigid in complex scenarios.
The second layer is weight-driven AI. Instead of hardcoding a single answer, the system calculates a score for multiple behaviors and then selects the highest one. This approach works well when you want enemy behavior to look more like actual thinking.
function decideByScore(store: GameStore): ActionType {
const scores = [
{ type: ActionType.ATTACK, score: calcAttackScore(store) }, // Calculate attack tendency
{ type: ActionType.ESCAPE, score: calcEscapeScore(store) } // Calculate retreat tendency
]
return scores.sort((a, b) => b.score - a.score)[0].type
}
This implementation replaces hard branches with a scoring model, giving AI behavior much more flexibility.
The third layer is model-driven AI, such as calling a local model or a cloud inference service. It is the most flexible option, but also the least predictable, so you should only use it after applying constraints and validation.
Introducing an AI scheduling layer is mandatory for complex projects
Once you have multiple AISystems, such as combat AI, patrol AI, and cooperative AI, you can no longer let each system run independently without coordination. At that point, you need an AIEngine for centralized scheduling.
class AIEngine {
private aiSystems: AISystem[] = []
run(store: GameStore) {
return this.aiSystems.map(ai => ai.decide(store)) // Collect decisions from multiple systems in parallel
}
}
AIEngine provides a unified entry point, unified throttling, unified logging, and unified priority arbitration.
Preventing AI from going out of control requires three layers of protection
The first layer is constraints. For example, under stunned, silenced, or frozen states, even if AISystem wants to attack, it must return an empty action.
The second layer is validation. After an Action is generated, it should pass a legality check to prevent invalid operations such as unauthorized behavior, casting a skill before cooldown ends, or cross-device state inconsistency.
The third layer is priority. Whether escape outranks attack, or survival outranks pursuit, must follow a unified strategy. Otherwise, multiple AI decisions will conflict with one another.
function safeDecide(store: GameStore): ActionType | 'NO_ACTION' {
if (store.stunned) return 'NO_ACTION' // Constraint: prevent action while stunned
const action = decide(store)
if (!isValid(action, store)) return 'NO_ACTION' // Validation: fall back on invalid actions
return action
}
This protective logic ensures that AI output always stays within the executable range of the rule system.
In multi-device HarmonyOS scenarios, AISystem must run only on the primary node
If every device runs its own AI decision logic independently, state drift is inevitable. Different devices never share exactly the same timing, input sequence, or synchronization latency.
The correct approach is for the primary node to generate the Action, while secondary nodes only synchronize the result. In other words, AI should not operate as distributed free-form inference. It should use centralized decision-making with distributed execution.
The recommended architectural flow is already clear
The Store provides real-time state, AISystem generates an Action based on that state, the System executes rules according to the Action, the Engine controls scheduling order, and the UI is responsible only for rendering the final state.
// Unified main flow: state -> decision -> execution
const action = aiSystem.decide(store) // Read state and generate behavior
engine.dispatch(action) // Hand it to the engine for unified scheduling
system.execute(store, action) // Let the rule system complete the state transition
The significance of this flow is that each layer does exactly one job, allowing the system to evolve over time without collapsing under complexity.
FAQ
1. Why not write AI directly inside BattleSystem?
Because BattleSystem is responsible for rule execution, while AI belongs to behavior decision-making. Mixing them creates tight coupling, makes testing and extension harder, and complicates future integration with weighted models or behavior trees.
2. Can AISystem modify the Store directly?
It is not recommended. AISystem should ideally output only an Action, and the System should execute that action and update state. This guarantees unified rules, complete logs, and traceable replay behavior.
3. How should AI be deployed in a multi-device HarmonyOS game?
Run AISystem centrally on the primary node and synchronize the decision results to other devices. This prevents state drift and behavioral divergence caused by independent inference on each endpoint.
Core summary
This article reconstructs the design approach for AISystem in HarmonyOS games. The core principle is to completely decouple rule execution from behavior decision-making. It provides a minimal implementation model, three progressive patterns, scheduling layer design, and multi-device coordination principles to help developers build scalable, testable, and controllable architectures for NPCs and auto-battle AI.