Godot 3.4 beta5

以下为创建一个插件基本工作流程,也许与一些人的不同,有更好的可以在评论区交流分享。

如果你只想了解一下插件制作流程的话,下面的代码不需自己手动写,直接复制粘贴就好。

创建插件

在 Godot 中创建添加插件很简单,点击编辑器左上角的 Project 菜单

在弹出的 Project Settings 窗口中,点击 Plugins 选项卡,点击 Create 按钮

弹出 Create a Plugin 窗口,在里面输入 PluginName(插件名)、Subfolder(插件的文件夹名)、Description(对插件的功能及其他的相关描述)、Author(作者)、Version(版本)、Language(使用哪种脚本语言来写插件)、Script Name(脚本的名字,必须以 .gd 结尾才能创建),Activate now(是否现在激活使用,先不勾选,因为在插件脚本里面写代码添加功能的时候,有时需要重新激活功能才能生效)。

创建一个如下的插件

点击 Create 按钮后,会自动切换到脚本视图,并有如下代码

tool
extends EditorPluginfunc _enter_tree() -> void:passfunc _exit_tree() -> void:pass

_enter_tree 方法会在激活插件的时候执行这个方法,_exit_tree() 方法会在取消激活的时候执行这个方法。

EditorPlugin 这个类继承的是 Node,是一个节点,和我们做游戏时使用的节点是一样的,激活插件的过程就是把这个插件节点添加到编辑器中,编辑器内部会对这些插件做一些其他初始化的操作,这个插件节点就会被添加到编辑中,就可以使用其中的功能了。

创建出来的文件如下:

  1. addons :全部插件文件夹。每次创建的新的插件,或者安装的插件,都会添加到这个文件夹里。
  2. test_plugin:刚刚创建的插件的文件夹
  3. plugin.gd:刚刚创建的插件的脚本

添加功能

我个人习惯是:如果是功能比较简答的脚本,那么就直接在这个插件文件夹下方创建文件;如果功能较多,那么我会添加一个 src 文件夹存放功能的源代码。

每个脚本的代码功能都是一步步按需求增加的,向这个脚本里添加一点,,而不是一下子就写好的,如果是预先设计了的除外。

比如说我们想做个行为树插件,那么我们先将行为树给设计出来。为了方便,我在这里只创建几个没有功能的脚本,官方的资源商店也有行为树的插件,如果想自己做行为树可以自己看看 行为树(一)了解与设计行为树代码 了解一下。

src 文件夹里创建了三个脚本,都是继承自 Node 节点 ,没有修改脚本。

如果想熟练使用插件,必须要了解 EditorPlugin (编辑器插件)和 EditorInterface (编辑器接口)类,在 EditorPlugin 中可通过 get_editor_interface() 方法获取 编辑器接口,来操作更多功能。

事先也要知道,刚上来做这些都会感到陌生、麻烦,但是这种东西必须多做用起来才能随心所欲,需要你自己多写写小玩意,然后经常 F1 查看帮助文档内容。比如这个插件脚本 EditorPlugin 类,看看里面有什么方法,不会了就看看,但是也要注意,不要一下子看太多,贪多嚼不烂,反而会消耗你的耐心,容易产生厌倦的负面情绪,这是学习方法之一,一定要注意。

那么我么现在给他添加到自定义节点里,双击 plugin.gd 脚本,在 _enter() 方法里添加一行代码:

add_custom_type("Root", "Node", preload("res://addons/test_plugin/src/Root.gd"), get_editor_interface().get_base_control().get_icon("Node", "EditorIcons"))

上行代码有两个方法:

  • add_custom_type
    用于添加自定义节点类型,在添加子节点的时候,会出现这个类型的节点。
    add_custom_type(添加时的名称, 继承的节点的类型,脚本类,显示的图标)

  • get_editor_interface().get_base_control().get_icon()
    这么一大行代码是用来获取节点的图标的。

    get_editor_interface().get_base_control() 用来获取 Godot 编辑器窗口的主容器。他是一个 Control 类型的节点(因为 Godot 编辑器就是用 Godot 做出来的,自然都是通过节点组成的)。可以通过它获取节点的图标,使用 get_icon() 进行获取。
    get_icon(图标类型的名称,主题类型) 上面我们获取 EditorIcons 编辑器图标,类型为 Node 类型的图标。

