引言:通用属性编辑器的核心挑战
在游戏引擎和编辑器开发中,通用属性系统是连接对象数据与用户界面的关键桥梁。它需要解决三大核心挑战:
-
类型多样性 - 支持基础类型、向量、颜色等复杂数据结构
-
数据绑定 - 实现属性值与对象数据的双向同步
-
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系统实现
在实际应用中,开发者可以基于此框架快速构建各种对象属性编辑器,大幅提升开发效率。