Flutter行为树管理插件flame_behavior_tree的使用
Flutter行为树管理插件flame_behavior_tree的使用
特性
该插件提供了一个 HasBehaviorTree
混入类,适用于 Flame 的 Component
。你可以将这个混入添加到任何组件中,并且它会自动处理与组件更新同步的行为树。
开始使用
首先,在你的 Flutter 项目中添加 flame_behavior_tree
包:
flutter pub add flame_behavior_tree
使用方法
1. 添加 HasBehaviorTree
混入
在你希望实现某种AI行为的组件中添加 HasBehaviorTree
混入。
class MyComponent extends PositionComponent with HasBehaviorTree {
// 组件的其他逻辑
}
2. 设置行为树
在组件的 onLoad
方法中设置行为树并指定其根节点。
class MyComponent extends PositionComponent with HasBehaviorTree {
Future<void> onLoad() async {
treeRoot = Selector(
children: [
Sequence(children: [task1, condition, task2]),
Sequence(...),
]
);
super.onLoad();
}
}
3. 调整行为树的更新频率
通过调整 tickInterval
来控制行为树的更新频率。
class MyComponent extends PositionComponent with HasBehaviorTree {
Future<void> onLoad() async {
treeRoot = Selector(...);
tickInterval = 4;
super.onLoad();
}
}
完整示例
下面是一个完整的示例,展示了如何使用 flame_behavior_tree
插件来实现一个简单的 AI 行为。
import 'dart:async';
import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flame/palette.dart';
import 'package:flame_behavior_tree/flame_behavior_tree.dart';
import 'package:flutter/material.dart';
typedef MyGame = FlameGame<GameWorld>;
const gameWidth = 320.0;
const gameHeight = 180.0;
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GameWidget<MyGame>.controlled(
gameFactory: () => MyGame(
world: GameWorld(),
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
),
),
),
);
}
}
class GameWorld extends World with HasGameReference {
[@override](/user/override)
Future<void> onLoad() async {
game.camera.moveTo(Vector2(gameWidth * 0.5, gameHeight * 0.5));
final house = RectangleComponent(
size: Vector2(100, 100),
position: Vector2(gameWidth * 0.5, 10),
paint: BasicPalette.cyan.paint()
..strokeWidth = 5
..style = PaintingStyle.stroke,
anchor: Anchor.topCenter,
);
final door = Door(
size: Vector2(20, 4),
position: Vector2(40, house.size.y),
anchor: Anchor.centerLeft,
);
final agent = Agent(
door: door,
house: house,
position: Vector2(gameWidth * 0.76, gameHeight * 0.9),
);
await house.add(door);
await addAll([house, agent]);
}
}
class Door extends RectangleComponent with TapCallbacks {
Door({super.position, super.size, super.anchor})
: super(paint: BasicPalette.brown.paint());
bool isOpen = false;
bool _isInProgress = false;
bool _isKnocking = false;
[@override](/user/override)
void onTapDown(TapDownEvent event) {
if (!_isInProgress) {
_isInProgress = true;
add(
RotateEffect.to(
isOpen ? 0 : -pi * 0.5,
EffectController(duration: 0.5, curve: Curves.easeInOut),
onComplete: () {
isOpen = !isOpen;
_isInProgress = false;
},
),
);
}
}
void knock() {
if (!_isKnocking) {
_isKnocking = true;
add(
MoveEffect.by(
Vector2(0, -1),
EffectController(
alternate: true,
duration: 0.1,
repeatCount: 2,
),
onComplete: () {
_isKnocking = false;
},
),
);
}
}
}
class Agent extends PositionComponent with HasBehaviorTree {
Agent({required this.door, required this.house, required Vector2 position})
: _startPosition = position.clone(),
super(position: position);
final Door door;
final PositionComponent house;
final Vector2 _startPosition;
[@override](/user/override)
Future<void> onLoad() async {
await add(CircleComponent(radius: 3, anchor: Anchor.center));
_setupBehaviorTree();
super.onLoad();
}
void _setupBehaviorTree() {
var isInside = false;
var isAtTheDoor = false;
var isAtCenterOfHouse = false;
var isMoving = false;
var wantsToGoOutside = false;
final walkTowardsDoorInside = Task(() {
if (!isAtTheDoor) {
isMoving = true;
add(
MoveEffect.to(
door.absolutePosition + Vector2(door.size.x * 0.8, -15),
EffectController(
duration: 3,
curve: Curves.easeInOut,
),
onComplete: () {
isMoving = false;
isAtTheDoor = true;
isAtCenterOfHouse = false;
},
),
);
}
return isAtTheDoor ? NodeStatus.success : NodeStatus.running;
});
final stepOutTheDoor = Task(() {
if (isInside) {
isMoving = true;
add(
MoveEffect.to(
door.absolutePosition + Vector2(door.size.x * 0.5, 10),
EffectController(
duration: 2,
curve: Curves.easeInOut,
),
onComplete: () {
isMoving = false;
isInside = false;
},
),
);
}
return !isInside ? NodeStatus.success : NodeStatus.running;
});
final walkTowardsInitialPosition = Task(
() {
if (isAtTheDoor) {
isMoving = true;
isAtTheDoor = false;
add(
MoveEffect.to(
_startPosition,
EffectController(
duration: 3,
curve: Curves.easeInOut,
),
onComplete: () {
isMoving = false;
wantsToGoOutside = false;
},
),
);
}
return !wantsToGoOutside ? NodeStatus.success : NodeStatus.running;
},
);
final walkTowardsDoorOutside = Task(() {
if (!isAtTheDoor) {
isMoving = true;
add(
MoveEffect.to(
door.absolutePosition + Vector2(door.size.x * 0.5, 10),
EffectController(
duration: 3,
curve: Curves.easeInOut,
),
onComplete: () {
isMoving = false;
isAtTheDoor = true;
},
),
);
}
return isAtTheDoor ? NodeStatus.success : NodeStatus.running;
});
final walkTowardsCenterOfTheHouse = Task(() {
if (!isAtCenterOfHouse) {
isMoving = true;
isInside = true;
add(
MoveEffect.to(
house.absoluteCenter,
EffectController(
duration: 3,
curve: Curves.easeInOut,
),
onComplete: () {
isMoving = false;
wantsToGoOutside = true;
isAtTheDoor = false;
isAtCenterOfHouse = true;
},
),
);
}
return isInside ? NodeStatus.success : NodeStatus.running;
});
final checkIfDoorIsOpen = Condition(() => door.isOpen);
final knockTheDoor = Task(() {
door.knock();
return NodeStatus.success;
});
final goOutsideSequence = Sequence(
children: [
Condition(() => wantsToGoOutside),
Selector(
children: [
Sequence(
children: [
Condition(() => isInside),
walkTowardsDoorInside,
Selector(
children: [
Sequence(
children: [
checkIfDoorIsOpen,
stepOutTheDoor,
],
),
knockTheDoor,
],
),
],
),
walkTowardsInitialPosition,
],
),
],
);
final goInsideSequence = Sequence(
children: [
Condition(() => !wantsToGoOutside),
Selector(
children: [
Sequence(
children: [
Condition(() => !isInside),
walkTowardsDoorOutside,
Selector(
children: [
Sequence(
children: [
checkIfDoorIsOpen,
walkTowardsCenterOfTheHouse,
],
),
knockTheDoor,
],
),
],
),
],
),
],
);
treeRoot = Selector(
children: [
Condition(() => isMoving),
goOutsideSequence,
goInsideSequence,
],
);
tickInterval = 2;
}
}
更多关于Flutter行为树管理插件flame_behavior_tree的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter行为树管理插件flame_behavior_tree的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flame_behavior_tree
是一个用于 Flutter 游戏开发的插件,它基于行为树(Behavior Tree)的概念,允许开发者以模块化和可维护的方式管理游戏中的 AI 行为。行为树是一种树状结构,用于定义和控制 AI 实体的决策过程。
安装
首先,你需要在 pubspec.yaml
文件中添加 flame_behavior_tree
依赖:
dependencies:
flame_behavior_tree: ^0.1.0
然后运行 flutter pub get
来安装依赖。
基本概念
行为树由多个节点组成,每个节点负责执行特定的任务或决策。常见的节点类型包括:
- Action Node: 执行具体的动作。
- Condition Node: 检查某个条件是否满足。
- Composite Node: 包含多个子节点,并决定如何执行这些子节点。常见的复合节点有
Sequence
和Selector
。 - Decorator Node: 修饰子节点,改变其行为。
使用示例
1. 创建行为树
首先,你需要创建一个行为树。以下是一个简单的例子:
import 'package:flame_behavior_tree/flame_behavior_tree.dart';
void main() {
// 创建一个行为树
final behaviorTree = Sequence([
// 条件节点:检查是否有敌人
Condition((blackboard) => blackboard['hasEnemy'] == true),
// 动作节点:攻击敌人
Action((blackboard) {
print('Attacking enemy!');
return Status.success;
}),
]);
// 创建一个黑板(用于存储数据)
final blackboard = {'hasEnemy': true};
// 执行行为树
behaviorTree.tick(blackboard);
}
2. 使用复合节点
复合节点可以包含多个子节点,并决定它们的执行顺序。例如,Sequence
会依次执行子节点,直到其中一个失败:
final behaviorTree = Sequence([
Condition((blackboard) => blackboard['hasEnemy'] == true),
Action((blackboard) {
print('Attacking enemy!');
return Status.success;
}),
Action((blackboard) {
print('Enemy defeated!');
return Status.success;
}),
]);
3. 使用 Selector
Selector
会依次执行子节点,直到其中一个成功:
final behaviorTree = Selector([
Condition((blackboard) => blackboard['hasEnemy'] == true),
Action((blackboard) {
print('No enemy found, patrolling...');
return Status.success;
}),
]);
4. 使用 Decorator
Decorator
节点可以修饰子节点的行为。例如,Inverter
会反转子节点的结果:
final behaviorTree = Inverter(
Condition((blackboard) => blackboard['hasEnemy'] == true),
);
集成到 Flame 游戏中
你可以将行为树集成到 Flame 游戏的 Component
中,例如在 update
方法中调用行为树的 tick
方法:
import 'package:flame/game.dart';
import 'package:flame_behavior_tree/flame_behavior_tree.dart';
class MyGame extends FlameGame {
late BehaviorTree behaviorTree;
[@override](/user/override)
Future<void> onLoad() async {
behaviorTree = Sequence([
Condition((blackboard) => blackboard['hasEnemy'] == true),
Action((blackboard) {
print('Attacking enemy!');
return Status.success;
}),
]);
}
[@override](/user/override)
void update(double dt) {
final blackboard = {'hasEnemy': true};
behaviorTree.tick(blackboard);
super.update(dt);
}
}