├── .DS_Store ├── .gitignore ├── BT1.tscn ├── LICENSE ├── PrintTest.bts ├── PrintTest.tscn ├── PrintTest2.bts ├── PrintTest2.tscn ├── README.md ├── README_zh.md ├── TestScene.gd ├── TestScene.tscn ├── addons ├── .DS_Store └── naive_behavior_tree │ ├── .DS_Store │ ├── behavior_tree │ ├── .DS_Store │ ├── classes │ │ ├── .DS_Store │ │ ├── BTNode.gd │ │ ├── BehaviorTree.gd │ │ ├── actions │ │ │ ├── BTAction.gd │ │ │ ├── BTActionFail.gd │ │ │ ├── BTActionRandomTimer.gd │ │ │ ├── BTActionSuccess.gd │ │ │ └── BTActionTimer.gd │ │ ├── composites │ │ │ ├── BTComposite.gd │ │ │ ├── BTCompositeDynamicGuardSelector.gd │ │ │ ├── BTCompositeParallel.gd │ │ │ ├── BTCompositeRandomSelector.gd │ │ │ ├── BTCompositeRandomSequence.gd │ │ │ ├── BTCompositeSelector.gd │ │ │ ├── BTCompositeSequence.gd │ │ │ └── BTCompositeSingle.gd │ │ └── decorators │ │ │ ├── BTDecorator.gd │ │ │ ├── BTDecoratorAlwaysFail.gd │ │ │ ├── BTDecoratorAlwaysSucceed.gd │ │ │ ├── BTDecoratorInvert.gd │ │ │ ├── BTDecoratorRandom.gd │ │ │ ├── BTDecoratorRepeat.gd │ │ │ ├── BTDecoratorUntilFail.gd │ │ │ ├── BTDecoratorUntilSuccess.gd │ │ │ └── BTLoopDecorator.gd │ └── lib │ │ └── test │ │ ├── Print.gd │ │ └── SignalTimer.gd │ ├── compiler │ ├── AST │ │ ├── AST.gd │ │ └── EXPAST.gd │ ├── Compiler.gd │ ├── Parser.gd │ ├── Tokenizer.gd │ └── lib │ │ ├── BasicLib.gd │ │ └── LibBase.gd │ ├── editor │ └── script_editor │ │ ├── BTSEdit.gd │ │ ├── BTSEditor.gd │ │ ├── BTSEditor.tscn │ │ ├── CompletionPopup.gd │ │ └── CompletionPopupItem.gd │ ├── icon.svg │ ├── icon.svg.import │ ├── import_plugin │ ├── BehaviorTreeScriptResource.gd │ ├── BehaviorTreeScriptResourceLoader.gd │ └── BehaviorTreeScriptResourceSaver.gd │ ├── inspector_plugin │ └── inspector_plugin.gd │ ├── plugin.cfg │ ├── plugin.gd │ └── remote_debug │ ├── CaptureFuncObject.gd │ ├── ClientProtocol.gd │ ├── DictionaryDatabase.gd │ ├── RemoteDebug.gd │ ├── RemoteDebugClient.gd │ ├── RemoteDebugProtocol.gd │ ├── RemoteDebugServer.gd │ ├── ServerProtocol.gd │ ├── ddb_test.gd │ └── remote_debug_view │ ├── NBTGraphNode.gd │ ├── NBTGraphNode.tscn │ ├── ParameterContainer.gd │ ├── ParameterView.gd │ ├── ParameterView.tscn │ ├── ProgressModelView.gd │ ├── ProgressModelView.tscn │ ├── RemoteDebugView.gd │ └── RemoteDebugView.tscn ├── default_env.tres ├── font ├── NotoSansSC-Regular.otf └── font_16.tres ├── icon.png ├── icon.png.import ├── images ├── .DS_Store ├── .gdignore ├── asset_icon.png ├── d1.png ├── d2.png └── d3.png ├── new_resource.bts ├── new_script.bts ├── project.godot ├── test ├── RaiixTester.gd ├── parser_test.gd └── tokenizer_test.gd ├── test_scenes ├── .DS_Store └── UISignalTest │ ├── UISignalTest.tscn │ ├── bt │ ├── set_text.gd │ └── wait_signal.gd │ ├── the_tree.bts │ ├── the_tree.bts.import │ └── the_tree.tscn └── tutor_scenes ├── t1 ├── t1.bts └── t1.tscn └── t2 ├── Test2.gd ├── Test2.tscn ├── show.gd ├── t2.bts └── t2.tscn /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .import 3 | *.translation 4 | export_presets.cfg 5 | .mono 6 | .tmp 7 | -------------------------------------------------------------------------------- /BT1.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeParallel.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeRandomSelector.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionTimer.gd" type="Script" id=3] 6 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" type="Script" id=4] 7 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" type="Script" id=5] 8 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd" type="Script" id=6] 9 | 10 | [node name="BehaviorTreeTest" type="Node"] 11 | script = ExtResource( 4 ) 12 | guard_path = NodePath("") 13 | enable = true 14 | resume_mode = 0 15 | agent_path = NodePath("") 16 | root_path = NodePath("sequence") 17 | process_mode = 1 18 | 19 | [node name="sequence" type="Node" parent="."] 20 | script = ExtResource( 5 ) 21 | guard_path = NodePath("") 22 | 23 | [node name="print_123 [parallel]" type="Node" parent="sequence"] 24 | script = ExtResource( 1 ) 25 | guard_path = NodePath("") 26 | policy = 0 27 | orchestrator = 1 28 | 29 | [node name="print" type="Node" parent="sequence/print_123 [parallel]"] 30 | script = ExtResource( 6 ) 31 | guard_path = NodePath("") 32 | msg = "1" 33 | 34 | [node name="@print@21" type="Node" parent="sequence/print_123 [parallel]"] 35 | script = ExtResource( 6 ) 36 | guard_path = NodePath("") 37 | msg = "2" 38 | 39 | [node name="@print@22" type="Node" parent="sequence/print_123 [parallel]"] 40 | script = ExtResource( 6 ) 41 | guard_path = NodePath("") 42 | msg = "3" 43 | 44 | [node name="timer" type="Node" parent="sequence"] 45 | script = ExtResource( 3 ) 46 | guard_path = NodePath("") 47 | wait = 1.0 48 | 49 | [node name="print" type="Node" parent="sequence"] 50 | script = ExtResource( 6 ) 51 | guard_path = NodePath("") 52 | msg = "a" 53 | 54 | [node name="@timer@23" type="Node" parent="sequence"] 55 | script = ExtResource( 3 ) 56 | guard_path = NodePath("") 57 | wait = 1.0 58 | 59 | [node name="random_selector" type="Node" parent="sequence"] 60 | script = ExtResource( 2 ) 61 | guard_path = NodePath("") 62 | 63 | [node name="print" type="Node" parent="sequence/random_selector"] 64 | script = ExtResource( 6 ) 65 | guard_path = NodePath("") 66 | msg = "b" 67 | 68 | [node name="@print@24" type="Node" parent="sequence/random_selector"] 69 | script = ExtResource( 6 ) 70 | guard_path = NodePath("") 71 | msg = "c" 72 | 73 | [node name="@timer@25" type="Node" parent="sequence"] 74 | script = ExtResource( 3 ) 75 | guard_path = NodePath("") 76 | wait = 1.0 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2021 Raiix 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PrintTest.bts: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /PrintTest.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionTimer.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeParallel.gd" type="Script" id=3] 6 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeRandomSelector.gd" type="Script" id=4] 7 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" type="Script" id=5] 8 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd" type="Script" id=6] 9 | 10 | [node name="PrintTest" type="Node"] 11 | script = ExtResource( 2 ) 12 | root_path = NodePath("sequence") 13 | 14 | [node name="sequence" type="Node" parent="."] 15 | script = ExtResource( 5 ) 16 | 17 | [node name="parallel" type="Node" parent="sequence"] 18 | script = ExtResource( 3 ) 19 | orchestrator = 1 20 | 21 | [node name="print" type="Node" parent="sequence/parallel"] 22 | script = ExtResource( 6 ) 23 | msg = "1" 24 | 25 | [node name="print2" type="Node" parent="sequence/parallel"] 26 | script = ExtResource( 6 ) 27 | msg = "2" 28 | 29 | [node name="print3" type="Node" parent="sequence/parallel"] 30 | script = ExtResource( 6 ) 31 | msg = "3" 32 | 33 | [node name="timer" type="Node" parent="sequence"] 34 | script = ExtResource( 1 ) 35 | 36 | [node name="print" type="Node" parent="sequence"] 37 | script = ExtResource( 6 ) 38 | msg = "a" 39 | 40 | [node name="timer2" type="Node" parent="sequence"] 41 | script = ExtResource( 1 ) 42 | 43 | [node name="random_selector" type="Node" parent="sequence"] 44 | script = ExtResource( 4 ) 45 | 46 | [node name="print" type="Node" parent="sequence/random_selector"] 47 | script = ExtResource( 6 ) 48 | msg = "b" 49 | 50 | [node name="print2" type="Node" parent="sequence/random_selector"] 51 | script = ExtResource( 6 ) 52 | msg = "c" 53 | 54 | [node name="timer3" type="Node" parent="sequence"] 55 | script = ExtResource( 1 ) 56 | -------------------------------------------------------------------------------- /PrintTest2.bts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/PrintTest2.bts -------------------------------------------------------------------------------- /PrintTest2.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/lib/test/SignalTimer.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" type="Script" id=3] 6 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" type="Script" id=4] 7 | 8 | [node name="PrintTest2" type="Node"] 9 | script = ExtResource( 3 ) 10 | root_path = NodePath("sequence") 11 | 12 | [node name="sequence" type="Node" parent="."] 13 | script = ExtResource( 4 ) 14 | 15 | [node name="print" type="Node" parent="sequence"] 16 | script = ExtResource( 1 ) 17 | msg = "=====| Start |=====" 18 | 19 | [node name="stimer" type="Node" parent="sequence"] 20 | script = ExtResource( 2 ) 21 | 22 | [node name="print2" type="Node" parent="sequence"] 23 | script = ExtResource( 1 ) 24 | msg = "=====| End |=====" 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Naive Behavior Tree Plugin 2 | 3 | Compile a behavior tree script, and turn it into a PackedScene file which contains a behavior tree. 4 | 5 | [简体中文](./README_zh.md) 6 | 7 | ## Introduction 8 | 9 | The code of BTNode part in this project is literally a copy of this project: . 10 | 11 | The compiler part is designed by myself, so it might have a lot of bugs. XD 12 | 13 | More tests need to be done, so that it can actually be used in a real project. 14 | 15 | The `BTS` is refer to `Behavior Tree Script`. 16 | 17 | ## Syntex 18 | 19 | ``` 20 | line = [[indent] [guardableTask] [comment]] 21 | guardableTask = [guard [...]] task 22 | guard = '(' task ')' 23 | task = name [param:value [...]] | subtreeRef 24 | ``` 25 | 26 | ## Behavior Tree Nodes 27 | 28 | ### BTNode 29 | 30 | All behavior tree nodes inherit from this node. 31 | 32 | ### BehaviorTree 33 | 34 | It stores the root node, and the `agent` storing your custom game object which can be used in `BTNode` like this: `tree.agent`. 35 | 36 | ### BTAction 37 | 38 | Refer to an action. Your custom node should inherit from this node. 39 | 40 | Example: 41 | 42 | ``` 43 | tool 44 | extends BTAction 45 | 46 | export(String) var msg:String 47 | 48 | #----- Methods ----- 49 | func execute(): 50 | print(msg) 51 | return SUCCEEDED 52 | ``` 53 | 54 | You need to override `execute()` function in order to implement your custom function. 55 | 56 | In this function, you can use `tree` to access the Behavior Tree itself, `tree.agent` to access your custom game object. 57 | 58 | Return `SUCCEEDED` to indicate the action is done, `FAILED` is fail, `RUNNING` is running. 59 | 60 | You can also use `yield` to wait for a signal in this function, which is considered a 'running' status. 61 | 62 | An example implemented a timer using `yield`: 63 | 64 | ``` 65 | tool 66 | extends BTAction 67 | 68 | export(float) var wait:float = 1 69 | 70 | #----- Methods ----- 71 | func execute(): 72 | print('wait for 1 sec') 73 | yield(get_tree().create_timer(wait), "timeout") 74 | print('wait for 2 sec') 75 | yield(get_tree().create_timer(2), "timeout") 76 | print('wait for 3 sec') 77 | yield(get_tree().create_timer(3), "timeout") 78 | print('done.') 79 | return SUCCEEDED 80 | 81 | ``` 82 | 83 | 84 | ## More Example 85 | 86 | ``` 87 | # Comment 88 | import alias: "path/to/your/custom/behavior/tree/node.gd" # Import custom node 89 | import dead?: "path/to/your/custom/behavior/tree/node.gd" # Yes, you can use '?' as an ID or alias name. 90 | 91 | subtree name: xxx # Define a subtree, the first node is root, use indent to indicate the relation of parent or child node. 92 | parrallel orchestrator: JOIN # the left of ':' is parameter,the right is an expression that eval at complie time 93 | alias 94 | alias 95 | 96 | tree # A bts file can only contain at least one tree. Same as the subtree. 97 | sequence 98 | $xxx # Refer to a subtree 99 | (dead?) alias # use 'dead?' as a guard. 100 | ``` 101 | 102 | ``` 103 | # 104 | # The behavior tree of a dog. 105 | # 106 | 107 | import bark:"res://dog/bt/BarkTask.gd" 108 | import care:"res://dog/bt/CareTask.gd" 109 | import mark:"res://dog/bt/MarkTask.gd" 110 | import walk:"res://dog/bt/WalkTask.gd" 111 | 112 | subtree name: caretree 113 | parallel 114 | care times: 3 115 | alwaysFail 116 | 'res://dog/bt/RestTask' # Use a path to a gdscript directly. 117 | 118 | tree 119 | selector 120 | $caretree # use '$' to refer to a subtree 121 | sequence 122 | bark times: randi_range(1, 3) # use a buit-in function that return a random integer between 1 and 3. 123 | walk 124 | "res://dog/bt/BarkTask" 125 | ``` 126 | 127 | ## Built-in nodes in BTS 128 | 129 | ``` 130 | # Actions 131 | fail # Always fail 132 | success # Always success 133 | timer wait: 1.0 # Wait for 1 sec. 134 | 135 | # Composites 136 | dynamic_guard_selector # Choose a child to run at a time by guard check that succeeded. 137 | parallel policy: SEQUENCE/SELECTOR orchestrator: RESUME/JOIN # Run all children at a time with the policy and orchestrator applied. 138 | selector # Choose a child to run in order. 139 | random_selector 140 | sequence # Run children one by one in order. 141 | random_sequence 142 | 143 | # Decorators - wrap the result of a child. 144 | always_fail 145 | always_succeed 146 | invert 147 | random success_posibility: 0.5 # Has a chance of 0.5 to run the child. 148 | repeat times: 1 # Repeat the child for 1 time. 149 | until_fail # Run child until it fail. 150 | until_success # Run child until it scceeded. 151 | 152 | ``` 153 | 154 | ## License 155 | 156 | MIT 157 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # 一个行为树编译器 2 | 3 | 输入行为树定义脚本,输出PackedScene并保存到项目文件中 4 | 5 | ## 介绍 6 | 7 | 本项目行为树节点部分参考 。 8 | 编译器部分为自己研发,所以会有很多bug。。。 9 | 希望有人能帮忙测试,以便使其能够实际应用。 10 | 一下使用`BTS`表示行为树脚本。 11 | 12 | ## 语法 13 | 14 | ``` 15 | line = [[indent] [guardableTask] [comment]] 16 | guardableTask = [guard [...]] task 17 | guard = '(' task ')' 18 | task = name [param:value [...]] | subtreeRef 19 | ``` 20 | 21 | ## 行为树节点 22 | 23 | ### BTNode 24 | 25 | 所有行为树节点都继承自此节点 26 | 27 | ### BehaviorTree 28 | 29 | 用于存放根节点,可设置`agent`,以便在子节点中获取:`tree.agent`。 30 | 可以设置为自己的游戏实体对象。 31 | 32 | ### BTAction 33 | 34 | 表示一个动作,自定义节点应该继承自该节点。 35 | 36 | 示例: 37 | 38 | ``` 39 | tool 40 | extends BTAction 41 | 42 | export(String) var msg:String 43 | 44 | #----- Methods ----- 45 | func execute(): 46 | print(msg) 47 | return SUCCEEDED 48 | ``` 49 | 50 | 需要重载`execute()`函数以便实现自定义功能。 51 | 函数中可访问`tree`来得到行为树,`tree.agent`可得到自定义的游戏实体对象。 52 | 返回`SUCCEEDED`表示该动作完成了,`FAILED`表示该动作失败了,`RUNNING`表示该动作正在执行。 53 | 可以使用`yield`来实现等待信号。 54 | 55 | 一个使用来`yield`实现的定时器: 56 | 57 | ``` 58 | tool 59 | extends BTAction 60 | 61 | export(float) var wait:float = 1 62 | 63 | #----- Methods ----- 64 | func execute(): 65 | print('wait for 1 sec') 66 | yield(get_tree().create_timer(wait), "timeout") 67 | print('wait for 2 sec') 68 | yield(get_tree().create_timer(2), "timeout") 69 | print('wait for 3 sec') 70 | yield(get_tree().create_timer(3), "timeout") 71 | print('done.') 72 | return SUCCEEDED 73 | 74 | ``` 75 | 76 | 77 | ## 示例 78 | 79 | ``` 80 | # 注释 81 | import alias: "path/to/your/custom/behavior/tree/node.gd" # 导入自定义节点 82 | import dead?: "path/to/your/custom/behavior/tree/node.gd" # 导入自定义节点,标识符可以使用问号 83 | 84 | subtree name: xxx # 定义子树,第一个节点为根节点,缩进表示节点的父子关系 85 | parrallel orchestrator: JOIN # 后面为参数,':'左边为参数名,右边为表达式,可包含函数,表达式在编译时执行求值 86 | alias 87 | alias 88 | 89 | tree # 必须仅包含1个树,第一个节点为根节点,缩进表示父子关系 90 | sequence 91 | $xxx # 引用子树 92 | (dead?) alias # 使用'dead?'作为护卫节点 93 | ``` 94 | 95 | ``` 96 | # 97 | # 小狗的行为树 98 | # 99 | 100 | import 叫:"res://dog/bt/BarkTask.gd" 101 | import 摇摆:"res://dog/bt/CareTask.gd" 102 | import 标记:"res://dog/bt/MarkTask.gd" 103 | import 走:"res://dog/bt/WalkTask.gd" 104 | 105 | subtree name: 摇摆树 106 | parallel # 并行 107 | 摇摆 次数: 3 # 摇摆3次 108 | alwaysFail # 总是失败 109 | res://dog/bt/RestTask' # 休息 110 | 111 | tree 112 | selector # 选择 113 | $摇摆树 # 引用子树 114 | sequence # 顺序 115 | 叫 次数: rand_rangei(1, 1) 116 | 走 117 | "res://dog/bt/BarkTask" # 直接使用字符串也行 118 | 标记 119 | ``` 120 | ## BTS中的内建节点 121 | 122 | ``` 123 | # Actions 124 | fail # Always fail 125 | success # Always success 126 | timer wait: 1.0 # Wait for 1 sec. 127 | 128 | # Composites 129 | dynamic_guard_selector # Choose a child to run at a time by guard check that succeeded. 130 | parallel policy: SEQUENCE/SELECTOR orchestrator: RESUME/JOIN # Run all children at a time with the policy and orchestrator applied. 131 | selector # Choose a child to run in order. 132 | random_selector 133 | sequence # Run children one by one in order. 134 | random_sequence 135 | 136 | # Decorators - wrap the result of a child. 137 | always_fail 138 | always_succeed 139 | invert 140 | random success_posibility: 0.5 # Has a chance of 0.5 to run the child. 141 | repeat times: 1 # Repeat the child for 1 time. 142 | until_fail # Run child until it fail. 143 | until_success # Run child until it scceeded. 144 | 145 | ``` 146 | 147 | ## 许可证 148 | 149 | MIT 150 | 151 | ## 其他 152 | 153 | B站地址:https://space.bilibili.com/15155009 154 | 155 | 爱发电主页:https://afdian.net/@raiix 156 | 157 | 欢迎赞助支持哟~ 158 | 159 | 蘩的游戏开发交流QQ群:837298758 160 | 161 | 来分享你刚编的游戏创意或者展现你的游戏开发技术吧~ 162 | -------------------------------------------------------------------------------- /TestScene.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | var tokenizer_test = preload('res://test/tokenizer_test.gd').new() 5 | var parser_test = preload('res://test/parser_test.gd').new() 6 | 7 | func _ready() -> void: 8 | # tokenizer_test.run_tests() 9 | # parser_test.run_tests() 10 | $Control/VSplitContainer/HSplitContainer/Output.text = parser_test.prettify_bt($Control/VSplitContainer/HSplitContainer/Input.text) 11 | 12 | #---- Methods ----- 13 | func set_children_owner(p:Node, o:Node): 14 | for child in p.get_children(): 15 | child.owner = o 16 | set_children_owner(child, o) 17 | 18 | #---- Signals ----- 19 | func _on_Input_text_changed() -> void: 20 | $Control/VSplitContainer/HSplitContainer/Output.text = parser_test.prettify_bt($Control/VSplitContainer/HSplitContainer/Input.text) 21 | 22 | 23 | func _on_CompileButton_pressed() -> void: 24 | var Compiler = load('res://addons/naive_behavior_tree/compiler/Compiler.gd') 25 | var c = Compiler.new() 26 | c.init() 27 | var bt = c.compile($Control/VSplitContainer/HSplitContainer/Input.text) 28 | if bt == null: 29 | return 30 | set_children_owner(bt, bt) 31 | 32 | var ps = PackedScene.new() 33 | ps.pack(bt) 34 | 35 | ResourceSaver.save('res://BT1.tscn', ps) 36 | 37 | -------------------------------------------------------------------------------- /TestScene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TestScene.gd" type="Script" id=1] 4 | [ext_resource path="res://font/NotoSansSC-Regular.otf" type="DynamicFontData" id=2] 5 | 6 | [sub_resource type="DynamicFont" id=1] 7 | font_data = ExtResource( 2 ) 8 | 9 | [node name="TestScene" type="Node"] 10 | script = ExtResource( 1 ) 11 | 12 | [node name="Control" type="Control" parent="."] 13 | anchor_right = 1.0 14 | anchor_bottom = 1.0 15 | __meta__ = { 16 | "_edit_use_anchors_": false 17 | } 18 | 19 | [node name="VSplitContainer" type="VSplitContainer" parent="Control"] 20 | anchor_right = 1.0 21 | anchor_bottom = 1.0 22 | __meta__ = { 23 | "_edit_use_anchors_": false 24 | } 25 | 26 | [node name="HSplitContainer" type="HSplitContainer" parent="Control/VSplitContainer"] 27 | margin_right = 1024.0 28 | margin_bottom = 444.0 29 | size_flags_vertical = 3 30 | size_flags_stretch_ratio = 3.0 31 | __meta__ = { 32 | "_edit_use_anchors_": false 33 | } 34 | 35 | [node name="Input" type="TextEdit" parent="Control/VSplitContainer/HSplitContainer"] 36 | margin_right = 506.0 37 | margin_bottom = 444.0 38 | size_flags_horizontal = 3 39 | size_flags_vertical = 3 40 | custom_fonts/font = SubResource( 1 ) 41 | text = "# 42 | # 测试输出 43 | # 44 | 45 | import print:\"res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd\" 46 | 47 | subtree name: print_123 48 | parallel orchestrator: JOIN 49 | print msg: '1' 50 | print msg: '2' 51 | print msg: '3' 52 | 53 | tree 54 | sequence 55 | $print_123 56 | timer wait: 1 57 | print msg:'a' 58 | timer wait: 1 59 | random_selector 60 | print msg: 'b' 61 | print msg: 'c' 62 | timer wait: 1 63 | 64 | 65 | " 66 | show_line_numbers = true 67 | draw_tabs = true 68 | __meta__ = { 69 | "_edit_use_anchors_": false 70 | } 71 | 72 | [node name="Output" type="TextEdit" parent="Control/VSplitContainer/HSplitContainer"] 73 | margin_left = 518.0 74 | margin_right = 1024.0 75 | margin_bottom = 444.0 76 | size_flags_horizontal = 3 77 | size_flags_vertical = 3 78 | custom_fonts/font = SubResource( 1 ) 79 | text = "asdasd" 80 | readonly = true 81 | show_line_numbers = true 82 | draw_tabs = true 83 | __meta__ = { 84 | "_edit_use_anchors_": false 85 | } 86 | 87 | [node name="CenterContainer" type="CenterContainer" parent="Control/VSplitContainer"] 88 | margin_top = 456.0 89 | margin_right = 1024.0 90 | margin_bottom = 600.0 91 | size_flags_horizontal = 3 92 | size_flags_vertical = 3 93 | 94 | [node name="CompileButton" type="Button" parent="Control/VSplitContainer/CenterContainer"] 95 | margin_left = 479.0 96 | margin_top = 62.0 97 | margin_right = 544.0 98 | margin_bottom = 82.0 99 | text = "Compile" 100 | 101 | [connection signal="text_changed" from="Control/VSplitContainer/HSplitContainer/Input" to="." method="_on_Input_text_changed"] 102 | [connection signal="pressed" from="Control/VSplitContainer/CenterContainer/CompileButton" to="." method="_on_CompileButton_pressed"] 103 | -------------------------------------------------------------------------------- /addons/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/addons/.DS_Store -------------------------------------------------------------------------------- /addons/naive_behavior_tree/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/addons/naive_behavior_tree/.DS_Store -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/addons/naive_behavior_tree/behavior_tree/.DS_Store -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/addons/naive_behavior_tree/behavior_tree/classes/.DS_Store -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/BTNode.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | class_name BTNode 4 | 5 | enum { 6 | FRESH, 7 | RUNNING, 8 | FAILED, 9 | SUCCEEDED, 10 | CANCELLED 11 | } 12 | 13 | var parent:Node = null 14 | var tree = null 15 | export(NodePath) var guard_path 16 | var guard:Node = null 17 | var status = FRESH setget _on_set_status 18 | func _on_set_status(v): 19 | status = v 20 | if tree: 21 | tree.emit_signal('task_status_changed', self) 22 | 23 | func _ready() -> void: 24 | if Engine.editor_hint: 25 | return 26 | if not guard: 27 | guard = get_node_or_null(guard_path) 28 | 29 | #----- Final Methods ----- 30 | func running(): 31 | self.status = RUNNING 32 | if parent: 33 | parent.child_running(self, self) 34 | 35 | func success(): 36 | self.status = SUCCEEDED 37 | end() 38 | if parent: 39 | parent.child_success(self) 40 | 41 | func fail(): 42 | self.status = FAILED 43 | end() 44 | if parent: 45 | parent.child_fail(self) 46 | 47 | func cancel(): 48 | cancel_running_children(0) 49 | self.status = CANCELLED 50 | end() 51 | 52 | func cancel_running_children(start_index): 53 | var i = start_index 54 | var n = get_child_count() 55 | while i < n: 56 | var child = get_child(i) as Node 57 | if child.has_method('_Class_Type_BTNode_'): 58 | if child.status == RUNNING: 59 | child.cancel() 60 | i += 1 61 | 62 | func check_guard(_parent): 63 | if guard == null: 64 | return true 65 | 66 | if not guard.check_guard(_parent): 67 | return false 68 | 69 | guard.parent = _parent.tree.guard_evaluator 70 | guard.tree = _parent.tree 71 | guard.start() 72 | guard.run() 73 | 74 | match guard.status: 75 | SUCCEEDED: 76 | return true 77 | FAILED: 78 | return false 79 | _: 80 | printerr('Illegal guard status: %s' % str(guard.status)) 81 | return false 82 | 83 | func _Class_Type_BTNode_(): 84 | pass 85 | #----- Abstract Methods ----- 86 | func run(): 87 | pass 88 | 89 | func child_success(task): 90 | pass 91 | 92 | func child_fail(task): 93 | pass 94 | 95 | func child_running(running_task, reporter): 96 | pass 97 | #----- Methods ----- 98 | func start(): 99 | tree.emit_signal('task_started', self) 100 | 101 | func end(): 102 | tree.emit_signal('task_ended', self) 103 | 104 | func reset(): 105 | if self.status == RUNNING: 106 | cancel() 107 | for child in get_children(): 108 | if child.has_method('_Class_Type_BTNode_'): 109 | child.reset() 110 | self.status = FRESH 111 | tree = null 112 | parent = null 113 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTNode 3 | class_name BehaviorTree, '../../icon.svg' 4 | 5 | signal task_started(task) 6 | signal task_ended(task) 7 | signal task_status_changed(task) 8 | 9 | signal tree_active 10 | signal tree_inactive 11 | 12 | export var enable := true setget _on_set_enable 13 | func _on_set_enable(v): 14 | if enable != v: 15 | if v: 16 | if resume_mode == ResumeMode.Reset: 17 | reset() 18 | emit_signal('tree_active') 19 | else: 20 | emit_signal('tree_inactive') 21 | enable = v 22 | var ready := false 23 | enum ResumeMode { 24 | Resume, 25 | Reset, 26 | } 27 | export(ResumeMode) var resume_mode = ResumeMode.Resume 28 | 29 | export(NodePath) var agent_path 30 | var agent 31 | 32 | export(NodePath) var root_path 33 | var root:BTNode = null 34 | 35 | enum ProcessMode { 36 | Process, 37 | Physics, 38 | } 39 | export(ProcessMode) var process_mode = ProcessMode.Physics setget _on_set_process_mode 40 | func _on_set_process_mode(v): 41 | process_mode = v 42 | set_process(process_mode == ProcessMode.Process) 43 | set_physics_process(process_mode == ProcessMode.Physics) 44 | 45 | class GuardEvaluator: 46 | extends BTNode 47 | 48 | func run(): 49 | pass 50 | 51 | func child_success(task): 52 | pass 53 | 54 | func child_fail(task): 55 | pass 56 | 57 | func child_running(running_task, reporter): 58 | pass 59 | 60 | var guard_evaluator:GuardEvaluator 61 | 62 | func _enter_tree() -> void: 63 | guard_evaluator = GuardEvaluator.new() 64 | guard_evaluator.tree = tree 65 | 66 | func _exit_tree() -> void: 67 | guard_evaluator.free() 68 | guard_evaluator = null 69 | 70 | func _ready() -> void: 71 | if Engine.editor_hint: 72 | return 73 | root = get_node(root_path) 74 | tree = self 75 | 76 | agent = get_node_or_null(agent_path) 77 | 78 | ready = true 79 | 80 | _on_set_process_mode(process_mode) 81 | 82 | 83 | func _notification(what: int) -> void: 84 | if Engine.editor_hint: 85 | return 86 | if not enable: 87 | return 88 | match what: 89 | NOTIFICATION_PHYSICS_PROCESS: 90 | if process_mode == ProcessMode.Physics: 91 | step() 92 | NOTIFICATION_PROCESS: 93 | if process_mode == ProcessMode.Process: 94 | step() 95 | #----- Methods ----- 96 | func child_running(running_task, reporter): 97 | running() 98 | 99 | func child_fail(task): 100 | fail() 101 | 102 | func child_success(task): 103 | success() 104 | 105 | func step(): 106 | if root.status == RUNNING: 107 | root.run() 108 | else: 109 | root.parent = self 110 | root.tree = self 111 | root.start() 112 | if root.check_guard(self): 113 | root.run() 114 | else: 115 | root.fail() 116 | 117 | func run(): 118 | pass 119 | 120 | func reset(): 121 | .reset() 122 | tree = self 123 | #----- Signals ----- 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/actions/BTAction.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTNode 3 | class_name BTAction 4 | 5 | var last_func_state:GDScriptFunctionState 6 | var is_finally_done:bool 7 | 8 | func _Class_Type_BTAction_(): 9 | pass 10 | #----- Abstract Methods ----- 11 | # In order to implement the function you want, you need to override this function. 12 | # must return status 13 | # support yiled statement 14 | func execute(): 15 | print('This is a empty action.') 16 | return SUCCEEDED 17 | 18 | #----- Final Methods ----- 19 | func start(): 20 | .start() 21 | last_func_state = null 22 | is_finally_done = false 23 | 24 | func run(): 25 | var res = null 26 | if last_func_state == null: 27 | res = execute() 28 | if res == null: 29 | printerr('The execute function must return a status value!') 30 | if res is GDScriptFunctionState: 31 | if res.is_valid(): 32 | wait_func_state_to_complete(res) 33 | running() 34 | else: 35 | fail() 36 | return 37 | match_res(res) 38 | else: 39 | if not is_finally_done: 40 | running() 41 | else: 42 | last_func_state = null 43 | is_finally_done = false 44 | 45 | func match_res(res): 46 | match res: 47 | SUCCEEDED: 48 | success() 49 | FAILED: 50 | fail() 51 | RUNNING: 52 | running() 53 | _: 54 | printerr('The result of execute function is illegal!') 55 | 56 | func wait_func_state_to_complete(func_state): 57 | last_func_state = func_state 58 | var res = yield(func_state, "completed") 59 | if res is GDScriptFunctionState: 60 | if res.is_valid(): 61 | wait_func_state_to_complete(res) 62 | else: 63 | is_finally_done = true 64 | fail() 65 | else: 66 | is_finally_done = true 67 | match_res(res) 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionFail.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTAction 3 | class_name BTActionFail 4 | 5 | #----- Methods ----- 6 | func execute(): 7 | return FAILED 8 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionRandomTimer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTAction 3 | class_name BTActionRandomTimer 4 | 5 | var start_time:float 6 | var wait:float = 1 #sec 7 | 8 | export(float) var min_wait:float = 0 #sec 9 | export(float) var max_wait:float = 1 #sec 10 | 11 | #----- Methods ----- 12 | func start(): 13 | start_time = OS.get_ticks_msec() 14 | wait = rand_range(min_wait, max_wait) 15 | .start() 16 | 17 | 18 | func execute(): 19 | return RUNNING if OS.get_ticks_msec() - start_time < wait * 1000 else SUCCEEDED 20 | 21 | func reset(): 22 | start_time = 0 23 | .reset() 24 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionSuccess.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTAction 3 | class_name BTActionSuccess 4 | 5 | #----- Methods ----- 6 | func execute(): 7 | return SUCCEEDED 8 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionTimer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTAction 3 | class_name BTActionTimer 4 | 5 | var start_time:float 6 | export(float) var wait:float = 1 #sec 7 | 8 | #----- Methods ----- 9 | func start(): 10 | start_time = OS.get_ticks_msec() 11 | .start() 12 | 13 | 14 | func execute(): 15 | return RUNNING if OS.get_ticks_msec() - start_time < wait * 1000 else SUCCEEDED 16 | 17 | func reset(): 18 | start_time = 0 19 | .reset() 20 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTComposite.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTNode 3 | class_name BTComposite 4 | 5 | 6 | #----- Methods ----- 7 | func get_bt_children(): 8 | var res = [] 9 | for child in get_children(): 10 | if child.has_method('_Class_Type_BTNode_'): 11 | res.append(child) 12 | return res 13 | 14 | func get_last_child(): 15 | var pre = get_child_count() - 1 16 | while pre >= 0 and not get_child(pre).has_method('_Class_Type_BTNode_'): 17 | pre -= 1 18 | if pre >= 0: 19 | return get_child(pre) 20 | return null 21 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeDynamicGuardSelector.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTComposite 3 | class_name BTCompositeDynamicGuardSelector 4 | 5 | # 动态条件选择器 6 | # 开始时按顺序判断子节点的条件是否成立 7 | # 只运行成立的某一个子节点 8 | # 若所有节点的条件都不成立,则失败 9 | 10 | # Dynamic Guard Selector 11 | # Choose a child that pass guard check to run in order. 12 | # Only run one child at a time. 13 | # If all child guard check fail, it fail. 14 | 15 | var running_child:BTNode 16 | 17 | #----- Methods ----- 18 | func child_running(running_task, reporter): 19 | running_child = running_task 20 | running() 21 | 22 | func child_success(task): 23 | running_child = null 24 | success() 25 | 26 | func child_fail(task): 27 | running_child = null 28 | fail() 29 | 30 | func run(): 31 | var child_to_run:BTNode = null 32 | for child in get_children(): 33 | if child.has_method('_Class_Type_BTNode_'): 34 | if child.check_guard(self): 35 | child_to_run = child 36 | break 37 | 38 | if running_child and running_child != child_to_run: 39 | running_child.cancel() 40 | running_child = null 41 | 42 | if child_to_run == null: 43 | fail() 44 | else: 45 | if running_child == null: 46 | running_child = child_to_run 47 | running_child.parent = self 48 | running_child.tree = tree 49 | running_child.start() 50 | running_child.run() 51 | 52 | func reset(): 53 | .reset() 54 | running_child = null 55 | 56 | func cancel(): 57 | .cancel() 58 | running_child = null 59 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeParallel.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTComposite 3 | class_name BTCompositeParallel 4 | 5 | enum Policy { 6 | SEQUENCE, # 只要有一个子节点失败,则并行节点失败;当所有子节点成功时,并行节点成功 7 | # As long as one child fail, it fail. 8 | # When all child success, it success. 9 | SELECTOR # 只要有一个子节点成功,则并行节点成功;只有所有节点失败,并行节点才失败 10 | # As long as one child success, it success. 11 | # Only all child fail, it fail. 12 | } 13 | 14 | enum Orchestrator { 15 | Resume, # 子节点会每帧都开始或者继续运行 16 | # 当一个子节点成功后完全不会等待其他子节点 17 | # Resume children every frame. 18 | Join # 子节点会执行到成功或者失败,子节点会在并行节点成功或者失败后才会重新执行 19 | # 例如:假设有一个子节点为定时器节点,等待1秒钟,若其他子节点在1秒内都执行成功了,则需要等待该定时器,直到1秒结束 20 | # Wait for all child fail/success to fail/success. 21 | } 22 | 23 | export(Policy) var policy 24 | export(Orchestrator) var orchestrator 25 | 26 | var no_running_tasks:bool = true 27 | var last_result 28 | var current_child_index:int 29 | 30 | #----- Methods ----- 31 | func run(): 32 | match orchestrator: 33 | Orchestrator.Resume: 34 | no_running_tasks = true 35 | last_result = null 36 | for child in get_children(): 37 | if child.has_method('_Class_Type_BTNode_'): 38 | if child.status == RUNNING: 39 | child.run() 40 | else: 41 | child.parent = self 42 | child.tree = tree 43 | child.start() 44 | if child.check_guard(self): 45 | child.run() 46 | else: 47 | child.fail() 48 | 49 | if last_result != null: 50 | cancel_running_children(current_child_index+1 if no_running_tasks else 0) 51 | if last_result: 52 | success() 53 | else: 54 | fail() 55 | return 56 | running() 57 | Orchestrator.Join: 58 | no_running_tasks = true 59 | last_result = null 60 | for child in get_children(): 61 | if child.has_method('_Class_Type_BTNode_'): 62 | match child.status: 63 | RUNNING: 64 | child.run() 65 | SUCCEEDED, FAILED: 66 | pass 67 | _: 68 | child.parent = self 69 | child.tree = tree 70 | child.start() 71 | if child.check_guard(self): 72 | child.run() 73 | else: 74 | child.fail() 75 | 76 | if last_result != null: 77 | cancel_running_children(current_child_index + 1 if no_running_tasks else 0) 78 | reset_all_children() 79 | if last_result: 80 | success() 81 | else: 82 | fail() 83 | return 84 | running() 85 | 86 | func child_running(running_task, reporter): 87 | no_running_tasks = false 88 | 89 | func child_success(task): 90 | match policy: 91 | Policy.SEQUENCE: 92 | match orchestrator: 93 | Orchestrator.Resume: 94 | last_result = null 95 | if no_running_tasks and get_child(current_child_index) == get_last_child(): 96 | last_result = true 97 | Orchestrator.Join: 98 | last_result = null 99 | if no_running_tasks and get_last_child().status == SUCCEEDED: 100 | last_result = true 101 | Policy.SELECTOR: 102 | last_result = true 103 | 104 | func child_fail(task): 105 | match policy: 106 | Policy.SEQUENCE: 107 | last_result = false 108 | Policy.SELECTOR: 109 | last_result = null 110 | if no_running_tasks and get_child(current_child_index) == get_last_child(): 111 | last_result = false 112 | 113 | func reset(): 114 | .reset() 115 | no_running_tasks = true 116 | 117 | func reset_all_children(): 118 | for child in get_children(): 119 | if child.has_method('_Class_Type_BTNode_'): 120 | child.reset() 121 | 122 | 123 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeRandomSelector.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTCompositeSelector 3 | class_name BTCompositeRandomSelector 4 | 5 | #----- Methods ----- 6 | func start(): 7 | .start() 8 | if random_children == null: 9 | random_children = create_random_children() 10 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeRandomSequence.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTCompositeSequence 3 | class_name BTCompositeRandomSequence 4 | 5 | 6 | #----- Methods ----- 7 | func start(): 8 | .start() 9 | if random_children == null: 10 | random_children = create_random_children() 11 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSelector.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTCompositeSingle 3 | class_name BTCompositeSelector 4 | 5 | # 顺序选择器 6 | # 按顺序从子节点中选择一个节点进行运行 7 | # 若该节点运行成功,则成功 8 | # 若失败,则选取下一个节点来运行 9 | 10 | # selector 11 | # Choose one child to run in order 12 | # If the child success, it success 13 | # Otherwise, it chooses next child to run. 14 | 15 | #----- Methods ----- 16 | func child_success(task): 17 | .child_success(task) 18 | success() 19 | 20 | func child_fail(task): 21 | .child_fail(task) 22 | var next_child = get_next_child() 23 | if next_child: 24 | current_child_index = next_child.get_index() 25 | run() 26 | else: 27 | fail() 28 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTCompositeSingle 3 | class_name BTCompositeSequence 4 | 5 | # 顺序 6 | # 按顺序运行子节点 7 | # 当一个子节点运行成功后,才运行下一个节点 8 | # 当所有子节点都运行成功后,才视为运行成功 9 | 10 | # sequence 11 | # Run children in order 12 | # Only when a child success, it runs the next child. 13 | # When all child success, it success. 14 | 15 | #----- Methods ----- 16 | func child_success(task): 17 | .child_success(task) 18 | var next_child = get_next_child() 19 | if next_child: 20 | current_child_index = next_child.get_index() 21 | run() 22 | else: 23 | success() 24 | 25 | func child_fail(task): 26 | .child_fail(task) 27 | fail() 28 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSingle.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTComposite 3 | class_name BTCompositeSingle 4 | 5 | var running_child:BTNode = null 6 | var current_child_index:int = 0 7 | 8 | var random_children = null 9 | 10 | #----- Methods ----- 11 | func child_running(running_task, reporter): 12 | running_child = running_task 13 | running() 14 | 15 | func child_success(task): 16 | running_child = null 17 | 18 | func child_fail(task): 19 | running_child = null 20 | 21 | func run(): 22 | if running_child: 23 | running_child.run() 24 | else: 25 | if current_child_index >= 0 and current_child_index < get_child_count(): 26 | if random_children != null: 27 | var children = get_bt_children() 28 | var current_child = get_child(current_child_index) 29 | var current = children.find(current_child) 30 | var last = children.size()-1 31 | if current < last: 32 | var other_child_index = randi() % (last - current + 1) + current 33 | # swap 34 | var temp = random_children[current] 35 | random_children[current] = random_children[other_child_index] 36 | random_children[other_child_index] = temp 37 | 38 | running_child = random_children[current] 39 | else: 40 | running_child = get_child(current_child_index) 41 | running_child.parent = self 42 | running_child.tree = tree 43 | running_child.start() 44 | if not running_child.check_guard(self): 45 | running_child.fail() 46 | else: 47 | running_child.run() 48 | 49 | func start(): 50 | running_child = null 51 | current_child_index = 0 52 | if get_children().size() <= 0: 53 | printerr('The composite single node has no child!') 54 | return 55 | if not get_child(current_child_index) is BTNode: 56 | var c = get_next_child() 57 | if c: 58 | current_child_index = c.get_index() 59 | else: 60 | printerr('The composite single node has no BTNode child!') 61 | .start() 62 | 63 | func cancel_running_children(start_index): 64 | .cancel_running_children(start_index) 65 | running_child = null 66 | 67 | func reset(): 68 | .reset() 69 | current_child_index = 0 70 | running_child = null 71 | random_children = null 72 | 73 | func create_random_children(): 74 | var temp = [] 75 | for child in get_children(): 76 | if child.has_method('_Class_Type_BTNode_'): 77 | temp.append(child) 78 | return temp 79 | 80 | func get_next_child(): 81 | var next = current_child_index + 1 82 | if next >= get_child_count(): 83 | return false 84 | while next < get_child_count() and not get_child(next).has_method('_Class_Type_BTNode_'): 85 | next += 1 86 | if next < get_child_count(): 87 | return get_child(next) 88 | return null 89 | 90 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecorator.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTNode 3 | class_name BTDecorator 4 | 5 | var child = null 6 | 7 | #----- Methods ----- 8 | func run(): 9 | if get_bt_child().status == RUNNING: 10 | child.run() 11 | else: 12 | child.parent = self 13 | child.tree = tree 14 | child.start() 15 | if child.check_guard(self): 16 | child.run() 17 | else: 18 | child.fail() 19 | 20 | func child_running(running_task, reporter): 21 | running() 22 | 23 | func child_fail(task): 24 | fail() 25 | 26 | func child_success(task): 27 | success() 28 | 29 | func reset(): 30 | child = null 31 | 32 | func get_bt_child(): 33 | if child: 34 | return child 35 | if get_child_count() == 0: 36 | return null 37 | var i = 0 38 | while i < get_child_count() and not get_child(i).has_method('_Class_Type_BTNode_'): 39 | i += 1 40 | child = get_child(i) 41 | return child 42 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorAlwaysFail.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTDecorator 3 | class_name BTDecoratorAlwaysFail 4 | 5 | 6 | #----- Methods ----- 7 | func child_success(task): 8 | child_fail(task) 9 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorAlwaysSucceed.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTDecorator 3 | class_name BTDecoratorAlwaysSucceed 4 | 5 | #----- Methods ----- 6 | func child_fail(task): 7 | child_success(task) 8 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorInvert.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTDecorator 3 | class_name BTDecoratorInvert 4 | 5 | #----- Methods ----- 6 | func child_success(task): 7 | .child_fail(task) 8 | 9 | func child_fail(task): 10 | .child_success(task) 11 | 12 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorRandom.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTDecorator 3 | class_name BTDecoratorRandom 4 | 5 | export(float) var success_posibility:float = 0.5 6 | 7 | #----- Methods ----- 8 | func run(): 9 | if get_bt_child(): 10 | .run() 11 | else: 12 | decide() 13 | 14 | func child_fail(task): 15 | decide() 16 | 17 | func child_success(task): 18 | decide() 19 | 20 | func decide(): 21 | if randf() <= success_posibility: 22 | success() 23 | else: 24 | fail() 25 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorRepeat.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTLoopDecorator 3 | class_name BTDecoratorRepeat 4 | 5 | export(int) var times:int 6 | 7 | var count:int 8 | 9 | #----- Methods ----- 10 | func start(): 11 | count = times 12 | .start() 13 | 14 | func condition(): 15 | return loop and count != 0 16 | 17 | func child_success(task): 18 | if count > 0: 19 | count -= 1 20 | if count == 0: 21 | .child_success(task) 22 | loop = false 23 | else: 24 | loop = true 25 | 26 | func child_fail(task): 27 | child_success(task) 28 | 29 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorUntilFail.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTLoopDecorator 3 | class_name BTDecoratorUntilFail 4 | 5 | #----- Methods ----- 6 | func child_success(task): 7 | loop = true 8 | 9 | func child_fail(task): 10 | success() 11 | loop = false 12 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorUntilSuccess.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTLoopDecorator 3 | class_name BTDecoratorUntilSuccess 4 | 5 | 6 | #----- Methods ----- 7 | func child_success(task): 8 | success() 9 | loop = false 10 | 11 | func child_fail(task): 12 | loop = true 13 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/classes/decorators/BTLoopDecorator.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTDecorator 3 | class_name BTLoopDecorator 4 | 5 | var loop:bool 6 | 7 | #----- Methods ----- 8 | func condition(): 9 | return loop 10 | 11 | func run(): 12 | loop = true 13 | while condition(): 14 | if get_bt_child().status == RUNNING: 15 | child.run() 16 | else: 17 | child.parent = self 18 | child.tree = tree 19 | child.start() 20 | if child.check_guard(self): 21 | child.run() 22 | else: 23 | child.fail() 24 | 25 | func child_running(running_task, reporter): 26 | .child_running(running_task, reporter) 27 | loop = false 28 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTAction 3 | 4 | export(String) var msg:String 5 | 6 | #----- Methods ----- 7 | func execute(): 8 | print(msg) 9 | return SUCCEEDED 10 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/behavior_tree/lib/test/SignalTimer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends BTAction 3 | 4 | export(float) var wait:float = 1 5 | 6 | #----- Methods ----- 7 | func execute(): 8 | print('wait for 1 sec') 9 | yield(get_tree().create_timer(wait), "timeout") 10 | print('wait for 2 sec') 11 | yield(get_tree().create_timer(2), "timeout") 12 | print('wait for 3 sec') 13 | yield(get_tree().create_timer(3), "timeout") 14 | print('done.') 15 | return SUCCEEDED 16 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/compiler/AST/AST.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Reference 3 | 4 | #----- Sub Classes ----- 5 | class ImportStatement: 6 | extends Reference 7 | 8 | var id 9 | var path 10 | 11 | func _init(i, p) -> void: 12 | id = i 13 | path = p 14 | 15 | 16 | class ImportPart: 17 | extends Reference 18 | 19 | var import_statement_list:Array # [ImportStatement] 20 | 21 | func _init() -> void: 22 | import_statement_list = [] 23 | 24 | 25 | class Parameter: 26 | extends Reference 27 | 28 | var id # token 29 | var exp_node # EXP tree node 30 | 31 | 32 | 33 | class Name: 34 | extends Reference 35 | 36 | var is_subtree_ref:bool 37 | var name # token 38 | 39 | 40 | 41 | 42 | class Task: 43 | extends Reference 44 | 45 | var name:Name 46 | var parameter_list:Array # [Parameter] 47 | 48 | func _init() -> void: 49 | parameter_list = [] 50 | 51 | 52 | class TreeNode: 53 | extends Reference 54 | 55 | var indent # indent token 56 | var guard_list:Array # [Task] 57 | var task:Task 58 | 59 | func _init() -> void: 60 | guard_list = [] 61 | 62 | 63 | class TreeStatement: 64 | extends Reference 65 | 66 | var is_subtree:bool 67 | var name # id token only 68 | 69 | var tree_node_list:Array # [TreeNode] - flat 70 | 71 | func _init() -> void: 72 | is_subtree = false 73 | tree_node_list = [] 74 | 75 | 76 | class TreePart: 77 | extends Reference 78 | 79 | var subtree_list:Array # [TreeStatement] 80 | var tree:TreeStatement # only one tree in a file 81 | 82 | func _init() -> void: 83 | subtree_list = [] 84 | 85 | 86 | 87 | 88 | #----- Properties ----- 89 | var import_part:ImportPart 90 | var tree_part:TreePart 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/compiler/AST/EXPAST.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Reference 3 | 4 | 5 | 6 | #----- Sub Classes ----- 7 | class EXPNode: 8 | extends Reference 9 | 10 | func execute(agent): 11 | pass 12 | 13 | func get_class(): 14 | return 'EXPNode' 15 | 16 | class BranchNode: 17 | extends EXPNode 18 | 19 | var children:Array # [EXPNode] 20 | 21 | func _init() -> void: 22 | children = [] 23 | 24 | func get_class(): 25 | return 'BranchNode' 26 | 27 | class OperatorNode: 28 | extends BranchNode 29 | 30 | var op # token 31 | 32 | func get_class(): 33 | return 'OperatorNode' 34 | 35 | class FuncNode: 36 | extends BranchNode 37 | 38 | var id # token 39 | 40 | func get_class(): 41 | return 'FuncNode' 42 | 43 | class LeafNode: 44 | extends EXPNode 45 | 46 | var token 47 | 48 | func get_class(): 49 | return 'LeafNode' 50 | 51 | 52 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/compiler/Parser.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Reference 3 | 4 | 5 | var Tokenizer = preload('./Tokenizer.gd') 6 | var AST = preload('./AST/AST.gd') 7 | var EXPAST = preload('./AST/EXPAST.gd') 8 | 9 | var tokenizer 10 | 11 | var preserved_id = ['import', 'name', 'subtree', 'tree'] 12 | 13 | var has_tree 14 | 15 | var has_error 16 | var is_print_error = false 17 | var fist_error:String = '' 18 | var last_error:String = '' 19 | 20 | #----- Gramar ----- 21 | # bt_file = import_part tree_part 22 | # 23 | # import_part = import_statement 24 | # | import_part LineBreakOrCommentOrIndentWithCommentOrNull import_statement 25 | # 26 | # import_statement = "import" Blank ID BlankOrNull : BlankOrNull String BlankOrNull CommentOrNull 27 | # 28 | # tree_part = tree_statement 29 | # | subtree_statement 30 | # | tree_part LineBreakOrCommentOrIndentWithCommentOrNull tree_statement 31 | # | tree_part LineBreakOrCommentOrIndentWithCommentOrNull subtree_statement 32 | # 33 | # tree_statement = "tree" BlankOrNull CommentOrNull LineBreakOrCommentOrIndentWithCommentOrNull tree_node_part 34 | # 35 | # subtree_statement = "subtree" Blank "name" BlankOrNull : BlankOrNull ID BlankOrNull CommentOrNull LineBreakOrCommentOrIndentWithCommentOrNull tree_node_part 36 | # 37 | # tree_node_part = tree_node_statement 38 | # | tree_node_part LineBreakOrCommentOrIndentWithCommentOrNull tree_node_statement 39 | # 40 | # tree_node_statement = Indent guard_part BlankOrNull task CommentOrNull 41 | # 42 | # guard_part = guard 43 | # | E 44 | # | guard_part BlankOrNull guard 45 | # 46 | # guard = "(" BlankOrNull task BlankOrNull ")" 47 | # 48 | # task = name Blank parameter_part 49 | # 50 | # name = subtree_ref | ID | String 51 | # 52 | # subtree_ref = $ ID 53 | # 54 | # parameter_part = parameter 55 | # | E 56 | # | parameter_part Blank parameter 57 | # 58 | # parameter = ID BlankOrNull : BlankOrNull exp 59 | # 60 | # exp = e1 61 | # | exp BlankOrNull + BlankOrNull e1 62 | # | exp BlankOrNull - BlankOrNull e1 63 | # 64 | # e1 = e2 65 | # | e1 BlankOrNull * BlankOrNull e2 66 | # | e1 BlankOrNull / BlankOrNull e2 67 | # 68 | # e2 = e3 69 | # | -e3 70 | # | +e3 71 | # 72 | # e3 = func 73 | # | ( BlankOrNull exp BlankOrNull) 74 | # | ID 75 | # | String 76 | # | Number 77 | # | Bool 78 | # | name 79 | # 80 | # func = ID "(" BlankOrNull arg_part BlankOrNull ")" 81 | # 82 | # arg_part = exp 83 | # | E 84 | # | arg_part BlankOrNull , BlankOrNull exp 85 | 86 | 87 | #----- Methods ----- 88 | func init(t): 89 | tokenizer = t 90 | 91 | 92 | func parse(): 93 | tokenizer.init(tokenizer.source) 94 | has_tree = false 95 | has_error = false 96 | 97 | var ast = AST.new() 98 | 99 | _bt_file(ast) 100 | 101 | var token = tokenizer.get_next() 102 | 103 | if token.type != Tokenizer.Token.EOF: 104 | error(token, 'EOF') 105 | 106 | if not has_tree: 107 | error(token, 'tree') 108 | 109 | return ast 110 | 111 | func match_blank(): 112 | var token = tokenizer.preview_next() 113 | if token.type == Tokenizer.Token.BLANK: 114 | tokenizer.get_next() 115 | else: 116 | error(token, 'Blank') 117 | 118 | # match 119 | # blank 120 | # null 121 | func match_blank_or_null(): 122 | var token = tokenizer.preview_next() 123 | while token.type == Tokenizer.Token.BLANK: 124 | tokenizer.get_next() 125 | token = tokenizer.preview_next() 126 | 127 | func match_value(value): 128 | var token = tokenizer.preview_next() 129 | if typeof(token.value) == typeof(value) and token.value == value: 130 | tokenizer.get_next() 131 | else: 132 | error(token, '\'%s\'' % value) 133 | 134 | 135 | func match_id(): 136 | var token = tokenizer.preview_next() 137 | if token.type != Tokenizer.Token.ID: 138 | error(token, 'ID') 139 | return null 140 | else: 141 | return tokenizer.get_next() 142 | 143 | func match_string(): 144 | var token = tokenizer.preview_next() 145 | if token.type != Tokenizer.Token.STRING: 146 | error(token, 'String') 147 | return null 148 | else: 149 | return tokenizer.get_next() 150 | 151 | func match_indent(): 152 | var token = tokenizer.preview_next() 153 | if token.type != Tokenizer.Token.INDENT: 154 | error(token, 'Indent') 155 | return null 156 | else: 157 | return tokenizer.get_next() 158 | 159 | func match_operator(): 160 | var token = tokenizer.preview_next() 161 | if token.type != Tokenizer.Token.OPERATOR: 162 | error(token, 'Operator') 163 | return null 164 | else: 165 | return tokenizer.get_next() 166 | 167 | func match_number(): 168 | var token = tokenizer.preview_next() 169 | if token.type != Tokenizer.Token.NUMBER: 170 | error(token, 'Number') 171 | return null 172 | else: 173 | return tokenizer.get_next() 174 | 175 | func match_bool(): 176 | var token = tokenizer.preview_next() 177 | if token.type != Tokenizer.Token.BOOL: 178 | error(token, 'Bool') 179 | return null 180 | else: 181 | return tokenizer.get_next() 182 | 183 | # match 184 | # line_break 185 | # line_break comment 186 | # indent comment 187 | # indent indent 188 | # null 189 | func match_line_break_or_comment_or_indent_with_comment_or_null(): 190 | var token = tokenizer.preview_next() 191 | while true: 192 | if token.type == Tokenizer.Token.LINE_BREAK: 193 | if tokenizer.preview_next(2).type == Tokenizer.Token.COMMENT: 194 | tokenizer.get_next() 195 | tokenizer.get_next() 196 | else: 197 | tokenizer.get_next() 198 | token = tokenizer.preview_next() 199 | elif token.type == Tokenizer.Token.INDENT: 200 | if tokenizer.preview_next(2).type == Tokenizer.Token.COMMENT: 201 | tokenizer.get_next() 202 | tokenizer.get_next() 203 | token = tokenizer.preview_next() 204 | elif tokenizer.preview_next(2).type == Tokenizer.Token.INDENT: 205 | tokenizer.get_next() 206 | token = tokenizer.preview_next() 207 | elif tokenizer.preview_next(2).type == Tokenizer.Token.LINE_BREAK: 208 | tokenizer.get_next() 209 | token = tokenizer.preview_next() 210 | elif tokenizer.preview_next(2).type == Tokenizer.Token.EOF: 211 | tokenizer.get_next() 212 | token = tokenizer.preview_next() 213 | else: 214 | break 215 | else: 216 | break 217 | 218 | func match_comment_or_null(): 219 | var token = tokenizer.preview_next() 220 | while token.type == Tokenizer.Token.COMMENT: 221 | tokenizer.get_next() 222 | token = tokenizer.preview_next() 223 | 224 | func get_indent_token_with_ID_followed(): # or with a left braket 225 | var pos = 1 226 | var token = tokenizer.preview_next(pos) 227 | while true: 228 | if token.type == Tokenizer.Token.LINE_BREAK: 229 | var next_token = tokenizer.preview_next(pos+1) 230 | if next_token.type == Tokenizer.Token.COMMENT: 231 | pos += 1 232 | pos += 1 233 | else: 234 | pos += 1 235 | token = tokenizer.preview_next(pos) 236 | elif token.type == Tokenizer.Token.INDENT: 237 | var next_token = tokenizer.preview_next(pos+1) 238 | if next_token.type == Tokenizer.Token.COMMENT: 239 | pos += 1 240 | pos += 1 241 | elif next_token.type == Tokenizer.Token.ID: 242 | pos += 1 243 | return pos 244 | elif next_token.type == Tokenizer.Token.STRING: 245 | pos += 1 246 | return pos 247 | elif next_token.type == Tokenizer.Token.OPERATOR and next_token.value == '$': 248 | pos += 1 249 | return pos 250 | elif next_token.type == Tokenizer.Token.LEFT_CLOSURE and next_token.value == '(': 251 | pos += 1 252 | return pos 253 | else: 254 | pos += 1 255 | token = tokenizer.preview_next(pos) 256 | else: 257 | break 258 | return -1 259 | 260 | func error(token, expect = ''): 261 | if token == null: 262 | last_error = expect 263 | if not has_error: 264 | fist_error = last_error 265 | has_error = true 266 | return 267 | 268 | var last_line_break = 0 if token.last_line_break == -1 else token.last_line_break 269 | 270 | var next_line_break = tokenizer.calc_next_line_break(last_line_break+1) 271 | var e = 'Expect %s ' % expect if expect != '' else 'Error ' 272 | e += 'at line: %d, column: %d.' % [token.line+1, token.start - last_line_break + 1] 273 | e += 'Got %s' % str(token) 274 | 275 | var e_line = tokenizer.source.substr(last_line_break, next_line_break-last_line_break) 276 | 277 | var e_locate = '' 278 | for i in range(token.start - last_line_break): 279 | e_locate += ' ' if tokenizer.source.ord_at(last_line_break+(1 if token.type != Tokenizer.Token.EOF else 0)+i) < 128 else ' ' 280 | for _i in range(token.length-1): 281 | e_locate += '~' 282 | e_locate += '^' 283 | 284 | last_error = '%s\n%s\n%s' % [e, e_line, e_locate] 285 | if is_print_error: 286 | printerr(last_error) 287 | if not has_error: 288 | fist_error = last_error 289 | has_error = true 290 | #----- Top Down Parsers ----- 291 | func _bt_file(ast): 292 | match_comment_or_null() 293 | match_line_break_or_comment_or_indent_with_comment_or_null() 294 | _import_part(ast) 295 | _tree_part(ast) 296 | match_blank_or_null() 297 | match_comment_or_null() 298 | match_line_break_or_comment_or_indent_with_comment_or_null() 299 | 300 | func _import_part(ast): 301 | ast.import_part = AST.ImportPart.new() 302 | 303 | var exclude = [Tokenizer.Token.LINE_BREAK, Tokenizer.Token.COMMENT, Tokenizer.Token.INDENT] 304 | var token = tokenizer.preview_next_without(exclude) 305 | while token.type == Tokenizer.Token.ID and token.value == 'import': 306 | match_line_break_or_comment_or_indent_with_comment_or_null() 307 | _import_statement(ast.import_part) 308 | token = tokenizer.preview_next_without(exclude) 309 | 310 | if has_error: 311 | break 312 | 313 | 314 | func _import_statement(import_part): 315 | match_value('import') 316 | match_blank() 317 | var id_token = match_id() 318 | match_blank_or_null() 319 | var string_token = id_token.to_string_token() 320 | string_token.value = '%s' % string_token.value 321 | 322 | var token = tokenizer.preview_next() 323 | if token.value == ':': 324 | match_value(':') 325 | match_blank_or_null() 326 | string_token = match_string() 327 | match_blank_or_null() 328 | 329 | match_comment_or_null() 330 | 331 | var import_statement = AST.ImportStatement.new(id_token, string_token) 332 | import_part.import_statement_list.append(import_statement) 333 | 334 | func _tree_part(ast): 335 | ast.tree_part = AST.TreePart.new() 336 | 337 | var exclude = [Tokenizer.Token.LINE_BREAK, Tokenizer.Token.COMMENT, Tokenizer.Token.INDENT] 338 | var token = tokenizer.preview_next_without(exclude) 339 | while token.type == Tokenizer.Token.ID: 340 | if token.value == 'tree': 341 | if not has_tree: 342 | has_tree = true 343 | match_line_break_or_comment_or_indent_with_comment_or_null() 344 | _tree_statement(ast.tree_part) 345 | else: 346 | error(token, 'no more than one tree') 347 | tokenizer.get_next() 348 | elif token.value == 'subtree': 349 | match_line_break_or_comment_or_indent_with_comment_or_null() 350 | _subtree_statement(ast.tree_part) 351 | else: 352 | break 353 | token = tokenizer.preview_next_without(exclude) 354 | 355 | func _tree_statement(tree_part): 356 | tree_part.tree = AST.TreeStatement.new() 357 | 358 | match_value('tree') 359 | match_blank_or_null() 360 | match_comment_or_null() 361 | match_line_break_or_comment_or_indent_with_comment_or_null() 362 | _tree_node_part(tree_part.tree) 363 | 364 | func _subtree_statement(tree_part): 365 | var subtree = AST.TreeStatement.new() 366 | subtree.is_subtree = true 367 | tree_part.subtree_list.append(subtree) 368 | 369 | match_value('subtree') 370 | match_blank() 371 | match_value('name') 372 | match_blank_or_null() 373 | match_value(':') 374 | match_blank_or_null() 375 | var id_token = match_id() 376 | subtree.name = id_token 377 | match_blank_or_null() 378 | match_comment_or_null() 379 | match_line_break_or_comment_or_indent_with_comment_or_null() 380 | _tree_node_part(subtree) 381 | 382 | func _tree_node_part(tree): 383 | var tree_node_cnt = 0 384 | var preview_pos = get_indent_token_with_ID_followed() 385 | while preview_pos != -1: 386 | match_line_break_or_comment_or_indent_with_comment_or_null() 387 | var tree_node = _tree_node_statement() 388 | tree.tree_node_list.append(tree_node) 389 | tree_node_cnt += 1 390 | preview_pos = get_indent_token_with_ID_followed() 391 | 392 | if has_error: 393 | break 394 | if tree_node_cnt == 0: 395 | error(null, 'Empty tree.') 396 | 397 | func _tree_node_statement(): 398 | var tree_node = AST.TreeNode.new() 399 | 400 | var indent_token = match_indent() 401 | tree_node.indent = indent_token 402 | var token = tokenizer.preview_next() 403 | if token.type == Tokenizer.Token.LEFT_CLOSURE and token.value == '(': 404 | _guard_part(tree_node) 405 | match_blank_or_null() 406 | tree_node.task = _task() 407 | match_blank_or_null() 408 | match_comment_or_null() 409 | 410 | return tree_node 411 | 412 | func _guard_part(tree_node): 413 | var exclude = [Tokenizer.Token.BLANK] 414 | var token = tokenizer.preview_next_without(exclude) 415 | while token.type == Tokenizer.Token.LEFT_CLOSURE and token.value == '(': 416 | match_blank_or_null() 417 | var task = _guard() # could be null task for no guard 418 | if task: 419 | tree_node.guard_list.append(task) 420 | token = tokenizer.preview_next_without(exclude) 421 | 422 | if has_error: 423 | break 424 | 425 | func _guard(): 426 | match_value('(') 427 | match_blank_or_null() 428 | var token = tokenizer.preview_next() 429 | var task = null 430 | if not (token.type == Tokenizer.Token.RIGHT_CLOSURE and token.value == ')'): 431 | task = _task() 432 | match_blank_or_null() 433 | match_value(')') 434 | 435 | return task 436 | 437 | func _task(): 438 | var task = AST.Task.new() 439 | 440 | var name = _name() 441 | task.name = name 442 | 443 | var exclude = [Tokenizer.Token.BLANK] 444 | var token = tokenizer.preview_next_without(exclude) 445 | if token.type == Tokenizer.Token.ID: 446 | _parameter_part(task) 447 | 448 | return task 449 | 450 | func _name(): 451 | var name = AST.Name.new() 452 | name.is_subtree_ref = false 453 | 454 | var token = tokenizer.preview_next() 455 | if token.type == Tokenizer.Token.OPERATOR and token.value == '$': 456 | _subtree_ref(name) 457 | elif token.type == Tokenizer.Token.ID: 458 | var id_token = match_id() 459 | name.name = id_token 460 | else: 461 | var string_token = match_string() 462 | name.name = string_token 463 | 464 | return name 465 | 466 | func _subtree_ref(name): 467 | match_value('$') 468 | name.is_subtree_ref = true 469 | var id_token = match_id() 470 | name.name = id_token 471 | 472 | func _parameter_part(task): 473 | var exclude = [Tokenizer.Token.BLANK] 474 | var token = tokenizer.preview_next_without(exclude) 475 | while token.type == Tokenizer.Token.ID: 476 | match_blank() 477 | var parameter = _parameter() 478 | task.parameter_list.append(parameter) 479 | token = tokenizer.preview_next_without(exclude) 480 | 481 | if has_error: 482 | break 483 | 484 | func _parameter(): 485 | var parameter = AST.Parameter.new() 486 | 487 | var id_token = match_id() 488 | parameter.id = id_token 489 | match_blank_or_null() 490 | match_value(':') 491 | match_blank_or_null() 492 | parameter.exp_node = _exp() 493 | if parameter.exp_node == null: 494 | error(tokenizer.preview_next(), 'a expression') 495 | 496 | return parameter 497 | 498 | func _exp(): 499 | var left_exp_node = _e1() 500 | var token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 501 | while token.type == Tokenizer.Token.OPERATOR and (token.value == '+' or token.value == '-'): 502 | var op_node = EXPAST.OperatorNode.new() 503 | 504 | match_blank_or_null() 505 | var op_token = match_operator() 506 | op_node.op = op_token 507 | 508 | match_blank_or_null() 509 | var right_exp_node = _e1() 510 | 511 | op_node.children = [left_exp_node, right_exp_node] 512 | left_exp_node = op_node 513 | 514 | token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 515 | 516 | if has_error: 517 | break 518 | return left_exp_node 519 | 520 | func _e1(): 521 | var left_exp_node = _e2() 522 | var token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 523 | while token.type == Tokenizer.Token.OPERATOR and (token.value == '*' or token.value == '/'): 524 | var op_node = EXPAST.OperatorNode.new() 525 | 526 | match_blank_or_null() 527 | var op_token = match_operator() 528 | op_node.op = op_token 529 | 530 | 531 | match_blank_or_null() 532 | var right_exp_node = _e2() 533 | 534 | op_node.children = [left_exp_node, right_exp_node] 535 | left_exp_node = op_node 536 | 537 | token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 538 | 539 | if has_error: 540 | break 541 | return left_exp_node 542 | 543 | 544 | func _e2(): 545 | var exp_node = null 546 | 547 | var token = tokenizer.preview_next() 548 | if token.type == Tokenizer.Token.OPERATOR and (token.value == '-' or token.value == '+'): 549 | exp_node = EXPAST.OperatorNode.new() 550 | 551 | var op_token = match_operator() 552 | exp_node.op = op_token 553 | 554 | var e3 = _e3() 555 | if exp_node: 556 | exp_node.children.append(e3) 557 | else: 558 | exp_node = e3 559 | 560 | return exp_node 561 | 562 | func _e3(): 563 | var exp_node = null 564 | 565 | var token = tokenizer.preview_next() 566 | if token.type == Tokenizer.Token.ID: 567 | token = tokenizer.preview_next(2) 568 | if token.type == Tokenizer.Token.LEFT_CLOSURE and token.value == '(': 569 | exp_node = _func() 570 | else: 571 | var id_token = match_id() 572 | exp_node = EXPAST.LeafNode.new() 573 | exp_node.token = id_token 574 | elif token.type == Tokenizer.Token.STRING: 575 | var string_token = match_string() 576 | exp_node = EXPAST.LeafNode.new() 577 | exp_node.token = string_token 578 | elif token.type == Tokenizer.Token.NUMBER: 579 | var number_token = match_number() 580 | exp_node = EXPAST.LeafNode.new() 581 | exp_node.token = number_token 582 | elif token.type == Tokenizer.Token.BOOL: 583 | var bool_token = match_bool() 584 | exp_node = EXPAST.LeafNode.new() 585 | exp_node.token = bool_token 586 | elif token.type == Tokenizer.Token.LEFT_CLOSURE and token.value == '(': 587 | match_value('(') 588 | match_blank_or_null() 589 | exp_node = _exp() 590 | match_blank_or_null() 591 | match_value(')') 592 | elif token.type == Tokenizer.Token.OPERATOR and token.value == '$': 593 | exp_node = _name() 594 | else: 595 | error(token, 'a valid expression') 596 | 597 | return exp_node 598 | 599 | func _func(): 600 | var func_node = EXPAST.FuncNode.new() 601 | 602 | var id_token = match_id() 603 | func_node.id = id_token 604 | 605 | match_value('(') 606 | var token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 607 | if token.type != Tokenizer.Token.RIGHT_CLOSURE or token.value != ')': 608 | match_blank_or_null() 609 | _arg_part(func_node) 610 | match_blank_or_null() 611 | match_value(')') 612 | 613 | return func_node 614 | 615 | func _arg_part(func_node): 616 | var exp_node = _exp() 617 | func_node.children.append(exp_node) 618 | 619 | var token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 620 | while token.type == Tokenizer.Token.COMMAS: 621 | match_blank_or_null() 622 | match_value(',') 623 | match_blank_or_null() 624 | exp_node = _exp() 625 | func_node.children.append(exp_node) 626 | token = tokenizer.preview_next_without([Tokenizer.Token.BLANK]) 627 | if exp_node == null: 628 | error(token, 'expression') 629 | 630 | if has_error: 631 | break 632 | 633 | 634 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/compiler/Tokenizer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Reference 3 | 4 | 5 | var source:String = '' 6 | 7 | var next:int 8 | var line_cnt:int 9 | var last_line_break:int 10 | 11 | var preserved_char_list:String = '#[]()*$%-+={}\'"\\/,.<>:;| \t\r\n' 12 | var digit_char_list:String = '0123456789' 13 | var op_char_list:String = '+-*/%$' 14 | var left_closure_char_list:String = '<[{(' 15 | var right_closure_char_list:String = ')}]>' 16 | var indent_char_list:String = '\t ' 17 | var blank_char_list:String = ' \t\r' 18 | var escape_char_list:String = 'trn\\\'"' 19 | 20 | var ID_trans_table 21 | var ID_fstate_list 22 | 23 | var String_trans_table 24 | var String_fstate_list 25 | 26 | var Number_trans_table 27 | var Number_fstate_list 28 | 29 | var Indent_trans_table 30 | var Indent_fstate_list 31 | 32 | var next_token_queue:Array 33 | 34 | var has_error:bool 35 | var is_print_error:bool = false 36 | var first_error:String 37 | var last_error:String 38 | 39 | enum TransitionConditionType { 40 | FUNC, 41 | CHAR, 42 | NOT_CHAR 43 | } 44 | 45 | class StateMachineResult: 46 | extends Reference 47 | 48 | enum { 49 | FRESH, 50 | SUCCESS, 51 | FAIL, 52 | EOF, 53 | } 54 | 55 | var state:int 56 | var next:int 57 | var line_cnt:int 58 | var last_line_break:int 59 | var status = FRESH 60 | var error:String = '' 61 | 62 | class Token: 63 | extends Reference 64 | 65 | enum { 66 | ID, 67 | STRING, 68 | NUMBER, 69 | BOOL, 70 | INDENT, 71 | BLANK, 72 | COMMENT, 73 | EOF, 74 | ERROR, 75 | LEFT_CLOSURE, 76 | RIGHT_CLOSURE, 77 | OPERATOR, 78 | COLON, 79 | COMMAS, 80 | LINE_BREAK, 81 | UNDEFINED 82 | } 83 | 84 | var type = UNDEFINED 85 | var raw:String 86 | var value 87 | var start:int 88 | var length:int 89 | var line:int 90 | var last_line_break:int 91 | 92 | func process(): 93 | match type: 94 | STRING: 95 | value = raw.substr(1, raw.length()-2).c_unescape() 96 | NUMBER: 97 | value = float(raw) 98 | BOOL: 99 | value = raw == 'true' 100 | INDENT: 101 | value = raw.length() - (1 if raw[0] == '\n' else 0) 102 | ID, LEFT_CLOSURE, RIGHT_CLOSURE, COLON, COMMAS, OPERATOR: 103 | value = raw 104 | 105 | func _to_string() -> String: 106 | match type: 107 | ID: 108 | return '[ID: %s]' % value 109 | STRING: 110 | return '[String: %s]' % value 111 | NUMBER: 112 | return '[Number: %f]' % value 113 | BOOL: 114 | return '[Bool: %s]' % ('true' if value else 'false') 115 | INDENT: 116 | return '[Indent: %d]' % value 117 | BLANK: 118 | return '[Blank]' 119 | COMMENT: 120 | return '[Comment: %s]' % raw 121 | EOF: 122 | return '[EOF]' 123 | ERROR: 124 | return '[Error]' 125 | LEFT_CLOSURE: 126 | return '[Left Closure: %s]' % value 127 | RIGHT_CLOSURE: 128 | return '[Right Closure: %s]' % value 129 | COLON: 130 | return '[Colon]' 131 | COMMAS: 132 | return '[Commas]' 133 | OPERATOR: 134 | return '[Operator: %s]' % value 135 | LINE_BREAK: 136 | return '[Line Break]' 137 | return '[Unknown Token]' 138 | 139 | func to_string_token(): 140 | var res = Token.new() 141 | res.type = STRING 142 | res.raw = raw 143 | res.value = str(value) 144 | res.start = start 145 | res.length = length 146 | res.line = line 147 | res.last_line_break = last_line_break 148 | return res 149 | 150 | #----- Methods ----- 151 | func init(_s:String): 152 | source = _s 153 | next = 0 154 | line_cnt = 0 155 | last_line_break = -1 156 | next_token_queue = [] 157 | 158 | has_error = false 159 | 160 | ID_trans_table = { 161 | 0: [gen_transition(1, 'is_valid_char_without_digit', '')], 162 | 1: [ 163 | gen_transition(1, 'is_valid_char_without_digit', ''), 164 | gen_transition(2, 'is_valid_char', '') 165 | ], 166 | 2: [gen_transition(2, 'is_valid_char', '')] 167 | } 168 | ID_fstate_list = [1, 2] 169 | 170 | String_trans_table = { 171 | 0: [ 172 | gen_transition_char(1, "'"), 173 | gen_transition_char(6, '"'), 174 | ], 175 | 1: [ 176 | gen_transition_char(5, "'"), 177 | gen_transition_char(2, '\\'), 178 | gen_transition_not_char(3, "'"), 179 | ], 180 | # 2: [ 181 | # gen_transition_char(4, "'"), 182 | # gen_transition_char(4, '\\'), 183 | # ], 184 | 2: gen_transitions_char_list(4, escape_char_list), 185 | 3: [ 186 | gen_transition_char(2, '\\'), 187 | gen_transition_char(5, "'"), 188 | gen_transition_not_char(3, "'"), 189 | ], 190 | 4: [ 191 | gen_transition_char(5, "'"), 192 | gen_transition_char(2, '\\'), 193 | gen_transition_not_char(3, "'"), 194 | ], 195 | 5: [], 196 | 6: [ 197 | gen_transition_char(8, '\\'), 198 | gen_transition_not_char(7, '"'), 199 | ], 200 | 7: [ 201 | gen_transition_char(5, '"'), 202 | gen_transition_char(8, '\\'), 203 | gen_transition_not_char(7, '"'), 204 | ], 205 | # 8: [ 206 | # gen_transition_char(9, '"'), 207 | # gen_transition_char(9, '\\'), 208 | # ], 209 | 8: gen_transitions_char_list(9, escape_char_list), 210 | 9: [ 211 | gen_transition_char(8, '\\'), 212 | gen_transition_char(5, '"'), 213 | gen_transition_not_char(7, '"'), 214 | ] 215 | } 216 | String_fstate_list = [5] 217 | 218 | Number_trans_table = { 219 | 0: [ 220 | gen_transition(1, 'is_digit_char', ''), 221 | gen_transition_char(2, '.'), 222 | ], 223 | 1: [ 224 | gen_transition(1, 'is_digit_char', ''), 225 | gen_transition_char(2, '.'), 226 | ], 227 | 2: [ 228 | gen_transition(3, 'is_digit_char', ''), 229 | ], 230 | 3: [ 231 | gen_transition(3, 'is_digit_char', ''), 232 | ] 233 | } 234 | Number_fstate_list = [1, 3] 235 | 236 | Indent_trans_table = { 237 | 0: [ 238 | gen_transition(1, 'is_indent_start_0', '^'), 239 | gen_transition(2, 'is_indent_start_1', ''), 240 | ], 241 | 1: gen_transitions_char_list(2, indent_char_list), 242 | 2: gen_transitions_char_list(2, indent_char_list), 243 | } 244 | Indent_fstate_list = [2] 245 | 246 | func preview_next(num:int=1): 247 | if num <= next_token_queue.size(): 248 | return next_token_queue[num-1] 249 | while num > next_token_queue.size(): 250 | next_token_queue.push_back(_get_next_token()) 251 | return preview_next(num) 252 | 253 | func preview_next_without(exclude:Array, num:int=1): 254 | var useless_type = [Token.EOF, Token.ERROR, Token.UNDEFINED] 255 | var cnt = 0 256 | var pos = 1 257 | while cnt < num: 258 | var t = preview_next(pos) 259 | if t.type in useless_type: 260 | return t 261 | if not t.type in exclude: 262 | cnt += 1 263 | pos += 1 264 | return preview_next(pos-1) 265 | 266 | func get_next(): 267 | if not next_token_queue.empty(): 268 | return next_token_queue.pop_front() 269 | return _get_next_token() 270 | 271 | func get_next_without_blank(exclude:Array): 272 | var t = get_next() 273 | while not t.type in exclude: 274 | t = get_next() 275 | return t 276 | 277 | func _get_next_token(): 278 | var t = _get_next() 279 | t.process() 280 | return t 281 | 282 | func _get_next(): 283 | var res:StateMachineResult 284 | var token = gen_token(Token.UNDEFINED, '', next, 1, line_cnt, last_line_break) 285 | 286 | if next >= source.length(): 287 | return gen_token(Token.EOF, '', next, 1, line_cnt, last_line_break) 288 | 289 | if source[next] == 't' or source[next] == 'f': # Bool 290 | var start = next 291 | if match_char('t') and match_char('r') and match_char('u') and match_char('e') and (next >= source.length() or source[next] in preserved_char_list+digit_char_list): 292 | return gen_token(Token.BOOL, source.substr(start, next - start), start, next - start, line_cnt, last_line_break) 293 | elif match_char('f') and match_char('a') and match_char('l') and match_char('s') and match_char('e') and (next >= source.length() or source[next] in preserved_char_list+digit_char_list): 294 | return gen_token(Token.BOOL, source.substr(start, next - start), start, next - start, line_cnt, last_line_break) 295 | next = start 296 | 297 | if source[next] == ':': # Colon 298 | next += 1 299 | return gen_token(Token.COLON, source.substr(next-1, 1), next-1, 1, line_cnt, last_line_break) 300 | elif source[next] == ',': # Commas 301 | next += 1 302 | return gen_token(Token.COMMAS, source.substr(next-1, 1), next-1, 1, line_cnt, last_line_break) 303 | 304 | if is_indent_start(next): # Indent 305 | res = run_state_machine(0, next, Indent_trans_table, Indent_fstate_list) 306 | token.type = Token.INDENT 307 | elif source[next] in left_closure_char_list: 308 | var c = source[next] 309 | next += 1 310 | return gen_token(Token.LEFT_CLOSURE, c, next-1, 1, line_cnt, last_line_break) 311 | elif source[next] in right_closure_char_list: 312 | var c = source[next] 313 | next += 1 314 | return gen_token(Token.RIGHT_CLOSURE, c, next-1, 1, line_cnt, last_line_break) 315 | elif source[next] == '.' or source[next] in digit_char_list: # Number 316 | res = run_state_machine(0, next, Number_trans_table, Number_fstate_list) 317 | token.type = Token.NUMBER 318 | elif source[next] in op_char_list: # Operator (only has one character) 319 | var c = source[next] 320 | next += 1 321 | return gen_token(Token.OPERATOR, c, next-1, 1, line_cnt, last_line_break) 322 | elif is_valid_char_without_digit(next): # ID 323 | res = run_state_machine(0, next, ID_trans_table, ID_fstate_list) 324 | token.type = Token.ID 325 | elif source[next] == '"' or source[next] == "'": # String 326 | res = run_state_machine(0, next, String_trans_table, String_fstate_list) 327 | token.type = Token.STRING 328 | elif source[next] == '#': # Comment 329 | var start = next 330 | while next < source.length() and source[next] != '\n': 331 | if source[next] == '\n': 332 | last_line_break = next 333 | line_cnt += 1 334 | next += 1 335 | var l = next - start 336 | return gen_token(Token.COMMENT, source.substr(start, l), start, l, line_cnt, last_line_break) 337 | elif source[next] == '\n': # Line Break 338 | var old_last_line_break = last_line_break 339 | last_line_break = next 340 | line_cnt += 1 341 | next += 1 342 | return gen_token(Token.LINE_BREAK, '', next-1, 1, line_cnt-1, old_last_line_break) 343 | elif source[next] in blank_char_list: # Blank 344 | var start = next 345 | while next < source.length() and source[next] in blank_char_list: 346 | if is_indent_start(next): 347 | break 348 | next += 1 349 | return gen_token(Token.BLANK, '', start, next - start, line_cnt, last_line_break) 350 | 351 | if res == null: 352 | var column = (next - last_line_break) if last_line_break != -1 else next 353 | var error = 'Cna\'t identify the token! at line: %d, column: %d:\n%s' % [line_cnt+1, column+1, calc_locate_error([ 354 | { 355 | 'line_break': last_line_break if last_line_break != -1 else 0, 356 | 'column': column 357 | } 358 | ])] 359 | _error(error) 360 | return gen_token(Token.ERROR, error, column, 1, line_cnt, last_line_break) 361 | 362 | match res.status: 363 | StateMachineResult.SUCCESS: 364 | var start = next 365 | var end = res.next 366 | next = end 367 | line_cnt += res.line_cnt 368 | last_line_break = res.last_line_break 369 | 370 | token.raw = source.substr(start, end - start) 371 | token.start = start 372 | token.length = end - start 373 | token.line = line_cnt - res.line_cnt 374 | token.last_line_break = last_line_break 375 | return token 376 | StateMachineResult.FAIL: 377 | _error(res.error) 378 | return gen_token(Token.ERROR, res.error, next, 1, line_cnt, last_line_break) 379 | StateMachineResult.EOF: 380 | next = res.next 381 | return gen_token(Token.EOF, '', next, 1, line_cnt, last_line_break) 382 | _: 383 | _error('Undefined state machien result status!') 384 | 385 | func run_state_machine(state, current_next, state_transition_table, final_state_list): 386 | var all_condition_fail = false 387 | var error_list = [] 388 | var l_cnt = 0 389 | var last_l_break = last_line_break 390 | while current_next < source.length() and not all_condition_fail: 391 | all_condition_fail = true 392 | for e in state_transition_table[state]: 393 | var cond = false 394 | match e.type: 395 | TransitionConditionType.FUNC: 396 | if e.options: 397 | cond = call(e.condition, current_next, e.options) 398 | else: 399 | cond = call(e.condition, current_next) 400 | TransitionConditionType.CHAR: 401 | cond = source[current_next] == e.char 402 | TransitionConditionType.NOT_CHAR: 403 | cond = source[current_next] != e.char 404 | if cond: 405 | state = e.to 406 | # advance 407 | if source[current_next] == '\n': 408 | l_cnt += 1 409 | last_l_break = current_next 410 | current_next += 1 411 | all_condition_fail = false 412 | break 413 | else: 414 | error_list.append({ 415 | 'line': l_cnt + line_cnt, 416 | 'column': (last_l_break - current_next) if last_l_break != -1 else current_next, 417 | 'line_break': last_l_break if last_l_break != -1 else 0, 418 | 'msg': e.error 419 | }) 420 | 421 | var res = StateMachineResult.new() 422 | res.state = state 423 | res.next = current_next 424 | res.line_cnt = l_cnt 425 | res.last_line_break = last_l_break 426 | res.status = StateMachineResult.SUCCESS 427 | if not state in final_state_list: 428 | assert(error_list.size() > 0) 429 | var error = calc_error(error_list) 430 | if current_next < source.length(): 431 | res.error = ('Illegal char \'%s\', expect %s at line: %d, column: %d:\n%s' % [source[current_next].c_escape(), error, error_list[0].line+1, error_list[0].column+1, calc_locate_error(error_list)]) 432 | res.status = StateMachineResult.FAIL 433 | else: 434 | # res.error = ('Expect %s.' % error) 435 | res.status = StateMachineResult.EOF 436 | return res 437 | 438 | func calc_next_line_break(start): 439 | while start < source.length(): 440 | if source[start] == '\n': 441 | return start 442 | start += 1 443 | return source.length() 444 | 445 | func calc_locate_error(error_list): 446 | assert(error_list.size() > 0) 447 | var error = '' 448 | var start = error_list[0].line_break 449 | var l = error_list[0].column 450 | error += source.substr(start, calc_next_line_break(start+1)) + '\n' 451 | for i in range(l): 452 | error += ' ' if source.ord_at(start + i) < 128 else ' ' 453 | error += '^' 454 | return error 455 | 456 | 457 | func calc_error(error_list): 458 | var error = '' 459 | var cnt = 0 460 | for e in error_list: 461 | error += '%s' % e.msg 462 | if cnt < error_list.size() - 1: 463 | error += ' or ' 464 | cnt += 1 465 | return error 466 | 467 | func is_valid_char(i:int): 468 | if source[i] in preserved_char_list: 469 | return false 470 | return true 471 | 472 | func is_valid_char_without_digit(i:int): 473 | if source[i] in digit_char_list: 474 | return false 475 | return is_valid_char(i) 476 | 477 | func is_indent_start(i:int): 478 | if i+1 >= source.length(): 479 | return false 480 | if (source[i] == '\n' or i == 0): 481 | if source[i if i == 0 else i+1] in indent_char_list: 482 | return true 483 | return false 484 | 485 | func is_indent_start_0(i:int): 486 | if i+1 >= source.length(): 487 | return false 488 | if (source[i] == '\n'): 489 | if source[i+1] in indent_char_list: 490 | return true 491 | return false 492 | 493 | func is_indent_start_1(i:int): 494 | if i >= source.length(): 495 | return false 496 | if (i == 0): 497 | if source[i] in indent_char_list: 498 | return true 499 | return false 500 | 501 | func is_digit_char(i:int): 502 | return source[i] in digit_char_list 503 | 504 | func gen_transition(to:int, conditioin, error, options = null): 505 | return { 506 | 'to': to, 507 | 'type': TransitionConditionType.FUNC, 508 | 'condition': conditioin, 509 | 'options': options, 510 | 'error': error 511 | } 512 | 513 | func gen_transition_char(to:int , c): 514 | return { 515 | 'to': to, 516 | 'type': TransitionConditionType.CHAR, 517 | 'char': c, 518 | 'error': '\'%s\'' % c.c_escape() 519 | } 520 | 521 | func gen_transition_not_char(to:int , c): 522 | return { 523 | 'to': to, 524 | 'type': TransitionConditionType.NOT_CHAR, 525 | 'char': c, 526 | 'error': 'no \'%s\'' % c.c_escape() 527 | } 528 | 529 | func gen_token(type, raw, start, l, line, last_l_break): 530 | var t = Token.new() 531 | t.type = type 532 | t.raw = raw 533 | t.start = start 534 | t.length = l 535 | t.line = line 536 | t.last_line_break = last_l_break 537 | return t 538 | 539 | func gen_transitions_char_list(to:int, list): 540 | var res = [] 541 | for e in list: 542 | res.append(gen_transition_char(to, e)) 543 | return res 544 | 545 | func match_char(c): 546 | if next >= source.length(): 547 | return false 548 | if source[next] == c: 549 | next += 1 550 | return true 551 | return false 552 | 553 | func _error(error): 554 | if not has_error: 555 | first_error = error 556 | has_error = true 557 | last_error = error 558 | if is_print_error: 559 | printerr(error) 560 | 561 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/compiler/lib/BasicLib.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends './LibBase.gd' 3 | 4 | 5 | #----- Lib ----- 6 | func lib_sin(x): 7 | return sin(x) 8 | 9 | func lib_cos(x): 10 | return cos(x) 11 | 12 | func lib_rand_range(a, b): 13 | return rand_range(a, b) 14 | 15 | func lib_randi(): 16 | return randi() 17 | 18 | func lib_randf(): 19 | return randf() 20 | 21 | func lib_randi_range(a, b): 22 | if a > b: 23 | var t = a 24 | a = b 25 | b = t 26 | elif a == b: 27 | return a 28 | return randi() % (b - a + 1) + a 29 | 30 | func lib_get_ticks_msec(): 31 | return OS.get_ticks_msec() 32 | 33 | func lib_get_system_time_msecs(): 34 | return OS.get_system_time_msecs() 35 | 36 | func lib_none(): 37 | return null 38 | 39 | func lib_array(head, tail): 40 | if tail == null: 41 | return [head] 42 | return [head] + tail 43 | 44 | func lib_choose(l): 45 | return l[lib_randi_range(0, l.size()-1)] 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/compiler/lib/LibBase.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Reference 3 | 4 | 5 | #----- Methods ----- 6 | func get_func_prefix(): 7 | return 'lib_' 8 | 9 | func _Class_Type_LibBase_(): 10 | pass 11 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/editor/script_editor/BTSEdit.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends TextEdit 3 | 4 | const Tokenizer = preload('../../compiler/Tokenizer.gd') 5 | 6 | var builtin_node_name_list := [ 7 | 'fail', 8 | 'success', 9 | 'timer', 10 | 11 | 'dynamic_guard_selector', 12 | 'parallel', 13 | 'selector', 14 | 'random_selector', 15 | 'sequence', 16 | 'random_sequence', 17 | 18 | 'always_fail', 19 | 'always_succeed', 20 | 'invert', 21 | 'random', 22 | 'repeat', 23 | 'until_fail', 24 | 'until_success', 25 | ] 26 | 27 | var indent_size := 4 28 | 29 | func _gui_input(event: InputEvent) -> void: 30 | if event is InputEventKey: 31 | if event.pressed: 32 | $AutoCompletionTimer.stop() 33 | $AutoCompletionTimer.start() 34 | if event.alt and event.scancode == KEY_UP: 35 | move_line_up() 36 | if event.alt and event.scancode == KEY_DOWN: 37 | move_line_down() 38 | if event.control and event.scancode == KEY_D: 39 | duplicate_text() 40 | elif event is InputEventMouseButton: 41 | if event.pressed: 42 | $CompletionPopup.hide() 43 | 44 | #----- Methods ----- 45 | func set_syntax_highlight_color(editor:EditorInterface): 46 | add_color_region('"', '"', editor.get_editor_settings().get('text_editor/highlighting/string_color'), false) 47 | add_color_region("'", "'", editor.get_editor_settings().get('text_editor/highlighting/string_color'), false) 48 | add_color_region('#', '', editor.get_editor_settings().get('text_editor/highlighting/comment_color'), true) 49 | 50 | add_keyword_color('subtree', editor.get_editor_settings().get('text_editor/highlighting/keyword_color')) 51 | add_keyword_color('tree', editor.get_editor_settings().get('text_editor/highlighting/keyword_color')) 52 | add_keyword_color('import', editor.get_editor_settings().get('text_editor/highlighting/keyword_color')) 53 | add_keyword_color('true', editor.get_editor_settings().get('text_editor/highlighting/keyword_color')) 54 | add_keyword_color('false', editor.get_editor_settings().get('text_editor/highlighting/keyword_color')) 55 | 56 | for s in builtin_node_name_list: 57 | add_keyword_color(s, editor.get_editor_settings().get('text_editor/highlighting/function_color')) 58 | 59 | indent_size = editor.get_editor_settings().get('text_editor/indent/size') 60 | 61 | func get_current_word(): 62 | if text.empty(): 63 | return '' 64 | var line := cursor_get_line() 65 | var column := cursor_get_column() 66 | 67 | var line_text := get_line(line) 68 | var t = Tokenizer.new() 69 | t.init(line_text) 70 | 71 | var res := '' 72 | while not t.preview_next().type in [Tokenizer.Token.EOF, Tokenizer.Token.ERROR]: 73 | var token = t.get_next() 74 | if token.type == Tokenizer.Token.ID: 75 | if token.start < column and token.start + token.length >= column: 76 | res = token.value.substr(0, column - token.start) 77 | break 78 | 79 | if res.length() < 2: 80 | return '' 81 | return res 82 | 83 | 84 | func get_current_pos(prefix:String): 85 | if text.empty(): 86 | return Vector2.ZERO 87 | var line := cursor_get_line() 88 | var column := cursor_get_column() 89 | var line_text := get_line(line) 90 | var font:Font = get_font('font') 91 | var res = font.get_string_size(line_text.substr(0, column)) 92 | var tab_size = indent_size 93 | var tab_count = 0 94 | for s in line_text: 95 | if s == '\t': 96 | tab_count += 1 97 | var tab_x = tab_size * tab_count * font.get_char_size(' '.ord_at(0)).x 98 | 99 | var line_number_w = 0 100 | var line_number = get_line_count()-1 101 | while line_number: 102 | line_number_w += 1 103 | line_number /= 10 104 | line_number_w = (line_number_w+1) * font.get_char_size('0'.ord_at(0)).x 105 | 106 | res.x += tab_x + line_number_w - font.get_string_size(prefix).x - 3 - scroll_horizontal # margin 107 | res.y = (line+1-scroll_vertical) * get_row_height() 108 | return res 109 | 110 | func get_row_height(): 111 | var font:Font = get_font('font') 112 | var line_spacing = get_constant("line_spacing") 113 | return font.get_height() + line_spacing 114 | 115 | func move_line_up(): 116 | var line = cursor_get_line() 117 | var max_line = get_line_count() 118 | var up_line = max(0, line-1) 119 | 120 | if up_line == line: 121 | return 122 | 123 | var up_line_text = get_line(up_line) 124 | var line_text = get_line(line) 125 | 126 | set_line(line, up_line_text) 127 | set_line(up_line, line_text) 128 | cursor_set_line(up_line) 129 | 130 | func move_line_down(): 131 | var line = cursor_get_line() 132 | var max_line = get_line_count() 133 | var down_line = min(line+1, max_line-1) 134 | 135 | if down_line == line: 136 | return 137 | 138 | var down_line_text = get_line(down_line) 139 | var line_text = get_line(line) 140 | 141 | set_line(line, down_line_text) 142 | set_line(down_line, line_text) 143 | cursor_set_line(down_line) 144 | 145 | func duplicate_text(): 146 | var line = cursor_get_line() 147 | var column = cursor_get_column() 148 | var line_text = get_line(line) 149 | cursor_set_column(line_text.length()) 150 | insert_text_at_cursor('\n%s' % line_text) 151 | cursor_set_line(line+1) 152 | cursor_set_column(column) 153 | #----- Signals ----- 154 | func _on_CompletionPopup_completion_selected(prefix:String, word:String) -> void: 155 | insert_text_at_cursor(word.substr(prefix.length(), word.length() - prefix.length())) 156 | 157 | 158 | func _on_AutoCompletionTimer_timeout() -> void: 159 | var prefix = get_current_word() 160 | $CompletionPopup.rect_position = get_current_pos(prefix) 161 | $CompletionPopup.build_completion_word_list(prefix, text) 162 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/editor/script_editor/BTSEditor.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends VBoxContainer 3 | 4 | 5 | onready var bts_edit = $BTSEdit 6 | 7 | onready var edit_menu = $HBoxContainer/EditMenu 8 | onready var file_menu = $HBoxContainer/FileMenu 9 | onready var title_label = $HBoxContainer/TitleLabel 10 | 11 | var title = 'Behavior Tree Script Editor' 12 | var current_res:BehaviorTreeScriptResource = null 13 | var has_changed = false 14 | 15 | #----- Methods ----- 16 | func init(editor:EditorPlugin): 17 | bts_edit.set_syntax_highlight_color(editor.get_editor_interface()) 18 | bts_edit.text = '' 19 | current_res = null 20 | update_title() 21 | 22 | file_menu.get_popup().connect('id_pressed', self, '_on_file_menuitem_pressed') 23 | edit_menu.get_popup().connect('id_pressed', self, '_on_edit_menuitem_pressed') 24 | 25 | func edit(res:BehaviorTreeScriptResource): 26 | current_res = res 27 | has_changed = false 28 | update_title() 29 | 30 | bts_edit.text = res.data 31 | bts_edit.clear_undo_history() 32 | 33 | func save(): 34 | if not has_changed: 35 | return 36 | if current_res == null: 37 | printerr('You did not open any bts file!') 38 | return 39 | 40 | var err = ResourceSaver.save(current_res.resource_path, current_res) 41 | if err != OK: 42 | printerr('Can\'t write file: "%s"! code: %d.' % [current_res.resource_path, err]) 43 | return 44 | 45 | has_changed = false 46 | update_title() 47 | 48 | func update_title(): 49 | if current_res == null: 50 | title_label.text = title 51 | else: 52 | title_label.text = '%s - %s%s' % [title, current_res.resource_path, ('*' if has_changed else '')] 53 | 54 | #----- Signals ----- 55 | func _on_BTSEdit_text_changed() -> void: 56 | has_changed = true 57 | update_title() 58 | if current_res: 59 | current_res.data = bts_edit.text 60 | 61 | func _on_file_menuitem_pressed(id:int): 62 | match id: 63 | 0: 64 | save() 65 | 66 | func _on_edit_menuitem_pressed(id:int): 67 | match id: 68 | 0: 69 | bts_edit.undo() 70 | 1: 71 | bts_edit.redo() 72 | 3: 73 | bts_edit.copy() 74 | 4: 75 | bts_edit.cut() 76 | 5: 77 | bts_edit.paste() 78 | 7: 79 | bts_edit.select_all() 80 | 8: 81 | bts_edit.text = '' 82 | 10: 83 | bts_edit.move_line_up() 84 | 11: 85 | bts_edit.move_line_down() 86 | 12: 87 | bts_edit.duplicate_text() 88 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/editor/script_editor/BTSEditor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/editor/script_editor/BTSEdit.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/editor/script_editor/BTSEditor.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/naive_behavior_tree/editor/script_editor/CompletionPopup.gd" type="Script" id=3] 6 | 7 | [sub_resource type="StyleBoxLine" id=1] 8 | content_margin_left = 11.0 9 | color = Color( 0.631373, 0.627451, 0.639216, 1 ) 10 | vertical = true 11 | 12 | [sub_resource type="StyleBoxFlat" id=2] 13 | bg_color = Color( 0.14902, 0.172549, 0.231373, 1 ) 14 | border_width_left = 1 15 | border_width_top = 1 16 | border_width_right = 1 17 | border_width_bottom = 1 18 | border_color = Color( 0.0980392, 0.113725, 0.152941, 1 ) 19 | 20 | [sub_resource type="StyleBoxFlat" id=3] 21 | content_margin_left = 2.0 22 | content_margin_right = 2.0 23 | content_margin_top = 2.0 24 | content_margin_bottom = 2.0 25 | 26 | [node name="BTSEditor" type="VBoxContainer"] 27 | anchor_right = 1.0 28 | anchor_bottom = 1.0 29 | size_flags_horizontal = 3 30 | size_flags_vertical = 3 31 | script = ExtResource( 2 ) 32 | __meta__ = { 33 | "_edit_use_anchors_": false 34 | } 35 | 36 | [node name="HBoxContainer" type="HBoxContainer" parent="."] 37 | margin_right = 1024.0 38 | margin_bottom = 20.0 39 | 40 | [node name="FileMenu" type="MenuButton" parent="HBoxContainer"] 41 | margin_right = 35.0 42 | margin_bottom = 20.0 43 | text = "File" 44 | items = [ "Save", null, 0, false, false, 0, 0, null, "", false ] 45 | 46 | [node name="EditMenu" type="MenuButton" parent="HBoxContainer"] 47 | margin_left = 39.0 48 | margin_right = 75.0 49 | margin_bottom = 20.0 50 | focus_mode = 2 51 | text = "Edit" 52 | items = [ "Undo", null, 0, false, false, 0, 0, null, "", false, "Redo", null, 0, false, false, 1, 0, null, "", false, "", null, 0, false, false, 2, 0, null, "", true, "Copy", null, 0, false, false, 3, 0, null, "", false, "Cut", null, 0, false, false, 4, 0, null, "", false, "Paste", null, 0, false, false, 5, 0, null, "", false, "", null, 0, false, false, 6, 0, null, "", true, "Select All", null, 0, false, false, 7, 0, null, "", false, "Clear", null, 0, false, false, 8, 0, null, "", false, "", null, 0, false, false, 9, 0, null, "", true, "Move Up", null, 0, false, false, 10, 0, null, "", false, "Move Down", null, 0, false, false, 11, 0, null, "", false, "Duplicate", null, 0, false, false, 12, 0, null, "", false ] 53 | 54 | [node name="TitleLabel" type="Label" parent="HBoxContainer"] 55 | margin_left = 79.0 56 | margin_top = 3.0 57 | margin_right = 346.5 58 | margin_bottom = 17.0 59 | custom_styles/normal = SubResource( 1 ) 60 | text = "Behavior Tree Script Editor - res://re.gd*" 61 | 62 | [node name="BTSEdit" type="TextEdit" parent="."] 63 | margin_top = 24.0 64 | margin_right = 1024.0 65 | margin_bottom = 600.0 66 | size_flags_horizontal = 3 67 | size_flags_vertical = 3 68 | text = "# 69 | # 测试输出 70 | # 71 | 72 | import print:\"res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd\" 73 | 74 | subtree name: print_123 75 | parallel orchestrator: JOIN 76 | print msg: '1' 77 | print msg: '2' 78 | print msg: '3' 79 | 80 | tree 81 | sequence 82 | $print_123 83 | timer wait: 1 84 | print msg:'a' 85 | timer wait: 1 86 | random_selector 87 | print msg: 'b' 88 | print msg: 'c' 89 | timer wait: 1 90 | 91 | 92 | " 93 | highlight_current_line = true 94 | syntax_highlighting = true 95 | show_line_numbers = true 96 | draw_tabs = true 97 | highlight_all_occurrences = true 98 | smooth_scrolling = true 99 | minimap_draw = true 100 | caret_blink = true 101 | script = ExtResource( 1 ) 102 | 103 | [node name="CompletionPopup" type="ScrollContainer" parent="BTSEdit"] 104 | visible = false 105 | margin_right = 474.0 106 | margin_bottom = 125.0 107 | focus_mode = 2 108 | custom_styles/bg = SubResource( 2 ) 109 | script = ExtResource( 3 ) 110 | __meta__ = { 111 | "_edit_use_anchors_": false 112 | } 113 | 114 | [node name="VBoxContainer" type="VBoxContainer" parent="BTSEdit/CompletionPopup"] 115 | margin_left = 1.0 116 | margin_top = 1.0 117 | margin_right = 473.0 118 | margin_bottom = 95.0 119 | size_flags_horizontal = 3 120 | custom_constants/separation = 0 121 | 122 | [node name="RichTextLabel" type="RichTextLabel" parent="BTSEdit/CompletionPopup/VBoxContainer"] 123 | margin_right = 472.0 124 | margin_bottom = 19.0 125 | custom_styles/normal = SubResource( 3 ) 126 | bbcode_enabled = true 127 | bbcode_text = "sadasdasd" 128 | text = "sadasdasd" 129 | fit_content_height = true 130 | 131 | [node name="RichTextLabel2" type="RichTextLabel" parent="BTSEdit/CompletionPopup/VBoxContainer"] 132 | margin_top = 19.0 133 | margin_right = 472.0 134 | margin_bottom = 34.0 135 | bbcode_enabled = true 136 | bbcode_text = "sadasdasd" 137 | text = "sadasdasd" 138 | fit_content_height = true 139 | 140 | [node name="RichTextLabel3" type="RichTextLabel" parent="BTSEdit/CompletionPopup/VBoxContainer"] 141 | margin_top = 34.0 142 | margin_right = 472.0 143 | margin_bottom = 49.0 144 | bbcode_enabled = true 145 | bbcode_text = "sadasdasd" 146 | text = "sadasdasd" 147 | fit_content_height = true 148 | 149 | [node name="RichTextLabel4" type="RichTextLabel" parent="BTSEdit/CompletionPopup/VBoxContainer"] 150 | margin_top = 49.0 151 | margin_right = 472.0 152 | margin_bottom = 64.0 153 | bbcode_enabled = true 154 | bbcode_text = "sadasdasd" 155 | text = "sadasdasd" 156 | fit_content_height = true 157 | 158 | [node name="RichTextLabel5" type="RichTextLabel" parent="BTSEdit/CompletionPopup/VBoxContainer"] 159 | margin_top = 64.0 160 | margin_right = 472.0 161 | margin_bottom = 79.0 162 | bbcode_enabled = true 163 | bbcode_text = "sadasdasd" 164 | text = "sadasdasd" 165 | fit_content_height = true 166 | 167 | [node name="RichTextLabel6" type="RichTextLabel" parent="BTSEdit/CompletionPopup/VBoxContainer"] 168 | margin_top = 79.0 169 | margin_right = 472.0 170 | margin_bottom = 94.0 171 | bbcode_enabled = true 172 | bbcode_text = "sadasdasd" 173 | text = "sadasdasd" 174 | fit_content_height = true 175 | 176 | [node name="AutoCompletionTimer" type="Timer" parent="BTSEdit"] 177 | wait_time = 0.3 178 | one_shot = true 179 | 180 | [connection signal="text_changed" from="BTSEdit" to="." method="_on_BTSEdit_text_changed"] 181 | [connection signal="completion_selected" from="BTSEdit/CompletionPopup" to="BTSEdit" method="_on_CompletionPopup_completion_selected"] 182 | [connection signal="visibility_changed" from="BTSEdit/CompletionPopup" to="BTSEdit/CompletionPopup" method="_on_CompletionPopup_visibility_changed"] 183 | [connection signal="timeout" from="BTSEdit/AutoCompletionTimer" to="BTSEdit" method="_on_AutoCompletionTimer_timeout"] 184 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/editor/script_editor/CompletionPopup.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ScrollContainer 3 | 4 | signal completion_selected(prefix, word) 5 | 6 | const CompletionPopupItem = preload('CompletionPopupItem.gd') 7 | const Tokenizer = preload('../../compiler/Tokenizer.gd') 8 | 9 | var keyword_list := [ 10 | 'fail', 11 | 'success', 12 | 'timer', 13 | 14 | 'dynamic_guard_selector', 15 | 'parallel', 16 | 'selector', 17 | 'random_selector', 18 | 'sequence', 19 | 'random_sequence', 20 | 21 | 'always_fail', 22 | 'always_succeed', 23 | 'invert', 24 | 'random', 25 | 'repeat', 26 | 'until_fail', 27 | 'until_success', 28 | 29 | 'tree', 30 | 'subtree', 31 | 'import', 32 | 33 | 'true', 34 | 'false', 35 | 36 | 'policy', 37 | 'SEQUENCE', 38 | 'SELECTOR', 39 | 40 | 'orchestrator', 41 | 'RESUME', 42 | 'JOIN', 43 | 44 | 'wait', 45 | 'success_posibility', 46 | 'times', 47 | ] 48 | 49 | onready var container:VBoxContainer = $VBoxContainer 50 | 51 | var current_item_index := -1 52 | var current_prefix := '' 53 | 54 | func _input(event: InputEvent) -> void: 55 | if not is_visible_in_tree(): 56 | return 57 | if event is InputEventKey: 58 | if event.pressed: 59 | match event.scancode: 60 | KEY_ESCAPE: 61 | hide() 62 | KEY_ENTER, KEY_TAB: 63 | var item = get_current_selected_item() 64 | if item: 65 | emit_signal('completion_selected', current_prefix, item.word) 66 | accept_event() 67 | hide() 68 | KEY_UP: 69 | current_item_index -= 1 70 | if current_item_index < 0: 71 | current_item_index = 0 72 | select(current_item_index) 73 | accept_event() 74 | KEY_DOWN: 75 | current_item_index += 1 76 | if current_item_index >= container.get_child_count(): 77 | current_item_index = container.get_child_count()-1 78 | select(current_item_index) 79 | accept_event() 80 | 81 | #----- Methods ----- 82 | func on_hide(): 83 | release_focus() 84 | 85 | func on_show(): 86 | pass 87 | 88 | func build_completion_word_list(prefix, source:String): 89 | clear() 90 | current_item_index = -1 91 | 92 | current_prefix = prefix 93 | if current_prefix.empty() or source.empty(): 94 | visible = false 95 | return 96 | 97 | var search_list := {} 98 | 99 | var t = Tokenizer.new() 100 | t.init(source) 101 | while not t.preview_next().type in [Tokenizer.Token.EOF, Tokenizer.Token.ERROR]: 102 | var token = t.get_next() 103 | if token.type == Tokenizer.Token.ID: 104 | search_list[token.value] = true 105 | 106 | for k in keyword_list: 107 | search_list[k] = true 108 | 109 | for k in search_list.keys(): 110 | if k != current_prefix and k.begins_with(current_prefix): 111 | add_guess_word(k) 112 | 113 | select(0) 114 | 115 | visible = not empty() 116 | 117 | func clear(): 118 | var cs = container.get_children().duplicate() 119 | for c in cs: 120 | container.remove_child(c) 121 | c.free() 122 | 123 | 124 | func empty(): 125 | return container.get_child_count() == 0 126 | 127 | func add_guess_word(w:String): 128 | var l = create_label(w) 129 | var id = container.get_child_count() 130 | l.connect('clicked', self, '_on_item_clicked', [l, id]) 131 | l.connect('doubleclicked', self, '_on_item_doubleclicked', [l, id]) 132 | container.add_child(l) 133 | 134 | func create_label(s:String): 135 | var l := CompletionPopupItem.new() 136 | l.set_word(s) 137 | return l 138 | 139 | func select(id:int): 140 | current_item_index = id 141 | unselect_all() 142 | if current_item_index < 0 or current_item_index >= container.get_child_count(): 143 | current_item_index = -1 144 | else: 145 | container.get_child(current_item_index).selected = true 146 | 147 | 148 | func unselect_all(): 149 | for c in container.get_children(): 150 | c.selected = false 151 | 152 | func get_current_selected_item(): 153 | if current_item_index < 0 or current_item_index >= container.get_child_count(): 154 | return null 155 | return container.get_child(current_item_index) 156 | #----- Signals ----- 157 | func _on_CompletionPopup_visibility_changed() -> void: 158 | if is_visible_in_tree(): 159 | on_show() 160 | else: 161 | on_hide() 162 | 163 | func _on_item_clicked(item:CompletionPopupItem, id:int): 164 | select(id) 165 | 166 | func _on_item_doubleclicked(item:CompletionPopupItem, id:int): 167 | emit_signal('completion_selected', current_prefix, item.word) 168 | hide() 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/editor/script_editor/CompletionPopupItem.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends RichTextLabel 3 | 4 | signal clicked 5 | signal doubleclicked 6 | 7 | var bg_style:StyleBoxFlat 8 | var word:String 9 | 10 | var selected := false setget _on_set_selected 11 | func _on_set_selected(v): 12 | if selected != v: 13 | if v: 14 | bg_style.bg_color.a = 1 15 | else: 16 | bg_style.bg_color.a = 0 17 | selected = v 18 | 19 | func _init() -> void: 20 | fit_content_height = true 21 | bbcode_enabled = true 22 | 23 | bg_style = StyleBoxFlat.new() 24 | bg_style.bg_color = Color("#515662") 25 | bg_style.bg_color.a = 0 26 | var margin = 3 27 | bg_style.content_margin_top = margin 28 | bg_style.content_margin_bottom = margin 29 | bg_style.content_margin_left = margin 30 | bg_style.content_margin_right = margin 31 | add_stylebox_override('normal', bg_style) 32 | 33 | func set_word(w:String): 34 | word = w 35 | bbcode_text = word 36 | 37 | func _gui_input(event: InputEvent) -> void: 38 | if event is InputEventMouseButton: 39 | if event.pressed and event.button_index == BUTTON_LEFT: 40 | emit_signal('clicked') 41 | if event.doubleclick: 42 | emit_signal('doubleclicked') 43 | 44 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.svg-180b05902c8c0faa2150d9a68978c8ea.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/naive_behavior_tree/icon.svg" 13 | dest_files=[ "res://.import/icon.svg-180b05902c8c0faa2150d9a68978c8ea.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=true 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=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/import_plugin/BehaviorTreeScriptResource.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Resource 3 | class_name BehaviorTreeScriptResource, '../icon.svg' 4 | 5 | # 6 | # load .bts file 7 | # which is a text file containing script that defines a behavior tree 8 | # 9 | 10 | export var __tools__ := 0 11 | 12 | var data:String 13 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/import_plugin/BehaviorTreeScriptResourceLoader.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ResourceFormatLoader 3 | class_name BehaviorTreeScriptResourceLoader 4 | 5 | func get_recognized_extensions() -> PoolStringArray: 6 | var res:PoolStringArray 7 | res.append('bts') 8 | return res 9 | 10 | func get_resource_type(path: String) -> String: 11 | return 'Resource' 12 | 13 | 14 | func handles_type(typename: String) -> bool: 15 | return typename == 'Resource' 16 | 17 | func load(path: String, original_path: String): 18 | var res = BehaviorTreeScriptResource.new() 19 | 20 | var file = File.new() 21 | var err = file.open(path, File.READ) 22 | if err != OK: 23 | printerr('Can\'t open "%s", code: %d' % [path, err]) 24 | return err 25 | 26 | res.data = file.get_as_text() 27 | return res 28 | 29 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/import_plugin/BehaviorTreeScriptResourceSaver.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ResourceFormatSaver 3 | class_name BehaviorTreeScriptResourceSaver 4 | 5 | 6 | 7 | func get_recognized_extensions(resource: Resource) -> PoolStringArray: 8 | var res:PoolStringArray 9 | res.append('bts') 10 | return res 11 | 12 | func recognize(resource: Resource) -> bool: 13 | return resource is BehaviorTreeScriptResource 14 | 15 | func save(path: String, resource: Resource, flags: int) -> int: 16 | var err 17 | var file = File.new() 18 | err = file.open(path, File.WRITE) 19 | if err != OK: 20 | printerr('Can\'t write file: "%s"! code: %d.' % [path, err]) 21 | return err 22 | 23 | file.store_string(resource.data) 24 | return OK 25 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/inspector_plugin/inspector_plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorInspectorPlugin 3 | 4 | signal compile_button_pressed(res) 5 | signal clean_button_pressed(res) 6 | 7 | #----- Methods ----- 8 | func can_handle(object: Object) -> bool: 9 | if object is BehaviorTreeScriptResource: 10 | return true 11 | return false 12 | 13 | func parse_begin(object: Object) -> void: 14 | pass 15 | 16 | func parse_category(object: Object, category: String) -> void: 17 | pass 18 | 19 | func parse_property(object: Object, type: int, path: String, hint: int, hint_text: String, usage: int) -> bool: 20 | if path == '__tools__': 21 | var res = object as BehaviorTreeScriptResource 22 | var b = Button.new() 23 | b.text = tr('Compile') 24 | b.size_flags_horizontal = Button.SIZE_EXPAND_FILL 25 | b.connect('pressed', self, '_on_compile_button_pressed', [res]) 26 | 27 | var clean_b = Button.new() 28 | clean_b.text = tr(('Clean')) 29 | clean_b.size_flags_horizontal = Button.SIZE_EXPAND_FILL 30 | clean_b.connect('pressed', self, '_on_clean_button_pressed', [res]) 31 | 32 | var l = Label.new() 33 | l.text = res.resource_path 34 | l.size_flags_horizontal = Label.SIZE_EXPAND_FILL 35 | l.size_flags_stretch_ratio = 2 36 | 37 | var hb = HBoxContainer.new() 38 | hb.add_child(l) 39 | hb.add_child(b) 40 | hb.add_child(clean_b) 41 | 42 | add_custom_control(hb) 43 | return true 44 | return false 45 | 46 | func parse_end() -> void: 47 | pass 48 | 49 | 50 | #----- Signals ----- 51 | func _on_compile_button_pressed(res): 52 | emit_signal('compile_button_pressed', res) 53 | 54 | func _on_clean_button_pressed(res): 55 | emit_signal('clean_button_pressed', res) 56 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="NaiveBehaviorTreePlugin" 4 | description="This is a BehaviorTree plugin with a BehaviorTreeSyntax compiler and some built-in BehaviorTree classes." 5 | author="Raiix" 6 | version="1.2" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | var use_bts_editor = true 5 | 6 | var InspectorPlugin = preload('./inspector_plugin/inspector_plugin.gd') 7 | var inspector_plugin 8 | 9 | var remote_debug = preload('./remote_debug/RemoteDebug.gd').new() 10 | 11 | var BTSCompiler = preload('./compiler/Compiler.gd') 12 | 13 | var BTSEditor = preload('res://addons/naive_behavior_tree/editor/script_editor/BTSEditor.tscn') 14 | var bts_editor 15 | 16 | var clean_confirm_dialog:ConfirmationDialog 17 | 18 | func _enter_tree() -> void: 19 | inspector_plugin = InspectorPlugin.new() 20 | add_inspector_plugin(inspector_plugin) 21 | 22 | inspector_plugin.connect('compile_button_pressed', self, '_on_compile_button_pressed') 23 | inspector_plugin.connect('clean_button_pressed', self, '_on_clean_button_pressed') 24 | 25 | clean_confirm_dialog = ConfirmationDialog.new() 26 | get_editor_interface().get_base_control().add_child(clean_confirm_dialog) 27 | clean_confirm_dialog.connect('confirmed', self, '_confirm_clean_file') 28 | 29 | bts_editor = BTSEditor.instance() 30 | get_editor_interface().get_editor_viewport().add_child(bts_editor) 31 | make_visible(false) 32 | 33 | 34 | func _ready() -> void: 35 | bts_editor.init(self) 36 | add_child(remote_debug) 37 | 38 | 39 | func _exit_tree() -> void: 40 | bts_editor.queue_free() 41 | 42 | remove_inspector_plugin(inspector_plugin) 43 | inspector_plugin = null 44 | 45 | clean_confirm_dialog.queue_free() 46 | 47 | #----- Overrides ----- 48 | func has_main_screen() -> bool: 49 | return use_bts_editor 50 | 51 | func make_visible(visible: bool) -> void: 52 | if bts_editor: 53 | bts_editor.visible = visible 54 | 55 | func handles(object: Object) -> bool: 56 | if object is BehaviorTreeScriptResource: 57 | return true 58 | return false 59 | 60 | func edit(object: Object) -> void: 61 | if object is BehaviorTreeScriptResource: 62 | bts_editor.edit(object) 63 | 64 | 65 | func save_external_data() -> void: 66 | bts_editor.save() 67 | 68 | func get_plugin_name() -> String: 69 | return 'Naive Behavior Tree Plugin' 70 | 71 | func get_plugin_icon() -> Texture: 72 | return preload("res://addons/naive_behavior_tree/icon.svg") 73 | #----- Methods ----- 74 | func compile_task_function(res:BehaviorTreeScriptResource): 75 | print('Begin to compile bts: "%s"...' % res.resource_path) 76 | if compile(res): 77 | print('Compile bts: "%s" success!' % res.resource_path) 78 | else: 79 | printerr('Compile bts: "%s" fail!' % res.resource_path) 80 | 81 | func set_children_owner(p:Node, o:Node): 82 | for child in p.get_children(): 83 | child.owner = o 84 | set_children_owner(child, o) 85 | 86 | 87 | func compile(res:BehaviorTreeScriptResource): 88 | var path = res.resource_path 89 | var basename = path.get_basename() 90 | var fileNmae = path.get_file().replace('.' + path.get_extension(), '') 91 | var ext = 'tscn' 92 | var output = '%s.%s' % [basename, ext] 93 | 94 | var dir = Directory.new() 95 | var err = dir.open('res://') 96 | if err != OK: 97 | printerr('Can\'t open root directory, code: %d' % err) 98 | return false 99 | 100 | if dir.file_exists(output): 101 | printerr('File: "%s" exists! Please clean before compile.' % output) 102 | return false 103 | 104 | var source = res.data 105 | 106 | var compiler = BTSCompiler.new() 107 | compiler.init() 108 | 109 | var bt = compiler.compile(source, path) 110 | if bt == null: 111 | printerr('Can\'t compile bts: "%s"' % path) 112 | return false 113 | bt.name = fileNmae 114 | set_children_owner(bt, bt) 115 | 116 | var ps = PackedScene.new() 117 | ps.pack(bt) 118 | 119 | err = ResourceSaver.save(output, ps) 120 | if err != OK: 121 | printerr('Can\'t save the output: "%s", code: %d' % [output, err]) 122 | bt.queue_free() 123 | return false 124 | return true 125 | 126 | 127 | func clean_file(res:BehaviorTreeScriptResource): 128 | var path = res.resource_path 129 | var basename = path.get_basename() 130 | var ext = 'tscn' 131 | var output = '%s.%s' % [basename, ext] 132 | 133 | print('Cleaning file "%s"' % output) 134 | var dir = Directory.new() 135 | var err = dir.open('res://') 136 | if err != OK: 137 | printerr('Can\'t open root directory, code: %d' % err) 138 | return 139 | 140 | if not dir.file_exists(output): 141 | printerr('File: "%s" does not exist!' % output) 142 | return 143 | 144 | err = dir.remove(output) 145 | if err != OK: 146 | printerr('Can\'t remove file: "%s", code: %d' % [output, err]) 147 | return 148 | 149 | print('Clean done.') 150 | #----- Signals ----- 151 | func _on_compile_button_pressed(res:BehaviorTreeScriptResource): 152 | compile_task_function(res) 153 | 154 | func _on_clean_button_pressed(res:BehaviorTreeScriptResource): 155 | clean_confirm_dialog.dialog_text = 'Are you sure to delete the result of compiling "%s"?\nThese may do some unchangable influences to your project!' % res.resource_path 156 | clean_confirm_dialog.set_meta('res', res) 157 | clean_confirm_dialog.popup_centered() 158 | 159 | func _confirm_clean_file(): 160 | if clean_confirm_dialog.has_meta('res'): 161 | clean_file(clean_confirm_dialog.get_meta('res')) 162 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/CaptureFuncObject.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | 4 | #=====| How to use? |===== 5 | # 1. var ref = CaptureFuncObject.new({capture values}, ref obj, ref func) 6 | # 2. func ref_func(arg1, arg2, ...) 7 | # func ref_func(arg1, arg2, ..., data) # data contains the caputre values 8 | # 3. ref.call_func([arg1, arg2, ...]) 9 | 10 | var data:Dictionary 11 | 12 | var the_obj:Object 13 | var the_func:String 14 | 15 | func _init(d:Dictionary, obj:Object, f:String, weak:bool=false) -> void: 16 | data = d 17 | if weak: 18 | the_obj = weakref(obj) 19 | else: 20 | the_obj = obj 21 | the_func = f 22 | 23 | func call_func(args:Array = []): 24 | if is_valid(): 25 | var obj = the_obj.get_ref() if (the_obj is WeakRef) else the_obj 26 | if obj.has_method('_ClassType_LambdaFuncObject_'): 27 | obj.callv(the_func, [args + data.values()]) 28 | else: 29 | if not data.empty(): 30 | args.append(data) 31 | obj.callv(the_func, args) 32 | else: 33 | printerr('%s is invalid to call func: %s' % [the_obj, the_func]) 34 | 35 | func is_valid(): 36 | if the_obj is WeakRef: 37 | return is_instance_valid(the_obj.get_ref()) 38 | return is_instance_valid(the_obj) 39 | 40 | func set_data(d): 41 | data = d 42 | 43 | func _ClassType_CaptureFuncObject_(): 44 | pass 45 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/ClientProtocol.gd: -------------------------------------------------------------------------------- 1 | extends './RemoteDebugProtocol.gd' 2 | 3 | 4 | signal set_current_behavior_tree(bt) 5 | 6 | const excluded_param := [ 7 | 'guard_path', 8 | ] 9 | 10 | func get_event_name_list(): 11 | return .get_event_name_list() + [ 12 | ] 13 | #----- APIs ----- 14 | func api_get_node_path(obj_id): 15 | var obj = instance_from_id(obj_id) as Node 16 | if not obj: 17 | printerr('obj id(%s) is invalid.' % obj_id) 18 | return 19 | return obj.get_path() 20 | 21 | func api_get_bt_data(obj_id): 22 | var tree = instance_from_id(obj_id) as BehaviorTree 23 | if not tree: 24 | return null 25 | var res := { 26 | 'tree_name': tree.name, 27 | 'tree_obj_id': obj_id, 28 | 'tree_status': tree.status, 29 | 'enable': tree.enable, 30 | 'root': null, 31 | } 32 | 33 | var tree_root_data = gen_tree_data(tree.get_node(tree.root_path)) 34 | res.root = tree_root_data 35 | 36 | return res 37 | 38 | func api_set_current_bt(obj_id): 39 | var tree = instance_from_id(obj_id) as BehaviorTree 40 | emit_signal('set_current_behavior_tree', tree) 41 | 42 | #----- Methods ----- 43 | func gen_tree_node_data(node:BTNode, single := false): 44 | if node == null: 45 | printerr('The node is null!') 46 | return null 47 | var script := node.get_script() as Script 48 | if script == null: 49 | printerr('The script of node[%s] is null.' % node.get_path()) 50 | var guard = node.get_node_or_null(node.guard_path) 51 | var res = { 52 | 'name': node.name, 53 | 'obj_id': node.get_instance_id(), 54 | 'script': script.resource_path if script else '', 55 | 'status': node.status, 56 | } 57 | if not single: 58 | res.guard = gen_tree_data(guard) if guard else null 59 | res.children = [] 60 | gen_tree_node_parameter_data(node, res) 61 | return res 62 | 63 | func gen_tree_node_parameter_data(node:BTNode, btn_data): 64 | var pd := {} 65 | for p in node.get_property_list(): 66 | if p.has('usage') and p.usage == (PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT): 67 | if not p.name in excluded_param: 68 | pd[p.name] = node.get(p.name) 69 | btn_data['params'] = pd 70 | 71 | func gen_tree_data(node:BTNode): 72 | var data = gen_tree_node_data(node) 73 | if data == null: 74 | return null 75 | for c in node.get_children(): 76 | if c is BTNode: 77 | var child_data = gen_tree_data(c) 78 | if child_data != null: 79 | data.children.append(child_data) 80 | return data 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/DictionaryDatabase.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | var data := {} 4 | 5 | 6 | 7 | func _init(_data:Dictionary = {}) -> void: 8 | data = _data 9 | 10 | #----- Methods ----- 11 | 12 | # path = 'k1/k2/k3' 13 | func get(path:String): 14 | var ss := path.split('/') 15 | var current_dic = data 16 | var index = 0 17 | for key in ss: 18 | if current_dic.has(key): 19 | if not current_dic[key] is Dictionary: 20 | if index < ss.size()-1: 21 | return null 22 | current_dic = current_dic[key] 23 | else: 24 | return null 25 | index += 1 26 | return current_dic 27 | 28 | func set(path:String, value): 29 | var ss := path.split('/') 30 | if ss.empty(): 31 | printerr('The path is empty.') 32 | return 33 | var current_dic = data 34 | var index := 0 35 | for key in ss: 36 | if not current_dic.has(key): 37 | if index == ss.size()-1: 38 | current_dic[key] = value 39 | return 40 | else: 41 | current_dic[key] = {} 42 | 43 | if index == ss.size()-1: 44 | current_dic[key] = value 45 | return 46 | else: 47 | if not current_dic[key] is Dictionary: 48 | printerr('Wrong path: %s, key is not dic: %s' % [path, key]) 49 | return 50 | current_dic = current_dic[key] 51 | index += 1 52 | 53 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/RemoteDebug.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | signal remote_node_selected(id) 5 | 6 | const CaptureFuncObject = preload('./CaptureFuncObject.gd') 7 | 8 | const TOOL_MENU_NAME := 'Behavior Tree Remote Debug' 9 | const TOOL_MENU_ENABLE_ID := 0 10 | 11 | const CLIENTS_MENU_NAME := 'Clients' 12 | const CLIENTS_MENU_ID := 1 13 | 14 | const REMOTE_DEBUG_CLIENT_AUTOLOAD_NAME := 'NBT_RemoteDebugClient' 15 | const REMOTE_DEBUG_CLIENT_PATH := 'RemoteDebugClient.gd' 16 | 17 | const SETTING_KEY_DEBUG := 'nbt_plugin/remote_debug/debug_mode' 18 | const SETTING_KEY_ENABLE := 'nbt_plugin/remote_debug/auto_enable_when_plugin_enabled' 19 | const SETTING_KEY_SEVER_ADDRESS := 'nbt_plugin/remote_debug/server_address' 20 | const SETTING_KEY_SEVER_PORT := 'nbt_plugin/remote_debug/server_port' 21 | 22 | var tool_menu:PopupMenu = null 23 | 24 | const RemoteDebugServer = preload('./RemoteDebugServer.gd') 25 | var remote_debug_server:RemoteDebugServer 26 | const REMOTE_DEBUG_VIEW_TITLE := 'NBT Remote Debug' 27 | 28 | const RemoteDebugView = preload('./remote_debug_view/RemoteDebugView.tscn') 29 | var remote_debug_view:Control 30 | 31 | const ProgressModelView = preload('./remote_debug_view/ProgressModelView.tscn') 32 | 33 | var remote_tree_np_list := [ 34 | @'/root/EditorNode/@@592/@@593/@@601/@@603/@@607/@@608/@@609/Scene/@@6996', 35 | @'/root/EditorNode/@@580/@@581/@@589/@@591/@@595/@@596/@@597/Scene/@@6782', 36 | ] 37 | 38 | var enable := false 39 | 40 | func _enter_tree() -> void: 41 | get_plugin().add_tool_submenu_item(TOOL_MENU_NAME, create_tool_item()) 42 | 43 | func _exit_tree() -> void: 44 | if enable: 45 | on_disable() 46 | get_plugin().remove_tool_menu_item(TOOL_MENU_NAME) 47 | 48 | func _ready() -> void: 49 | connect('remote_node_selected', self, '_on_remote_node_selected') 50 | 51 | if ProjectSettings.has_setting(SETTING_KEY_ENABLE): 52 | enable = ProjectSettings.get_setting(SETTING_KEY_ENABLE) 53 | else: 54 | ProjectSettings.set_setting(SETTING_KEY_ENABLE, enable) 55 | if not ProjectSettings.has_setting(SETTING_KEY_DEBUG): 56 | ProjectSettings.set_setting(SETTING_KEY_DEBUG, false) 57 | if not ProjectSettings.has_setting(SETTING_KEY_SEVER_ADDRESS): 58 | ProjectSettings.set_setting(SETTING_KEY_SEVER_ADDRESS, 'localhost') 59 | if not ProjectSettings.has_setting(SETTING_KEY_SEVER_PORT): 60 | ProjectSettings.set_setting(SETTING_KEY_SEVER_PORT, 45537) 61 | 62 | tool_menu.set_item_checked(TOOL_MENU_ENABLE_ID, enable) 63 | if enable: 64 | on_enable() 65 | 66 | #----- Methods ----- 67 | func _find_scene_dock(node:Node): 68 | if node.name == tr('Scene'): 69 | return node 70 | 71 | for c in node.get_children(): 72 | var n = _find_scene_dock(c) 73 | if n: 74 | return n 75 | 76 | return null 77 | func find_remote_tree(): 78 | for p in remote_tree_np_list: 79 | var n = get_node_or_null(p) 80 | if n: 81 | return n 82 | 83 | var scene:Node = _find_scene_dock(get_plugin().get_editor_interface().get_base_control()) 84 | if scene == null: 85 | printerr('Can\'t even find the scene dock, no remote debug available!') 86 | return 87 | 88 | for c in scene.get_children(): 89 | if c is Tree: 90 | return c 91 | 92 | return null 93 | 94 | func create_tool_item(): 95 | if tool_menu == null: 96 | tool_menu = PopupMenu.new() 97 | tool_menu.add_check_item('Enable', TOOL_MENU_ENABLE_ID) 98 | tool_menu.connect('id_pressed', self, '_on_tool_menu_clicked') 99 | create_clients_submenu() 100 | update_clients_submenu() 101 | return tool_menu 102 | 103 | 104 | func get_plugin() -> EditorPlugin: 105 | return get_parent() as EditorPlugin 106 | 107 | 108 | func get_remote_tree() -> Tree: 109 | return find_remote_tree() as Tree 110 | 111 | func get_remote_debug_client_script_path(): 112 | var p = preload(REMOTE_DEBUG_CLIENT_PATH) 113 | return p.resource_path 114 | 115 | func create_clients_submenu(): 116 | var menu := PopupMenu.new() 117 | menu.connect('id_pressed', self, '_on_clients_menu_item_clicked') 118 | tool_menu.add_child(menu) 119 | menu.name = CLIENTS_MENU_NAME 120 | tool_menu.add_submenu_item(CLIENTS_MENU_NAME, CLIENTS_MENU_NAME, CLIENTS_MENU_ID) 121 | 122 | func update_clients_submenu(removed_peer := null): 123 | if not enable: 124 | tool_menu.set_item_disabled(CLIENTS_MENU_ID, false) 125 | return -1 126 | 127 | if not remote_debug_server: 128 | return 129 | 130 | var current_client_index = -1 131 | var clients_menu := tool_menu.get_node(CLIENTS_MENU_NAME) as PopupMenu 132 | for i in clients_menu.get_item_count(): 133 | if clients_menu.is_item_checked(i) and clients_menu.get_item_text(i) != remote_debug_server.get_peer_id(removed_peer): 134 | current_client_index = i 135 | break 136 | 137 | clients_menu.clear() 138 | 139 | for peer_id in remote_debug_server.clients.keys(): 140 | clients_menu.add_check_item(peer_id) 141 | 142 | if current_client_index == -1: 143 | current_client_index = 0 144 | 145 | if current_client_index >= 0 and current_client_index < clients_menu.get_item_count(): 146 | clients_menu.set_item_checked(current_client_index, true) 147 | 148 | tool_menu.set_item_disabled(CLIENTS_MENU_ID, clients_menu.get_item_count() == 0) 149 | 150 | return current_client_index 151 | 152 | func on_enable(): 153 | var tree = get_remote_tree() 154 | if tree == null: 155 | printerr('Can\'t not find the remote tree. Disabling...') 156 | enable = false 157 | return 158 | tree.connect('item_activated', self, '_on_remote_tree_item_doubleclicked') 159 | get_plugin().add_autoload_singleton(REMOTE_DEBUG_CLIENT_AUTOLOAD_NAME, get_remote_debug_client_script_path()) 160 | 161 | remote_debug_server = RemoteDebugServer.new() 162 | 163 | if ProjectSettings.has_setting(SETTING_KEY_SEVER_PORT): 164 | remote_debug_server.port = ProjectSettings.get_setting(SETTING_KEY_SEVER_PORT) 165 | 166 | add_child(remote_debug_server) 167 | remote_debug_server.connect('client_connected', self, '_on_client_connected') 168 | remote_debug_server.connect('client_disconnected', self, '_on_client_disconnected') 169 | remote_debug_server.connect('current_peer_changed', self, '_on_current_peer_changed') 170 | remote_debug_server.protocol.connect('tree_active', self, '_on_tree_active') 171 | remote_debug_server.protocol.connect('tree_inactive', self, '_on_tree_inactive') 172 | remote_debug_server.protocol.connect('tree_node_status_changed', self, '_on_tree_node_status_changed') 173 | 174 | update_clients_submenu() 175 | 176 | remote_debug_view = RemoteDebugView.instance() 177 | get_plugin().add_control_to_bottom_panel(remote_debug_view, REMOTE_DEBUG_VIEW_TITLE) 178 | remote_debug_view.connect('request_open_script', self, '_on_request_open_script') 179 | remote_debug_view.connect('request_screenshot', self, '_on_take_screenshot') 180 | 181 | print('enable remote debug') 182 | enable = true 183 | 184 | 185 | 186 | func on_disable(): 187 | var tree = get_remote_tree() 188 | if tree: 189 | tree.disconnect('item_activated', self, '_on_remote_tree_item_doubleclicked') 190 | get_plugin().remove_autoload_singleton(REMOTE_DEBUG_CLIENT_AUTOLOAD_NAME) 191 | 192 | remote_debug_server.queue_free() 193 | remote_debug_server = null 194 | 195 | get_plugin().remove_control_from_bottom_panel(remote_debug_view) 196 | remote_debug_view.queue_free() 197 | remote_debug_view = null 198 | 199 | print('disable remote debug') 200 | enable = false 201 | 202 | update_clients_submenu() 203 | 204 | func print_debug_msg(msg): 205 | if ProjectSettings.get_setting(SETTING_KEY_DEBUG): 206 | print('[RemoteDebug]: %s' % msg) 207 | #----- Lambdas ----- 208 | func __get_node_path_call_back(node_path): 209 | print_debug_msg(node_path) 210 | 211 | 212 | func __get_bt_data_call_back(bt_data): 213 | remote_debug_view.on_recieve_all_data(bt_data) 214 | if bt_data == null: 215 | if remote_debug_view.is_visible_in_tree(): 216 | get_plugin().hide_bottom_panel() 217 | return 218 | get_plugin().make_bottom_panel_item_visible(remote_debug_view) 219 | #----- Signals ----- 220 | func _on_tool_menu_clicked(id:int): 221 | match id: 222 | TOOL_MENU_ENABLE_ID: 223 | var checked = not tool_menu.is_item_checked(TOOL_MENU_ENABLE_ID) 224 | tool_menu.set_item_checked(TOOL_MENU_ENABLE_ID, checked) 225 | if checked: 226 | on_enable() 227 | else: 228 | on_disable() 229 | 230 | func _on_remote_tree_item_doubleclicked(): 231 | var tree := get_remote_tree() 232 | var tree_item := tree.get_selected() 233 | var remote_node_id = tree_item.get_metadata(0) 234 | emit_signal('remote_node_selected', remote_node_id) 235 | 236 | func _on_client_connected(peer:PacketPeerStream): 237 | var id = update_clients_submenu() 238 | remote_debug_server.set_current_peer_by_index(id) 239 | 240 | func _on_client_disconnected(peer:PacketPeerStream): 241 | var id = update_clients_submenu(peer) 242 | remote_debug_server.set_current_peer_by_index(id) 243 | 244 | func _on_clients_menu_item_clicked(id:int): 245 | var menu := tool_menu.get_node(CLIENTS_MENU_NAME) as PopupMenu 246 | var checked := menu.is_item_checked(id) 247 | if not checked: 248 | for i in menu.get_item_count(): 249 | menu.set_item_checked(i, false) 250 | menu.set_item_checked(id, true) 251 | remote_debug_server.set_current_peer_by_index(id) 252 | 253 | func _on_remote_node_selected(obj_id): 254 | if not enable: 255 | return 256 | remote_debug_server.protocol.call_api('get_node_path', [obj_id], CaptureFuncObject.new({}, self, '__get_node_path_call_back')) 257 | 258 | remote_debug_server.protocol.call_api('get_bt_data', [obj_id], CaptureFuncObject.new({}, self, '__get_bt_data_call_back')) 259 | 260 | remote_debug_server.protocol.call_api('set_current_bt', [obj_id]) 261 | 262 | 263 | func _on_tree_active(event): 264 | remote_debug_view.on_update_data('enable', true) 265 | 266 | func _on_tree_inactive(event): 267 | remote_debug_view.on_update_data('enable', false) 268 | 269 | func _on_current_peer_changed(peer): 270 | if not remote_debug_view: 271 | return 272 | remote_debug_view.on_recieve_all_data(null) 273 | 274 | func _on_tree_node_status_changed(event): 275 | remote_debug_view.on_update_tree_node_data(event.data.obj_id, event.data) 276 | 277 | func _on_request_open_script(path): 278 | var script = load(path) as Script 279 | if script == null: 280 | printerr('%s is not a script!' % path) 281 | return 282 | get_plugin().get_editor_interface().edit_resource(script) 283 | 284 | 285 | func _on_take_screenshot(): 286 | if remote_debug_view == null: 287 | return 288 | if remote_debug_view.db.data.empty(): 289 | return 290 | 291 | var theme = get_plugin().get_editor_interface().get_base_control().theme 292 | 293 | var p = ProgressModelView.instance() 294 | add_child(p) 295 | p.title.text = 'Taking screen shot please wait...' 296 | p.theme = theme 297 | var pb:ProgressBar = p.progress_bar 298 | pb.max_value = 6 299 | pb.value = 0 300 | p.show_modal(true) 301 | 302 | var vp = Viewport.new() 303 | add_child(vp) 304 | vp.render_target_clear_mode = Viewport.CLEAR_MODE_ONLY_NEXT_FRAME 305 | vp.render_target_update_mode = Viewport.UPDATE_ALWAYS 306 | vp.render_target_v_flip = true 307 | 308 | # print('start taking screenshot (1)') 309 | pb.value = 1 310 | 311 | yield(get_tree().create_timer(0.1), 'timeout') 312 | # print('setting up viewport (2)') 313 | var temp_view = RemoteDebugView.instance() 314 | vp.add_child(temp_view) 315 | temp_view.theme = theme 316 | temp_view.get_node('VBoxContainer/HBoxContainer').visible = false 317 | pb.value = 2 318 | var graph_edit = temp_view.graph_edit as GraphEdit 319 | graph_edit.get_child(0).visible = false 320 | 321 | yield(get_tree().create_timer(0.1), 'timeout') 322 | # print('setting up graph node view (3)') 323 | var data = remote_debug_view.db.data as Dictionary 324 | temp_view.on_recieve_all_data(data, false) 325 | temp_view.show() 326 | 327 | yield(get_tree().create_timer(0.5), 'timeout') 328 | var root = temp_view.obj_id_node_map[temp_view.db.get('root/obj_id')] 329 | var ct_tree = temp_view.calc_tree_size(root) 330 | vp.size = ct_tree.tree_size + Vector2(20, 20) 331 | 332 | 333 | pb.value = 3 334 | 335 | yield(get_tree().create_timer(0.1), 'timeout') 336 | # print('adjust graph node view (4)') 337 | graph_edit.scroll_offset = Vector2(-10, -10) 338 | graph_edit.minimap_enabled = false 339 | graph_edit.use_snap = false 340 | 341 | pb.value = 4 342 | 343 | yield(get_tree().create_timer(0.1), 'timeout') 344 | # print('taking image data (5)') 345 | var img = vp.get_texture().get_data() as Image 346 | img.save_png('res://nbt_screen_shot.png') 347 | pb.value = 5 348 | 349 | yield(get_tree().create_timer(0.1), 'timeout') 350 | # print('done! (6)') 351 | vp.queue_free() 352 | pb.value = 6 353 | 354 | yield(get_tree().create_timer(0.1), 'timeout') 355 | p.queue_free() 356 | 357 | 358 | 359 | 360 | 361 | 362 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/RemoteDebugClient.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | signal server_message(peer, data) 4 | 5 | const SETTING_KEY_DEBUG := 'nbt_plugin/remote_debug/debug_mode' 6 | const SETTING_KEY_SEVER_ADDRESS := 'nbt_plugin/remote_debug/server_address' 7 | const SETTING_KEY_SEVER_PORT := 'nbt_plugin/remote_debug/server_port' 8 | 9 | var address := 'localhost' 10 | var port := 45537 11 | 12 | var peer:PacketPeerStream = null 13 | 14 | var connected := false 15 | 16 | var Protocol := preload('./ClientProtocol.gd') 17 | var protocol := Protocol.new() 18 | 19 | var current_tree:BehaviorTree = null 20 | 21 | func _ready() -> void: 22 | peer = PacketPeerStream.new() 23 | var stream_peer = StreamPeerTCP.new() 24 | peer.stream_peer = stream_peer 25 | var err = stream_peer.connect_to_host(address, port) 26 | if err != OK: 27 | print_debug_msg('Can\'t connect to the server: %s:%s' % [address, port]) 28 | print_debug_msg('Removing myself.') 29 | queue_free() 30 | queue_free() 31 | return 32 | print_debug_msg('connecting to server: %s:%s' % [address, port]) 33 | connected = false 34 | 35 | protocol.connect('request_put_var', self, 'put_var') 36 | connect('server_message', protocol, 'on_recieve_data') 37 | protocol.connect('set_current_behavior_tree', self, 'set_current_behavior_tree') 38 | 39 | if ProjectSettings.has_setting(SETTING_KEY_SEVER_ADDRESS): 40 | address = ProjectSettings.get_setting(SETTING_KEY_SEVER_ADDRESS) 41 | if ProjectSettings.has_setting(SETTING_KEY_SEVER_PORT): 42 | port = ProjectSettings.get_setting(SETTING_KEY_SEVER_PORT) 43 | 44 | 45 | func _process(delta: float) -> void: 46 | if not peer: 47 | return 48 | var stream_peer := peer.stream_peer 49 | if not stream_peer.is_connected_to_host(): 50 | return 51 | if not connected and stream_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: 52 | connected = true 53 | on_connected() 54 | if stream_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: 55 | recieve_msg_from_peer() 56 | elif not stream_peer.is_connected_to_host(): 57 | print_debug_msg('Disconnected from server, removing myself') 58 | queue_free() 59 | #----- Methods ----- 60 | func print_debug_msg(msg:String): 61 | if ProjectSettings.get_setting(SETTING_KEY_DEBUG): 62 | print('[RemoteDebugClient]: %s' % msg) 63 | 64 | func recieve_msg_from_peer(): 65 | if not peer: 66 | return 67 | for i in peer.get_available_packet_count(): 68 | var data = peer.get_var() 69 | var err = peer.get_packet_error() 70 | if err != OK: 71 | print_debug_msg('Error happens while fetching packet [Error:%s]' % err) 72 | continue 73 | emit_signal('server_message', peer, data) 74 | # print_debug_msg('from server: %s' % data) 75 | 76 | func put_var(v): 77 | if peer == null: 78 | print_debug_msg('peer == null!') 79 | return 80 | var err = peer.put_var(v) 81 | if err != OK: 82 | print_debug_msg('can\'t put var. [Error:%s]' % err) 83 | return 84 | 85 | func on_connected(): 86 | print_debug_msg('Connected to the server.') 87 | 88 | func set_current_behavior_tree(bt): 89 | if current_tree: 90 | current_tree.disconnect('tree_active', self, '_on_tree_active') 91 | current_tree.disconnect('tree_inactive', self, '_on_tree_inactive') 92 | current_tree.disconnect('task_status_changed', self, '_on_tree_node_status_changed') 93 | current_tree = bt 94 | print_debug_msg('set current bt: %s' % bt) 95 | if current_tree: 96 | current_tree.connect('tree_active', self, '_on_tree_active') 97 | current_tree.connect('tree_inactive', self, '_on_tree_inactive') 98 | current_tree.connect('task_status_changed', self, '_on_tree_node_status_changed') 99 | #----- Signals ----- 100 | func _on_tree_active(): 101 | protocol.boardcast_event('tree_active') 102 | 103 | func _on_tree_inactive(): 104 | protocol.boardcast_event('tree_inactive') 105 | 106 | func _on_tree_node_status_changed(task): 107 | var data = protocol.gen_tree_node_data(task, true) 108 | protocol.boardcast_event('tree_node_status_changed', data) 109 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/RemoteDebugProtocol.gd: -------------------------------------------------------------------------------- 1 | extends Reference 2 | 3 | signal request_put_var(data) 4 | 5 | var request_id_count := -1 6 | 7 | var pandding_request_map_list := {} 8 | 9 | enum { 10 | REQ, 11 | RES, 12 | SUB, # for event 13 | } 14 | 15 | #----- APIs ----- 16 | 17 | #----- Methods ----- 18 | func _init() -> void: 19 | var event_list = get_event_name_list() 20 | for e in event_list: 21 | add_user_signal(e, [TYPE_DICTIONARY]) 22 | 23 | func get_api_name(name:String) -> String: 24 | return 'api_%s' % name 25 | 26 | func get_event_name_list(): 27 | return [ 28 | 'something_changed', 29 | ] 30 | 31 | func gen_event(name:String, data = null): 32 | return { 33 | 'name': name, 34 | 'data': data, 35 | } 36 | 37 | func boardcast_event(name:String, data = null): 38 | var event = gen_event(name, data) 39 | emit_signal('request_put_var', gen_sub(event)) 40 | 41 | func call_api(name:String, args := [], callback = null): 42 | var req:Dictionary = gen_req(name, callback, args) 43 | if not pandding_request_map_list.has(name): 44 | pandding_request_map_list[name] = [] 45 | pandding_request_map_list[name].append(req) 46 | req = req.duplicate() 47 | req.erase('callback') 48 | emit_signal('request_put_var', req) 49 | 50 | func get_new_request_id(): 51 | request_id_count += 1 52 | return request_id_count 53 | 54 | func gen_req(name:String, callback = null, args := []): 55 | return { 56 | 'id': get_new_request_id(), 57 | 'name': name, 58 | 'type': REQ, 59 | 'args': args, 60 | 'callback': callback, 61 | } 62 | 63 | func gen_res(req:Dictionary, data = null): 64 | return { 65 | 'id': req.id, 66 | 'name': req.name, 67 | 'type': RES, 68 | 'data': data, 69 | } 70 | 71 | func gen_sub(event): 72 | return { 73 | 'type': SUB, 74 | 'event': event, 75 | } 76 | 77 | func on_recieve_data(peer, data): 78 | if not data is Dictionary: 79 | return 80 | if not data.has('type'): 81 | return 82 | match data.type: 83 | REQ: 84 | var func_name = get_api_name(data.name) 85 | if not has_method(func_name): 86 | printerr('api func %s does not exist.' % func_name) 87 | return 88 | var res_data = callv(func_name, data.args) 89 | var res = gen_res(data, res_data) 90 | emit_signal('request_put_var', res) 91 | RES: 92 | var name = data.name 93 | if pandding_request_map_list.has(name): 94 | for req in pandding_request_map_list[name]: 95 | if req.id == data.id: 96 | if req.callback: 97 | req.callback.call_func([data.data]) 98 | pandding_request_map_list[name].clear() 99 | SUB: 100 | var event = data.event 101 | emit_signal(event.name, event) 102 | 103 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/RemoteDebugServer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | 4 | signal client_connected(peer) 5 | signal client_disconnected(peer) 6 | signal client_message(peer, data) 7 | signal current_peer_changed(peer) 8 | 9 | var port := 45537 10 | 11 | var server:TCP_Server 12 | 13 | var clients := {} 14 | 15 | var current_peer:PacketPeerStream = null 16 | 17 | var Protocol := preload('./ServerProtocol.gd') 18 | var protocol := Protocol.new() 19 | 20 | const SETTING_KEY_DEBUG := 'nbt_plugin/remote_debug/debug_mode' 21 | 22 | func _enter_tree() -> void: 23 | server = TCP_Server.new() 24 | 25 | var err := server.listen(port) 26 | if err != OK: 27 | printerr('Remote debug server can\'t listen on port: %s [ERROR:%s]' % [port, err]) 28 | return 29 | 30 | print_debug_msg('Remote debug server lisien on port: %s' % port) 31 | 32 | func _exit_tree() -> void: 33 | clients.clear() 34 | server.stop() 35 | print_debug_msg('Remote debug server stopped.') 36 | 37 | func _ready() -> void: 38 | protocol.connect('request_put_var', self, 'put_var') 39 | connect('client_message', protocol, 'on_recieve_data') 40 | 41 | func _process(delta: float) -> void: 42 | if not server.is_listening(): 43 | return 44 | if server.is_connection_available(): 45 | var stream_peer:StreamPeerTCP = server.take_connection() 46 | var peer := PacketPeerStream.new() 47 | peer.stream_peer = stream_peer 48 | var peer_id = get_peer_id(peer) 49 | 50 | if clients.has(peer_id): 51 | clients[peer_id].free() 52 | clients[peer_id] = peer 53 | print_debug_msg('[%s] connected.' % peer_id) 54 | emit_signal('client_disconnected', peer) 55 | 56 | check_all_client_status() 57 | recieve_msg_from_current_peer() 58 | #----- Methods ----- 59 | func print_debug_msg(msg:String): 60 | if ProjectSettings.get_setting(SETTING_KEY_DEBUG): 61 | print('[RemoteDebugServer]: %s' % msg) 62 | 63 | func check_all_client_status(): 64 | var removed_client_list := [] 65 | for peer_id in clients.keys(): 66 | var peer:PacketPeerStream = clients[peer_id] 67 | var stream_peer := peer.stream_peer 68 | var status = stream_peer.get_status() 69 | if status != StreamPeerTCP.STATUS_CONNECTED: 70 | print_debug_msg('[%s] has disconnected.' % peer_id) 71 | removed_client_list.append(peer_id) 72 | 73 | for peer_id in removed_client_list: 74 | var peer = clients[peer_id] 75 | clients.erase(peer_id) 76 | emit_signal('client_disconnected', peer) 77 | 78 | func recieve_msg_from_current_peer(): 79 | if current_peer == null: 80 | return 81 | for i in current_peer.get_available_packet_count(): 82 | var data = current_peer.get_var() 83 | var err = current_peer.get_packet_error() 84 | if err != OK: 85 | print_debug_msg('Error happens while fetching packet [Error:%s]' % err) 86 | continue 87 | emit_signal('client_message', current_peer, data) 88 | # print_debug_msg('from client: %s' % data) 89 | 90 | func get_peer_id(peer:PacketPeerStream) -> String: 91 | if not peer: 92 | return '' 93 | var stream_peer = peer.stream_peer 94 | return '%s:%s' % [stream_peer.get_connected_host(), stream_peer.get_connected_port()] 95 | 96 | func set_current_peer_by_index(id:int): 97 | if id < 0 or id >= clients.keys().size(): 98 | set_current_peer('') 99 | return 100 | set_current_peer(clients.keys()[id]) 101 | 102 | func set_current_peer(peer_id): 103 | if clients.has(peer_id): 104 | if clients[peer_id] == current_peer: 105 | return 106 | current_peer = clients[peer_id] 107 | else: 108 | current_peer = null 109 | print_debug_msg('set current peer to: %s' % (get_peer_id(current_peer) if current_peer else 'null')) 110 | emit_signal('current_peer_changed', current_peer) 111 | 112 | func put_var(v): 113 | if current_peer == null: 114 | print_debug_msg('current_peer == null!') 115 | return 116 | var err = current_peer.put_var(v) 117 | if err != OK: 118 | print_debug_msg('can\'t put var. [Error:%s]' % err) 119 | return 120 | #----- Signals ----- 121 | 122 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/ServerProtocol.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends './RemoteDebugProtocol.gd' 3 | 4 | 5 | func get_event_name_list(): 6 | return .get_event_name_list() + [ 7 | 'tree_active', 8 | 'tree_inactive', 9 | 'tree_node_status_changed', 10 | ] 11 | #----- APIs ----- 12 | 13 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/ddb_test.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorScript 3 | 4 | const DictionaryDatabase = preload('./DictionaryDatabase.gd') 5 | 6 | func _run() -> void: 7 | var db = DictionaryDatabase.new() 8 | 9 | db.set('a', 1) 10 | db.set('b', 1) 11 | db.set('c', {'a': 1}) 12 | db.set('a/c', {'a': 1}) 13 | db.set('d/c', {'a': 1}) 14 | 15 | print(db.data) 16 | 17 | print(db.get('d/c')) 18 | print(db.get('d/c/a')) 19 | print(db.get('a')) 20 | print(db.get('d/a')) 21 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/NBTGraphNode.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | 4 | signal request_open_script(path) 5 | signal request_show_children 6 | signal request_hide_children 7 | 8 | enum { 9 | PARENT_SLOT = 0, 10 | CHILDREN_SLOT = 0, 11 | GUARD_SLOT = 1, 12 | } 13 | 14 | const PARENT_SLOT_COLOR = Color.rebeccapurple 15 | const CHILDREN_SLOT_COLOR = Color.royalblue 16 | const GUARD_SLOT_COLOR = Color.goldenrod 17 | 18 | enum { 19 | NONE_TYPE = 0, 20 | BT_NODE_TYPE = 0, 21 | } 22 | 23 | var source_data 24 | enum BTNStatus { 25 | Fresh = BTNode.FRESH, 26 | Running = BTNode.RUNNING, 27 | Failed = BTNode.FAILED, 28 | Succeeded = BTNode.SUCCEEDED, 29 | Cancelled = BTNode.CANCELLED, 30 | } 31 | export(BTNStatus) var status = BTNode.FRESH setget _on_set_status 32 | func _on_set_status(v): 33 | if v == BTNStatus.Succeeded: 34 | highlight_color.a = 1.0 35 | status = v 36 | update() 37 | 38 | onready var script_button = $ScriptButton 39 | onready var param_container = $ParameterContainer 40 | onready var hide_children_button = $HBoxContainer/HBoxContainer/HideChildrenButton 41 | 42 | var highlight_color := Color(1, 1, 1, 0) 43 | 44 | var hide_children := false 45 | var auto_hide_children := true 46 | 47 | var builtin_node_name_map := { 48 | 'BTActionFail': 'Fail', 49 | 'BTActionSuccess': 'Success', 50 | 'BTActionTimer': 'Timer', 51 | 'BTActionRandomTimer': 'Random Timer', 52 | 'BTCompositeDynamicGuardSelector': 'Dynamic Guard Selector', 53 | 'BTCompositeParallel': 'Parallel', 54 | 'BTCompositeRandomSelector': 'Random Selector', 55 | 'BTCompositeRandomSequence': 'Random Sequence', 56 | 'BTCompositeSelector': 'Selector', 57 | 'BTCompositeSequence': 'Sequence', 58 | 'BTDecoratorAlwaysFail': 'Always Fail', 59 | 'BTDecoratorAlwaysSucceed': 'Always Succeed', 60 | 'BTDecoratorInvert': 'Invert', 61 | 'BTDecoratorRandom': 'Random', 62 | 'BTDecoratorRepeat': 'Repeat', 63 | 'BTDecoratorUntilFail': 'Until Fail', 64 | 'BTDecoratorUntilSuccess': 'Until Success', 65 | } 66 | 67 | func _ready() -> void: 68 | set_slot(PARENT_SLOT, true, BT_NODE_TYPE, PARENT_SLOT_COLOR, true, BT_NODE_TYPE, CHILDREN_SLOT_COLOR) 69 | set_slot(GUARD_SLOT, false, NONE_TYPE, Color.white, true, BT_NODE_TYPE, GUARD_SLOT_COLOR) 70 | 71 | rect_size = Vector2.ZERO 72 | 73 | func _notification(what: int) -> void: 74 | match what: 75 | NOTIFICATION_DRAW: 76 | match status: 77 | BTNode.RUNNING: 78 | draw_style_box(get_stylebox('running'), Rect2(Vector2.ZERO, rect_size)) 79 | BTNode.FAILED: 80 | draw_style_box(get_stylebox('failed'), Rect2(Vector2.ZERO, rect_size)) 81 | BTNode.SUCCEEDED: 82 | draw_style_box(get_stylebox('succeeded'), Rect2(Vector2.ZERO, rect_size)) 83 | BTNode.CANCELLED: 84 | draw_style_box(get_stylebox('cancelled'), Rect2(Vector2.ZERO, rect_size)) 85 | draw_rect(Rect2(Vector2.ZERO, rect_size), highlight_color, false, 3) 86 | 87 | func _process(delta: float) -> void: 88 | if highlight_color.a > 0: 89 | highlight_color.a = lerp(highlight_color.a, 0, 0.03) 90 | update() 91 | #----- Methods ----- 92 | func set_data(node_data, _auto_hide_children := true): 93 | source_data = node_data 94 | title = source_data.name 95 | auto_hide_children = _auto_hide_children 96 | if auto_hide_children: 97 | if '[' in title and ']' in title: 98 | hide_children = true 99 | update_hide_children_button() 100 | self.status = source_data.status 101 | update_script_button() 102 | if node_data.has('params'): 103 | param_container.set_data(node_data.params, source_data.script) 104 | update() 105 | 106 | func update_data(node_data:Dictionary): 107 | for k in node_data.keys(): 108 | if source_data.has(k): 109 | source_data[k] = node_data[k] 110 | if k == 'params': 111 | param_container.update_data(node_data.params) 112 | title = source_data.name 113 | self.status = source_data.status 114 | update_script_button() 115 | update() 116 | 117 | func update_script_button(): 118 | var script_path:String = source_data.script 119 | var type = script_path.get_file().get_basename() 120 | if builtin_node_name_map.has(type): 121 | script_button.text = builtin_node_name_map[type] 122 | else: 123 | script_button.text = type 124 | 125 | 126 | func update_hide_children_button(): 127 | if hide_children: 128 | hide_children_button.text = '+' 129 | else: 130 | hide_children_button.text = '-' 131 | #----- Signals ----- 132 | func _on_ScriptButton_pressed() -> void: 133 | emit_signal('request_open_script', source_data.script) 134 | 135 | 136 | func _on_HideChildrenButton_pressed() -> void: 137 | hide_children = not hide_children 138 | update_hide_children_button() 139 | if hide_children: 140 | emit_signal('request_hide_children') 141 | else: 142 | emit_signal('request_show_children') 143 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/NBTGraphNode.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/remote_debug/remote_debug_view/NBTGraphNode.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/remote_debug/remote_debug_view/ParameterContainer.gd" type="Script" id=2] 5 | 6 | [sub_resource type="StyleBoxFlat" id=1] 7 | draw_center = false 8 | border_width_left = 3 9 | border_width_top = 3 10 | border_width_right = 3 11 | border_width_bottom = 3 12 | border_color = Color( 0.0588235, 0.776471, 0.886275, 1 ) 13 | border_blend = true 14 | 15 | [sub_resource type="StyleBoxFlat" id=2] 16 | draw_center = false 17 | border_width_left = 3 18 | border_width_top = 3 19 | border_width_right = 3 20 | border_width_bottom = 3 21 | border_color = Color( 0.992157, 0.0862745, 0.0862745, 1 ) 22 | border_blend = true 23 | 24 | [sub_resource type="StyleBoxFlat" id=3] 25 | draw_center = false 26 | border_width_left = 3 27 | border_width_top = 3 28 | border_width_right = 3 29 | border_width_bottom = 3 30 | border_color = Color( 0.886275, 0.615686, 0.0666667, 1 ) 31 | border_blend = true 32 | 33 | [sub_resource type="StyleBoxFlat" id=4] 34 | draw_center = false 35 | border_width_left = 3 36 | border_width_top = 3 37 | border_width_right = 3 38 | border_width_bottom = 3 39 | border_color = Color( 1, 1, 1, 1 ) 40 | border_blend = true 41 | 42 | [sub_resource type="StyleBoxFlat" id=5] 43 | draw_center = false 44 | border_width_left = 3 45 | border_width_top = 3 46 | border_width_right = 3 47 | border_width_bottom = 3 48 | border_color = Color( 0.219608, 0.937255, 0.0431373, 1 ) 49 | border_blend = true 50 | 51 | [sub_resource type="Theme" id=6] 52 | GraphNode/styles/cancelled = SubResource( 1 ) 53 | GraphNode/styles/failed = SubResource( 2 ) 54 | GraphNode/styles/running = SubResource( 3 ) 55 | GraphNode/styles/status_changed = SubResource( 4 ) 56 | GraphNode/styles/succeeded = SubResource( 5 ) 57 | 58 | [node name="NBTGraphNode" type="GraphNode"] 59 | margin_right = 192.0 60 | margin_bottom = 99.0 61 | size_flags_horizontal = 5 62 | size_flags_vertical = 5 63 | theme = SubResource( 6 ) 64 | custom_constants/separation = 5 65 | title = "sdsd" 66 | slot/0/left_enabled = true 67 | slot/0/left_type = 0 68 | slot/0/left_color = Color( 0.4, 0.2, 0.6, 1 ) 69 | slot/0/right_enabled = true 70 | slot/0/right_type = 0 71 | slot/0/right_color = Color( 0.25, 0.41, 0.88, 1 ) 72 | slot/1/left_enabled = false 73 | slot/1/left_type = 0 74 | slot/1/left_color = Color( 1, 1, 1, 1 ) 75 | slot/1/right_enabled = true 76 | slot/1/right_type = 0 77 | slot/1/right_color = Color( 0.85, 0.65, 0.13, 1 ) 78 | slot/2/left_enabled = false 79 | slot/2/left_type = 0 80 | slot/2/left_color = Color( 1, 1, 1, 1 ) 81 | slot/2/right_enabled = false 82 | slot/2/right_type = 0 83 | slot/2/right_color = Color( 1, 1, 1, 1 ) 84 | slot/3/left_enabled = false 85 | slot/3/left_type = 0 86 | slot/3/left_color = Color( 1, 1, 1, 1 ) 87 | slot/3/right_enabled = false 88 | slot/3/right_type = 0 89 | slot/3/right_color = Color( 1, 1, 1, 1 ) 90 | script = ExtResource( 1 ) 91 | __meta__ = { 92 | "_edit_use_anchors_": false 93 | } 94 | 95 | [node name="HBoxContainer" type="HBoxContainer" parent="."] 96 | margin_left = 16.0 97 | margin_top = 24.0 98 | margin_right = 176.0 99 | margin_bottom = 44.0 100 | 101 | [node name="Label" type="Label" parent="HBoxContainer"] 102 | margin_top = 3.0 103 | margin_right = 78.0 104 | margin_bottom = 17.0 105 | size_flags_horizontal = 3 106 | text = "Parent" 107 | valign = 1 108 | 109 | [node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"] 110 | margin_left = 82.0 111 | margin_right = 160.0 112 | margin_bottom = 20.0 113 | size_flags_horizontal = 3 114 | 115 | [node name="Label2" type="Label" parent="HBoxContainer/HBoxContainer"] 116 | margin_top = 3.0 117 | margin_right = 57.0 118 | margin_bottom = 17.0 119 | size_flags_horizontal = 3 120 | text = "Children" 121 | align = 2 122 | valign = 1 123 | 124 | [node name="HideChildrenButton" type="Button" parent="HBoxContainer/HBoxContainer"] 125 | margin_left = 61.0 126 | margin_right = 78.0 127 | margin_bottom = 20.0 128 | text = "-" 129 | flat = true 130 | 131 | [node name="Label3" type="Label" parent="."] 132 | margin_left = 16.0 133 | margin_top = 49.0 134 | margin_right = 176.0 135 | margin_bottom = 63.0 136 | text = "Guard" 137 | align = 2 138 | valign = 1 139 | 140 | [node name="ScriptButton" type="Button" parent="."] 141 | margin_left = 16.0 142 | margin_top = 68.0 143 | margin_right = 176.0 144 | margin_bottom = 88.0 145 | text = "Click to open the Script" 146 | 147 | [node name="ParameterContainer" type="VBoxContainer" parent="."] 148 | margin_left = 16.0 149 | margin_top = 93.0 150 | margin_right = 176.0 151 | margin_bottom = 93.0 152 | custom_constants/separation = 0 153 | script = ExtResource( 2 ) 154 | 155 | [connection signal="pressed" from="HBoxContainer/HBoxContainer/HideChildrenButton" to="." method="_on_HideChildrenButton_pressed"] 156 | [connection signal="pressed" from="ScriptButton" to="." method="_on_ScriptButton_pressed"] 157 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/ParameterContainer.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends VBoxContainer 3 | 4 | 5 | var data:Dictionary 6 | 7 | var ParameterView := preload('./ParameterView.tscn') 8 | 9 | var translate_map := {} 10 | #var translate_map := { 11 | # 'policy': { 12 | # BTCompositeParallel.Policy.SELECTOR: 'Selector', 13 | # BTCompositeParallel.Policy.SEQUENCE: 'Sequence', 14 | # }, 15 | # 'orchestrator': { 16 | # BTCompositeParallel.Orchestrator.Join: 'Join', 17 | # BTCompositeParallel.Orchestrator.Resume: 'Resume', 18 | # }, 19 | #} 20 | 21 | #----- Methods ----- 22 | func property_hint_string_to_dict(s:String): 23 | var ss = s.split(',') 24 | var res = {} 25 | for p in ss: 26 | var ps = p.split(':') 27 | if ps.size() == 2: 28 | res[ps[1]] = ps[0] 29 | return res 30 | 31 | func set_data(_data, node_script_path): 32 | data = _data 33 | 34 | if not node_script_path.empty(): 35 | var node_script = load(node_script_path) as Script 36 | var ps = node_script.get_script_property_list() 37 | for p in ps: 38 | if p.usage == (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE): 39 | if p.hint == PROPERTY_HINT_ENUM: 40 | translate_map[p.name] = property_hint_string_to_dict(p.hint_string) 41 | 42 | update_content() 43 | 44 | func update_data(_data:Dictionary): 45 | for k in _data.keys(): 46 | data[k] = _data[k] 47 | update_content() 48 | 49 | func translated_data_value(data, k): 50 | var data_k = str(data[k]) 51 | if translate_map.has(k): 52 | if translate_map[k].has(data_k): 53 | return translate_map[k][data_k] 54 | return data[k] 55 | 56 | func update_content(): 57 | var children = get_children() 58 | for c in children: 59 | remove_child(c) 60 | 61 | var new_children = [] 62 | 63 | for k in data.keys(): 64 | var use_old_child = false 65 | for c in children: 66 | if c.get_key().text == k: 67 | new_children.append(c) 68 | children.erase(c) 69 | c.get_value().text = str(translated_data_value(data, k)) 70 | use_old_child = true 71 | break 72 | if not use_old_child: 73 | var c = ParameterView.instance() 74 | c.get_key().text = k 75 | c.get_value().text = str(translated_data_value(data, k)) 76 | new_children.append(c) 77 | 78 | for c in children: 79 | c.free() 80 | 81 | for c in new_children: 82 | add_child(c) 83 | 84 | 85 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/ParameterView.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends PanelContainer 3 | 4 | #----- Methods ----- 5 | func get_key(): 6 | return get_node('HBoxContainer/Label') 7 | 8 | func get_value(): 9 | return get_node('HBoxContainer/Label2') 10 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/ParameterView.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/remote_debug/remote_debug_view/ParameterView.gd" type="Script" id=1] 4 | 5 | [sub_resource type="StyleBoxFlat" id=1] 6 | content_margin_left = 3.0 7 | content_margin_right = 3.0 8 | content_margin_top = 3.0 9 | content_margin_bottom = 3.0 10 | bg_color = Color( 0.12549, 0.141176, 0.192157, 1 ) 11 | 12 | [node name="ParameterView" type="PanelContainer"] 13 | anchor_left = 0.5 14 | anchor_top = 0.5 15 | anchor_right = 0.5 16 | anchor_bottom = 0.5 17 | margin_left = -80.5 18 | margin_top = -12.5 19 | margin_right = 80.5 20 | margin_bottom = 12.5 21 | script = ExtResource( 1 ) 22 | __meta__ = { 23 | "_edit_use_anchors_": false 24 | } 25 | 26 | [node name="HBoxContainer" type="HBoxContainer" parent="."] 27 | margin_left = 7.0 28 | margin_top = 7.0 29 | margin_right = 164.0 30 | margin_bottom = 27.0 31 | 32 | [node name="Label" type="Label" parent="HBoxContainer"] 33 | margin_top = 3.0 34 | margin_right = 106.0 35 | margin_bottom = 17.0 36 | size_flags_horizontal = 3 37 | text = "parameter name" 38 | valign = 1 39 | 40 | [node name="Label2" type="Label" parent="HBoxContainer"] 41 | margin_left = 110.0 42 | margin_right = 157.0 43 | margin_bottom = 20.0 44 | size_flags_horizontal = 3 45 | custom_styles/normal = SubResource( 1 ) 46 | text = "values" 47 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/ProgressModelView.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends WindowDialog 3 | 4 | 5 | onready var title := $VBoxContainer/Label 6 | onready var progress_bar := $VBoxContainer/ProgressBar 7 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/ProgressModelView.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/remote_debug/remote_debug_view/ProgressModelView.gd" type="Script" id=1] 4 | 5 | [node name="ProgressModelView" type="WindowDialog"] 6 | anchor_left = 0.5 7 | anchor_top = 0.5 8 | anchor_right = 0.5 9 | anchor_bottom = 0.5 10 | margin_left = -178.0 11 | margin_top = -47.0 12 | margin_right = 178.0 13 | margin_bottom = 47.0 14 | popup_exclusive = true 15 | window_title = "Processing..." 16 | script = ExtResource( 1 ) 17 | __meta__ = { 18 | "_edit_use_anchors_": false 19 | } 20 | 21 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 22 | margin_left = 7.0 23 | margin_top = 7.0 24 | margin_right = 349.0 25 | margin_bottom = 87.0 26 | custom_constants/separation = 25 27 | 28 | [node name="Label" type="Label" parent="VBoxContainer"] 29 | margin_right = 342.0 30 | margin_bottom = 14.0 31 | text = "title" 32 | 33 | [node name="ProgressBar" type="ProgressBar" parent="VBoxContainer"] 34 | margin_top = 39.0 35 | margin_right = 342.0 36 | margin_bottom = 53.0 37 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/RemoteDebugView.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Control 3 | 4 | signal request_open_script(path) 5 | signal request_screenshot 6 | 7 | const DictionaryDatabase := preload('../DictionaryDatabase.gd') 8 | const NBTGraphNode := preload('./NBTGraphNode.tscn') 9 | const NBTGraphNodeType := preload('./NBTGraphNode.gd') 10 | 11 | var db := DictionaryDatabase.new() 12 | var obj_id_node_map := {} 13 | 14 | var horizontal_margin := 100.0 15 | var vertical_margin := 15.0 16 | 17 | onready var title_label = $VBoxContainer/HBoxContainer/TitleLabel 18 | onready var graph_edit = $VBoxContainer/GraphEdit 19 | onready var popup_menu = $PopupMenu 20 | 21 | func _ready() -> void: 22 | connect('visibility_changed', self, '_on_visibility_changed') 23 | 24 | #----- Methods ----- 25 | func on_show(): 26 | update_title() 27 | update_content() 28 | pass 29 | 30 | func on_hide(): 31 | # db.data.clear() 32 | pass 33 | 34 | func update_content(_auto_hide_children := true): 35 | if db.data.empty(): 36 | graph_edit.hide() 37 | return 38 | graph_edit.show() 39 | 40 | var children := [] 41 | for c in graph_edit.get_children(): 42 | if c is NBTGraphNodeType: 43 | graph_edit.remove_child(c) 44 | children.append(c) 45 | for c in children: 46 | c.free() 47 | obj_id_node_map.clear() 48 | 49 | if db.data.root != null: 50 | create_tree(db.data.root, _auto_hide_children) 51 | 52 | 53 | yield(get_tree(), 'idle_frame') 54 | set_tree_node_visible(obj_id_node_map[db.get('root/obj_id')], true) 55 | update_connections() 56 | sort_nodes() 57 | 58 | func get_status_text(s): 59 | match s: 60 | BTNode.CANCELLED: 61 | return 'Cancelled' 62 | BTNode.RUNNING: 63 | return 'Running' 64 | BTNode.SUCCEEDED: 65 | return 'Succeeded' 66 | BTNode.FAILED: 67 | return 'Failed' 68 | BTNode.FRESH: 69 | return 'Fresh' 70 | return 'Undefined' 71 | 72 | func create_tree(node_data, _auto_hide_children := true): 73 | # add node 74 | var node = NBTGraphNode.instance() 75 | graph_edit.add_child(node) 76 | node.set_data(node_data, _auto_hide_children) 77 | node.connect('request_open_script', self, '_on_request_open_script') 78 | node.connect('request_hide_children', self, '_on_request_hide_children', [node]) 79 | node.connect('request_show_children', self, '_on_request_show_children', [node]) 80 | obj_id_node_map[node_data.obj_id] = node 81 | 82 | # add guard 83 | if node_data.guard != null: 84 | create_tree(node_data.guard, _auto_hide_children) 85 | 86 | # add children 87 | for child_data in node_data.children: 88 | create_tree(child_data, _auto_hide_children) 89 | 90 | 91 | func update_title(): 92 | if db.data.empty(): 93 | title_label.text = 'Please select a BehaviorTree node in the remote scene.' 94 | return 95 | title_label.text = '%s(%s) - %s' % [db.get('tree_name'), get_status_text(db.get('tree_status')), ('active' if db.get('enable') else 'inactive')] 96 | 97 | func on_recieve_all_data(data, _auto_hide_children := true): 98 | if data == null: 99 | db.data.clear() 100 | else: 101 | db.data = data 102 | update_title() 103 | update_content(_auto_hide_children) 104 | 105 | func on_update_data(path:String, data): 106 | db.set(path, data) 107 | update_title() 108 | 109 | func on_update_tree_node_data(remote_obj_id, node_data): 110 | if remote_obj_id == db.get('tree_obj_id'): 111 | db.set('tree_name', node_data.name) 112 | db.set('tree_status', node_data.status) 113 | update_title() 114 | return 115 | 116 | if obj_id_node_map.has(remote_obj_id): 117 | obj_id_node_map[remote_obj_id].update_data(node_data) 118 | else: 119 | printerr('obj[%s]\'s graph node does not exist, bug!' % remote_obj_id) 120 | 121 | 122 | func gen_ct_node(obj_id): 123 | return { 124 | 'obj_id': obj_id, 125 | 'tree_size': Vector2.ZERO, 126 | 'node_size': Vector2.ZERO, 127 | 'guard': null, 128 | 'children': [], 129 | } 130 | 131 | func calc_tree_size(root:NBTGraphNodeType): 132 | var root_data = calc_node_size(root) 133 | var root_size = root_data.node_size 134 | 135 | var child_size = Vector2.ZERO 136 | if not root.hide_children: 137 | for c in root.source_data.children: 138 | var child = obj_id_node_map[c.obj_id] 139 | var child_tree_data = calc_tree_size(child) 140 | var child_tree_size = child_tree_data.tree_size 141 | child_size.x = max(child_size.x, child_tree_size.x) 142 | child_size.y += child_tree_size.y + vertical_margin 143 | 144 | root_data.children.append(child_tree_data) 145 | 146 | root_size.x += child_size.x + horizontal_margin 147 | root_size.y = max(root_size.y, child_size.y) 148 | 149 | root_data.tree_size = root_size 150 | 151 | return root_data 152 | 153 | func calc_node_size(node:NBTGraphNodeType): 154 | var res_data = gen_ct_node(node.source_data.obj_id) 155 | var res = node.rect_size 156 | if node.source_data.guard != null: 157 | var guard = obj_id_node_map[node.source_data.guard.obj_id] 158 | var guard_tree_data = calc_tree_size(guard) 159 | var guard_tree_size = guard_tree_data.tree_size 160 | res.x = res.x + guard_tree_size.x + horizontal_margin 161 | res.y = max(res.y, guard_tree_size.y) 162 | res_data.guard = guard_tree_data 163 | res_data.node_size = res 164 | return res_data 165 | 166 | func place_node(ct_tree_root, origin:Vector2): 167 | var root:NBTGraphNodeType = obj_id_node_map[ct_tree_root.obj_id] 168 | root.offset.x = origin.x 169 | root.offset.y = origin.y + (ct_tree_root.tree_size.y - ct_tree_root.node_size.y) / 2.0 170 | 171 | if ct_tree_root.guard != null: 172 | var guard_origin = Vector2(root.offset.x + root.rect_size.x + horizontal_margin, root.offset.y) 173 | place_node(ct_tree_root.guard, guard_origin) 174 | 175 | origin.x += ct_tree_root.node_size.x + horizontal_margin 176 | for c in ct_tree_root.children: 177 | place_node(c, origin) 178 | origin.y += c.tree_size.y + vertical_margin 179 | 180 | func sort_nodes(): 181 | if db.get('root') == null: 182 | return 183 | var ct_tree = calc_tree_size(obj_id_node_map[db.get('root/obj_id')]) 184 | place_node(ct_tree, Vector2.ZERO) 185 | 186 | func connect_tree_node(root:NBTGraphNodeType): 187 | if root.source_data.guard != null: 188 | var guard = obj_id_node_map[root.source_data.guard.obj_id] 189 | graph_edit.connect_node(root.name, NBTGraphNodeType.GUARD_SLOT, guard.name, NBTGraphNodeType.PARENT_SLOT) 190 | connect_tree_node(guard) 191 | 192 | if not root.hide_children: 193 | for c in root.source_data.children: 194 | var child = obj_id_node_map[c.obj_id] 195 | graph_edit.connect_node(root.name, NBTGraphNodeType.CHILDREN_SLOT, child.name, NBTGraphNodeType.PARENT_SLOT) 196 | connect_tree_node(child) 197 | 198 | func update_connections(): 199 | graph_edit.clear_connections() 200 | 201 | var root = obj_id_node_map[db.get('root/obj_id')] 202 | if root == null: 203 | return 204 | 205 | connect_tree_node(root) 206 | 207 | func set_tree_node_visible(root:NBTGraphNodeType, v:bool): 208 | if root == null: 209 | return 210 | root.visible = v 211 | if root.source_data.guard != null: 212 | var guard = obj_id_node_map[root.source_data.guard.obj_id] 213 | set_tree_node_visible(guard, v) 214 | 215 | for c in root.source_data.children: 216 | var child = obj_id_node_map[c.obj_id] 217 | set_tree_node_visible(child, (not root.hide_children) && v) 218 | #----- Signals ----- 219 | func _on_visibility_changed(): 220 | if Engine.editor_hint: 221 | return 222 | if is_visible_in_tree(): 223 | on_show() 224 | else: 225 | on_hide() 226 | 227 | func _on_request_open_script(path): 228 | emit_signal('request_open_script', path) 229 | 230 | 231 | 232 | func _on_ScreenShotButton_pressed() -> void: 233 | emit_signal('request_screenshot') 234 | 235 | 236 | func _on_PopupMenu_id_pressed(id: int) -> void: 237 | match id: 238 | 0: 239 | sort_nodes() 240 | 2: 241 | emit_signal('request_screenshot') 242 | 243 | 244 | func _on_GraphEdit_popup_request(position: Vector2) -> void: 245 | popup_menu.rect_global_position = get_global_mouse_position() 246 | popup_menu.popup() 247 | 248 | func _on_request_hide_children(node:NBTGraphNodeType): 249 | var offset:Vector2 = graph_edit.scroll_offset - node.offset 250 | for c in node.source_data.children: 251 | var child = obj_id_node_map[c.obj_id] 252 | set_tree_node_visible(child, false) 253 | sort_nodes() 254 | update_connections() 255 | graph_edit.scroll_offset = offset + node.offset 256 | 257 | func _on_request_show_children(node:NBTGraphNodeType): 258 | var offset:Vector2 = graph_edit.scroll_offset - node.offset 259 | for c in node.source_data.children: 260 | var child = obj_id_node_map[c.obj_id] 261 | set_tree_node_visible(child, true) 262 | sort_nodes() 263 | update_connections() 264 | graph_edit.scroll_offset = offset + node.offset 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /addons/naive_behavior_tree/remote_debug/remote_debug_view/RemoteDebugView.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/remote_debug/remote_debug_view/RemoteDebugView.gd" type="Script" id=1] 4 | 5 | [node name="RemoteDebugView" type="Control"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | rect_min_size = Vector2( 0, 100 ) 9 | size_flags_horizontal = 3 10 | script = ExtResource( 1 ) 11 | __meta__ = { 12 | "_edit_use_anchors_": false 13 | } 14 | 15 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 16 | anchor_right = 1.0 17 | anchor_bottom = 1.0 18 | __meta__ = { 19 | "_edit_use_anchors_": false 20 | } 21 | 22 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] 23 | margin_right = 1024.0 24 | margin_bottom = 14.0 25 | 26 | [node name="TitleLabel" type="Label" parent="VBoxContainer/HBoxContainer"] 27 | margin_right = 75.0 28 | margin_bottom = 14.0 29 | text = "sdsdsdsdsd" 30 | valign = 1 31 | 32 | [node name="ScreenShotButton" type="Button" parent="VBoxContainer/HBoxContainer"] 33 | visible = false 34 | margin_left = 79.0 35 | margin_right = 195.0 36 | margin_bottom = 20.0 37 | text = "Take Screenshot" 38 | 39 | [node name="GraphEdit" type="GraphEdit" parent="VBoxContainer"] 40 | margin_top = 18.0 41 | margin_right = 1024.0 42 | margin_bottom = 600.0 43 | size_flags_vertical = 3 44 | scroll_offset = Vector2( 0, -24 ) 45 | minimap_enabled = false 46 | 47 | [node name="PopupMenu" type="PopupMenu" parent="."] 48 | anchor_left = 0.5 49 | anchor_top = 0.5 50 | anchor_right = 0.5 51 | anchor_bottom = 0.5 52 | margin_left = -10.0 53 | margin_top = -10.0 54 | margin_right = 10.0 55 | margin_bottom = 10.0 56 | items = [ "Sort Nodes", null, 0, false, false, 0, 0, null, "", false, "", null, 0, false, false, 1, 0, null, "", true, "Take Screenshot", null, 0, false, false, 2, 0, null, "", false ] 57 | __meta__ = { 58 | "_edit_use_anchors_": false 59 | } 60 | 61 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/ScreenShotButton" to="." method="_on_ScreenShotButton_pressed"] 62 | [connection signal="popup_request" from="VBoxContainer/GraphEdit" to="." method="_on_GraphEdit_popup_request"] 63 | [connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"] 64 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /font/NotoSansSC-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/font/NotoSansSC-Regular.otf -------------------------------------------------------------------------------- /font/font_16.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://font/NotoSansSC-Regular.otf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | font_data = ExtResource( 1 ) 7 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.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=true 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=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/images/.DS_Store -------------------------------------------------------------------------------- /images/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/images/.gdignore -------------------------------------------------------------------------------- /images/asset_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/images/asset_icon.png -------------------------------------------------------------------------------- /images/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/images/d1.png -------------------------------------------------------------------------------- /images/d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/images/d2.png -------------------------------------------------------------------------------- /images/d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/images/d3.png -------------------------------------------------------------------------------- /new_resource.bts: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /new_script.bts: -------------------------------------------------------------------------------- 1 | 2 | 3 | tree 4 | sequence 5 | timer -------------------------------------------------------------------------------- /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 | _global_script_classes=[ { 12 | "base": "BTNode", 13 | "class": "BTAction", 14 | "language": "GDScript", 15 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTAction.gd" 16 | }, { 17 | "base": "BTAction", 18 | "class": "BTActionFail", 19 | "language": "GDScript", 20 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionFail.gd" 21 | }, { 22 | "base": "BTAction", 23 | "class": "BTActionRandomTimer", 24 | "language": "GDScript", 25 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionRandomTimer.gd" 26 | }, { 27 | "base": "BTAction", 28 | "class": "BTActionSuccess", 29 | "language": "GDScript", 30 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionSuccess.gd" 31 | }, { 32 | "base": "BTAction", 33 | "class": "BTActionTimer", 34 | "language": "GDScript", 35 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionTimer.gd" 36 | }, { 37 | "base": "BTNode", 38 | "class": "BTComposite", 39 | "language": "GDScript", 40 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTComposite.gd" 41 | }, { 42 | "base": "BTComposite", 43 | "class": "BTCompositeDynamicGuardSelector", 44 | "language": "GDScript", 45 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeDynamicGuardSelector.gd" 46 | }, { 47 | "base": "BTComposite", 48 | "class": "BTCompositeParallel", 49 | "language": "GDScript", 50 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeParallel.gd" 51 | }, { 52 | "base": "BTCompositeSelector", 53 | "class": "BTCompositeRandomSelector", 54 | "language": "GDScript", 55 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeRandomSelector.gd" 56 | }, { 57 | "base": "BTCompositeSequence", 58 | "class": "BTCompositeRandomSequence", 59 | "language": "GDScript", 60 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeRandomSequence.gd" 61 | }, { 62 | "base": "BTCompositeSingle", 63 | "class": "BTCompositeSelector", 64 | "language": "GDScript", 65 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSelector.gd" 66 | }, { 67 | "base": "BTCompositeSingle", 68 | "class": "BTCompositeSequence", 69 | "language": "GDScript", 70 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" 71 | }, { 72 | "base": "BTComposite", 73 | "class": "BTCompositeSingle", 74 | "language": "GDScript", 75 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSingle.gd" 76 | }, { 77 | "base": "BTNode", 78 | "class": "BTDecorator", 79 | "language": "GDScript", 80 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecorator.gd" 81 | }, { 82 | "base": "BTDecorator", 83 | "class": "BTDecoratorAlwaysFail", 84 | "language": "GDScript", 85 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorAlwaysFail.gd" 86 | }, { 87 | "base": "BTDecorator", 88 | "class": "BTDecoratorAlwaysSucceed", 89 | "language": "GDScript", 90 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorAlwaysSucceed.gd" 91 | }, { 92 | "base": "BTDecorator", 93 | "class": "BTDecoratorInvert", 94 | "language": "GDScript", 95 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorInvert.gd" 96 | }, { 97 | "base": "BTDecorator", 98 | "class": "BTDecoratorRandom", 99 | "language": "GDScript", 100 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorRandom.gd" 101 | }, { 102 | "base": "BTLoopDecorator", 103 | "class": "BTDecoratorRepeat", 104 | "language": "GDScript", 105 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorRepeat.gd" 106 | }, { 107 | "base": "BTLoopDecorator", 108 | "class": "BTDecoratorUntilFail", 109 | "language": "GDScript", 110 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorUntilFail.gd" 111 | }, { 112 | "base": "BTLoopDecorator", 113 | "class": "BTDecoratorUntilSuccess", 114 | "language": "GDScript", 115 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTDecoratorUntilSuccess.gd" 116 | }, { 117 | "base": "BTDecorator", 118 | "class": "BTLoopDecorator", 119 | "language": "GDScript", 120 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/decorators/BTLoopDecorator.gd" 121 | }, { 122 | "base": "Node", 123 | "class": "BTNode", 124 | "language": "GDScript", 125 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/BTNode.gd" 126 | }, { 127 | "base": "BTNode", 128 | "class": "BehaviorTree", 129 | "language": "GDScript", 130 | "path": "res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" 131 | }, { 132 | "base": "Resource", 133 | "class": "BehaviorTreeScriptResource", 134 | "language": "GDScript", 135 | "path": "res://addons/naive_behavior_tree/import_plugin/BehaviorTreeScriptResource.gd" 136 | }, { 137 | "base": "ResourceFormatLoader", 138 | "class": "BehaviorTreeScriptResourceLoader", 139 | "language": "GDScript", 140 | "path": "res://addons/naive_behavior_tree/import_plugin/BehaviorTreeScriptResourceLoader.gd" 141 | }, { 142 | "base": "ResourceFormatSaver", 143 | "class": "BehaviorTreeScriptResourceSaver", 144 | "language": "GDScript", 145 | "path": "res://addons/naive_behavior_tree/import_plugin/BehaviorTreeScriptResourceSaver.gd" 146 | }, { 147 | "base": "Reference", 148 | "class": "RaiixTester", 149 | "language": "GDScript", 150 | "path": "res://test/RaiixTester.gd" 151 | } ] 152 | _global_script_class_icons={ 153 | "BTAction": "", 154 | "BTActionFail": "", 155 | "BTActionRandomTimer": "", 156 | "BTActionSuccess": "", 157 | "BTActionTimer": "", 158 | "BTComposite": "", 159 | "BTCompositeDynamicGuardSelector": "", 160 | "BTCompositeParallel": "", 161 | "BTCompositeRandomSelector": "", 162 | "BTCompositeRandomSequence": "", 163 | "BTCompositeSelector": "", 164 | "BTCompositeSequence": "", 165 | "BTCompositeSingle": "", 166 | "BTDecorator": "", 167 | "BTDecoratorAlwaysFail": "", 168 | "BTDecoratorAlwaysSucceed": "", 169 | "BTDecoratorInvert": "", 170 | "BTDecoratorRandom": "", 171 | "BTDecoratorRepeat": "", 172 | "BTDecoratorUntilFail": "", 173 | "BTDecoratorUntilSuccess": "", 174 | "BTLoopDecorator": "", 175 | "BTNode": "", 176 | "BehaviorTree": "res://addons/naive_behavior_tree/icon.svg", 177 | "BehaviorTreeScriptResource": "res://addons/naive_behavior_tree/icon.svg", 178 | "BehaviorTreeScriptResourceLoader": "", 179 | "BehaviorTreeScriptResourceSaver": "", 180 | "RaiixTester": "" 181 | } 182 | 183 | [application] 184 | 185 | config/name="Behavior Tree Syntax Compiler" 186 | run/main_scene="res://TestScene.tscn" 187 | config/icon="res://icon.png" 188 | 189 | [debug] 190 | 191 | gdscript/warnings/unused_argument=false 192 | gdscript/warnings/return_value_discarded=false 193 | 194 | [display] 195 | 196 | window/dpi/allow_hidpi=true 197 | 198 | [nbt_plugin] 199 | 200 | remote_debug/debug_mode=false 201 | remote_debug/server_address="localhost" 202 | remote_debug/server_port=45537 203 | remote_debug/auto_enable_when_plugin_enabled=true 204 | 205 | [physics] 206 | 207 | common/enable_pause_aware_picking=true 208 | 209 | [rendering] 210 | 211 | environment/default_environment="res://default_env.tres" 212 | -------------------------------------------------------------------------------- /test/RaiixTester.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Reference 3 | class_name RaiixTester 4 | 5 | func get_name(): 6 | return 'Unkown Test' 7 | 8 | # call methods that start with '_test_' 9 | func run_tests(): 10 | print(">=====| Runing Tests of %s |=====<" % (get_name())) 11 | var methods = [] 12 | for m in get_method_list(): 13 | if m.name.find("_test_") != -1: 14 | methods.append(m.name) 15 | print("Found total test num: " + str(methods.size())) 16 | var cnt = 0 17 | for m in methods: 18 | print("> Testing " + m + "[%d/%d]" % [cnt+1, methods.size()]) 19 | call(m) 20 | cnt += 1 21 | print("Done!") 22 | 23 | func _run() -> void: 24 | run_tests() 25 | -------------------------------------------------------------------------------- /test/parser_test.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends RaiixTester 3 | 4 | var Parser = preload('res://addons/naive_behavior_tree/compiler/Parser.gd') 5 | var EXPAST = preload('res://addons/naive_behavior_tree/compiler/AST/EXPAST.gd') 6 | 7 | var source:String = """ 8 | import bark: "res://dog/bt/bark.gd" # 狗叫 9 | 10 | # ss 11 | # sdsd 12 | import run: "res://dog/bt/run.gd" 13 | import dead?: "res://dog/bt/dead_condition.gd" 14 | 15 | subtree name: bark_or_run # asd 16 | 17 | 18 | # e 19 | # e 20 | random_selector posibility: 0.5 # 随机选择一个行为 21 | 22 | bark # 叫 23 | run 24 | 25 | subtree name: asda 26 | asdas sad: 0.3 27 | saesad 28 | sadasd 29 | asd 30 | asd 31 | 32 | tree #sds+ 33 | 34 | 35 | # sd 36 | (dead?) $bark_or_run # 如果没死的话,就叫或者跑 37 | $中文 arg1: sin( true ) # ee 38 | 39 | """ 40 | 41 | var source_2:String = """ 42 | # 43 | # 小狗的行为树 44 | # 45 | 46 | import 叫:"res://dog/bt/BarkTask.gd" 47 | import 摇摆:"res://dog/bt/CareTask.gd" 48 | import 标记:"res://dog/bt/MarkTask.gd" 49 | import 走:"res://dog/bt/WalkTask.gd" 50 | 51 | subtree name: 摇摆树 52 | parallel # 并行 53 | 摇摆 次数: 3 # 摇摆3次 54 | alwaysFail # 总是失败 55 | 'res://dog/bt/RestTask' # 休息 56 | 57 | tree 58 | selector # 选择 59 | $摇摆树 # 引用子树 60 | sequence # 顺序 61 | 叫 次数: rand_rangei(1, 1) 62 | 走 63 | "res://dog/bt/BarkTask" # 直接使用字符串也行 64 | 标记 65 | 66 | 67 | 68 | """ 69 | 70 | func get_name(): 71 | return 'Parser Tests' 72 | 73 | #----- Tests ----- 74 | func test_show_tokens(): 75 | var p = Parser.new() 76 | var t = p.Tokenizer.new() 77 | t.init(source) 78 | var token = t.get_next() 79 | while token.type != p.Tokenizer.Token.EOF: 80 | print(token) 81 | token = t.get_next() 82 | 83 | 84 | func _test_should_be_right(): 85 | var p = Parser.new() 86 | var t = p.Tokenizer.new() 87 | t.init(source_2) 88 | p.init(t) 89 | var ast = p.parse() 90 | 91 | print('#----- Import Part -----') 92 | for i in ast.import_part.import_statement_list: 93 | print('import %s: %s' % [str(i.id), str(i.path)]) 94 | print('#----- Tree Part -----') 95 | for subtree in ast.tree_part.subtree_list: 96 | print('subtree name: %s' % str(subtree.name)) 97 | print(tree_to_string(subtree)) 98 | print('\n') 99 | print('tree') 100 | print(tree_to_string(ast.tree_part.tree)) 101 | 102 | 103 | #----- Methods ----- 104 | func prettify_bt(s:String): 105 | var p = Parser.new() 106 | var t = p.Tokenizer.new() 107 | t.init(s) 108 | p.init(t) 109 | var ast = p.parse() 110 | if p.has_error: 111 | return p.fist_error 112 | 113 | var res = '' 114 | res += ('#----- Import Part -----') 115 | for i in ast.import_part.import_statement_list: 116 | res += '\n' + ('import %s: %s' % [str(i.id), str(i.path)]) 117 | res += '\n' + ('#----- Tree Part -----') 118 | for subtree in ast.tree_part.subtree_list: 119 | res += '\n' + ('subtree name: %s' % str(subtree.name)) 120 | res += '\n' + (tree_to_string(subtree)) 121 | res += '\n' 122 | res += '\n' + ('tree') 123 | res += '\n' + (tree_to_string(ast.tree_part.tree)) 124 | res += '\n' 125 | return res 126 | 127 | func tree_to_string(tree): 128 | var res = '' 129 | for tree_node in tree.tree_node_list: 130 | res += tree_node_to_string(tree_node) + '\n' 131 | return res 132 | 133 | func tree_node_to_string(tree): 134 | var indent = '' 135 | for _i in tree.indent.value: 136 | indent += '\t' 137 | 138 | var guard_part = '' 139 | var cnt = 0 140 | for t in tree.guard_list: 141 | guard_part += task_to_string(t) 142 | if cnt < tree.guard_list.size() - 1: 143 | guard_part += ', ' 144 | 145 | cnt += 1 146 | 147 | var task = task_to_string(tree.task) 148 | 149 | if guard_part.empty(): 150 | return '%s%s' % [indent, task] 151 | else: 152 | return '%s(%s) %s' % [indent, guard_part, task] 153 | 154 | func task_to_string(task): 155 | var name = ('$' if task.name.is_subtree_ref else '') + str(task.name.name) 156 | 157 | var parameter_part = '' 158 | var cnt = 0 159 | for p in task.parameter_list: 160 | var exp_str = ('%s' if p.exp_node is EXPAST.FuncNode else '(%s)') % exp_to_string(p.exp_node) 161 | parameter_part += '%s: %s' % [p.id.value, exp_str] 162 | if cnt < task.parameter_list.size() - 1: 163 | parameter_part += ' ' 164 | cnt += 1 165 | 166 | if parameter_part.empty(): 167 | return name 168 | else: 169 | return '%s %s' % [name, parameter_part] 170 | 171 | func exp_to_string(exp_node): 172 | if exp_node is EXPAST.LeafNode: 173 | return str(exp_node.token.value) 174 | elif exp_node is EXPAST.OperatorNode: 175 | var children = exp_node.get('children') 176 | return '(%s %s %s)' % [exp_to_string(children[0]), exp_node.op.value, exp_to_string(children[1])] 177 | elif exp_node is EXPAST.FuncNode: 178 | var arg_part = '' 179 | var cnt = 0 180 | for arg in exp_node.children: 181 | arg_part += exp_to_string(arg) 182 | if cnt < exp_node.children.size() - 1: 183 | arg_part += ', ' 184 | cnt += 1 185 | return '%s(%s)' % [exp_node.id.value, arg_part] 186 | 187 | -------------------------------------------------------------------------------- /test/tokenizer_test.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends RaiixTester 3 | 4 | var Tokenizer = preload('res://addons/naive_behavior_tree/compiler/Tokenizer.gd') 5 | 6 | func get_name(): 7 | return 'Tokenizer Tests' 8 | 9 | #----- Methods ----- 10 | func print_next(t, blank=false, eof=false): 11 | var token = t.get_next() 12 | while token.type == Tokenizer.Token.BLANK: 13 | if blank: 14 | print(token) 15 | token = t.get_next() 16 | if token.type != Tokenizer.Token.ERROR or eof: 17 | print(token) 18 | #----- Tests ----- 19 | func test_should_be_right(): 20 | var t = Tokenizer.new() 21 | var source = 'ID123 \'Sing\\\\al String\' "Double String" "th3e\\"3" "\\"t12" 中文 213 2.32 .32 "中文" \n \n\n trueID123 #注释\n\t123 true false :: 123: 666' 22 | t.init(source) 23 | while not t.preview_next().type in [Tokenizer.Token.EOF, Tokenizer.Token.ERROR]: 24 | print_next(t) 25 | 26 | 27 | func test_should_be_right2(): 28 | var t = Tokenizer.new() 29 | var source = 'true)' 30 | t.init(source) 31 | while not t.preview_next().type in [Tokenizer.Token.EOF, Tokenizer.Token.ERROR]: 32 | print_next(t) 33 | 34 | func test_wrong_number(): 35 | var t = Tokenizer.new() 36 | var source = '123. .' 37 | t.init(source) 38 | print_next(t) 39 | print_next(t) 40 | 41 | func test_wrong_String1(): 42 | var t = Tokenizer.new() 43 | var source = '"' 44 | t.init(source) 45 | print_next(t) 46 | print_next(t) 47 | 48 | func test_wrong_String2(): 49 | var t = Tokenizer.new() 50 | var source = '123"' 51 | t.init(source) 52 | print_next(t) 53 | print_next(t) 54 | 55 | func test_wrong_String3(): 56 | var t = Tokenizer.new() 57 | var source = "'\\'" 58 | t.init(source) 59 | print_next(t) 60 | print_next(t) 61 | 62 | func test_wrong_String4(): 63 | var t = Tokenizer.new() 64 | var source = '"\\a"' 65 | t.init(source) 66 | print_next(t) 67 | print_next(t) 68 | 69 | func _test_header_comment1(): 70 | var t = Tokenizer.new() 71 | var source = '#\n# 666\n#\ntree timer wait: 0.3' 72 | t.init(source) 73 | while not t.preview_next().type in [Tokenizer.Token.EOF, Tokenizer.Token.ERROR]: 74 | print_next(t, true, true) 75 | 76 | func _test_header_comment2(): 77 | var t = Tokenizer.new() 78 | var source = '# sadsds\n# 666\n#\ntree timer wait: 0.3' 79 | t.init(source) 80 | while not t.preview_next().type in [Tokenizer.Token.EOF, Tokenizer.Token.ERROR]: 81 | print_next(t, true, true) 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /test_scenes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rayxuln/Behavior-Tree-Syntax-Compiler/5e07a025a27fbbd5eda4a22641d06493b144677a/test_scenes/.DS_Store -------------------------------------------------------------------------------- /test_scenes/UISignalTest/UISignalTest.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://test_scenes/UISignalTest/the_tree.tscn" type="PackedScene" id=1] 4 | 5 | [node name="Control" type="Control"] 6 | anchor_right = 1.0 7 | anchor_bottom = 1.0 8 | __meta__ = { 9 | "_edit_use_anchors_": false 10 | } 11 | 12 | [node name="Button" type="Button" parent="."] 13 | anchor_left = 0.5 14 | anchor_top = 0.5 15 | anchor_right = 0.5 16 | anchor_bottom = 0.5 17 | margin_left = -33.0 18 | margin_top = -10.0 19 | margin_right = 33.0 20 | margin_bottom = 10.0 21 | text = "Click Me" 22 | __meta__ = { 23 | "_edit_use_anchors_": false 24 | } 25 | 26 | [node name="Label" type="Label" parent="."] 27 | anchor_left = 0.5 28 | anchor_top = 0.5 29 | anchor_right = 0.5 30 | anchor_bottom = 0.5 31 | margin_left = -322.0 32 | margin_top = -37.9169 33 | margin_right = 322.0 34 | margin_bottom = -23.9169 35 | text = "something is on." 36 | align = 1 37 | valign = 1 38 | 39 | [node name="the_tree" parent="." instance=ExtResource( 1 )] 40 | agent_path = NodePath("..") 41 | -------------------------------------------------------------------------------- /test_scenes/UISignalTest/bt/set_text.gd: -------------------------------------------------------------------------------- 1 | extends BTAction 2 | 3 | export(String) var msg:String = '' 4 | export(String) var n:String = '' 5 | 6 | 7 | #----- Methods ----- 8 | func execute(): 9 | tree.agent.get_node(n).text = msg 10 | return SUCCEEDED 11 | -------------------------------------------------------------------------------- /test_scenes/UISignalTest/bt/wait_signal.gd: -------------------------------------------------------------------------------- 1 | extends BTAction 2 | 3 | export(String) var obj:String = '' 4 | export(String) var sig:String = '' 5 | 6 | 7 | #----- Methods ----- 8 | func execute(): 9 | yield(tree.agent.get_node(obj), sig) 10 | return SUCCEEDED 11 | -------------------------------------------------------------------------------- /test_scenes/UISignalTest/the_tree.bts: -------------------------------------------------------------------------------- 1 | 2 | import show: 'bt/set_text.gd' 3 | import yield: 'bt/wait_signal.gd' 4 | 5 | 6 | 7 | tree 8 | sequence 9 | show msg: 'click the button!' n: 'Label' 10 | yield obj: 'Button' sig: 'pressed' 11 | show msg: 'click again!' n: 'Label' 12 | yield obj: 'Button' sig: 'pressed' 13 | show msg: 'Thanks!' n: 'Label' 14 | yield obj: 'Button' sig: 'pressed' 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test_scenes/UISignalTest/the_tree.bts.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="raiix.bts.importer" 4 | type="Resource" 5 | path="res://.import/the_tree.bts-08069db595361e2867554abe9adfd834.res" 6 | 7 | [deps] 8 | 9 | source_file="res://test_scenes/UISignalTest/the_tree.bts" 10 | dest_files=[ "res://.import/the_tree.bts-08069db595361e2867554abe9adfd834.res" ] 11 | 12 | [params] 13 | 14 | -------------------------------------------------------------------------------- /test_scenes/UISignalTest/the_tree.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" type="Script" id=2] 5 | [ext_resource path="res://test_scenes/UISignalTest/bt/wait_signal.gd" type="Script" id=3] 6 | [ext_resource path="res://test_scenes/UISignalTest/bt/set_text.gd" type="Script" id=4] 7 | 8 | [node name="the_tree" type="Node"] 9 | script = ExtResource( 1 ) 10 | root_path = NodePath("sequence") 11 | 12 | [node name="sequence" type="Node" parent="."] 13 | script = ExtResource( 2 ) 14 | 15 | [node name="show" type="Node" parent="sequence"] 16 | script = ExtResource( 4 ) 17 | msg = "click the button!" 18 | n = "Label" 19 | 20 | [node name="yield" type="Node" parent="sequence"] 21 | script = ExtResource( 3 ) 22 | obj = "Button" 23 | sig = "pressed" 24 | 25 | [node name="show2" type="Node" parent="sequence"] 26 | script = ExtResource( 4 ) 27 | msg = "click again!" 28 | n = "Label" 29 | 30 | [node name="yield2" type="Node" parent="sequence"] 31 | script = ExtResource( 3 ) 32 | obj = "Button" 33 | sig = "pressed" 34 | 35 | [node name="show3" type="Node" parent="sequence"] 36 | script = ExtResource( 4 ) 37 | msg = "Thanks!" 38 | n = "Label" 39 | 40 | [node name="yield3" type="Node" parent="sequence"] 41 | script = ExtResource( 3 ) 42 | obj = "Button" 43 | sig = "pressed" 44 | -------------------------------------------------------------------------------- /tutor_scenes/t1/t1.bts: -------------------------------------------------------------------------------- 1 | # t1 2 | 3 | import print: "res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd" 4 | 5 | tree 6 | sequence 7 | print msg: "1" 8 | timer wait: 1 9 | print msg: "2" 10 | timer wait: 1 11 | print msg: "3" 12 | timer wait: 1 13 | 14 | 15 | -------------------------------------------------------------------------------- /tutor_scenes/t1/t1.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionTimer.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/lib/test/Print.gd" type="Script" id=2] 5 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" type="Script" id=3] 6 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" type="Script" id=4] 7 | 8 | [node name="t1" type="Node"] 9 | script = ExtResource( 3 ) 10 | root_path = NodePath("sequence") 11 | 12 | [node name="sequence" type="Node" parent="."] 13 | script = ExtResource( 4 ) 14 | 15 | [node name="print" type="Node" parent="sequence"] 16 | script = ExtResource( 2 ) 17 | msg = "1" 18 | 19 | [node name="timer" type="Node" parent="sequence"] 20 | script = ExtResource( 1 ) 21 | 22 | [node name="print2" type="Node" parent="sequence"] 23 | script = ExtResource( 2 ) 24 | msg = "2" 25 | 26 | [node name="timer2" type="Node" parent="sequence"] 27 | script = ExtResource( 1 ) 28 | 29 | [node name="print3" type="Node" parent="sequence"] 30 | script = ExtResource( 2 ) 31 | msg = "3" 32 | 33 | [node name="timer3" type="Node" parent="sequence"] 34 | script = ExtResource( 1 ) 35 | -------------------------------------------------------------------------------- /tutor_scenes/t2/Test2.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | 4 | #func _ready() -> void: 5 | # $t2.connect('task_status_changed', self, '_on_task_status_changed') 6 | # 7 | # 8 | # 9 | #func _on_task_status_changed(task): 10 | # print('task: %s[%s] = %s' % [task.name, task.get_instance_id(), task.status]) 11 | -------------------------------------------------------------------------------- /tutor_scenes/t2/Test2.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://tutor_scenes/t2/t2.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://tutor_scenes/t2/Test2.gd" type="Script" id=2] 5 | 6 | [node name="Control" type="Control"] 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | script = ExtResource( 2 ) 10 | __meta__ = { 11 | "_edit_use_anchors_": false 12 | } 13 | 14 | [node name="Label" type="Label" parent="."] 15 | margin_left = 118.433 16 | margin_top = 196.548 17 | margin_right = 835.432 18 | margin_bottom = 241.548 19 | text = "eee" 20 | __meta__ = { 21 | "_edit_use_anchors_": false 22 | } 23 | 24 | [node name="t2" parent="." instance=ExtResource( 1 )] 25 | agent_path = NodePath("..") 26 | -------------------------------------------------------------------------------- /tutor_scenes/t2/show.gd: -------------------------------------------------------------------------------- 1 | extends BTAction 2 | 3 | export(String) var s:String = '' 4 | 5 | #----- Methods ----- 6 | func execute(): 7 | tree.agent.get_node('Label').text = s 8 | return SUCCEEDED 9 | -------------------------------------------------------------------------------- /tutor_scenes/t2/t2.bts: -------------------------------------------------------------------------------- 1 | 2 | import show 3 | 4 | tree 5 | sequence 6 | show s: "3" 7 | timer wait: 1 8 | show s: "2" 9 | timer wait: 1 10 | show s: "1" 11 | timer wait: 1 12 | show s: "Hello world!" 13 | timer wait: 3 14 | parallel 15 | show s: "OK" 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tutor_scenes/t2/t2.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/actions/BTActionTimer.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/BehaviorTree.gd" type="Script" id=2] 5 | [ext_resource path="res://tutor_scenes/t2/show.gd" type="Script" id=3] 6 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeSequence.gd" type="Script" id=4] 7 | [ext_resource path="res://addons/naive_behavior_tree/behavior_tree/classes/composites/BTCompositeParallel.gd" type="Script" id=5] 8 | 9 | [node name="t2" type="Node"] 10 | script = ExtResource( 2 ) 11 | root_path = NodePath("sequence") 12 | 13 | [node name="sequence" type="Node" parent="."] 14 | script = ExtResource( 4 ) 15 | 16 | [node name="show" type="Node" parent="sequence"] 17 | script = ExtResource( 3 ) 18 | s = "3" 19 | 20 | [node name="timer" type="Node" parent="sequence"] 21 | script = ExtResource( 1 ) 22 | 23 | [node name="show2" type="Node" parent="sequence"] 24 | script = ExtResource( 3 ) 25 | s = "2" 26 | 27 | [node name="timer2" type="Node" parent="sequence"] 28 | script = ExtResource( 1 ) 29 | 30 | [node name="show3" type="Node" parent="sequence"] 31 | script = ExtResource( 3 ) 32 | s = "1" 33 | 34 | [node name="timer3" type="Node" parent="sequence"] 35 | script = ExtResource( 1 ) 36 | 37 | [node name="show4" type="Node" parent="sequence"] 38 | script = ExtResource( 3 ) 39 | s = "Hello world!" 40 | 41 | [node name="timer4" type="Node" parent="sequence"] 42 | script = ExtResource( 1 ) 43 | wait = 3.0 44 | 45 | [node name="parallel" type="Node" parent="sequence"] 46 | script = ExtResource( 5 ) 47 | 48 | [node name="show" type="Node" parent="sequence/parallel"] 49 | script = ExtResource( 3 ) 50 | s = "OK" 51 | --------------------------------------------------------------------------------