Godot插件制作的一些神秘小技巧

使用引擎多少会需要自己搓一些工具
Godot给出的方案是通过插件来扩展引擎的功能

但受制于脚本的功能性,通过C#脚本来编写插件时会遇到一些问题
(或许通过GDExtension扩展会好一点) 本文采用C#格式的API

判断脚本类型

一般来说,插件通过GodotObject传递原始对象
理论上我们可以通过GodotObject.GetClass()获取对象
但由于脚本添加的自定义类型本质上是在GodotObject上附加了这个脚本
附加的脚本不会在Godot的ClassDB中添加新的元数据
也就是说没有改变这个GodotObject的实际类型
这会导致GodotObject.GetClass()返回的是基类类型

比如说下面的代码

[GlobalClass]
[Tool]
public partial class TestCamera : Camera3D
{
	// ...
}

那么在插件上下文中,通过GetClass()返回的是Camera3D的类型

这会导致在制作查看器插件或者Gizmo插件时,无法通过这种方式对类型的判断
进而使得查看器插件的_CanHandle与Gizmo插件的_HasGizmo方法不是很好写

还是有办法解决这个问题的
通过比较脚本类型即可
_CanHandle为例

public partial class TestCameraInspector : EditorInspectorPlugin
{
	// ...
	public override bool _CanHandle(GodotObject o)
	{
		var script = GD.Load<Script>("path of script");
		return script == o.GetScript().AsGodotObject();
	}
	// ...
}

这样就可以判断脚本类型了

但如果说要扩展脚本功能,那么这个方法就不能判断了
毕竟相当于附加的脚本变了
这种情况可能直接判断基类型或者通过添加元数据判断更好

与脚本对象交互

由于上面的问题,导致GodotObject没法直接转换为需要的脚本问题
只能直接转换为继承基类型
比如上面的TestCamera,在插件给的GodotObject只能强制转型到Camera3D
那插件怎么传递数据到脚本对象呢
只能通过动态调用的方法实现

在GodotObject中,动态调用相关的主要有下面的方法

  • Get()可以获取某个属性值
  • Set()可以设定某个属性值
  • Call()可以调用某个方法 还有其他的一些查询方法与衍生方法

动态调用是可以调用脚本方法的,只要脚本在生存期内
在编写插件的场景中,也就是脚本要能在编辑器中运行,记得给脚本带[Tool]特性即可

下面举个例子

[GlobalClass]
[Tool]
public partial class TestCamera : Camera3D
{
	public void TestMethod(int i)
	{
		GD.Print(i);
	}
}

public partial class TestCameraInspector : EditorInspectorPlugin
{
	// ...
	// 这里给TestCamera栏添加一个按钮来触发这个TestMethod
	public override void _ParseCategory(GodotObject o, string category)
    {
        if (category != "TestCamera") return;
        var buttonTest = new Button()
        {
	        Text = "Test"
        };
        buttonTest.ButtonUp += () => {o.Call("TestMethod", 233)};
        AddCustomControl(buttonTest);
    }
	// ...
}

隐藏某个属性

有时候我们需要序列化一个属性,但希望只通过脚本内部管理
Godot本身没有这样的设置,[Export]默认是会把这个属性放在编辑器里的
这时候可以通过检查器插件来实现

[GlobalClass]
[Tool]
public partial class TestCamera : Camera3D
{
	[Export]
	public int HideProperty{ get;set; }
}

public partial class TestCameraInspector : EditorInspectorPlugin
{
	// ...
    public override bool _ParseProperty(
        GodotObject @object, Variant.Type type, string name, PropertyHint hintType, string hintString,
        PropertyUsageFlags usageFlags, bool wide)
    {
        return name == "HideProperty";
    }
	// ...
}

这样就实现了对HideProperty的隐藏
原理是_ParseProperty同返回值判断是否替换原有的控件
如果返回为true,则说明需要替换
但这里没有定义新的控件,那么就替换为空了