基于Qt Property Browser的通用属性系统:Any类与向量/颜色属性的完美结合
引言:通用属性编辑器的核心挑战
在游戏引擎和编辑器开发中,通用属性系统是连接对象数据与用户界面的关键桥梁。它需要解决三大核心挑战:
类型多样性 - 支持基础类型、向量、颜色等复杂数据结构
数据绑定 - 实现属性值与对象数据的双向同步
UI扩展性 - 为不同类型提供定制化编辑器
本文将深入探讨如何结合Qt Property Browser框架与OGRE的Any类,构建一个强大的通用属性系统,特别关注向量和颜色属性的实现细节。
一、系统架构设计
1.1 整体架构
1.2 核心组件关系
二、Any属性容器实现
2.1 属性基类设计
class PropertyElement {
public:enum Type { Type_Bool, Type_Int, Type_Float, Type_Vector3, Type_Color };virtual Type GetType() const = 0;virtual void Set(const Any& value) = 0;virtual Any Get() const = 0;virtual void Reset() = 0;virtual bool GetModified() const = 0;// 元数据支持virtual QString GetName() const { return m_name; }virtual void SetDescription(const QString& desc) { m_description = desc; }protected:QString m_name;QString m_description;
};
2.2 向量属性实现
class PropertyVector3 : public PropertyElement {
public:PropertyVector3(std::shared_ptr<DReferenced> spRef, const char* name,Delegate<void(const D3DXVECTOR3&)> setter,Delegate<D3DXVECTOR3()> getter): PropertyElement(name), m_spRef(spRef), m_set(setter), m_get(getter), m_default(getter()){// 设置分量名称m_strXName = tr("X");m_strYName = tr("Y");m_strZName = tr("Z");}Type GetType() const override { return Type_Vector3; }void Set(const Any& value) override {if (!type_info_equal(value.getType(), typeid(D3DXVECTOR3))) return;D3DXVECTOR3 vec = any_cast<D3DXVECTOR3>(value);if (!m_set.empty()) {// 支持撤销/重做的属性设置GLOBAL_UNDO_MANAGE->push(new Vector3UndoCommand(m_spRef, vec, m_set, m_get, this->GetName()));}}Any Get() const override {return Any(m_get());}// 设置分量显示名称void SetComponentNames(const QString& x, const QString& y, const QString& z) {m_strXName = x;m_strYName = y;m_strZName = z;}private:Delegate<void(const D3DXVECTOR3&)> m_set;Delegate<D3DXVECTOR3()> m_get;D3DXVECTOR3 m_default;QString m_strXName;QString m_strYName;QString m_strZName;std::shared_ptr<DReferenced> m_spRef;
};
三、Qt属性浏览器集成
3.1 属性管理器设计
class Vector3PropertyManager : public QtAbstractPropertyManager {Q_OBJECT
public:Vector3PropertyManager(QObject* parent = nullptr);// 核心接口D3DXVECTOR3 value(const QtProperty* property) const;QString valueText(const QtProperty* property) const;// 设置分量名称void setComponentNames(QtProperty* property, const QString& xName,const QString& yName,const QString& zName);public slots:void setValue(QtProperty* property, const D3DXVECTOR3& value);signals:void valueChanged(QtProperty* property, const D3DXVECTOR3& value);void componentNamesChanged(QtProperty* property, const QString& xName,const QString& yName,const QString& zName);protected:// 初始化/反初始化属性void initializeProperty(QtProperty* property) override;void uninitializeProperty(QtProperty* property) override;private:struct Vector3Data {D3DXVECTOR3 value;QString xName;QString yName;QString zName;};QMap<const QtProperty*, Vector3Data> m_values;
};
3.2 属性编辑器工厂
class Vector3EditorFactory : public QtAbstractEditorFactory<Vector3PropertyManager> {Q_OBJECT
public:explicit Vector3EditorFactory(QObject* parent = nullptr);protected:// 工厂方法void connectPropertyManager(Vector3PropertyManager* manager) override;QWidget* createEditor(Vector3PropertyManager* manager, QtProperty* property,QWidget* parent) override;void disconnectPropertyManager(Vector3PropertyManager* manager) override;private slots:void slotPropertyChanged(QtProperty* property, const D3DXVECTOR3& value);void slotComponentNamesChanged(QtProperty* property,const QString& xName,const QString& yName,const QString& zName);void slotSetValue();void slotEditorDestroyed(QObject* editor);private:QMap<QtProperty*, QList<QWidget*>> m_createdEditors;QMap<QWidget*, QtProperty*> m_editorToProperty;
};
四、向量属性编辑器的实现细节
4.1 紧凑型向量编辑器
class CompactVector3Editor : public QWidget {Q_OBJECT
public:explicit CompactVector3Editor(QWidget* parent = nullptr);void setValue(const D3DXVECTOR3& value);D3DXVECTOR3 value() const;void setComponentNames(const QString& x, const QString& y, const QString& z);signals:void valueChanged(const D3DXVECTOR3& value);private:void setupUI();QDoubleSpinBox* m_spinX;QDoubleSpinBox* m_spinY;QDoubleSpinBox* m_spinZ;QLabel* m_labelX;QLabel* m_labelY;QLabel* m_labelZ;
};
4.2 带颜色标识的向量编辑器
class ColorVector3Editor : public CompactVector3Editor {
public:explicit ColorVector3Editor(QWidget* parent = nullptr);void setColorHint(const QColor& color);protected:void paintEvent(QPaintEvent* event) override;private:QColor m_colorHint;
};// 实现示例
void ColorVector3Editor::paintEvent(QPaintEvent* event) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);// 绘制背景色块QRect rect = this->rect().adjusted(2, 2, -2, -2);painter.setBrush(m_colorHint);painter.setPen(Qt::NoPen);painter.drawRoundedRect(rect, 3, 3);CompactVector3Editor::paintEvent(event);
}
五、颜色属性的特殊处理
5.1 RGB颜色属性
class Color3Property : public PropertyVector3 {
public:Color3Property(std::shared_ptr<DReferenced> spRef, const char* name,Delegate<void(const D3DXVECTOR3&)> setter,Delegate<D3DXVECTOR3()> getter): PropertyVector3(spRef, name, setter, getter) {// 设置分量名称为颜色通道SetComponentNames("R", "G", "B");// 设置值范围限制m_minValue = D3DXVECTOR3(0, 0, 0);m_maxValue = D3DXVECTOR3(1, 1, 1);}QColor toQColor() const {D3DXVECTOR3 c = Get();return QColor::fromRgbF(c.x, c.y, c.z);}void setColorRange(const D3DXVECTOR3& min, const D3DXVECTOR3& max) {m_minValue = min;m_maxValue = max;}private:D3DXVECTOR3 m_minValue;D3DXVECTOR3 m_maxValue;
};
5.2 颜色选择器集成
QWidget* Vector3EditorFactory::createEditor(Vector3PropertyManager* manager, QtProperty* property,QWidget* parent)
{// 检查是否为颜色属性bool isColor = false;if (auto prop = dynamic_cast<Color3Property*>(property)) {isColor = true;}if (isColor) {// 创建颜色选择器ColorPickerButton* colorButton = new ColorPickerButton(parent);QColor color = manager->value(property).toQColor();colorButton->setColor(color);connect(colorButton, &ColorPickerButton::colorChanged, [=](const QColor& newColor) {D3DXVECTOR3 vec(newColor.redF(), newColor.greenF(), newColor.blueF());manager->setValue(property, vec);});return colorButton;} else {// 创建标准向量编辑器CompactVector3Editor* editor = new CompactVector3Editor(parent);editor->setValue(manager->value(property));// 设置分量名称Vector3Data data = manager->getData(property);editor->setComponentNames(data.xName, data.yName, data.zName);connect(editor, &CompactVector3Editor::valueChanged, [=](const D3DXVECTOR3& value) {manager->setValue(property, value);});return editor;}
}
六、高级功能实现
6.1 撤销/重做支持
class Vector3UndoCommand : public QUndoCommand {
public:Vector3UndoCommand(std::shared_ptr<DReferenced> object,const D3DXVECTOR3& newValue,Delegate<void(const D3DXVECTOR3&)> setter,Delegate<D3DXVECTOR3()> getter,const QString& name): m_object(object), m_newValue(newValue), m_setter(setter), m_getter(getter), m_propertyName(name){m_oldValue = m_getter();setText(QString("Change %1").arg(name));}void undo() override {if (!m_object.expired() && !m_setter.empty()) {m_setter(m_oldValue);}}void redo() override {if (!m_object.expired() && !m_setter.empty()) {m_setter(m_newValue);}}private:std::weak_ptr<DReferenced> m_object;D3DXVECTOR3 m_oldValue;D3DXVECTOR3 m_newValue;Delegate<void(const D3DXVECTOR3&)> m_setter;Delegate<D3DXVECTOR3()> m_getter;QString m_propertyName;
};
6.2 属性动画支持
class AnimatedVector3Property : public PropertyVector3 {
public:AnimatedVector3Property(/* 参数同PropertyVector3 */): PropertyVector3(/* 参数 */){m_animator = new QPropertyAnimation(this, "value");}void animateTo(const D3DXVECTOR3& target, int duration = 1000) {m_animator->stop();m_animator->setDuration(duration);m_animator->setStartValue(QVariant::fromValue(Get()));m_animator->setEndValue(QVariant::fromValue(target));m_animator->start();}// 重写Set方法以支持动画void Set(const D3DXVECTOR3& value) override {if (m_animator->state() == QPropertyAnimation::Running) {m_animator->setCurrentValue(QVariant::fromValue(value));} else {PropertyVector3::Set(value);}}private:QPropertyAnimation* m_animator;
};
七、性能优化策略
7.1 批量更新处理
void Vector3PropertyManager::setValues(const QMap<QtProperty*, D3DXVECTOR3>& values) {// 开始批量更新beginBatchUpdate();QMapIterator<QtProperty*, D3DXVECTOR3> it(values);while (it.hasNext()) {it.next();setValue(it.key(), it.value());}// 结束批量更新,只触发一次全局更新endBatchUpdate();
}
7.2 属性值缓存
class CachedVector3Property : public PropertyVector3 {
public:D3DXVECTOR3 Get() const override {if (m_cacheValid) {return m_cachedValue;}m_cachedValue = PropertyVector3::Get();m_cacheValid = true;return m_cachedValue;}void Set(const D3DXVECTOR3& value) override {if (value != Get()) {PropertyVector3::Set(value);m_cachedValue = value;}}private:mutable D3DXVECTOR3 m_cachedValue;mutable bool m_cacheValid = false;
};
八、实际应用案例
8.1 材质编辑器属性
void setupMaterialProperties(QtTreePropertyBrowser* browser, Material* material) {// 创建材质属性组QtProperty* materialGroup = browser->addProperty("Material Properties");// 添加漫反射颜色属性Color3Property* diffuseProp = new Color3Property(material->getSharedPtr(),"Diffuse Color",Delegate<void(const D3DXVECTOR3&)>(material, &Material::setDiffuseColor),Delegate<D3DXVECTOR3()>(material, &Material::getDiffuseColor));materialGroup->addSubProperty(diffuseProp);// 添加高光颜色属性Color3Property* specularProp = new Color3Property(material->getSharedPtr(),"Specular Color",Delegate<void(const D3DXVECTOR3&)>(material, &Material::setSpecularColor),Delegate<D3DXVECTOR3()>(material, &Material::getSpecularColor));materialGroup->addSubProperty(specularProp);// 添加UV偏移属性PropertyVector3* uvOffsetProp = new PropertyVector3(material->getSharedPtr(),"UV Offset",Delegate<void(const D3DXVECTOR3&)>(material, &Material::setUVOffset),Delegate<D3DXVECTOR3()>(material, &Material::getUVOffset));uvOffsetProp->SetComponentNames("U", "V", "W");materialGroup->addSubProperty(uvOffsetProp);
}
8.2 变换组件属性
void setupTransformProperties(QtTreePropertyBrowser* browser, Transform* transform) {QtProperty* transformGroup = browser->addProperty("Transform");// 位置属性PropertyVector3* positionProp = new PropertyVector3(transform->getSharedPtr(),"Position",Delegate<void(const D3DXVECTOR3&)>(transform, &Transform::setPosition),Delegate<D3DXVECTOR3()>(transform, &Transform::getPosition));positionProp->SetComponentNames("X", "Y", "Z");transformGroup->addSubProperty(positionProp);// 旋转属性(欧拉角)PropertyVector3* rotationProp = new PropertyVector3(transform->getSharedPtr(),"Rotation",Delegate<void(const D3DXVECTOR3&)>(transform, &Transform::setEulerAngles),Delegate<D3DXVECTOR3()>(transform, &Transform::getEulerAngles));rotationProp->SetComponentNames("Pitch", "Yaw", "Roll");transformGroup->addSubProperty(rotationProp);// 缩放属性PropertyVector3* scaleProp = new PropertyVector3(transform->getSharedPtr(),"Scale",Delegate<void(const D3DXVECTOR3&)>(transform, &Transform::setScale),Delegate<D3DXVECTOR3()>(transform, &Transform::getScale));scaleProp->SetComponentNames("X", "Y", "Z");transformGroup->addSubProperty(scaleProp);
}
结论:构建现代编辑器的基础设施
通过结合Qt Property Browser框架和Any类,我们实现了:
统一属性管理:通过Any类支持任意数据类型
高效数据绑定:使用委托模式实现对象与UI的双向绑定
类型特定编辑器:为向量、颜色等类型提供最佳编辑体验
高级功能支持:撤销/重做、动画、批量更新等
这种设计模式已在多个知名引擎中验证:
Unity:使用SerializedObject/SerializedProperty系统
Unreal Engine:基于UProperty的细节面板
Godot:通过Variant和Property系统实现
在实际应用中,开发者可以基于此框架快速构建各种对象属性编辑器,大幅提升开发效率。