首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >OpenClaw:用 C++/SDL2 重造一只 1997 年的猫

OpenClaw:用 C++/SDL2 重造一只 1997 年的猫

原创
作者头像
用户12339161
发布于 2026-10-22 14:33:02
发布于 2026-10-22 14:33:02
160
举报

OpenClaw 是对 Monolith 1997 年平台游戏《Captain Claw》的开源引擎重实现。原版是 Windows 95 时代的 16 位色 DirectDraw 程序,资源封在 CLAW.REZ 归档里;OpenClaw 用 C++ 与 SDL2 重写整套运行时,读取原版资源文件运行,从而把一只海盗猫带到了 Linux、macOS 与现代 Windows 上,并支持 1080p、自定义关卡与 MOD。它不是模拟器,而是"资源兼容的引擎重写"——这个定位决定了它的架构。

一、四层架构

代码按职责分为四层,层间只允许单向依赖:

  • 平台层:SDL2 封装窗口、音频、输入、文件系统,隔离操作系统差异;
  • 资源层:RezArchive 解析 REZ 归档,按路径取出精灵图、关卡、音效、字体;
  • 游戏层:实体、碰撞、状态机、关卡逻辑,全部与渲染无关;
  • 渲染层:把 8 位调色板位图转成 32 位纹理上传 GPU,做缩放与混合。

这种切分让"换渲染后端"或"加新关卡格式"都不必动游戏逻辑,也是它能被社区持续 MOD 化改造的前提。

二、资源层:解析 REZ 归档

REZ 是 Monolith 自有的归档格式,本质是"头部 + 目录表 + 数据区"。引擎启动时先建索引,把路径映射到偏移与长度,之后所有资源按名取用。

代码语言:javascript
复制
// 示意:REZ 归档索引构建
class RezArchive {
public:
    bool open(const std::string& path) {
        m_file.open(path, std::ios::binary);
        if (!m_file) return false;

        Header hdr{};
        m_file.read(reinterpret_cast<char*>(&hdr), sizeof(hdr));
        if (std::memcmp(hdr.magic, "REZ", 3) != 0) return false;

        // 目录表:条目数 + 每项(路径、偏移、大小)
        m_file.seekg(hdr.dir_offset);
        uint32_t count = 0;
        m_file.read(reinterpret_cast<char*>(&count), sizeof(count));

        for (uint32_t i = 0; i < count; ++i) {
            Entry e{};
            uint16_t nameLen = 0;
            m_file.read(reinterpret_cast<char*>(&nameLen), sizeof(nameLen));
            e.path.resize(nameLen);
            m_file.read(e.path.data(), nameLen);
            m_file.read(reinterpret_cast<char*>(&e.offset), sizeof(e.offset));
            m_file.read(reinterpret_cast<char*>(&e.size),   sizeof(e.size));
            m_index[normalize(e.path)] = e;   // 大小写不敏感、统一分隔符
        }
        return true;
    }

    std::vector<uint8_t> read(const std::string& path) const {
        auto it = m_index.find(normalize(path));
        if (it == m_index.end()) return {};
        std::vector<uint8_t> buf(it->second.size);
        m_file.seekg(it->second.offset);
        m_file.read(reinterpret_cast<char*>(buf.data()), buf.size());
        return buf;
    }
private:
    struct Entry { std::string path; uint32_t offset, size; };
    mutable std::ifstream m_file;
    std::unordered_map<std::string, Entry> m_index;
};

路径归一化是必须的:原版资源里大小写与反斜杠混用,不统一就会"资源明明存在却加载失败"。

三、固定时间步主循环

平台游戏的手感极度依赖确定性物理,因此主循环采用固定时间步累加器:渲染帧率可变,逻辑步长恒定为 1/60 秒,避免高刷屏下跳跃高度不一致。

代码语言:javascript
复制
constexpr double STEP = 1.0 / 60.0;

void Engine::run() {
    double acc = 0.0;
    uint64_t prev = SDL_GetTicks64();

    while (!m_quit) {
        uint64_t now = SDL_GetTicks64();
        double frame = std::min((now - prev) / 1000.0, 0.25); // 防死亡螺旋
        prev = now;
        acc += frame;

        pollEvents();                 // SDL_PollEvent → 输入状态
        while (acc >= STEP) {
            update(STEP);             // 逻辑:移动、碰撞、状态机
            acc -= STEP;
        }
        render(acc / STEP);           // 传插值系数,做平滑渲染
        SDL_GL_SwapWindow(m_window);
    }
}

四、实体与状态机

Claw 的行为是典型的状态机:待机、行走、跳跃、攀爬、游泳、攻击、受伤、死亡。每个状态负责自己的动画帧推进与转移条件,输入只产生"意图",由状态决定是否接受。

代码语言:javascript
复制
class ClawState {
public:
    virtual ~ClawState() = default;
    virtual void enter(Claw& c) {}
    virtual ClawState* update(Claw& c, const Input& in, double dt) = 0;
};

class JumpState : public ClawState {
public:
    ClawState* update(Claw& c, const Input& in, double dt) override {
        c.vel.y += GRAVITY * dt;            // 重力积分
        c.pos   += c.vel * dt;
        if (c.onGround()) return &g_idle;   // 落地 → 待机
        if (in.attack && c.canAttack())
            return &g_attack;
        return this;
    }
};

// 碰撞:与关卡 tile 做 AABB 分轴求解,先 X 后 Y 避免"贴墙卡住"
void resolve(Claw& c, const Level& lv, double dt) {
    c.pos.x += c.vel.x * dt;
    for (auto& t : lv.solidTilesNear(c.aabb()))
        if (intersects(c.aabb(), t)) c.pos.x = pushOutX(c, t), c.vel.x = 0;

    c.pos.y += c.vel.y * dt;
    c.onGround = false;
    for (auto& t : lv.solidTilesNear(c.aabb()))
        if (intersects(c.aabb(), t)) {
            c.pos.y = pushOutY(c, t);
            if (c.vel.y > 0) c.onGround = true;
            c.vel.y = 0;
        }
}

分轴求解是 2D 平台游戏的标准做法:合轴求解会让角色在斜坡与墙角处抖动或穿模。

五、MOD 与关卡扩展

OpenClaw 的社区活力来自可扩展性。关卡以文本/二进制资源描述 tile 布局与实体出生点,MOD 只需按约定放入覆盖目录,资源层优先读覆盖目录、其次读 REZ,逻辑层完全无感。自定义精灵则通过调色板替换实现——原版是 8 位索引色,换一张 PAL 就能整体换肤,成本极低。

六、构建与移植

依赖只有 SDL2、SDL2_image、SDL2_mixer 与 CMake,Windows 用 MSVC、Linux 用 GCC、macOS 用 Clang 均可直接构建。移植新平台的工作量集中在平台层:把窗口、音频回调、定时器映射到目标系统,游戏层一行不改——这正是分层架构的红利。

结语

OpenClaw 的价值不在"复刻一只猫",而在示范了一种低成本复活老游戏的工程路径:以资源格式为契约,重写运行时,把平台差异压到最薄的一层。对做引擎、做兼容层、做老资源再利用的开发者来说,它是一份可读、可改、可运行的活教材。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、四层架构
  • 二、资源层:解析 REZ 归档
  • 三、固定时间步主循环
  • 四、实体与状态机
  • 五、MOD 与关卡扩展
  • 六、构建与移植
  • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档