然后我们在 _exit_tree 方法下添加一行代码:

remove_custom_type("Root")

如果关闭这个插件,则选择添加的节点的列表中就不会出现这个节点了。

然后我们激活这个插件

要注意我为什么要先写代码再激活。是因为如果先激活再写,里面的代码有时不会生效,或者生效后,再修改后上次的脚本添加的节点就会失效,且有时里面的功能不能使用且报错。它并不会自动更新。注意是“有些情况下”,而非全部情况,但如果是拥有复杂功能的插件,还是建议取消激活,然后打开。

可以看我这个脚本没有在 _exit_tree 中添加 remove_custom_type("Root") 时激活,然后写完重新激活后出现的情况。

第一次添加的没有被移除,因为第二次激活时会重新添加,而第一次的有没有被移除,所以出现了两个,所以在添加功能的时候最好先不要激活插件。如果出现了这个情况,可以关闭这个项目重新打开。

然后我们添加时搜索 Root,就会出现我们添加的自定义节点


最后代码为

tool
extends EditorPluginfunc _enter_tree() -> void:# 添加自定义类型节点add_custom_type("Root", "Node", preload("res://addons/test_plugin/src/Root.gd"), get_editor_interface().get_base_control().get_icon("Node", "EditorIcons"))func _exit_tree() -> void:# 移除自定义类型接节点remove_custom_type("Root")

我想在选中 Root 节点的时候在底部出现一个功能栏,我可以自己从中点击节点进行添加节点,而不必每次都要点击上方的添加节点按钮。

现在我们在 test_plugin 文件夹下创建一个 util 文件夹,用于存放我们各种其他辅助功能的文件夹。

创建一个新的场景,添加 PanelContainer 类型的节点为根节点,再添加 ScrollContainer 节点,ScrollContainer 下添加 HBoxContainer 节点。如下:

设置 PanelContainerrect_min_size 属性的 y 值为 100,让这个节点显示出来最小高度为 100

util 文件夹下创建一个 add_node_panel 文件夹,保存这个场景,命名为 AddNodePanel 。给这个场景创建一个脚本,脚本名称按默认的名称 AddNodePanel.gd 即可。

下面的脚本代码可以自己复制粘贴,不需自己手动抄写(要注意,插件里的场景的脚本都要加 tool,否则不生效 )

tool
extends PanelContainer## 行为树脚本列表
var bt_script_list = {"Composite": load("res://addons/test_plugin/src/Composite.gd"),"Leaf": load("res://addons/test_plugin/src/Leaf.gd"),
}
var _plugin : EditorPluginonready var hbox = $ScrollContainer/HBoxContainer## 这个场景在哪个插件中
func set_plugin(value: EditorPlugin) -> void:_plugin = value# 如果当前这个节点还没有添加到节点上# hbox 就会为 null,等到节点发出 ready 之前# hbox 才会准备好获取到了节点if not self.is_inside_tree():yield(self, "ready")# 添加 BT 脚本对应的按钮for key in bt_script_list:# 创建一个工具按钮var b = ToolButton.new()# 设置它的一个自定义数据b.set_meta("bt_data", {script = bt_script_list[key],name = key,})b.text = keyb.connect("pressed", self, "_on_Button_pressed", [b])b.icon = _plugin.get_editor_interface().get_base_control().get_icon("Node", "EditorIcons")b.rect_min_size = Vector2(100, 40)hbox.add_child(b)func _on_Button_pressed(button: Button) -> void:# 添加到场景中var data = button.get_meta("bt_data")var bt_node = data.script.new() as Nodebt_node.name = data.name# 选中的节点var nodes = _plugin.get_editor_interface().get_selection().get_selected_nodes()if nodes.size() > 0:nodes[0].add_child(bt_node, true)bt_node.owner = nodes[0].owner if nodes[0].owner != null else nodes[0]else:print("没有选中节点")

plugin.gd 脚本中添加变量 add_node_panel

var add_node_panel = preload("res://addons/test_plugin/util/add_node_panel/AddNodePanel.tscn").instance()

_enter_tree() 方法下添加代码:

add_control_to_bottom_panel(add_node_panel, "Behavitor Tree")
add_node_panel.set_plugin(self)

