C++中如何为不同枚举类状态使用std::unordered_map实现精灵表
我正在C++中实现一个精灵表(Sprite Sheet)类,这类文件包含多行多列的图像,每行对应游戏角色的一种动作——比如玩家角色的第一行是向左移动,第二行是向右移动;而鲨鱼角色的第一行可能是向上游动,不同角色的行对应的动作各不相同。
为了表示这些角色状态,我定义了不同的枚举类,比如玩家角色的状态:
enum class HeroState { Idle, MovingLeft, MovingRight, };
其他角色还有各自的枚举类,比如SharkState、BombState等。
我希望实现一个可参数化的SpriteSheet类,能将精灵表的行与对应状态、列数关联起来,期望的用法大致如下:
heroSpriteSheet->setSpriteSheetData({{HeroState::Idle, 0, 19}, {HeroState::MovingLeft, 1, 14}}); sharkSpriteSheet->setSpriteSheetData({{SharkState::SwimLeft, 0, 12}, {SharkState::SwimRight, 1, 12}});
之后在代码中,只需传入状态就能查询到对应的行和列信息。
我原本考虑用std::unordered_map<State, RowAndColumnInfo>来存储映射,但不知道如何适配不同的状态枚举类(比如HeroState、VillainState)。应该用模板类,还是有更优的方案?目前还在规划阶段。
1. 模板类方案(最直接推荐)
把SpriteSheet设计为模板类,以状态枚举类型作为模板参数,让每个角色的精灵表实例绑定到对应的枚举类,完美适配不同状态类型。
首先定义存储行列信息的结构体:
struct RowColumnInfo { int row; int columnCount; };
然后实现模板化的SpriteSheet类:
template <typename StateEnum> class SpriteSheet { private: std::unordered_map<StateEnum, RowColumnInfo> stateMap; public: // 批量设置状态与行列的映射 void setSpriteSheetData(const std::initializer_list<std::pair<StateEnum, RowColumnInfo>>& data) { for (const auto& pair : data) { stateMap[pair.first] = pair.second; } } // 根据状态查询行列信息,返回std::optional避免空指针风险 std::optional<RowColumnInfo> getInfo(StateEnum state) const { auto it = stateMap.find(state); if (it != stateMap.end()) { return it->second; } return std::nullopt; } };
实际使用示例(修正原语法错误,枚举类成员用::访问):
// 玩家精灵表实例 auto heroSpriteSheet = std::make_unique<SpriteSheet<HeroState>>(); heroSpriteSheet->setSpriteSheetData({ {HeroState::Idle, {0, 19}}, {HeroState::MovingLeft, {1, 14}} }); // 鲨鱼精灵表实例 auto sharkSpriteSheet = std::make_unique<SpriteSheet<SharkState>>(); sharkSpriteSheet->setSpriteSheetData({ {SharkState::SwimLeft, {0, 12}}, {SharkState::SwimRight, {1, 12}} });
这个方案的优势:
- 类型安全:编译期就能检查传入的状态是否匹配当前精灵表的枚举类,避免类型错误
- 实现简单:无需额外基类或类型擦除逻辑,代码清晰直接
- 性能优异:模板实例化后没有运行时类型开销,效率高
2. 类型擦除方案(适配统一存储场景)
如果需要把不同角色的SpriteSheet统一存储到同一个容器中(比如std::vector<std::unique_ptr<BaseSpriteSheet>>),模板类无法满足,这时可以用类型擦除:
先定义非模板基类:
class BaseSpriteSheet { public: virtual ~BaseSpriteSheet() = default; // 可添加通用接口,比如绘制精灵等,但状态查询无法在基类统一定义 }; template <typename StateEnum> class SpriteSheet : public BaseSpriteSheet { // 内部实现同模板类方案 };
不过这种方案下,状态查询的接口无法在基类统一(因为状态类型不同),所以如果不需要统一管理不同类型的精灵表,优先选模板类方案。
3. 枚举类哈希适配(注意点)
默认std::unordered_map不支持枚举类作为键,需要提供自定义哈希函数。可以通过特化std::hash实现:
template <typename Enum> struct std::hash<Enum> { std::size_t operator()(Enum e) const { return static_cast<std::size_t>(e); } };
或者在声明unordered_map时指定哈希函数:
std::unordered_map<HeroState, RowColumnInfo, std::hash<int>> stateMap;
前者更通用,推荐使用。
内容的提问来源于stack exchange,提问作者Sami Lakka

