├── grass_tile.png ├── screenshot.png ├── worker ├── worker.png ├── worker.png.import ├── worker.gd └── worker.tscn ├── resource ├── resource.png ├── resource.tscn └── resource.png.import ├── construction_site ├── construction_site.png ├── construction_site.gd ├── progress_bar.svg.import ├── construction_site.png.import ├── construction_site.tscn └── progress_bar.svg ├── .gitignore ├── addons └── behavior_tree │ ├── plugin.cfg │ ├── behavior_tree_player │ ├── behavior_tree_player.gd │ ├── icon.svg.import │ └── icon.svg │ ├── behavior_tree.gd │ ├── behavior_tree_graph │ ├── create_behavior_node_dialog │ │ ├── create_behavior_node_dialog.tscn │ │ └── create_behavior_node_dialog.gd │ ├── behavior_node │ │ ├── behavior_node.tscn │ │ └── behavior_node.gd │ └── behavior_tree_graph_panel │ │ ├── behavior_tree_graph_panel.tscn │ │ └── behavior_tree_graph_panel.gd │ ├── plugin.gd │ └── nodes.gd ├── default_env.tres ├── icon.svg.import ├── grass_tile.png.import ├── screenshot.png.import ├── project.godot ├── main.tscn ├── icon.svg └── README.md /grass_tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jummit/behavior-tree/HEAD/grass_tile.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jummit/behavior-tree/HEAD/screenshot.png -------------------------------------------------------------------------------- /worker/worker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jummit/behavior-tree/HEAD/worker/worker.png -------------------------------------------------------------------------------- /resource/resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jummit/behavior-tree/HEAD/resource/resource.png -------------------------------------------------------------------------------- /construction_site/construction_site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jummit/behavior-tree/HEAD/construction_site/construction_site.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | export.cfg 4 | export_presets.cfg 5 | 6 | # Mono-specific ignores 7 | .mono/ 8 | data_*/ -------------------------------------------------------------------------------- /addons/behavior_tree/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Behavior Tree" 4 | description="" 5 | author="Jummit" 6 | version="1.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /construction_site/construction_site.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | var progress := 0.0 setget set_progress 4 | var completed := false 5 | 6 | func set_progress(to): 7 | progress = to 8 | $ProgressBar.value = progress 9 | if progress == 10: 10 | completed = true 11 | -------------------------------------------------------------------------------- /resource/resource.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://resource/resource.png" type="Texture" id=1] 4 | 5 | [sub_resource type="CircleShape2D" id=1] 6 | radius = 35.533 7 | 8 | [node name="Resource" type="Area2D" groups=[ 9 | "Resources", 10 | ]] 11 | 12 | [node name="Sprite" type="Sprite" parent="."] 13 | position = Vector2( 0, -74.9153 ) 14 | texture = ExtResource( 1 ) 15 | 16 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 17 | position = Vector2( 12.7356, -44.9492 ) 18 | shape = SubResource( 1 ) 19 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_player/behavior_tree_player.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | """ 5 | Node that is used to play a behavior tree resource in game 6 | """ 7 | 8 | export var root_node := NodePath("../") 9 | export var behavior_tree : Resource 10 | 11 | onready var subject := get_node(root_node) 12 | 13 | var Nodes = preload("res://addons/behavior_tree/nodes.gd").new() 14 | 15 | func _ready() -> void: 16 | if Engine.editor_hint: 17 | return 18 | yield(subject, "ready") 19 | behavior_tree.strip_comments() 20 | while true: 21 | var result = Nodes.tick(behavior_tree.get_first_node(), subject, 22 | behavior_tree) 23 | if result is GDScriptFunctionState: 24 | yield(result, "completed") 25 | else: 26 | yield(get_tree(), "idle_frame") 27 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.svg-218a8f2b3041327d8a5756f3a245f83b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.svg" 13 | dest_files=[ "res://.import/icon.svg-218a8f2b3041327d8a5756f3a245f83b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /grass_tile.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/grass_tile.png-1b58c18cf6941171bd517ce26e716f8a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://grass_tile.png" 13 | dest_files=[ "res://.import/grass_tile.png-1b58c18cf6941171bd517ce26e716f8a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /screenshot.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/screenshot.png-024a21af5d37bf0f0dd0e2bccdd149d0.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://screenshot.png" 13 | dest_files=[ "res://.import/screenshot.png-024a21af5d37bf0f0dd0e2bccdd149d0.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /worker/worker.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/worker.png-ff767a56b147bc01c336ad1dc2468e25.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://worker/worker.png" 13 | dest_files=[ "res://.import/worker.png-ff767a56b147bc01c336ad1dc2468e25.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /resource/resource.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/resource.png-8aa929ea9fb88c1fbc16ad9c9f676d4f.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://resource/resource.png" 13 | dest_files=[ "res://.import/resource.png-8aa929ea9fb88c1fbc16ad9c9f676d4f.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /construction_site/progress_bar.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/progress_bar.svg-72c612ca0e59721cc2a87cc771ac6a34.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://construction_site/progress_bar.svg" 13 | dest_files=[ "res://.import/progress_bar.svg-72c612ca0e59721cc2a87cc771ac6a34.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_player/icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.svg-df08fb3500e91fedfc5a5a8657cfa9b5.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/behavior_tree/behavior_tree_player/icon.svg" 13 | dest_files=[ "res://.import/icon.svg-df08fb3500e91fedfc5a5a8657cfa9b5.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /construction_site/construction_site.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/construction_site.png-71636dc502d570e3127a7beb10afdc92.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://construction_site/construction_site.png" 13 | dest_files=[ "res://.import/construction_site.png-71636dc502d570e3127a7beb10afdc92.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Resource 3 | 4 | """ 5 | Resource that stores a behavior tree 6 | """ 7 | 8 | export var graphs : Dictionary 9 | 10 | const Nodes = preload("res://addons/behavior_tree/nodes.gd") 11 | 12 | func remove_unused_graphs() -> void: 13 | for graph in graphs: 14 | if graph == "Main": 15 | continue 16 | var used := false 17 | for graph_to_search in graphs: 18 | if is_group_used(graphs[graph_to_search], graph): 19 | used = true 20 | break 21 | if not used: 22 | graphs.erase(graph) 23 | 24 | 25 | func get_first_node() -> Dictionary: 26 | for node in graphs.Main: 27 | if node.type != "Comment": 28 | return node 29 | return {} 30 | 31 | 32 | func strip_comments() -> void: 33 | for graph in graphs.values(): 34 | var comments := [] 35 | for node in graph.duplicate(): 36 | if node.type == "Comment": 37 | graph.erase(node) 38 | 39 | 40 | static func get_flat_nodes(nodes : Array, array := []) -> Array: 41 | for node in nodes: 42 | array.append(node) 43 | get_flat_nodes(node.get("children", []), array) 44 | return array 45 | 46 | 47 | static func is_group_used(nodes : Array, group : String) -> bool: 48 | for node in get_flat_nodes(nodes): 49 | if Nodes.get_type_data(node.type).type == Nodes.NodeType.GROUP and\ 50 | node.property == group: 51 | return true 52 | return false 53 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | [application] 12 | 13 | config/name="Behavior Tree Demo" 14 | config/description="Project to showcase the behavior tree plugin." 15 | run/main_scene="res://main.tscn" 16 | config/icon="res://icon.svg" 17 | 18 | [debug] 19 | 20 | gdscript/warnings/return_value_discarded=false 21 | 22 | [editor_plugins] 23 | 24 | enabled=PoolStringArray( "res://addons/behavior_tree/plugin.cfg" ) 25 | 26 | [importer_defaults] 27 | 28 | texture={ 29 | "compress/bptc_ldr": 0, 30 | "compress/hdr_mode": 0, 31 | "compress/lossy_quality": 0.7, 32 | "compress/mode": 0, 33 | "compress/normal_map": 0, 34 | "detect_3d": false, 35 | "flags/anisotropic": false, 36 | "flags/filter": false, 37 | "flags/mipmaps": false, 38 | "flags/repeat": 0, 39 | "flags/srgb": 2, 40 | "process/HDR_as_SRGB": false, 41 | "process/fix_alpha_border": true, 42 | "process/invert_color": false, 43 | "process/premult_alpha": false, 44 | "size_limit": 0, 45 | "stream": false, 46 | "svg/scale": 1.0 47 | } 48 | 49 | [rendering] 50 | 51 | quality/2d/use_pixel_snap=true 52 | quality/2d/use_transform_snap=true 53 | quality/2d/use_camera_snap=true 54 | environment/default_environment="res://default_env.tres" 55 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_graph/create_behavior_node_dialog/create_behavior_node_dialog.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/behavior_tree/behavior_tree_graph/create_behavior_node_dialog/create_behavior_node_dialog.gd" type="Script" id=1] 4 | 5 | [node name="CreateBehaviorNodeDialog" type="ConfirmationDialog"] 6 | anchor_left = 0.5 7 | anchor_top = 0.5 8 | anchor_right = 0.5 9 | anchor_bottom = 0.5 10 | margin_left = -100.0 11 | margin_top = -219.0 12 | margin_right = 100.0 13 | margin_bottom = 219.0 14 | rect_min_size = Vector2( 200, 240 ) 15 | window_title = "Create Behavior Node" 16 | script = ExtResource( 1 ) 17 | __meta__ = { 18 | "_edit_use_anchors_": false 19 | } 20 | 21 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 22 | anchor_right = 1.0 23 | anchor_bottom = 1.0 24 | margin_left = 8.0 25 | margin_top = 8.0 26 | margin_right = -8.0 27 | margin_bottom = -36.0 28 | __meta__ = { 29 | "_edit_use_anchors_": false 30 | } 31 | 32 | [node name="SearchEdit" type="LineEdit" parent="VBoxContainer"] 33 | margin_right = 184.0 34 | margin_bottom = 24.0 35 | 36 | [node name="NodeList" type="Tree" parent="VBoxContainer"] 37 | margin_top = 28.0 38 | margin_right = 184.0 39 | margin_bottom = 394.0 40 | size_flags_horizontal = 3 41 | size_flags_vertical = 3 42 | hide_root = true 43 | __meta__ = { 44 | "_edit_use_anchors_": false 45 | } 46 | 47 | [connection signal="about_to_show" from="." to="." method="_on_about_to_show"] 48 | [connection signal="text_changed" from="VBoxContainer/SearchEdit" to="." method="_on_SearchEdit_text_changed"] 49 | [connection signal="text_entered" from="VBoxContainer/SearchEdit" to="." method="_on_SearchEdit_text_entered"] 50 | [connection signal="item_activated" from="VBoxContainer/NodeList" to="." method="_on_NodeList_item_activated"] 51 | -------------------------------------------------------------------------------- /construction_site/construction_site.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://construction_site/construction_site.png" type="Texture" id=1] 4 | [ext_resource path="res://construction_site/construction_site.gd" type="Script" id=2] 5 | [ext_resource path="res://construction_site/progress_bar.svg" type="Texture" id=3] 6 | 7 | [sub_resource type="StyleBoxTexture" id=2] 8 | texture = ExtResource( 3 ) 9 | region_rect = Rect2( 0, 11, 66, 11 ) 10 | margin_left = 1.0 11 | margin_right = 1.0 12 | margin_top = 1.0 13 | margin_bottom = 1.0 14 | 15 | [sub_resource type="StyleBoxTexture" id=3] 16 | texture = ExtResource( 3 ) 17 | region_rect = Rect2( 0, 0, 66, 11 ) 18 | margin_left = 1.0 19 | margin_right = 1.0 20 | margin_top = 1.0 21 | margin_bottom = 1.0 22 | 23 | [sub_resource type="BitmapFont" id=4] 24 | 25 | [sub_resource type="RectangleShape2D" id=1] 26 | extents = Vector2( 128, 128.4 ) 27 | 28 | [node name="ConstructionSite" type="Area2D" groups=[ 29 | "ConstructionSites", 30 | ]] 31 | script = ExtResource( 2 ) 32 | 33 | [node name="ProgressBar" type="ProgressBar" parent="."] 34 | margin_left = -144.0 35 | margin_top = -297.717 36 | margin_right = -72.0 37 | margin_bottom = -286.717 38 | rect_scale = Vector2( 4, 4 ) 39 | custom_styles/fg = SubResource( 2 ) 40 | custom_styles/bg = SubResource( 3 ) 41 | custom_fonts/font = SubResource( 4 ) 42 | max_value = 10.0 43 | percent_visible = false 44 | __meta__ = { 45 | "_edit_use_anchors_": false 46 | } 47 | 48 | [node name="Sprite" type="Sprite" parent="."] 49 | position = Vector2( 0, -123.39 ) 50 | scale = Vector2( 4, 4 ) 51 | texture = ExtResource( 1 ) 52 | 53 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 54 | position = Vector2( 0, -123.39 ) 55 | shape = SubResource( 1 ) 56 | 57 | [node name="Tween" type="Tween" parent="."] 58 | -------------------------------------------------------------------------------- /main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://construction_site/construction_site.tscn" type="PackedScene" id=2] 4 | [ext_resource path="res://resource/resource.tscn" type="PackedScene" id=3] 5 | [ext_resource path="res://worker/worker.tscn" type="PackedScene" id=4] 6 | [ext_resource path="res://grass_tile.png" type="Texture" id=5] 7 | 8 | [node name="Main" type="YSort"] 9 | 10 | [node name="Ground" type="Sprite" parent="."] 11 | scale = Vector2( 4, 4 ) 12 | texture = ExtResource( 5 ) 13 | centered = false 14 | region_enabled = true 15 | region_rect = Rect2( 0, 0, 480, 300 ) 16 | 17 | [node name="Resource1" parent="." instance=ExtResource( 3 )] 18 | position = Vector2( 377, 198 ) 19 | 20 | [node name="Resource2" parent="." instance=ExtResource( 3 )] 21 | position = Vector2( 177, 411 ) 22 | 23 | [node name="Resource3" parent="." instance=ExtResource( 3 )] 24 | position = Vector2( 813, 270 ) 25 | 26 | [node name="Resource4" parent="." instance=ExtResource( 3 )] 27 | position = Vector2( 907, 167 ) 28 | 29 | [node name="Resource5" parent="." instance=ExtResource( 3 )] 30 | position = Vector2( 99, 203 ) 31 | 32 | [node name="Resource6" parent="." instance=ExtResource( 3 )] 33 | position = Vector2( 745, 186 ) 34 | 35 | [node name="Resource7" parent="." instance=ExtResource( 3 )] 36 | position = Vector2( 81, 524 ) 37 | 38 | [node name="Resource12" parent="." instance=ExtResource( 3 )] 39 | position = Vector2( 831, 571 ) 40 | 41 | [node name="Resource13" parent="." instance=ExtResource( 3 )] 42 | position = Vector2( 197, 579 ) 43 | 44 | [node name="Resource14" parent="." instance=ExtResource( 3 )] 45 | position = Vector2( 910, 510 ) 46 | 47 | [node name="Resource15" parent="." instance=ExtResource( 3 )] 48 | position = Vector2( 467, 155 ) 49 | 50 | [node name="ConstructionSite" parent="." instance=ExtResource( 2 )] 51 | position = Vector2( 480.751, 496.812 ) 52 | 53 | [node name="Worker" parent="." instance=ExtResource( 4 )] 54 | position = Vector2( 767, 422 ) 55 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_graph/create_behavior_node_dialog/create_behavior_node_dialog.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ConfirmationDialog 3 | 4 | """ 5 | Dialog to search and choose nodes to add to the graph 6 | """ 7 | 8 | signal node_selected(node) 9 | 10 | const NODES = preload("res://addons/behavior_tree/nodes.gd").NODES 11 | const NodeType = preload("res://addons/behavior_tree/nodes.gd").NodeType 12 | 13 | onready var search_edit : LineEdit = $VBoxContainer/SearchEdit 14 | onready var node_list : Tree = $VBoxContainer/NodeList 15 | 16 | var type_names := { 17 | NodeType.COMPOSITE: "Composites", 18 | NodeType.DECORATOR: "Decorators", 19 | NodeType.LEAF: "Leafs", 20 | } 21 | 22 | func _ready() -> void: 23 | search_edit.right_icon = get_icon("Search", "EditorIcons") 24 | 25 | 26 | func update_list(search_term := "") -> void: 27 | node_list.clear() 28 | var root := node_list.create_item() 29 | var roots := { 30 | NodeType.ROOT: root, 31 | NodeType.GROUP: root, 32 | NodeType.COMMENT: root, 33 | } 34 | var is_first := true 35 | for node in NODES: 36 | if search_term and not search_term.to_lower() in node.name.to_lower(): 37 | continue 38 | if not node.type in roots: 39 | var new_root := node_list.create_item(root) 40 | new_root.set_selectable(0, false) 41 | new_root.set_text(0, type_names[node.type]) 42 | roots[node.type] = new_root 43 | 44 | var new_item := node_list.create_item(roots[node.type]) 45 | if search_term and is_first: 46 | new_item.select(0) 47 | is_first = false 48 | new_item.set_text(0, node.name) 49 | new_item.set_metadata(0, node.name) 50 | 51 | 52 | func _on_NodeList_item_activated() -> void: 53 | emit_signal("node_selected", node_list.get_selected().get_metadata(0)) 54 | hide() 55 | 56 | 57 | func _on_SearchEdit_text_changed(new_text : String) -> void: 58 | update_list(new_text) 59 | 60 | 61 | func _on_about_to_show() -> void: 62 | update_list() 63 | yield(get_tree(), "idle_frame") 64 | search_edit.clear() 65 | search_edit.grab_focus() 66 | 67 | 68 | func _on_SearchEdit_text_entered(new_text: String) -> void: 69 | if not node_list.get_selected(): 70 | return 71 | emit_signal("node_selected", node_list.get_selected().get_metadata(0)) 72 | hide() 73 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_player/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 54 | 55 | 61 | 62 | -------------------------------------------------------------------------------- /addons/behavior_tree/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var behavior_tree_graph_panel := preload("behavior_tree_graph/behavior_tree_graph_panel/behavior_tree_graph_panel.tscn").instance() 5 | var create_behavior_node_dialog := preload("behavior_tree_graph/create_behavior_node_dialog/create_behavior_node_dialog.tscn").instance() 6 | var editing_player : BehaviorTreePlayer 7 | var bottom_panel_button : ToolButton 8 | 9 | const BehaviorTreePlayer = preload("behavior_tree_player/behavior_tree_player.gd") 10 | const BehaviorTree = preload("behavior_tree.gd") 11 | 12 | func _enter_tree() -> void: 13 | add_custom_type("BehaviorTreePlayer", "Node", BehaviorTreePlayer, 14 | preload("behavior_tree_player/icon.svg")) 15 | get_editor_interface().get_base_control().add_child(create_behavior_node_dialog) 16 | behavior_tree_graph_panel.create_node_dialog = create_behavior_node_dialog 17 | behavior_tree_graph_panel.undo_redo = get_undo_redo() 18 | get_editor_interface().get_selection().connect("selection_changed", self, "_on_editor_selection_changed") 19 | bottom_panel_button = add_control_to_bottom_panel(behavior_tree_graph_panel, "Behavior Tree") 20 | bottom_panel_button.hide() 21 | 22 | 23 | func _exit_tree() -> void: 24 | remove_custom_type("BehaviorTreePlayer") 25 | remove_control_from_bottom_panel(behavior_tree_graph_panel) 26 | behavior_tree_graph_panel.queue_free() 27 | create_behavior_node_dialog.queue_free() 28 | 29 | 30 | func handles(object : Object) -> bool: 31 | return object is BehaviorTreePlayer 32 | 33 | 34 | func edit(object : Object) -> void: 35 | editing_player = object 36 | if not editing_player.behavior_tree: 37 | editing_player.behavior_tree = BehaviorTree.new() 38 | behavior_tree_graph_panel.behavior_tree = editing_player.behavior_tree 39 | bottom_panel_button.show() 40 | make_bottom_panel_item_visible(behavior_tree_graph_panel) 41 | 42 | 43 | func apply_changes() -> void: 44 | if editing_player: 45 | behavior_tree_graph_panel.save_graph() 46 | 47 | 48 | func _on_editor_selection_changed() -> void: 49 | for node in get_editor_interface().get_selection().get_selected_nodes(): 50 | if node is BehaviorTreePlayer: 51 | return 52 | if bottom_panel_button: 53 | bottom_panel_button.hide() 54 | hide_bottom_panel() 55 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_graph/behavior_node/behavior_node.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/behavior_tree/behavior_tree_graph/behavior_node/behavior_node.gd" type="Script" id=1] 4 | 5 | [node name="BehaviorNode" type="GraphNode"] 6 | margin_right = 90.0 7 | margin_bottom = 79.0 8 | custom_constants/separation = 5 9 | show_close = true 10 | slot/0/left_enabled = false 11 | slot/0/left_type = 0 12 | slot/0/left_color = Color( 1, 1, 1, 1 ) 13 | slot/0/right_enabled = false 14 | slot/0/right_type = 0 15 | slot/0/right_color = Color( 1, 1, 1, 1 ) 16 | slot/1/left_enabled = false 17 | slot/1/left_type = 0 18 | slot/1/left_color = Color( 1, 1, 1, 1 ) 19 | slot/1/right_enabled = false 20 | slot/1/right_type = 0 21 | slot/1/right_color = Color( 1, 1, 1, 1 ) 22 | slot/2/left_enabled = false 23 | slot/2/left_type = 0 24 | slot/2/left_color = Color( 1, 1, 1, 1 ) 25 | slot/2/right_enabled = false 26 | slot/2/right_type = 0 27 | slot/2/right_color = Color( 1, 1, 1, 1 ) 28 | script = ExtResource( 1 ) 29 | __meta__ = { 30 | "_edit_use_anchors_": false 31 | } 32 | 33 | [node name="CommentLabel" type="Label" parent="."] 34 | margin_left = 16.0 35 | margin_top = 24.0 36 | margin_right = 74.0 37 | margin_bottom = 38.0 38 | mouse_filter = 0 39 | autowrap = true 40 | 41 | [node name="PropertyEdit" type="LineEdit" parent="."] 42 | margin_left = 16.0 43 | margin_top = 43.0 44 | margin_right = 74.0 45 | margin_bottom = 68.0 46 | rect_min_size = Vector2( 0, 25 ) 47 | expand_to_text_length = true 48 | 49 | [node name="EditButton" type="Button" parent="."] 50 | margin_left = 16.0 51 | margin_top = 73.0 52 | margin_right = 74.0 53 | margin_bottom = 93.0 54 | hint_tooltip = "Edit the group in the editor" 55 | text = "Edit" 56 | 57 | [connection signal="focus_exited" from="CommentLabel" to="." method="_on_CommentLabel_focus_exited"] 58 | [connection signal="gui_input" from="CommentLabel" to="." method="_on_CommentLabel_gui_input"] 59 | [connection signal="focus_exited" from="PropertyEdit" to="." method="_on_PropertyEdit_focus_exited"] 60 | [connection signal="text_changed" from="PropertyEdit" to="." method="_on_PropertyEdit_text_changed"] 61 | [connection signal="text_entered" from="PropertyEdit" to="." method="_on_PropertyEdit_text_entered"] 62 | [connection signal="pressed" from="EditButton" to="." method="_on_EditButton_pressed"] 63 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 54 | 55 | 61 | 62 | -------------------------------------------------------------------------------- /worker/worker.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | export var speed := 250.0 4 | 5 | var resources := 0 6 | var target_object : Area2D 7 | var target : Vector2 8 | 9 | const ConstructionSite = preload("res://construction_site/construction_site.gd") 10 | 11 | onready var tween : Tween = $Tween 12 | 13 | func near_resource() -> int: 14 | return OK if get_overlapping_areas().size() > 0 else FAILED 15 | 16 | 17 | func gather_resource(): 18 | if not get_overlapping_areas().size() or\ 19 | not get_overlapping_areas().front().is_in_group("Resources"): 20 | return FAILED 21 | else: 22 | yield(get_tree().create_timer(1.0), "timeout") 23 | if get_overlapping_areas().size(): 24 | get_overlapping_areas().front().free() 25 | target_object = null 26 | resources += 1 27 | return OK 28 | else: 29 | return FAILED 30 | 31 | 32 | func find_nearest_resource(max_range := "") -> int: 33 | target_object = get_nearest(get_tree().get_nodes_in_group("Resources")) 34 | if not target_object: 35 | return FAILED 36 | if max_range and\ 37 | target_object.position.distance_to(position) > float(max_range): 38 | return FAILED 39 | target = target_object.position 40 | return OK 41 | 42 | 43 | func target_random_point() -> int: 44 | target = Vector2(randf(), randf()) * get_viewport().size 45 | return OK 46 | 47 | 48 | func walk() -> int: 49 | if not target: 50 | return FAILED 51 | tween.interpolate_property(self, "position", position, target, 52 | position.distance_to(target) / speed, Tween.TRANS_LINEAR, 53 | Tween.EASE_IN) 54 | tween.start() 55 | yield(tween, "tween_completed") 56 | target = Vector2() 57 | return OK 58 | 59 | 60 | func find_nearest_construction_site() -> int: 61 | target_object = get_nearest(get_tree().get_nodes_in_group("ConstructionSites")) 62 | if not target_object: 63 | return FAILED 64 | target = target_object.position 65 | return OK 66 | 67 | 68 | func build() -> int: 69 | if not get_overlapping_areas().size()\ 70 | or not get_overlapping_areas().front() is ConstructionSite\ 71 | or resources <= 0 or get_overlapping_areas().front().completed: 72 | return FAILED 73 | else: 74 | if not get_overlapping_areas().size(): 75 | return FAILED 76 | else: 77 | resources -= 1 78 | get_overlapping_areas().front().progress += 1 79 | yield(get_tree().create_timer(0.5), "timeout") 80 | return OK 81 | 82 | 83 | func get_nearest(nodes : Array) -> Area2D: 84 | var nearest : Area2D 85 | var nearest_distance := INF 86 | for resource in nodes: 87 | var distance := position.distance_squared_to(resource.position) 88 | if distance < nearest_distance: 89 | nearest = resource 90 | nearest_distance = distance 91 | return nearest 92 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_graph/behavior_tree_graph_panel/behavior_tree_graph_panel.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/behavior_tree/behavior_tree_graph/behavior_tree_graph_panel/behavior_tree_graph_panel.gd" type="Script" id=1] 4 | 5 | [node name="BehaviorTreeGraphPanel" type="Control"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | rect_min_size = Vector2( 0, 280 ) 9 | script = ExtResource( 1 ) 10 | __meta__ = { 11 | "_edit_use_anchors_": false 12 | } 13 | 14 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 15 | anchor_right = 1.0 16 | anchor_bottom = 1.0 17 | __meta__ = { 18 | "_edit_use_anchors_": false 19 | } 20 | 21 | [node name="TopBar" type="HBoxContainer" parent="VBoxContainer"] 22 | margin_right = 1024.0 23 | margin_bottom = 14.0 24 | 25 | [node name="Label" type="Label" parent="VBoxContainer/TopBar"] 26 | margin_right = 32.0 27 | margin_bottom = 14.0 28 | text = "Path:" 29 | __meta__ = { 30 | "_edit_use_anchors_": false 31 | } 32 | 33 | [node name="NavigationButtonsContainer" type="HBoxContainer" parent="VBoxContainer/TopBar"] 34 | margin_left = 36.0 35 | margin_right = 1024.0 36 | margin_bottom = 14.0 37 | size_flags_horizontal = 3 38 | size_flags_vertical = 3 39 | __meta__ = { 40 | "_edit_use_anchors_": false 41 | } 42 | 43 | [node name="GraphEdit" type="GraphEdit" parent="VBoxContainer"] 44 | margin_top = 18.0 45 | margin_right = 1024.0 46 | margin_bottom = 600.0 47 | size_flags_horizontal = 3 48 | size_flags_vertical = 3 49 | right_disconnects = true 50 | scroll_offset = Vector2( -5, -289 ) 51 | __meta__ = { 52 | "_edit_use_anchors_": false 53 | } 54 | 55 | [connection signal="connection_from_empty" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_connection_from_empty"] 56 | [connection signal="connection_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_connection_request"] 57 | [connection signal="connection_to_empty" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_connection_to_empty"] 58 | [connection signal="copy_nodes_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_copy_nodes_request"] 59 | [connection signal="delete_nodes_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_delete_nodes_request"] 60 | [connection signal="disconnection_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_disconnection_request"] 61 | [connection signal="duplicate_nodes_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_duplicate_nodes_request"] 62 | [connection signal="paste_nodes_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_paste_nodes_request"] 63 | [connection signal="popup_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_popup_request"] 64 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_graph/behavior_node/behavior_node.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | 4 | """ 5 | A node in a behavior tree 6 | 7 | The layout is configured depending on the node type. 8 | """ 9 | 10 | signal group_name_changed(from, to) 11 | signal group_edited(group) 12 | 13 | var data : Dictionary 14 | var type : int 15 | 16 | const NODES = preload("res://addons/behavior_tree/nodes.gd").NODES 17 | const NodeType = preload("res://addons/behavior_tree/nodes.gd").NodeType 18 | const Nodes = preload("res://addons/behavior_tree/nodes.gd") 19 | 20 | onready var property_edit : LineEdit = $PropertyEdit 21 | onready var edit_button : Button = $EditButton 22 | onready var comment_label : Label = $CommentLabel 23 | 24 | func init(_data : Dictionary) -> void: 25 | data = _data 26 | var type_data := Nodes.get_type_data(data.type) 27 | type = type_data.type 28 | title = type_data.name 29 | if type != NodeType.GROUP: 30 | edit_button.free() 31 | if not "has_property" in type_data: 32 | property_edit.free() 33 | else: 34 | property_edit.text = data.get("property", "") 35 | if type != NodeType.COMMENT: 36 | comment_label.free() 37 | else: 38 | property_edit.hide() 39 | comment_label.text = data.get("property", "Click to edit comment") 40 | property_edit.expand_to_text_length = false 41 | comment = true 42 | resizable = true 43 | if type_data.name == "Breakpoint": 44 | overlay = GraphNode.OVERLAY_BREAKPOINT 45 | offset = data.get("position", Vector2()) 46 | if not get_child_count(): 47 | var control := Label.new() 48 | control.rect_min_size.y = 20 49 | add_child(control) 50 | if type != NodeType.COMMENT: 51 | set_slot(0, type != NodeType.ROOT, 0, Color.white, 52 | type != NodeType.LEAF and type != NodeType.GROUP, 53 | 0, Color.white) 54 | set_deferred("rect_size", data.get("size", Vector2())) 55 | 56 | 57 | func exit_comment_edit() -> void: 58 | if type == NodeType.COMMENT: 59 | comment_label.text = property_edit.text if property_edit.text else\ 60 | "Click to edit text." 61 | comment_label.show() 62 | property_edit.hide() 63 | 64 | 65 | func _on_EditButton_pressed() -> void: 66 | if property_edit.text: 67 | emit_signal("group_edited", property_edit.text) 68 | 69 | 70 | func _on_CommentLabel_gui_input(event : InputEvent) -> void: 71 | if event is InputEventMouseButton and event.pressed and\ 72 | event.button_index == BUTTON_LEFT: 73 | comment_label.hide() 74 | property_edit.show() 75 | property_edit.call_deferred("grab_focus") 76 | 77 | 78 | func _on_PropertyEdit_focus_exited() -> void: 79 | exit_comment_edit() 80 | 81 | 82 | func _on_PropertyEdit_text_entered(_new_text : String) -> void: 83 | exit_comment_edit() 84 | 85 | 86 | func _on_PropertyEdit_text_changed(new_text: String) -> void: 87 | if type == NodeType.GROUP: 88 | emit_signal("group_name_changed", data.get("property", ""), new_text) 89 | data.property = new_text 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Behavior Tree Plugin 2 | 3 | ![screenshot](screenshot.png) 4 | 5 | This plugin allows you to create complex AI behaviors using a tree of nodes. 6 | It uses a `GraphEdit` to edit the tree instead of the common Node-based approach, making it easier to use and better integrated into Godot. 7 | 8 | To learn about behaviour trees, check out this Gamasutra article by Chris Simpson: [Behavior trees for AI: How they work](https://www.gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php) 9 | 10 | ## Usage 11 | 12 | To add a behavior tree to a node, enable the plugin and add a `BehaviourTreePlayer`. An editor similar to the visual shader editor will show up in the bottom panel where you can edit the behavior tree. 13 | 14 | ## Nodes 15 | 16 | ### Misc 17 | 18 | |Node|Description| 19 | |-|-| 20 | |Root|Used for cosmetic reasons. Executes the next node.| 21 | |Group|Executes a group graph. See `Groups`| 22 | |Comment|Editable comment. Click the text to edit it. Stripped from the graph before execution.| 23 | 24 | ### Composites 25 | 26 | Composites execute a variable amount of branches in different ways. The order of execution is calculated by the position of the nodes. They are executed from top to bottom. 27 | 28 | |Node|Description| 29 | |-|-| 30 | |Selector|Executes until one branch succeeds. Returns failed if none succeeded, and success otherwise.| 31 | |Sequence|Executes until one branch fails. Returns failed if one failed, and success otherwise.| 32 | |Randomizer|Simmilar to sequence, but in random order.| 33 | 34 | ### Decorators 35 | 36 | Decorators modify the execution of the next node in different ways. 37 | 38 | |Node|Description| 39 | |-|-| 40 | |Inverter|Inverts the output of the next node.| 41 | |Repeater|Repeats the previous node infinitly, or the number of times put in the property field.| 42 | |Repeat Until Failed|Repeats the next node until it fails.| 43 | |Repeat Until Succeeded|Repeats the next node until it succeeds.| 44 | |Succeeder|Executes the next node, but always succeeds.| 45 | |Failer|Executes the next node, but always fails.| 46 | 47 | ### Leafs 48 | 49 | Leafs are the final nodes. They have different functionality. 50 | 51 | |Node|Description| 52 | |-|-| 53 | |Condition|Succeeds if the given expression is true. It is executed in the context of the root node.| 54 | |Function|Executes a function of the root node and uses its return value as status.| 55 | |Expression|Executes an expression in the context of the root node. Always succeeds.| 56 | |Wait|Waits the given amount of seconds.| 57 | |Breakpoint|Halts execution if hit.| 58 | 59 | ## Groups 60 | 61 | Groups are ways to organize and reuse node arrangements. 62 | 63 | To create a group, add a `Group` node and enter the group name. To edit the group, click the `Edit` button of the `Group` node. The graph of the group will be opened in the editor. A `Root` will be generated when opening a graph for the first time. 64 | 65 | To rename the group, just change the name in the `Group` node. 66 | 67 | To go back to the previous graph, use the `Path` section above the graph editor. Groups can be used globaly and can be nested. 68 | -------------------------------------------------------------------------------- /worker/worker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://worker/worker.png" type="Texture" id=1] 4 | [ext_resource path="res://addons/behavior_tree/behavior_tree.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/behavior_tree/behavior_tree_player/behavior_tree_player.gd" type="Script" id=3] 6 | [ext_resource path="res://worker/worker.gd" type="Script" id=4] 7 | 8 | [sub_resource type="RectangleShape2D" id=1] 9 | extents = Vector2( 48.6934, 64.1159 ) 10 | 11 | [sub_resource type="Resource" id=2] 12 | script = ExtResource( 2 ) 13 | graphs = { 14 | "BuildHouse": [ { 15 | "children": [ { 16 | "children": [ { 17 | "position": Vector2( 320, -160 ), 18 | "property": "resources > 0", 19 | "type": "Condition" 20 | }, { 21 | "position": Vector2( 320, -80 ), 22 | "property": "find_nearest_construction_site", 23 | "type": "Function" 24 | }, { 25 | "position": Vector2( 320, 0 ), 26 | "property": "walk", 27 | "type": "Function" 28 | }, { 29 | "children": [ { 30 | "position": Vector2( 460, 80 ), 31 | "property": "target_object.completed", 32 | "type": "Condition" 33 | } ], 34 | "position": Vector2( 320, 80 ), 35 | "type": "Inverter" 36 | }, { 37 | "children": [ { 38 | "position": Vector2( 540, 160 ), 39 | "property": "build", 40 | "type": "Function" 41 | } ], 42 | "position": Vector2( 320, 160 ), 43 | "type": "Repeat Until Failed" 44 | } ], 45 | "outputs": 5, 46 | "position": Vector2( 120, 0 ), 47 | "type": "Sequence" 48 | } ], 49 | "position": Vector2( 0, 0 ), 50 | "type": "Root" 51 | } ], 52 | "GatherStone": [ { 53 | "position": Vector2( 320, -120 ), 54 | "property": "Go to the first resource", 55 | "size": Vector2( 249, 295 ), 56 | "type": "Comment" 57 | }, { 58 | "position": Vector2( 580, -40 ), 59 | "property": "Mine the nearest resources too", 60 | "size": Vector2( 455, 285 ), 61 | "type": "Comment" 62 | }, { 63 | "children": [ { 64 | "children": [ { 65 | "position": Vector2( 340, -60 ), 66 | "property": "find_nearest_resource", 67 | "type": "Function" 68 | }, { 69 | "position": Vector2( 340, 20 ), 70 | "property": "walk", 71 | "type": "Function" 72 | }, { 73 | "children": [ { 74 | "children": [ { 75 | "position": Vector2( 780, 20 ), 76 | "property": "find_nearest_resource 180", 77 | "type": "Function" 78 | }, { 79 | "position": Vector2( 780, 100 ), 80 | "property": "walk", 81 | "type": "Function" 82 | }, { 83 | "position": Vector2( 780, 180 ), 84 | "property": "gather_resource", 85 | "type": "Function" 86 | } ], 87 | "outputs": 3, 88 | "position": Vector2( 600, 100 ), 89 | "type": "Sequence" 90 | } ], 91 | "position": Vector2( 340, 100 ), 92 | "type": "Repeat Until Failed" 93 | } ], 94 | "outputs": 3, 95 | "position": Vector2( 160, 20 ), 96 | "type": "Sequence" 97 | } ], 98 | "position": Vector2( 40, 20 ), 99 | "type": "Root" 100 | } ], 101 | "Main": [ { 102 | "children": [ { 103 | "children": [ { 104 | "position": Vector2( 300, -100 ), 105 | "property": "BuildHouse", 106 | "type": "Group" 107 | }, { 108 | "position": Vector2( 300, 0 ), 109 | "property": "GatherStone", 110 | "type": "Group" 111 | }, { 112 | "position": Vector2( 300, 100 ), 113 | "property": "WalkRandomly", 114 | "type": "Group" 115 | } ], 116 | "outputs": 3, 117 | "position": Vector2( 120, 0 ), 118 | "type": "Selector" 119 | } ], 120 | "position": Vector2( -20, 0 ), 121 | "type": "Root" 122 | } ], 123 | "WalkRandomly": [ { 124 | "children": [ { 125 | "children": [ { 126 | "position": Vector2( 320, -80 ), 127 | "property": "target_random_point", 128 | "type": "Function" 129 | }, { 130 | "position": Vector2( 320, 0 ), 131 | "property": "walk", 132 | "type": "Function" 133 | }, { 134 | "position": Vector2( 320, 80 ), 135 | "property": "2", 136 | "type": "Wait" 137 | } ], 138 | "outputs": 3, 139 | "position": Vector2( 140, 0 ), 140 | "type": "Sequence" 141 | } ], 142 | "position": Vector2( 0, 0 ), 143 | "type": "Root" 144 | } ] 145 | } 146 | 147 | [node name="Worker" type="Area2D"] 148 | script = ExtResource( 4 ) 149 | 150 | [node name="Sprite" type="Sprite" parent="."] 151 | position = Vector2( 0, -61.4489 ) 152 | scale = Vector2( 4, 4 ) 153 | texture = ExtResource( 1 ) 154 | 155 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 156 | position = Vector2( 0, -61.4489 ) 157 | shape = SubResource( 1 ) 158 | 159 | [node name="Tween" type="Tween" parent="."] 160 | 161 | [node name="BehaviorTreePlayer" type="Node" parent="."] 162 | script = ExtResource( 3 ) 163 | behavior_tree = SubResource( 2 ) 164 | -------------------------------------------------------------------------------- /addons/behavior_tree/nodes.gd: -------------------------------------------------------------------------------- 1 | """ 2 | Node metadata and tick functions 3 | """ 4 | 5 | enum NodeType { 6 | ROOT, 7 | GROUP, 8 | COMMENT, 9 | COMPOSITE, 10 | DECORATOR, 11 | LEAF, 12 | } 13 | 14 | const NODES := [ 15 | { 16 | name = "Root", 17 | type = NodeType.ROOT, 18 | }, 19 | { 20 | name = "Group", 21 | type = NodeType.GROUP, 22 | has_property = true, 23 | }, 24 | { 25 | name = "Comment", 26 | type = NodeType.COMMENT, 27 | has_property = true, 28 | }, 29 | { 30 | name = "Selector", 31 | type = NodeType.COMPOSITE, 32 | }, 33 | { 34 | name = "Sequence", 35 | type = NodeType.COMPOSITE, 36 | }, 37 | { 38 | name = "Randomizer", 39 | type = NodeType.COMPOSITE, 40 | }, 41 | { 42 | name = "Condition", 43 | type = NodeType.LEAF, 44 | has_property = true, 45 | }, 46 | { 47 | name = "Function", 48 | type = NodeType.LEAF, 49 | has_property = true, 50 | }, 51 | { 52 | name = "Expression", 53 | type = NodeType.LEAF, 54 | has_property = true, 55 | }, 56 | { 57 | name = "Wait", 58 | type = NodeType.LEAF, 59 | has_property = true, 60 | }, 61 | { 62 | name = "Breakpoint", 63 | type = NodeType.LEAF, 64 | }, 65 | { 66 | name = "Inverter", 67 | type = NodeType.DECORATOR, 68 | }, 69 | { 70 | name = "Repeater", 71 | type = NodeType.DECORATOR, 72 | has_property = true, 73 | }, 74 | { 75 | name = "Repeat Until Failed", 76 | type = NodeType.DECORATOR, 77 | }, 78 | { 79 | name = "Repeat Until Succeeded", 80 | type = NodeType.DECORATOR, 81 | }, 82 | { 83 | name = "Succeeder", 84 | type = NodeType.DECORATOR, 85 | }, 86 | { 87 | name = "Failer", 88 | type = NodeType.DECORATOR, 89 | }, 90 | ] 91 | 92 | static func get_type_data(type_name : String) -> Dictionary: 93 | for type_data in NODES: 94 | if type_data.name == type_name: 95 | return type_data 96 | return {} 97 | 98 | 99 | func tick_root(subject : Node, data : Dictionary, tree : Resource) -> int: 100 | var result = tick(data.children.front(), subject, tree) 101 | if result is GDScriptFunctionState: 102 | result = yield(result, "completed") 103 | return result 104 | 105 | 106 | func tick_group(subject : Node, data : Dictionary, tree : Resource) -> int: 107 | for child in tree.graphs[data.property]: 108 | var result = tick(child, subject, tree) 109 | if result is GDScriptFunctionState: 110 | result = yield(result, "completed") 111 | if result == OK: 112 | return OK 113 | return FAILED 114 | 115 | 116 | func tick_selector(subject : Node, data : Dictionary, tree : Resource) -> int: 117 | for child in data.children: 118 | var result = tick(child, subject, tree) 119 | if result is GDScriptFunctionState: 120 | result = yield(result, "completed") 121 | if result == OK: 122 | return OK 123 | return FAILED 124 | 125 | 126 | func tick_sequence(subject : Node, data : Dictionary, tree : Resource) -> int: 127 | for child in data.children: 128 | var result = tick(child, subject, tree) 129 | if result is GDScriptFunctionState: 130 | result = yield(result, "completed") 131 | if result == FAILED: 132 | return FAILED 133 | return OK 134 | 135 | 136 | func tick_randomizer(subject : Node, data : Dictionary, tree : Resource) -> int: 137 | var shuffled : Array = data.children.duplicate() 138 | shuffled.shuffle() 139 | for child in shuffled: 140 | var result = tick(child, subject, tree) 141 | if result is GDScriptFunctionState: 142 | result = yield(result, "completed") 143 | if result == FAILED: 144 | return FAILED 145 | return OK 146 | 147 | 148 | func tick_condition(subject : Node, data : Dictionary, tree : Resource) -> int: 149 | var expression := Expression.new() 150 | expression.parse(data.property) 151 | var result = expression.execute([], subject) 152 | if expression.has_execute_failed(): 153 | push_error("Expression '%s' failed with error '%s' " % 154 | [data.property, expression.get_error_text()]) 155 | return OK if result else FAILED 156 | 157 | 158 | func tick_function(subject : Node, data : Dictionary, tree : Resource) -> int: 159 | var args : PoolStringArray = data.property.split(" ") 160 | var function := args[0] 161 | args.remove(0) 162 | return subject.callv(function, args) 163 | 164 | 165 | func tick_expression(subject : Node, data : Dictionary, tree : Resource) -> int: 166 | var expression := Expression.new() 167 | expression.parse(data.property) 168 | expression.execute([], subject) 169 | return OK 170 | 171 | 172 | func tick_wait(subject : Node, data : Dictionary, tree : Resource) -> int: 173 | yield(subject.get_tree().create_timer(float(data.property)), "timeout") 174 | return OK 175 | 176 | 177 | func tick_breakpoint(_subject : Node, _data : Dictionary, tree : Resource) -> int: 178 | breakpoint 179 | return OK 180 | 181 | 182 | func tick_inverter(subject : Node, data : Dictionary, tree : Resource) -> int: 183 | var result = tick(data.children.front(), subject, tree) 184 | if result is GDScriptFunctionState: 185 | result = yield(result, "completed") 186 | return OK if result == FAILED else FAILED 187 | 188 | 189 | func tick_repeater(subject : Node, data : Dictionary, tree : Resource) -> int: 190 | var amounts_left := int(data.property) 191 | while true: 192 | if data.property: 193 | amounts_left -= 1 194 | if not amounts_left: 195 | break 196 | var result = tick(data.children.front(), subject, tree) 197 | if result is GDScriptFunctionState: 198 | result = yield(result, "completed") 199 | if result == FAILED: 200 | return FAILED 201 | return OK 202 | 203 | 204 | func tick_repeat_until_failed(subject : Node, data : Dictionary, tree : Resource) -> int: 205 | while true: 206 | var result = tick(data.children.front(), subject, tree) 207 | if result is GDScriptFunctionState: 208 | result = yield(result, "completed") 209 | if result == FAILED: 210 | return OK 211 | return OK 212 | 213 | 214 | func tick_repeat_until_succeeded(subject : Node, data : Dictionary, tree : Resource) -> int: 215 | while true: 216 | var result = tick(data.children.front(), subject, tree) 217 | if result is GDScriptFunctionState: 218 | result = yield(result, "completed") 219 | if result == OK: 220 | return OK 221 | return OK 222 | 223 | 224 | func tick_succeeder(subject : Node, data : Dictionary, tree : Resource) -> int: 225 | var result = tick(data.children.front(), subject, tree) 226 | if result is GDScriptFunctionState: 227 | result = yield(result, "completed") 228 | return OK 229 | 230 | 231 | func tick_failer(subject : Node, data : Dictionary, tree : Resource) -> int: 232 | var result = tick(data.children.front(), subject, tree) 233 | if result is GDScriptFunctionState: 234 | result = yield(result, "completed") 235 | return FAILED 236 | 237 | 238 | func tick(node : Dictionary, subject : Node, tree : Resource) -> int: 239 | return call("tick_" + node.type.to_lower().replace(" ", "_"), subject, node, tree) 240 | -------------------------------------------------------------------------------- /addons/behavior_tree/behavior_tree_graph/behavior_tree_graph_panel/behavior_tree_graph_panel.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Control 3 | 4 | """ 5 | The behavior graph panel 6 | """ 7 | 8 | var behavior_tree : BehaviorTree setget set_behavior_tree 9 | 10 | var graph : String setget set_graph 11 | var create_node_dialog : ConfirmationDialog 12 | var to_node : String 13 | var from_node : String 14 | var at_position : Vector2 15 | var undo_redo : UndoRedo 16 | 17 | var copied := [] 18 | 19 | const BehaviorTree = preload("res://addons/behavior_tree/behavior_tree.gd") 20 | const BehaviorNode = preload("res://addons/behavior_tree/behavior_tree_graph/behavior_node/behavior_node.tscn") 21 | 22 | onready var graph_edit : GraphEdit = $VBoxContainer/GraphEdit 23 | onready var navigation_buttons : HBoxContainer = $VBoxContainer/TopBar/NavigationButtonsContainer 24 | 25 | func _ready() -> void: 26 | var create_node_button := Button.new() 27 | create_node_button.text = "Add Node..." 28 | create_node_button.connect("pressed", self, "_on_CreateNodeButton_pressed") 29 | create_node_button.flat = true 30 | create_node_button.hint_tooltip = "Open the node creation dialog" 31 | graph_edit.get_zoom_hbox().add_child(create_node_button) 32 | graph_edit.get_zoom_hbox().move_child(create_node_button, 0) 33 | 34 | create_node_dialog.connect("node_selected", self, 35 | "_on_CreateBehaviorNodeDialog_node_selected") 36 | create_node_dialog.connect("hide", self, "_on_CreateBehaviorNodeDialog_hide") 37 | 38 | 39 | func set_behavior_tree(to : BehaviorTree) -> void: 40 | behavior_tree = to 41 | _clear_navigation_buttons() 42 | _add_navigation_button("Main") 43 | set_graph("Main") 44 | 45 | 46 | func set_graph(to : String) -> void: 47 | graph = to 48 | for node in graph_edit.get_children(): 49 | if node is GraphNode: 50 | node.queue_free() 51 | graph_edit.clear_connections() 52 | if not graph in behavior_tree.graphs: 53 | behavior_tree.graphs[graph] = [{type = "Root"}] 54 | _add_nodes(behavior_tree.graphs[graph]) 55 | 56 | 57 | func save_graph() -> void: 58 | behavior_tree.graphs[graph] = _save_nodes(graph_edit.get_children()) 59 | behavior_tree.remove_unused_graphs() 60 | 61 | 62 | func _add_nodes(nodes : Array, select := false, offset := Vector2()) -> void: 63 | var graph_nodes := {} 64 | for node in BehaviorTree.get_flat_nodes(nodes): 65 | var new_node := BehaviorNode.instance() 66 | graph_edit.add_child(new_node) 67 | new_node.connect("close_request", self, 68 | "_on_BehaviorNode_close_request", [new_node]) 69 | new_node.connect("offset_changed", self, 70 | "_on_BehaviorNode_offset_changed", [new_node]) 71 | new_node.connect("resize_request", self, 72 | "_on_BehaviorNode_resize_request", [new_node]) 73 | new_node.connect("group_edited", self, "_on_BehaviorNode_group_edited") 74 | new_node.connect("group_name_changed", self, 75 | "_on_BehaviorNode_group_name_changed") 76 | new_node.init(node) 77 | new_node.offset += offset 78 | new_node.selected = select 79 | graph_nodes[node] = new_node.name 80 | for node in BehaviorTree.get_flat_nodes(nodes): 81 | for child in node.get("children", []): 82 | graph_edit.connect_node(graph_nodes[node], 0, graph_nodes[child], 0) 83 | 84 | 85 | class YSorter: 86 | static func _sort(a : Dictionary, b : Dictionary) -> bool: 87 | return a.position.y < b.position.y 88 | 89 | func _save_nodes(nodes : Array) -> Array: 90 | var data := {} 91 | for node in nodes: 92 | if not node is GraphNode or node.is_queued_for_deletion(): 93 | continue 94 | data[node.name] = node.data 95 | # clear children array because it will be repopulated later 96 | node.data.erase("children") 97 | var root_nodes := data.values() 98 | for connection in graph_edit.get_connection_list(): 99 | var from : String = connection.from 100 | var to : String = connection.to 101 | if not (from in data and to in data): 102 | continue 103 | if not "children" in data[from]: 104 | data[from].children = [] 105 | data[from].children.append(data[to]) 106 | root_nodes.erase(data[to]) 107 | # sort the nodes by height to make execution order correct 108 | for node in BehaviorTree.get_flat_nodes(root_nodes): 109 | node.get("children", []).sort_custom(YSorter, "_sort") 110 | return root_nodes 111 | 112 | # GraphEdit 113 | 114 | func _on_GraphEdit_disconnection_request(from : String, from_slot : int, 115 | to : String, to_slot : int) -> void: 116 | graph_edit.disconnect_node(from, from_slot, to, to_slot) 117 | 118 | 119 | func _on_GraphEdit_connection_request(from : String, from_slot : int, 120 | to : String, to_slot : int) -> void: 121 | graph_edit.connect_node(from, from_slot, to, to_slot) 122 | 123 | 124 | func _on_GraphEdit_connection_from_empty(to : String, _to_slot : int, 125 | release_position : Vector2) -> void: 126 | to_node = to 127 | _show_create_dialog(true) 128 | 129 | 130 | func _on_GraphEdit_connection_to_empty(from : String, _from_slot : int, 131 | _release_position : Vector2) -> void: 132 | from_node = from 133 | _show_create_dialog(true) 134 | 135 | 136 | func _on_GraphEdit_copy_nodes_request() -> void: 137 | var min_pos := Vector2.INF 138 | var to_copy := [] 139 | for node in graph_edit.get_children(): 140 | if node is GraphNode and node.selected: 141 | to_copy.append(node) 142 | if node.offset < min_pos: 143 | min_pos = node.offset 144 | copied = _save_nodes(to_copy) 145 | for node in BehaviorTree.get_flat_nodes(copied): 146 | node.position -= min_pos 147 | 148 | 149 | func _on_GraphEdit_delete_nodes_request() -> void: 150 | for node in graph_edit.get_children(): 151 | if node is GraphNode and node.selected: 152 | graph_edit.remove_child(node) 153 | node.queue_free() 154 | save_graph() 155 | set_graph(graph) 156 | 157 | 158 | func _on_GraphEdit_duplicate_nodes_request() -> void: 159 | var to_duplicate := [] 160 | for node in graph_edit.get_children(): 161 | if node is GraphNode: 162 | if node.selected: 163 | to_duplicate.append(node) 164 | node.selected = false 165 | var nodes := _save_nodes(to_duplicate) 166 | _add_nodes(nodes, true, Vector2.ONE * 30) 167 | 168 | 169 | func _on_GraphEdit_paste_nodes_request() -> void: 170 | if not copied: 171 | return 172 | for node in graph_edit.get_children(): 173 | if node is GraphNode: 174 | node.selected = false 175 | _add_nodes(copied, true, graph_edit.get_local_mouse_position()\ 176 | + graph_edit.scroll_offset) 177 | 178 | 179 | func _on_GraphEdit_popup_request(_position : Vector2) -> void: 180 | _show_create_dialog(true) 181 | 182 | # Create Dialog 183 | 184 | func _show_create_dialog(at_mouse := false) -> void: 185 | create_node_dialog.popup() 186 | if at_mouse: 187 | create_node_dialog.rect_position = get_viewport().get_mouse_position() 188 | at_position = graph_edit.get_local_mouse_position() +\ 189 | graph_edit.scroll_offset 190 | 191 | 192 | func _on_CreateNodeButton_pressed() -> void: 193 | _show_create_dialog() 194 | 195 | 196 | func _on_CreateBehaviorNodeDialog_node_selected(type : String) -> void: 197 | var new_node : GraphNode = BehaviorNode.instance() 198 | graph_edit.add_child(new_node) 199 | new_node.connect("group_edited", self, "_on_BehaviorNode_group_edited") 200 | new_node.init({ 201 | type = type, 202 | }) 203 | if from_node and new_node.is_slot_enabled_left(0): 204 | graph_edit.connect_node(from_node, 0, new_node.name, 0) 205 | elif to_node and new_node.is_slot_enabled_right(0): 206 | graph_edit.connect_node(new_node.name, 0, to_node, 0) 207 | if type == "Comment": 208 | new_node.comment_label.hide() 209 | new_node.property_edit.show() 210 | if at_position: 211 | new_node.offset = at_position 212 | if is_instance_valid(new_node.property_edit): 213 | new_node.property_edit.call_deferred("grab_focus") 214 | 215 | 216 | func _on_CreateBehaviorNodeDialog_hide() -> void: 217 | from_node = "" 218 | to_node = "" 219 | 220 | 221 | # Behavior Nodes 222 | 223 | func _on_BehaviorNode_close_request(node : GraphNode) -> void: 224 | node.queue_free() 225 | save_graph() 226 | set_graph(graph) 227 | 228 | 229 | func _on_BehaviorNode_offset_changed(node : GraphNode) -> void: 230 | node.data.position = node.offset 231 | 232 | 233 | func _on_BehaviorNode_resize_request(new_minsize : Vector2, 234 | node : GraphNode) -> void: 235 | node.rect_size = new_minsize 236 | node.data.size = new_minsize 237 | 238 | 239 | func _on_BehaviorNode_group_edited(group : String) -> void: 240 | _add_navigation_button(group) 241 | save_graph() 242 | set_graph(group) 243 | 244 | 245 | func _on_BehaviorNode_group_name_changed(from : String, to : String) -> void: 246 | if from in behavior_tree.graphs: 247 | behavior_tree.graphs[to] = behavior_tree.graphs[from] 248 | behavior_tree.graphs.erase(from) 249 | 250 | 251 | # Navigation Buttons 252 | 253 | func _add_navigation_button(group : String) -> void: 254 | var navigation_button := Button.new() 255 | navigation_button.text = group 256 | navigation_button.connect("pressed", self, "_on_NavigationButton_pressed", 257 | [navigation_button]) 258 | navigation_buttons.add_child(navigation_button) 259 | 260 | 261 | func _clear_navigation_buttons() -> void: 262 | for navigation_button in navigation_buttons.get_children(): 263 | navigation_button.queue_free() 264 | 265 | 266 | func _on_NavigationButton_pressed(button : Button) -> void: 267 | for child_num in navigation_buttons.get_child_count() - button.get_index() - 1: 268 | navigation_buttons.get_child(button.get_index() + 1).free() 269 | save_graph() 270 | set_graph(button.text) 271 | -------------------------------------------------------------------------------- /construction_site/progress_bar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 34 | 38 | 42 | 43 | 45 | 49 | 53 | 57 | 58 | 60 | 64 | 68 | 69 | 71 | 75 | 79 | 80 | 82 | 86 | 90 | 94 | 95 | 97 | 101 | 105 | 109 | 110 | 112 | 116 | 120 | 124 | 128 | 129 | 139 | 146 | 151 | 158 | 159 | 169 | 176 | 181 | 188 | 189 | 199 | 205 | 213 | 218 | 219 | 229 | 236 | 241 | 251 | 257 | 263 | 268 | 275 | 280 | 287 | 288 | 299 | 301 | 305 | 309 | 313 | 314 | 320 | 325 | 330 | 335 | 336 | 346 | 357 | 367 | 377 | 379 | 383 | 387 | 388 | 399 | 401 | 405 | 409 | 410 | 421 | 422 | 441 | 448 | 449 | 451 | 452 | 454 | image/svg+xml 455 | 457 | 458 | 459 | 460 | 461 | 475 | 481 | 488 | 489 | 495 | 505 | 506 | 512 | 518 | 519 | --------------------------------------------------------------------------------