_exit_tree() 下添加如下一行代码:

remove_control_from_bottom_panel(add_node_panel)

最终 plugin.gd 里的全部代码

tool
extends EditorPluginvar add_node_panel = preload("util/add_node_panel/AddNodePanel.tscn").instance()func _enter_tree() -> void:# 添加自定义类型节点add_custom_type("Root", "Node", preload("src/Root.gd"), get_editor_interface().get_base_control().get_icon("Node", "EditorIcons"))# 添加底部功能栏add_control_to_bottom_panel(add_node_panel, "Behavitor Tree")add_node_panel.set_plugin(self) # 设置 add_node_panel 所在的插件add_node_panel.show()  # 让节点显示出来,而不是默认的折叠func _exit_tree() -> void:# 移除自定义类型接节点remove_custom_type("Root")# 移除底部功能栏remove_control_from_bottom_panel(add_node_panel)

我们新创建一个场景,添加根节点为 Node2D 类型的节点,保存为 res://Test.tscn 场景。

再添加 Root 节点,下面就出现了 Behavitor Tree 功能面板了,点击上面的按钮,节点就会添加到选中的节点上。

功能的增改

但是上面的功能逻辑不是很好,选中行为树类型的节点后才显示下面的功能栏,就像选中 AnimationPlayer 类型的节点后,才在下面显示对应的内容一样。

所以我们将 plugin.gd 代码修改一下

tool
extends EditorPluginvar add_node_panel = preload("util/add_node_panel/AddNodePanel.tscn").instance()func _enter_tree() -> void:# 添加自定义类型节点add_custom_type("Root", "Node", preload("src/Root.gd"), get_editor_interface().get_base_control().get_icon("Node", "EditorIcons"))# 设置 add_node_panel 所在的插件add_node_panel.set_plugin(self)func _exit_tree() -> void:# 移除自定义类型接节点remove_custom_type("Root")# 进行操作时,比如选中节点,或者编辑脚本等情况时会调用这个方法
func handles(object: Object) -> bool:# 如果操作的对象是 Rootif object is preload("src/Root.gd"):return truereturn false# visible 值为 handles() 方法返回值
func make_visible(visible: bool) -> void:if visible:# 添加底部功能栏add_control_to_bottom_panel(add_node_panel, "Behavitor Tree")# 让节点显示出来,而不是默认的折叠add_node_panel.show()else:# 移除底部功能栏remove_control_from_bottom_panel(add_node_panel)

但是还有一点问题就是在 handles() 方法部分,这种只能在选中了 Root 类型的节点才能返回 true,显示出面板,其他的就不行了,所以我们还添加个判断方法。在插件里添加如下代码:

const BTNode = [preload("res://addons/test_plugin/src/Root.gd"),preload("res://addons/test_plugin/src/Composite.gd"),preload("res://addons/test_plugin/src/Leaf.gd"),
]## 是否为 行为树节点
func is_bt_node(node: Node) -> bool:for n in BTNode:if node is n:return truereturn false

handle() 方法内容稍改一下:

func handles(object: Object) -> bool:# 如果操作的对象是 Rootif is_bt_node(object):return truereturn false

完整代码:

tool
extends EditorPluginconst BTNode = [preload("res://addons/test_plugin/src/Root.gd"),preload("res://addons/test_plugin/src/Composite.gd"),preload("res://addons/test_plugin/src/Leaf.gd"),
]
var add_node_panel = preload("util/add_node_panel/AddNodePanel.tscn").instance()func _enter_tree() -> void:# 添加自定义类型节点add_custom_type("Root", "Node", preload("src/Root.gd"), get_editor_interface().get_base_control().get_icon("Node", "EditorIcons"))# 设置 add_node_panel 所在的插件add_node_panel.set_plugin(self)func _exit_tree() -> void:# 移除自定义类型接节点remove_custom_type("Root")# 进行操作时,比如选中节点,或者编辑脚本等情况时会调用这个方法
func handles(object: Object) -> bool:# 如果操作的对象是 Rootif is_bt_node(object):return truereturn false# visible 值为 handles() 方法返回值
func make_visible(visible: bool) -> void:if visible:# 添加底部功能栏add_control_to_bottom_panel(add_node_panel, "Behavitor Tree")add_node_panel.show()else:# 移除底部功能栏remove_control_from_bottom_panel(add_node_panel)## 是否为 行为树节点
func is_bt_node(node: Node) -> bool:for n in BTNode:if node is n:return truereturn false

