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

如何将类类型写入二进制文件并还原以实现关卡加载系统?

解决二进制关卡加载中的类类型序列化与还原问题

首先得明确:typeid(type).name()的结果不能用于反向映射类类型,因为它是编译器实现定义的——不同编译器(甚至同一编译器的不同版本)返回的字符串格式都不一样,完全没有标准化的保证,所以这条路走不通。下面给你几个可行的方案,从手动控制到依赖第三方库,你可以根据项目需求选择:


方案1:手动分配唯一类型ID(最可靠、无依赖)

这是游戏开发中最常用的方案,核心思路是给每个需要序列化的类分配一个固定的唯一标识(比如整数枚举),写入二进制时存这个ID,读取时用工厂模式根据ID创建对应对象。

示例代码:

#include <memory>
#include <unordered_map>
#include <fstream>

// 所有可生成对象的基类
class GameObject {
public:
    virtual ~GameObject() = default;
    // 序列化:写入自身数据到二进制流(先写类型ID,再写属性)
    virtual void serialize(std::ostream& stream) const = 0;
    // 反序列化:从二进制流读取属性
    virtual void deserialize(std::istream& stream) = 0;
};

// 自定义类型ID枚举,每个可序列化类对应唯一值
enum class ObjectType {
    Player,
    Enemy,
    Collectible,
    // 新增类时在这里添加对应的ID
};

// 对象工厂:负责根据类型ID创建实例
class GameObjectFactory {
private:
    using Creator = std::unique_ptr<GameObject>(*)();
    std::unordered_map<ObjectType, Creator> creators_;

public:
    // 注册类型与对应的创建函数
    void register_type(ObjectType type, Creator func) {
        creators_[type] = func;
    }

    // 根据ID创建对象
    std::unique_ptr<GameObject> create(ObjectType type) {
        auto it = creators_.find(type);
        return it != creators_.end() ? it->second() : nullptr;
    }
};

// 全局工厂实例(也可以用单例模式封装)
GameObjectFactory g_ObjectFactory;

// 示例子类:Player
class Player : public GameObject {
public:
    float pos_x = 0.f;
    float pos_y = 0.f;
    float scale = 1.f;

    void serialize(std::ostream& stream) const override {
        // 先写入类型ID
        const ObjectType type = ObjectType::Player;
        stream.write(reinterpret_cast<const char*>(&type), sizeof(type));
        // 再写入属性数据
        stream.write(reinterpret_cast<const char*>(&pos_x), sizeof(pos_x));
        stream.write(reinterpret_cast<const char*>(&pos_y), sizeof(pos_y));
        stream.write(reinterpret_cast<const char*>(&scale), sizeof(scale));
    }

    void deserialize(std::istream& stream) override {
        stream.read(reinterpret_cast<char*>(&pos_x), sizeof(pos_x));
        stream.read(reinterpret_cast<char*>(&pos_y), sizeof(pos_y));
        stream.read(reinterpret_cast<char*>(&scale), sizeof(scale));
    }
};

// 自动注册Player到工厂(匿名命名空间确保只执行一次)
namespace {
    bool registerPlayer = [](){
        g_ObjectFactory.register_type(ObjectType::Player, [](){
            return std::make_unique<Player>();
        });
        return true;
    }();
}

// 从二进制流加载单个对象的函数
std::unique_ptr<GameObject> loadGameObject(std::istream& stream) {
    ObjectType type;
    // 先读取类型ID
    stream.read(reinterpret_cast<char*>(&type), sizeof(type));
    // 创建对象
    auto obj = g_ObjectFactory.create(type);
    if (obj) {
        // 反序列化属性
        obj->deserialize(stream);
    }
    return obj;
}

优点:

  • 完全可控,没有依赖,跨平台兼容性拉满
  • 性能高,类型ID是整数,读写和查找都极快
  • 新增类时只需要添加枚举值+注册代码,逻辑清晰

方案2:类型注册表+自定义字符串ID(减少手动枚举)

如果不想维护枚举,可以用自定义字符串标识(比如"Player"、"Enemy")配合类型注册表,写入时存字符串,读取时根据字符串查找对应的创建函数。

示例代码:

#include <memory>
#include <unordered_map>
#include <string>

class GameObjectFactory {
private:
    using Creator = std::unique_ptr<GameObject>(*)();
    std::unordered_map<std::string, Creator> creators_;

public:
    // 模板方法,自动推导类型
    template<typename T>
    void registerType(const std::string& typeName) {
        creators_[typeName] = [](){
            return std::make_unique<T>();
        };
    }

    std::unique_ptr<GameObject> create(const std::string& typeName) {
        auto it = creators_.find(typeName);
        return it != creators_.end() ? it->second() : nullptr;
    }
};

// 注册示例
g_ObjectFactory.registerType<Player>("Player");
g_ObjectFactory.registerType<Enemy>("Enemy");

写入时把字符串"Player"写入二进制,读取时先读字符串,再调用create方法即可。这种方案比枚举更灵活,但字符串读写的性能略低于整数,而且要确保字符串唯一(可以用宏定义统一管理)。


方案3:使用成熟的序列化库(最省心)

如果项目允许引入第三方库,直接用现成的序列化库可以省去手动处理类型映射的麻烦,比如:

  • Boost.Serialization:C++老牌序列化库,支持多态类型的自动序列化
  • Protobuf/FlatBuffers:Google出品的跨平台序列化方案,性能极高,适合游戏场景
  • nlohmann/json:如果可以接受JSON格式(虽然不是二进制,但也可以转二进制存储),用法非常简洁

以Boost.Serialization为例,它会自动处理类型信息的序列化与反序列化,示例代码:

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/export.hpp>
#include <fstream>
#include <memory>

class GameObject {
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        // 序列化基类通用数据(如果有的话)
    }
public:
    virtual ~GameObject() = default;
};

class Player : public GameObject {
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive& ar, const unsigned int version) {
        // 先序列化基类
        ar & boost::serialization::base_object<GameObject>(*this);
        // 再序列化自身属性
        ar & pos_x & pos_y & scale;
    }
public:
    float pos_x = 0.f;
    float pos_y = 0.f;
    float scale = 1.f;
};

// 导出Player类型,让序列化库识别它
BOOST_CLASS_EXPORT(Player)

// 保存对象到二进制文件
void saveGameObject(const GameObject& obj, const std::string& path) {
    std::ofstream ofs(path, std::ios::binary);
    boost::archive::binary_oarchive oa(ofs);
    oa & obj;
}

// 从二进制文件加载对象
std::unique_ptr<GameObject> loadGameObject(const std::string& path) {
    std::ifstream ifs(path, std::ios::binary);
    boost::archive::binary_iarchive ia(ifs);
    std::unique_ptr<GameObject> obj;
    ia & obj;
    return obj;
}

优点:

  • 几乎不需要手动处理类型映射,库会自动搞定
  • 支持复杂数据结构、多态、版本兼容等高级特性
  • 跨语言兼容性好(比如Protobuf可以和Java、Python交互)

总结

  • 如果追求极致可控和性能,选方案1(手动类型ID+工厂),这是游戏行业的标准做法
  • 如果想减少枚举维护的工作量,选方案2(自定义字符串ID+注册表)
  • 如果项目可以引入第三方库,选方案3(成熟序列化库),省心省力

内容的提问来源于stack exchange,提问作者fes dfadfa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 22:17:43