基于Qt Property Browser的通用属性系统:Any类与向量/颜色属性的完美结合

引言:通用属性编辑器的核心挑战

在游戏引擎和编辑器开发中,通用属性系统是连接对象数据与用户界面的关键桥梁。它需要解决三大核心挑战:

  1. 类型多样性 - 支持基础类型、向量、颜色等复杂数据结构

  2. 数据绑定 - 实现属性值与对象数据的双向同步

  3. 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类,我们实现了:

  1. 统一属性管理:通过Any类支持任意数据类型

  2. 高效数据绑定:使用委托模式实现对象与UI的双向绑定

  3. 类型特定编辑器:为向量、颜色等类型提供最佳编辑体验

  4. 高级功能支持:撤销/重做、动画、批量更新等

这种设计模式已在多个知名引擎中验证:

  • Unity:使用SerializedObject/SerializedProperty系统

  • Unreal Engine:基于UProperty的细节面板

  • Godot:通过Variant和Property系统实现

在实际应用中,开发者可以基于此框架快速构建各种对象属性编辑器,大幅提升开发效率。

相关推荐
小马敲马29 分钟前
[4.2-2] NCCL新版本的register如何实现的?
开发语言·c++·人工智能·算法·性能优化·nccl
我们从未走散1 小时前
面试题-----微服务业务
java·开发语言·微服务·架构
歪歪1001 小时前
Vue原理与高级开发技巧详解
开发语言·前端·javascript·vue.js·前端框架·集成学习
紫金修道1 小时前
python安装部署rknn-toolkit2(ModuleNotFoundError: No module named ‘rknn_toolkit2‘)
开发语言·python
漫步企鹅2 小时前
【VS Code - Qt】如何基于Docker Linux配置Windows10下的VS Code,开发调试ARM 版的Qt应用程序?
linux·qt·docker·arm·vs code·开发调试
EndingCoder2 小时前
Next.js API 路由:构建后端端点
开发语言·前端·javascript·ecmascript·全栈·next.js·api路由
2401_858286113 小时前
CD64.【C++ Dev】多态(3): 反汇编剖析单继承下的虚函数表
开发语言·c++·算法·继承·面向对象·虚函数·反汇编
pzzqq3 小时前
buildroot编译qt 5.9.8 arm64版本踩坑
开发语言·qt