├── 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 |
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 |
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 | 
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 |
519 |
--------------------------------------------------------------------------------