mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-10-31 17:06:50 +08:00 
			
		
		
		
	feat: Add initial version of behavior_tree and flame_behavior_tree package (#3045)
				
					
				
			First pass on adding behavior tree for flame. This PR adds 2 packages: - behavior_tree: A pure dart implementation of behavior tree. - flame_behavior_tree: A bridge package that integrates behavior_tree with flame. Demo: https://github.com/flame-engine/flame/assets/33748002/1d2b00ab-1b6e-406e-9052-a24370c8f1ab
This commit is contained in:
		
							
								
								
									
										323
									
								
								packages/flame_behavior_tree/example/lib/main.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								packages/flame_behavior_tree/example/lib/main.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,323 @@ | ||||
| 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 | ||||
|   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 | ||||
|   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 | ||||
|   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 | ||||
|   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; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 DevKage
					DevKage