游戏类继承体系中多类型枚举属性的统一管理架构实现咨询
解决游戏对象继承式属性系统的方案
这种带层级继承的多类型属性系统在游戏开发里确实很常见,之前你遇到的问题——多态下map无法直接处理不同枚举、StatsHolder需要向下转型、variant使用繁琐——核心是要平衡类型安全、继承扩展性和使用便捷性。
下面给你一个基于编译期类型绑定和CRTP的方案,既能保留你想要的简洁接口,又能避免向下转型和手动处理variant的麻烦:
核心思路
- 用独立枚举定义每个层级的属性(比如
PhysicalObjectStats、BaseUnitStats) - 用模板trait绑定每个枚举值到对应的属性类型(编译期确保类型匹配)
- 用CRTP给每个层级的类注入类型安全的属性访问方法
- 基类统一存储所有属性(用variant,但用户不用直接操作)
完整代码实现
1. 基础类型与工具类
#include <memory> #include <unordered_map> #include <variant> #include <type_traits> #include <stdexcept> // 支持的属性类型集合,可根据需求扩展 using StatValue = std::variant<int, bool, float>; // 模板trait:绑定枚举类型和枚举值到对应的属性类型 template <typename EnumType> struct StatTypeTraits; // 辅助函数:生成枚举值的唯一key(避免不同枚举的相同数值冲突) template <typename EnumType> std::size_t get_stat_key(EnumType stat) { return static_cast<std::size_t>(stat) + typeid(EnumType).hash_code(); }
2. 定义各层级的属性枚举
// 物理对象属性 enum class PhysicalObjectStats { Speed, Size }; // 基础单位属性 enum class BaseUnitStats { HitPoints, HasArmor }; // 弓箭手单位属性 enum class BaseArcherStats { ArrowCount, Range };
3. 绑定枚举值与属性类型
// 物理对象的类型绑定 template <> struct StatTypeTraits<PhysicalObjectStats> { template <PhysicalObjectStats S> struct ValueType; }; template <> struct StatTypeTraits<PhysicalObjectStats>::ValueType<PhysicalObjectStats::Speed> { using type = int; }; template <> struct StatTypeTraits<PhysicalObjectStats>::ValueType<PhysicalObjectStats::Size> { using type = float; }; // 基础单位的类型绑定 template <> struct StatTypeTraits<BaseUnitStats> { template <BaseUnitStats S> struct ValueType; }; template <> struct StatTypeTraits<BaseUnitStats>::ValueType<BaseUnitStats::HitPoints> { using type = int; }; template <> struct StatTypeTraits<BaseUnitStats>::ValueType<BaseUnitStats::HasArmor> { using type = bool; }; // 弓箭手的类型绑定 template <> struct StatTypeTraits<BaseArcherStats> { template <BaseArcherStats S> struct ValueType; }; template <> struct StatTypeTraits<BaseArcherStats>::ValueType<BaseArcherStats::ArrowCount> { using type = int; }; template <> struct StatTypeTraits<BaseArcherStats>::ValueType<BaseArcherStats::Range> { using type = float; };
4. 构建继承式的游戏类体系
这里用CRTP给每个层级的类注入属性访问方法,同时继承父类的所有属性:
// 基础属性存储类,所有游戏类的共同父类 class BaseStatsHolder { protected: std::unordered_map<std::size_t, StatValue> stats_; }; // CRTP模板:给子类注入特定枚举的属性访问方法 template <typename Derived, typename EnumType> class StatAccessor { public: // 类型安全的Set方法:编译期检查值的类型是否匹配 template <EnumType Stat> void SetStat(typename StatTypeTraits<EnumType>::template ValueType<Stat>::type value) { auto& self = static_cast<Derived&>(*this); self.stats_[get_stat_key(Stat)] = std::move(value); } // 类型安全的Get方法:自动推导返回类型 template <EnumType Stat> typename StatTypeTraits<EnumType>::template ValueType<Stat>::type GetStat() const { const auto& self = static_cast<const Derived&>(*this); auto it = self.stats_.find(get_stat_key(Stat)); if (it == self.stats_.end()) { throw std::runtime_error("Stat not found"); // 或者返回默认值:return {}; } return std::get<typename StatTypeTraits<EnumType>::template ValueType<Stat>::type>(it->second); } }; // 物理对象类:继承存储类+物理属性访问器 class PhysicalObject : public BaseStatsHolder, public StatAccessor<PhysicalObject, PhysicalObjectStats> {}; // 基础单位类:继承物理对象+基础单位属性访问器 class BaseUnit : public PhysicalObject, public StatAccessor<BaseUnit, BaseUnitStats> {}; // 弓箭手类:继承基础单位+弓箭手属性访问器 class BaseArcher : public BaseUnit, public StatAccessor<BaseArcher, BaseArcherStats> {};
5. 使用示例
int main() { // 创建弓箭手实例,用基类指针持有 std::shared_ptr<PhysicalObject> obj = std::make_shared<BaseArcher>(); // 转换到对应层级的指针(如果需要访问子类属性) auto baseUnit = std::dynamic_pointer_cast<BaseUnit>(obj); auto archer = std::dynamic_pointer_cast<BaseArcher>(obj); // 设置物理属性 obj->SetStat<PhysicalObjectStats::Speed>(15); obj->SetStat<PhysicalObjectStats::Size>(2.5f); // 设置基础单位属性 if (baseUnit) { baseUnit->SetStat<BaseUnitStats::HitPoints>(100); baseUnit->SetStat<BaseUnitStats::HasArmor>(true); } // 设置弓箭手属性 if (archer) { archer->SetStat<BaseArcherStats::ArrowCount>(20); archer->SetStat<BaseArcherStats::Range>(30.0f); } // 获取属性 std::cout << "Speed: " << obj->GetStat<PhysicalObjectStats::Speed>() << "\n"; if (baseUnit) { std::cout << "Has Armor: " << std::boolalpha << baseUnit->GetStat<BaseUnitStats::HasArmor>() << "\n"; } if (archer) { std::cout << "Arrow Count: " << archer->GetStat<BaseArcherStats::ArrowCount>() << "\n"; } return 0; }
方案优势
- 类型安全:编译期检查属性值的类型是否匹配,避免运行时错误
- 无向下强制转型:通过
dynamic_pointer_cast安全转换到子类指针,比C-style转型更安全 - 扩展性强:新增层级或属性时,只需添加枚举和对应的类型绑定,不需要修改现有代码
- 无需手动处理variant:用户不用关注底层的variant存储,所有转换都由模板自动完成
关于你理想接口的说明
你最初想要的SetValue<int>(PhysicalObjectStats::speed)这种接口,因为枚举值是运行时参数,编译期无法确定它对应的类型,所以很难做到完全的类型安全。上面的方案用模板参数传递枚举值(SetStat<PhysicalObjectStats::Speed>),是在类型安全和便捷性之间的最优平衡。
如果一定要支持运行时枚举值的接口,可以添加一个基于variant的非类型安全版本,但需要手动处理类型转换,比如:
template <typename EnumType> void SetStatRuntime(EnumType stat, StatValue value) { stats_[get_stat_key(stat)] = std::move(value); }
但这种方式会丢失编译期类型检查,建议只在必要时使用。
内容的提问来源于stack exchange,提问作者pashchenkoromak
相关产品推荐
相关产品推荐

