使用GDExtension定义了一个状态机,头文件card_state.h
完整内容如下:
#ifndef CARD_STATE_H
#define CARD_STATE_H
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/input_event.hpp>
namespace godot {
class CardState : public Node {
GDCLASS(CardState, Node)
protected:
static void _bind_methods();
public:
enum State {
BASE,
CLICKED,
DRAGGING,
AIMING,
RELEASED
};
void enter();
void exit();
void on_input(InputEvent *_event);
void on_gui_input(InputEvent *_event);
void on_mouse_entered();
void on_mouse_exited();
};
}
VARIANT_ENUM_CAST(CardState::State);
#endif
注意到代码中定义一个public的枚举成员State,并且使用了VARIANT_ENUM_CAST(CardState::State);
将其注册。
VARIANT_ENUM_CAST
宏在godot_cpp\core\binder_common.hpp
源代码中的定义如下:
#define VARIANT_ENUM_CAST(m_enum) \
namespace godot { \
MAKE_ENUM_TYPE_INFO(m_enum) \
template <> \
struct VariantCaster<m_enum> { \
static _FORCE_INLINE_ m_enum cast(const Variant &p_variant) { \
return (m_enum)p_variant.operator int64_t(); \
} \
}; \
template <> \
struct PtrToArg<m_enum> { \
_FORCE_INLINE_ static m_enum convert(const void *p_ptr) { \
return m_enum(*reinterpret_cast<const int64_t *>(p_ptr)); \
} \
typedef int64_t EncodeT; \
_FORCE_INLINE_ static void encode(m_enum p_val, void *p_ptr) { \
*reinterpret_cast<int64_t *>(p_ptr) = p_val; \
} \
}; \
}
这个宏其实是用结构体模板对c++中的enum进行封装。
之后在card_state.cpp
中添加如下代码:
void CardState::_bind_methods() {
BIND_ENUM_CONSTANT(BASE);
BIND_ENUM_CONSTANT(CLICKED);
BIND_ENUM_CONSTANT(DRAGGING);
BIND_ENUM_CONSTANT(AIMING);
BIND_ENUM_CONSTANT(RELEASED);
ClassDB::bind_method(D_METHOD("enter"), &CardState::enter);
ClassDB::bind_method(D_METHOD("exit"), &CardState::exit);
ClassDB::bind_method(D_METHOD("on_input", "event"), &CardState::on_input);
ClassDB::bind_method(D_METHOD("on_gui_input", "event"), &CardState::on_gui_input);
ClassDB::bind_method(D_METHOD("on_mouse_entered"), &CardState::on_mouse_entered);
ClassDB::bind_method(D_METHOD("on_mouse_exited"), &CardState::on_mouse_exited);
ADD_SIGNAL(
MethodInfo(
"transition_requested",
PropertyInfo(Variant::OBJECT, "from", PROPERTY_HINT_RESOURCE_TYPE, "CardState"),
PropertyInfo(Variant::INT, "to", PROPERTY_HINT_ENUM, "BASE,CLICKED,DRAGGING,AIMING,RELEASED")
)
);
}
其中BIND_ENUM_CONSTANT宏在源代码godot_cpp\core\class_db.hpp
中的定义为
#define BIND_ENUM_CONSTANT(m_constant) \
::godot::ClassDB::bind_integer_constant(get_class_static(), ::godot::_gde_constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant);
而bind_integer_constant
方法完整的函数签名为:
static void bind_integer_constant(const StringName &p_class_name, const StringName &p_enum_name, const StringName &p_constant_name, GDExtensionInt p_constant_value, bool p_is_bitfield = false);
可知在godot引擎中枚举的实现其实是将枚举字符串与整型常量绑定。
之后又使用
ADD_SIGNAL(
MethodInfo(
"transition_requested",
PropertyInfo(Variant::OBJECT, "from", PROPERTY_HINT_RESOURCE_TYPE, "CardState"),
PropertyInfo(Variant::INT, "to", PROPERTY_HINT_ENUM, "BASE,CLICKED,DRAGGING,AIMING,RELEASED")
)
);
注册了信号方法transition_requested(from:CardState, to:int)
,把枚举作为第二个参数的类型签名。但这么做有问题,导入到godot编辑器当中以后,界面提示的的信号方法第二个参数类型就是INT而非枚举类型。查阅了源代码之后发现PropertyInfo方法中的PROPERTY_HINT_ENUM参数只在注册属性的时候有具体实现,在注册信号时是没有具体实现的。在注册信号的方法的时候,只有当参数的类型签名是Object的时候才能自定义在编辑器中的提示。
但是这一需求在GDScript中却能够实现,只需要在脚本中添加以下代码即可:
enum State {BASE, CLICKED, DRAGGING, AIMING, RELEASED}
signal transition_requested(from: CardState, to: State)
@export var state: State
在GDExtension中定义参数类型签名为int时,虽然实参类型使用枚举也可以类型转换为整型,但是这样做没有对参数类型进行严格约束,不符合设计原则。目前笔者尚不知应该如何解决这个问题,特在此记录。