然后就是不断地对内容进行增加删除,增加删除,使脚本越来越强,功能越来越丰富。随之而来的问题就是内容太多了,代码太乱了,如果把所有内容都写到 plugin.gd 这一个脚本里,那未免太多、太复杂、太乱,所以我们可以对脚本中的代码中有共同特点的方法都放在一个脚本里,这样我们就符合了面向对象的六大基本原则之一的“单一职责原则”,让一个类(也就是一个脚本)只拥有一个功能。

我们这个脚本太简单,但是其实可以不用这么做,但是如果你的脚本代码太多的时候建议一定要这样做。

我们就按现在的代码来讲,我们上方的 is_bt_node() 方法和 BTNode 属性可以将同特征的属性和方法都封装到一个 BTClassDB (行为树类数据库)脚本里

我们在 util 文件夹下创建一个 Reference 类型的脚本,名称为 BTClassDB

BTClassDB.gd 脚本代码如下:

## 行为树 Class 数据库
extends Reference## 行为树节点
const BTNode = [preload("res://addons/test_plugin/src/Root.gd"),preload("res://addons/test_plugin/src/Composite.gd"),preload("res://addons/test_plugin/src/Leaf.gd"),
]## 是否为 行为树节点
static func is_bt_node(node: Node) -> bool:for n in BTNode:if node is n:return truereturn false

plugin.gd 脚本则修改为如下代码

tool
extends EditorPluginconst BTClassDB = preload("util/BTClassDB.gd")   #(增加的部分)行为树的 Class 数据库 var add_node_panel = preload("util/add_node_panel/AddNodePanel.tscn").instance()func _enter_tree() -> void:# 添加自定义类型节点add_custom_type("Root", "Node", preload("src/Root.gd"), get_editor_interface().get_base_control().get_icon("Node", "EditorIcons"))# 设置 add_node_panel 所在的插件add_node_panel.set_plugin(self)func _exit_tree() -> void:# 移除自定义类型接节点remove_custom_type("Root")# 进行操作时,比如选中节点,或者编辑脚本等情况时会调用这个方法
func handles(object: Object) -> bool:# 如果操作的对象是 Rootif BTClassDB.is_bt_node(object): #(修改的部分)判断操作对象是否是行为树return truereturn false# visible 值为 handles() 方法返回值
func make_visible(visible: bool) -> void:if visible:# 添加底部功能栏add_control_to_bottom_panel(add_node_panel, "Behavitor Tree")add_node_panel.show()else:# 移除底部功能栏remove_control_from_bottom_panel(add_node_panel)

之后就是这样,不断地增加功能,然后功能太多了就创建出来脚本,将代码都剪进去,然后整理一下,不断重复 不断重复 ……

结语

孰能生巧,做多了思考多了,有些编程思想你不去学你都能自己领悟出来,当然如果是有人引领,那确实会进步飞快,如果没有 还是得多做…

