# FunctionTree
**Repository Path**: LaoDie1/function-tree
## Basic Information
- **Project Name**: FunctionTree
- **Description**: Godot 的行为功能相关插件
- **Primary Language**: 其他
- **License**: MIT
- **Default Branch**: master
- **Homepage**: https://gitee.com/LaoDie1/function-tree
- **GVP Project**: No
## Statistics
- **Stars**: 4
- **Forks**: 2
- **Created**: 2021-12-15
- **Last Updated**: 2022-06-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: GDscript, Godot
## README
# Godot 功能树
> 项目地址:[Apprentice/FunctionTree (gitee.com)](https://gitee.com/LaoDie1/function-tree)
使用 FunctionTree 快速开发角色功能
## 示例教程
> *在 test 文件夹中有个 test01 示例,可进入参考查看*
### 添加基本节点
添加一个 `KinematicBody2D` 节点,选中点击添加节点,搜索 `FunctionRoot` 点击添加。添加后选中 `FunctionRoot` ,右侧将会出现一列可添加的节点列表,双击添加 ~~Blackboard~~(已更改为 `Reference` 类型,下方请忽略这个节点)、`Standard`、`Custom` 节点,如下图所示

场景根节点更名为 `Player`,创建一个 **player** 文件夹,保存场景到里面。
### 添加移动功能
选中 `Standard` 节点,添加 `Actions` 节点,然后选中 `Actions` 节点,双击添加右侧节点列表中的 `Move` 节点

选中 `Custom` 节点,添加 `Controllers` 节点,然后选中 `Controllers` 节点,双击添加 `CustomFunction` 节点

双击场景树中的 `CustomFunction` 节点,重命名为 `CMove`,意为 Control Move(控制移动),然后在编辑器最右侧检查器面板中给这个脚本扩展一下


在代码编辑视图中按下 `Ctrl + Alt + Shift + S` ,或者点击代码编辑上的**代码**菜单(这个是我自己的一个脚本插件添加上的,默认没有这个),如下图,然后弹出重写函数弹窗,勾选 `_process_input` 方法,点击 OK 按钮

生成如下代码:
```swift
extends "res://addons/function_tree/src/custom/CustomFunction.gd"
#(override)
func _process_input(arg0):
._process_input(arg0)
pass
```
重写 `_process_input` ,获取用户输入,根据玩家按下的键盘控制节点移动
```swift
extends "res://addons/function_tree/src/custom/CustomFunction.gd"
#(override)
func _process_input(arg0):
# 获取用户输入(下面四个是小键盘上的按键,按下进行控制)(这个是Godot3.4中的方法)
var dir = Input.get_vector("ui_left","ui_right","ui_up","ui_down")
# 获取 Move 功能节点,操控其移动
get_function("Move").control(dir)
```
我们将文件系统中 **icon.png** ,Godot 小图标拖拽到场景中
还差一点,给场景根节点 Player 添加一个脚本,并写入如下代码
```swift
extends KinematicBody2D
export var move_speed : int = 300
onready var root = $FunctionRoot
func _ready():
# 设置移动速度
root.get_property().move_speed = move_speed
```
让我们按下 F6 ,进行运行当前场景,按下小键盘进行移动
### 添加旋转功能
选中场景树中的 `Actions` 节点,双击添加 `TurnTo` 节点,选中 `Controllers` 节点,双击右侧 `CustomFunction` 添加节点,并重命名为 `CTurnTo`,选中这个 `CTurnTo` 节点,在编辑器最右侧检查器面板中进行扩展脚本

在代码编辑视图中按下 `Ctrl + Alt + Shift + S` ,或者点击代码编辑上的**代码**菜单,弹出窗口中勾选 `_process_input` ,点击 OK,写入控制旋转代码,让节点随着鼠标位置进行旋转,脚本代码如下
```swift
extends "res://addons/function_tree/src/custom/CustomFunction.gd"
#(override)
func _process_input(arg0):
# 获取鼠标的全局位置
var mouse_pos = (host as Node2D).get_global_mouse_position()
# 旋转到鼠标位置
get_function("TurnTo").control(mouse_pos)
```
按下 F6 运行当前场景,按小键盘让角色移动到场景中,鼠标移动一下查看是否进行了旋转
### 添加技能
选中 `Standard` 节点,添加 `Skills` 节点,然后选中 `Skills` 节点,双击添加右侧节点列表中的 `Sprint` 节点,选中 `Sprint` 节点,修改 `duration` 属性持续时间设为 `0.5` 秒

添加键位映射,添加鼠标的映射,添加 `click` ,

设置为鼠标左键点击
Godot 的 Input 类获取 `click` 事件,就代表要获取鼠标点击的事件,点击 `关闭` 按钮

选中场景树中的 `Controllers` 节点,双击右侧 `CustomFunction` 添加节点,并重命名为 `CSprint`,选中这个 `CSprint` 节点,在编辑器最右侧检查器面板中进行扩展脚本,重写 `_process_input` 方法,控制 `Sprint` 技能的使用 ,脚本代码如下
```swift
extends "res://addons/function_tree/src/custom/CustomFunction.gd"
#(override)
func _process_input(arg0):
if Input.is_action_pressed("click"):
# 获取鼠标的全局位置
var mouse_pos = (host as Node2D).get_global_mouse_position()
# 操作 Sprint 方法,冲刺到鼠标位置
get_function("Sprint").control(mouse_pos)
```
按下 F6 运行当前场景,点击鼠标,看看节点是否进行了冲刺
---
## 横版游戏角色教程
跟上面创建角色相同,创建如下节点(只要选中节点的父节点,场景树旁的面板会显示出其可选子节点),先 Ctrl + A
* FunctionRoot
* Blackboard
* Standard
* Actions
* PlatformMove(平台游戏移动)
* Gravity(重力)
* Jump(跳跃)
* Custom
* Controllers
选中 `Controllers` 节点,双击添加 `CustomFunction`,重命名为 `CMove`,并扩展脚本,脚本代码如下:
```swift
extends "res://addons/function_tree/src/custom/CustomFunction.gd"
#(override)
func _process_input(arg0):
var d = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
if d:
get_function("Move").control(Vector2(d, 0))
```
然后选中场景根节点,添加 `CollisionShape2D` 节点,再将 `icon.png` 图标拖拽到场景中,给 `CollisionShape2D` 节点添加一个 `RectangleShape2D` 碰撞形状,设置成添加的 icon 图像大小。
场景节点如下:
现在给 Player 节点添加脚本,脚本代码如下
```swift
extends KinematicBody2D
export var move_speed : int = 300
export (float, 0, 1.0) var gravity : float = 0.7
export var max_gravity : int = 2000
export var jump_height : int = 700
onready var root = $FunctionRoot
func _ready():
root.property.move_speed = move_speed
root.property.gravity = gravity
root.property.max_gravity = max_gravity
root.property.jump_height = jump_height
```
可以将角色移动到场景中间,现在按下 F6 运行当前场景,就可以看到角色一直下坠
再在上面添加一个 `StaticBody2D` ,给 `StaticBody2D` 添加一个 `CollisionShape2D`,`CollisionShape2D` 设置一个 `RectangleShape2D`,调整其大小
然后按 F6 运行当前场景,按下小键盘左右键,可以进行左右移动
现在给他添加跳跃控制,点击 `Controllers` 节点,场景树节点右侧面板双击添加一个 `CustomFunction` 节点,重命名为 `CJump`,并扩展脚本,脚本代码如下:
```swift
extends "res://addons/function_tree/src/custom/CustomFunction.gd"
#(override)
func _process_input(arg0):
if (Input.is_action_pressed("ui_up")
&& host.is_on_floor()
):
get_function("Jump").control()
```
最基本的横版游戏角色的功能就做出来了,其中还有爬墙,多级跳跃等功能,快上手试试吧 ;)
---
经过这个简单的教程之后,不知你对这个插件是否有了兴趣?现在我将介绍一下这个插件的一个制作思路。**分为 Standard 类的节点和 Custom 类的节点。将通用功能放在 Standard 类下的节点,将各种各样的逻辑和操作写在 Custom 类的节点下,在 Custom 下的节点中调用 Standard 下的节点的功能**。如此进行分类。如下流程图所示
```mermaid
graph TD
Root(根节点)-->Standard(标准节点)
Root(根节点)-->Custom(自定义节点)
Standard-->Layer1(标准节点层1)
Standard-->Layer...(标准节点层...)
Custom-->CustomLayer(自定义节点层...)
Layer1-->Function1(标准功能子节点...)
Layer1-->Function2(标准功能子节点...)
CustomLayer-->CustomFunction(自定义功能子节点...)
CustomLayer-->CustomFunction1(自定义功能子节点...)
```
## 插件介绍
### 方法和属性
`blackboard` 属性或 `get_blackboard()` 方法获取黑板,黑板中记录有整个功能树的全局数据
`property` 属性或 `get_property()` 方法获取全局属性
`enabled` 属性或 `get_enabled()` 方法获取全局功能可用性属性
注册为功能节点:`register_function(名称)`,注册后可使用 `get_function` 方法进行获取这个节点
获取功能层节点:`get_layer(注册的功能节点名)`
获取功能节点:`get_function(注册的功能节点名)`
执行功能节点的功能:`get_function(注册的功能节点名).control(传入的数据)`
### 节点
不要动态的增减节点,因为场景运行后 FunctionRoot 根节点会扫描一遍子节点并记录,子节点也会注册到数据中,增减节点会出现问题。
有两个类别节点:Standard、Custom。 Standard 下的节点是标准节点,不进行扩展脚本,只修改属性连接信号,Custom 下的节点是自定义节点,必须要进行扩展脚本才有功能。这两个类别节点下是各个功能层节点,用以对功能进行划分。
#### FunctionRoot
`FunctionRoot` 功能树根节点 `_ready()` 时会先遍历得到所有子功能节点,然后给这些节点设置基本属性,调用节点的初始化方法,执行步骤如下
* `_init_data` > `_init_node` > `_init_finished`
子节点的 `_process` 和 `_physics_process` 都是 false,不会执行,需要重写 `_process_input` 、`_process_execute`、`_process_finish` 方法进行使用,执行顺序为
* `_process_input` > `_process_execute` > `_process_finish`
- `_process_input` 先接收用户输入
- `_process_execute` 进行执行所有功能
- `_process_finish` 对执行完成后进行一些处理
#### Standard 与 Custom 下的节点的区别
Standard 类下方的子节点尽量都不要重写(如果你了解这个节点的执行逻辑的话,也可以扩展脚本,但不建议这样做)
Custom 类下方的子节点都要进行重写才能使用其功能,否则其只有逻辑,没有功能没有效果
Standard 下的节点提供功能,节点只进行属性的设置,增加功能节点;在 Custom 下的节点中进行扩展脚本调用 Standard 下的节点的功能。
#### Layer 节点
每个 Layer 都是一个功能类别的分类,Actions 是动作层,Skills 是技能层等等,每个层下面都是一个具体功能的节点。使用 `register_layer` 方法注册层节点到全局数据中
#### Function 节点
执行具体的功能逻辑,调用这个节点的 `control` 方法进行使用这个节点的功能。使用 `register_function` 方法注册层节点到全局数据中。