You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

游戏类继承体系中多类型枚举属性的统一管理架构实现咨询

解决游戏对象继承式属性系统的方案

这种带层级继承的多类型属性系统在游戏开发里确实很常见,之前你遇到的问题——多态下map无法直接处理不同枚举、StatsHolder需要向下转型、variant使用繁琐——核心是要平衡类型安全、继承扩展性和使用便捷性。

下面给你一个基于编译期类型绑定和CRTP的方案,既能保留你想要的简洁接口,又能避免向下转型和手动处理variant的麻烦:

核心思路

  1. 用独立枚举定义每个层级的属性(比如PhysicalObjectStats、BaseUnitStats)
  2. 用模板trait绑定每个枚举值到对应的属性类型(编译期确保类型匹配)
  3. 用CRTP给每个层级的类注入类型安全的属性访问方法
  4. 基类统一存储所有属性(用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;
}

方案优势

  1. 类型安全:编译期检查属性值的类型是否匹配,避免运行时错误
  2. 无向下强制转型:通过dynamic_pointer_cast安全转换到子类指针,比C-style转型更安全
  3. 扩展性强:新增层级或属性时,只需添加枚举和对应的类型绑定,不需要修改现有代码
  4. 无需手动处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 23:18:11