【Godot】Godot 插件制作流程相关推荐

  1. Android学习笔记 ------ Gradle Plugin 插件制作流程

    1.开始 创建一个Library项目,并且删除build.gradle中原来的数据 2.处理Library的build.gradle文件 1.添加groovy插件 apply plugin: 'gro ...

  2. Godot全局插件支持库

    简介 GodotGlobalPlugins提供Godot全局插件支持,常用插件无需再手动复制到新项目中,省时省力!同时还能减少磁盘占用空间,插件更新更方便! 下载地址: GodotGlobalPlug ...

  3. html按钮分享功能实现,一个分享按钮的插件使用介绍(可扩展,内附开发制作流程)...

    前几天由于工作需要制作一个分享按钮,考虑到后续其他项目可能也会用到,于是就打算写成插件化,正好也给我自己的插件jquery.hooray增加一个新的功能,为了不浪费大家时间,我先把demo放出来,如果 ...

  4. Cadence Allegro贴片和插件元器件封装制作流程总结

    目录 1. 0805电阻封装的制作 1.1 计算焊盘尺寸 1.2 制作焊盘(用于生成*.pad) 1.2 封装制作(生成*.dra) 1.3 设置参数.栅格grid和原点 1.4 放置焊盘 1.5 放 ...

  5. 【UE5教程】影棚拍摄于虚拟场景合成制作流程学习

    用虚幻引擎预算虚拟生产5 你会学到什么 使用虚幻引擎5进行虚拟生产 使用虚幻引擎5的独立虚拟制作 用虚幻引擎预算虚拟生产5 用虚幻引擎5进行穷人虚拟生产 用虚幻引擎5进行自制虚拟制作 虚幻引擎5独立虚 ...

  6. 浅谈导航电子地图的组成和制作流程

    这是四维公司在周四的时候做的一个小小的活动(由于一直有点忙,所以今天才整理此篇文章),主要是讨论行人导航地图的应用与发展.由于我们经理比较忙,所以就让我过去看看.这次活动在蓝色港湾酒吧一条街的全明星运 ...

  7. 影视后期行业概述、制作流程、岗位划分、薪资待遇、课程介绍详解

    影视后期行业概述.制作流程.岗位划分.薪资待遇.课程介绍详解 本篇围绕影视后期行业概念定义.应用与细分.影视制作流程.市场上职责岗位划分.影视后期课程详情.影视后期薪资结构与前景.授课方式与课程划分. ...

  8. 3D建模:角色手办的制作流程,分分钟一个手办

    写在前面的话: 当一位动漫中的角色在人们心中的认可度达到一定程度时,与此相关的衍生物便会接踵而来.作为一位备受欢迎的萌妹子/帅哥,实体化当然成为了粉丝们的一致理想与追求,那么该角色的手办制作就会在这样 ...

  9. Web前端开发之网站制作流程详细讲解

    一个网站想要顺利的运行就离不开HTML5网页技术开发人员,因此熟悉整个网站的开发建设流程对HTML5网页技术开发人员尤为重要,本篇文章扣丁学堂小编就和大家分享一下Web前端开发之网站制作流程,希望可以 ...

  10. 次时代游戏的制作流程以及游戏美术需要掌握哪些基本软件

    我想很多接触游戏开发引擎的小伙伴们应该对"次世代建模"这个词都很熟悉,也有可能你是第一次接触这个词,觉得这是一门很复杂的技术,其实只要你用对方法去学习,就会没有那么复杂了. 本文在 ...

最新文章

  1. [UT]Unit Test理解
  2. leetcode--搜索插入位置--python
  3. python范数norm的计算
  4. 使用python xmodem 模块下载及上传文件
  5. 《iOS应用逆向工程(第2版)》高清电子书 PDF
  6. 使用LAMP创建基于wordpress的个从博客网站
  7. Java集合转化为数组
  8. 为什么 Eureka 比 ZooKeeper 更适合做注册中心?
  9. 蓝桥杯 振兴中华——2013年省赛C/C++ A组真题3
  10. 数值方法与计算机方法是,计算机数值方法.pdf
  11. 【ESP 保姆级教程】疯狂点灯篇 —— 案例:ESP8266 + LED + 按键 + 阿里云生活物联网平台 + 公有版App + 天猫精灵(项目:我之家)
  12. 初识人工智能,机器学习,深度学习的关系(概念)
  13. js 实现表格合并单元格
  14. 【吴刚】电商网站详情页设计初级入门标准视频教程-吴刚-专题视频课程
  15. 信息时代,书香更宜人
  16. 简单使用SAXReader解析xml数据
  17. GIT客户端连接码云
  18. 闲谈“个人核心竞争力”与“危机感”
  19. 《向上生长》读书笔记
  20. 使用 JS 循环解决经典数学问题!

热门文章

  1. 城市智慧停车管理模式方案科普
  2. 与次爱的人相濡以沫,与最爱的人相忘于江湖
  3. 中科方德桌面操作系统_国产操作系统——中科方德桌面操作系统,USB无线网卡配置...
  4. SSM系类代码:org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
  5. 64位Win10 Modelsim破解及证书LICENSE.TXT无法生成解决方法
  6. 油猴/暴力猴工具换cook脚本
  7. 图书馆管理系统(连接数据库)
  8. 嵌入式C语言数据类型
  9. 屏幕录像专家出现未注册字样
  10. 安装Windows刷机adb环境手册