SFML瓦片地图开发指南:从原理到实战的性能优化方案
在游戏开发中地图系统往往是新手开发者最容易低估复杂度的部分。很多人以为地图就是一张大图片直到实际开发时才发现内存占用爆炸、加载卡顿、碰撞检测困难等问题接踵而至。SFML 作为轻量级的多媒体库虽然不直接提供完整的地图引擎但通过瓦片Tile和瓦片清单Tile Sheet的合理运用完全可以构建出高效、灵活的地图系统。本文将深入解析 SFML 中瓦片地图的实现原理从基础概念到完整实战带你掌握为什么瓦片地图是 2D 游戏开发的必备技术如何设计高效的瓦片清单纹理动态加载与渲染优化技巧碰撞检测与地图交互的实现常见性能陷阱与解决方案无论你是正在开发平台跳跃、RPG 还是策略游戏这套方法都能帮你构建可扩展的地图系统。1. 瓦片地图的核心价值从内存杀手到性能利器1.1 传统位图地图的问题假设你要开发一个 1920×1080 分辨率的游戏场景如果使用单张位图即使采用压缩格式内存占用也相当可观// 假设使用 RGBA 格式每个像素 4 字节 1920 × 1080 × 4 8,294,400 字节 ≈ 8MB这还只是一个场景如果游戏有多个大型地图内存占用将迅速膨胀。更糟糕的是玩家通常只能看到屏幕的一小部分大部分资源都被浪费了。1.2 瓦片地图的解决方案瓦片地图将场景分解为可复用的基本单元瓦片每个瓦片尺寸固定如 32×32、64×64 像素。通过组合这些瓦片来构建整个地图// 使用 32×32 瓦片构建同样 1920×1080 的场景 横向瓦片数1920 / 32 60 纵向瓦片数1080 / 32 34 总瓦片数60 × 34 2040 个瓦片关键优势在于多个瓦片可以共享同一纹理资源。即使地图上有 2040 个瓦片实际纹理内存可能只需要几十KB。2. 瓦片清单设计纹理组织的艺术2.1 什么是瓦片清单Tile Sheet瓦片清单是一张包含所有瓦片图像的纹理图集Texture Atlas。合理的瓦片清单设计直接影响游戏性能和开发效率。// 理想的瓦片清单特征 - 尺寸为 2 的幂次方1024×1024、2048×2048 等 - 瓦片排列紧凑无浪费空间 - 逻辑相关的瓦片分组排列 - 预留扩展空间供后续更新2.2 创建瓦片清单的实践建议使用图像编辑工具如 Aseprite、Tiled 或 Photoshop创建瓦片清单确定瓦片尺寸根据游戏风格选择 16×16、32×32 或 64×64规划瓦片布局将地形、建筑、装饰物等分类排列添加边界间隔瓦片间留 1-2 像素间隔避免纹理渗漏导出优化格式使用 PNG 等支持透明度的格式3. SFML 环境准备与基础配置3.1 项目设置与依赖配置确保你的开发环境已正确配置 SFML。以下是 CMakeLists.txt 的基本配置cmake_minimum_required(VERSION 3.10) project(TileMapDemo) set(CMAKE_CXX_STANDARD 17) # 查找 SFML 库 find_package(SFML 2.5 COMPONENTS graphics system window REQUIRED) # 创建可执行文件 add_executable(TileMapDemo main.cpp) # 链接 SFML 库 target_link_libraries(TileMapDemo sfml-graphics sfml-window sfml-system) # 复制资源文件到输出目录 file(COPY resources DESTINATION ${CMAKE_BINARY_DIR})3.2 基础项目结构TileMapDemo/ ├── CMakeLists.txt ├── main.cpp └── resources/ ├── tilesheet.png # 瓦片清单纹理 ├── map01.txt # 地图数据文件 └── config.ini # 配置文件4. 瓦片地图核心类设计4.1 瓦片数据结构定义首先定义瓦片的基本属性// Tile.h #ifndef TILE_H #define TILE_H #include SFML/Graphics.hpp // 瓦片类型枚举 enum class TileType { EMPTY 0, // 空瓦片 GROUND 1, // 地面 WALL 2, // 墙壁 WATER 3, // 水 LAVA 4, // 岩浆 // 可扩展更多类型 }; struct Tile { TileType type; // 瓦片类型 int textureIndex; // 在瓦片清单中的索引 bool collidable; // 是否可碰撞 bool animated; // 是否动画瓦片 int animationFrame; // 当前动画帧 // 构造函数 Tile(TileType t TileType::EMPTY, int idx -1, bool coll false) : type(t), textureIndex(idx), collidable(coll), animated(false), animationFrame(0) {} }; #endif4.2 瓦片地图类实现核心的瓦片地图管理类// TileMap.h #ifndef TILEMAP_H #define TILEMAP_H #include SFML/Graphics.hpp #include vector #include string #include Tile.h class TileMap : public sf::Drawable, public sf::Transformable { private: std::vectorstd::vectorTile m_tiles; // 瓦片数据 sf::VertexArray m_vertices; // 顶点数组 sf::Texture m_tileset; // 瓦片清单纹理 sf::Vector2u m_tileSize; // 单个瓦片尺寸 unsigned int m_width, m_height; // 地图尺寸瓦片数 virtual void draw(sf::RenderTarget target, sf::RenderStates states) const; public: TileMap(); // 加载瓦片清单纹理 bool loadTileset(const std::string tilesetPath, sf::Vector2u tileSize); // 从文件加载地图数据 bool loadMapFromFile(const std::string filename); // 动态创建地图 void createMap(unsigned int width, unsigned int height, const std::vectorstd::vectorint tileData); // 获取指定位置的瓦片 Tile getTile(unsigned int x, unsigned int y) const; // 设置指定位置的瓦片 void setTile(unsigned int x, unsigned int y, const Tile tile); // 检查碰撞 bool checkCollision(const sf::FloatRect bounds) const; // 更新动画瓦片 void update(float deltaTime); // 获取地图尺寸信息 sf::Vector2u getMapSize() const { return sf::Vector2u(m_width, m_height); } sf::Vector2f getWorldSize() const { return sf::Vector2f(m_width * m_tileSize.x, m_height * m_tileSize.y); } }; #endif5. 完整的瓦片地图实现5.1 核心渲染逻辑// TileMap.cpp #include TileMap.h #include fstream #include sstream #include iostream TileMap::TileMap() : m_vertices(sf::Quads), m_tileSize(0, 0), m_width(0), m_height(0) { } bool TileMap::loadTileset(const std::string tilesetPath, sf::Vector2u tileSize) { // 加载纹理 if (!m_tileset.loadFromFile(tilesetPath)) { std::cerr Failed to load tileset: tilesetPath std::endl; return false; } m_tileSize tileSize; return true; } bool TileMap::loadMapFromFile(const std::string filename) { std::ifstream file(filename); if (!file.is_open()) { std::cerr Failed to open map file: filename std::endl; return false; } // 读取地图尺寸 file m_width m_height; // 调整瓦片数据容器大小 m_tiles.resize(m_height, std::vectorTile(m_width)); // 读取瓦片数据 for (unsigned int y 0; y m_height; y) { for (unsigned int x 0; x m_width; x) { int tileType, textureIndex, collidable; file tileType textureIndex collidable; m_tiles[y][x] Tile( static_castTileType(tileType), textureIndex, collidable ! 0 ); } } file.close(); // 重新构建顶点数组 m_vertices.clear(); m_vertices.setPrimitiveType(sf::Quads); m_vertices.resize(m_width * m_height * 4); // 填充顶点数据 for (unsigned int y 0; y m_height; y) { for (unsigned int x 0; x m_width; x) { Tile tile m_tiles[y][x]; if (tile.textureIndex 0) continue; // 跳过空瓦片 // 获取瓦片在纹理中的位置 int tu tile.textureIndex % (m_tileset.getSize().x / m_tileSize.x); int tv tile.textureIndex / (m_tileset.getSize().x / m_tileSize.x); // 获取当前瓦片的四个顶点 sf::Vertex* quad m_vertices[(x y * m_width) * 4]; // 定义四个顶点的位置 quad[0].position sf::Vector2f(x * m_tileSize.x, y * m_tileSize.y); quad[1].position sf::Vector2f((x 1) * m_tileSize.x, y * m_tileSize.y); quad[2].position sf::Vector2f((x 1) * m_tileSize.x, (y 1) * m_tileSize.y); quad[3].position sf::Vector2f(x * m_tileSize.x, (y 1) * m_tileSize.y); // 定义四个顶点的纹理坐标 quad[0].texCoords sf::Vector2f(tu * m_tileSize.x, tv * m_tileSize.y); quad[1].texCoords sf::Vector2f((tu 1) * m_tileSize.x, tv * m_tileSize.y); quad[2].texCoords sf::Vector2f((tu 1) * m_tileSize.x, (tv 1) * m_tileSize.y); quad[3].texCoords sf::Vector2f(tu * m_tileSize.x, (tv 1) * m_tileSize.y); } } return true; } void TileMap::draw(sf::RenderTarget target, sf::RenderStates states) const { states.transform * getTransform(); states.texture m_tileset; target.draw(m_vertices, states); }5.2 地图数据文件格式创建简单的地图数据文件格式# map01.txt - 地图数据文件 20 15 # 地图宽度20瓦片高度15瓦片 1 0 1 # 瓦片类型1, 纹理索引0, 可碰撞1 1 0 1 # 重复直到填满地图...更复杂的版本可以使用 CSV 或 JSON 格式// map01.json - JSON 格式地图数据 { width: 20, height: 15, tileSize: 32, tileset: resources/tilesheet.png, layers: [ { name: ground, data: [1,1,1,1,1,1,1,1,...] }, { name: decorations, data: [0,0,0,5,0,0,6,0,...] } ] }6. 高级特性实现6.1 动画瓦片支持扩展 Tile 类和 TileMap 类以支持动画瓦片// 在 Tile.h 中添加动画瓦片结构 struct AnimatedTile { std::vectorint frameIndices; // 动画帧序列 float frameDuration; // 每帧持续时间秒 bool loop; // 是否循环播放 }; // 在 TileMap.cpp 中添加动画更新逻辑 void TileMap::update(float deltaTime) { static float accumulatedTime 0.0f; accumulatedTime deltaTime; for (unsigned int y 0; y m_height; y) { for (unsigned int x 0; x m_width; x) { Tile tile m_tiles[y][x]; if (tile.animated) { // 更新动画逻辑 // 这里需要维护动画状态机 } } } }6.2 视口裁剪优化只渲染可见区域的瓦片以提升性能void TileMap::draw(sf::RenderTarget target, sf::RenderStates states) const { states.transform * getTransform(); states.texture m_tileset; // 获取视口边界世界坐标 sf::FloatRect viewBounds target.getView().getViewport(); // 计算可见瓦片范围 unsigned int startX std::max(0, static_castint(viewBounds.left / m_tileSize.x)); unsigned int endX std::min(m_width, static_castunsigned int( (viewBounds.left viewBounds.width) / m_tileSize.x 1)); unsigned int startY std::max(0, static_castint(viewBounds.top / m_tileSize.y)); unsigned int endY std::min(m_height, static_castunsigned int( (viewBounds.top viewBounds.height) / m_tileSize.y 1)); // 只渲染可见瓦片 for (unsigned int y startY; y endY; y) { for (unsigned int x startX; x endX; x) { // 渲染单个瓦片或使用批处理优化 } } }7. 碰撞检测系统7.1 基于瓦片的碰撞检测bool TileMap::checkCollision(const sf::FloatRect bounds) const { // 将边界框转换为瓦片坐标 int leftTile static_castint(bounds.left / m_tileSize.x); int rightTile static_castint((bounds.left bounds.width) / m_tileSize.x); int topTile static_castint(bounds.top / m_tileSize.y); int bottomTile static_castint((bounds.top bounds.height) / m_tileSize.y); // 检查边界内的所有瓦片 for (int y topTile; y bottomTile; y) { for (int x leftTile; x rightTile; x) { // 检查坐标是否有效 if (x 0 x static_castint(m_width) y 0 y static_castint(m_height)) { const Tile tile m_tiles[y][x]; if (tile.collidable) { // 创建瓦片的边界框进行精确检测 sf::FloatRect tileBounds( x * m_tileSize.x, y * m_tileSize.y, m_tileSize.x, m_tileSize.y ); if (bounds.intersects(tileBounds)) { return true; } } } } } return false; }7.2 精确碰撞检测优化对于需要更精确碰撞的场景如斜坡、平台边缘// 扩展 Tile 结构支持碰撞形状 struct CollisionShape { enum Type { RECTANGLE, SLOPE_LEFT, SLOPE_RIGHT, TRIANGLE }; Type type; std::vectorsf::Vector2f vertices; // 自定义碰撞多边形 }; // 在碰撞检测中使用精确形状 bool checkPreciseCollision(const sf::FloatRect bounds, const Tile tile, const sf::Vector2f tilePosition) { // 根据瓦片的碰撞形状类型进行检测 switch (tile.collisionShape.type) { case CollisionShape::RECTANGLE: return bounds.intersects(sf::FloatRect( tilePosition, sf::Vector2f(m_tileSize) )); case CollisionShape::SLOPE_LEFT: // 实现斜坡碰撞逻辑 break; // 其他形状... } return false; }8. 性能优化与最佳实践8.1 批处理与渲染优化// 使用顶点数组批处理 void TileMap::optimizeRendering() { // 合并相同纹理的连续瓦片 // 减少绘制调用次数 } // 使用纹理图集避免状态切换 class TextureAtlas { private: sf::Texture m_texture; std::mapstd::string, sf::IntRect m_regions; public: bool loadFromFile(const std::string filename); void addRegion(const std::string name, const sf::IntRect region); const sf::IntRect getRegion(const std::string name) const; };8.2 内存管理与动态加载对于大型地图实现分块加载机制class ChunkedTileMap { private: std::vectorstd::vectorTileChunk m_chunks; sf::Vector2u m_chunkSize; // 每个区块的瓦片数 public: void loadChunk(int chunkX, int chunkY); void unloadChunk(int chunkX, int chunkY); void updateVisibleChunks(const sf::View view); };9. 实战案例平台游戏地图9.1 完整的主循环示例// main.cpp #include SFML/Graphics.hpp #include TileMap.h int main() { // 创建窗口 sf::RenderWindow window(sf::VideoMode(800, 600), SFML Tile Map Demo); window.setFramerateLimit(60); // 创建瓦片地图 TileMap map; if (!map.loadTileset(resources/tilesheet.png, sf::Vector2u(32, 32))) { return -1; } if (!map.loadMapFromFile(resources/map01.txt)) { return -1; } // 创建玩家精灵 sf::Texture playerTexture; if (!playerTexture.loadFromFile(resources/player.png)) { return -1; } sf::Sprite player(playerTexture); player.setPosition(100, 100); // 游戏主循环 sf::Clock clock; while (window.isOpen()) { sf::Time deltaTime clock.restart(); float dt deltaTime.asSeconds(); // 事件处理 sf::Event event; while (window.pollEvent(event)) { if (event.type sf::Event::Closed) window.close(); } // 玩家移动逻辑 sf::Vector2f movement(0, 0); if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) movement.x - 200.0f * dt; if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) movement.x 200.0f * dt; if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) movement.y - 200.0f * dt; if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) movement.y 200.0f * dt; // 碰撞检测 sf::FloatRect nextBounds player.getGlobalBounds(); nextBounds.left movement.x; nextBounds.top movement.y; if (!map.checkCollision(nextBounds)) { player.move(movement); } // 更新游戏状态 map.update(dt); // 渲染 window.clear(); window.draw(map); window.draw(player); window.display(); } return 0; }10. 常见问题与解决方案10.1 纹理渗漏Bleeding问题问题现象瓦片边缘出现相邻瓦片的像素解决方案在瓦片清单中为每个瓦片添加 1 像素边框使用纹理 clamping 模式调整纹理坐标时向内收缩 0.5 像素// 修正纹理坐标计算 quad[0].texCoords sf::Vector2f(tu * m_tileSize.x 0.5f, tv * m_tileSize.y 0.5f); quad[1].texCoords sf::Vector2f((tu 1) * m_tileSize.x - 0.5f, tv * m_tileSize.y 0.5f); // ... 其他顶点类似10.2 性能瓶颈排查低帧率可能原因渲染了不可见瓦片 → 实现视口裁剪纹理状态切换频繁 → 使用纹理图集碰撞检测过于复杂 → 使用空间分区优化10.3 内存占用优化// 使用瓦片对象池避免频繁内存分配 class TilePool { private: std::vectorTile m_pool; size_t m_nextIndex; public: void initialize(size_t capacity); Tile* acquireTile(); void releaseTile(Tile* tile); };瓦片地图技术是 2D 游戏开发的基石掌握它意味着你能够构建任意规模的世界而不用担心性能问题。本文介绍的方法虽然基于 SFML但其核心思想适用于任何图形框架。关键在于理解空间复用、批处理渲染和动态加载这些通用优化技术。实际项目中你可以进一步扩展这个基础系统添加多层地图支持、实现自动瓦片Autotiling、集成地图编辑器导出格式等。良好的地图系统应该像乐高积木一样让关卡设计师能够快速构建丰富场景而不用担心技术限制。

相关新闻

Go语言Context机制解析与并发编程实践

Go语言Context机制解析与并发编程实践

1. Go Context源码深度解析与实战指南Context是Go语言并发编程的核心机制之一,它像一根看不见的线,将跨goroutine的请求链路、超时控制和元数据传递串联起来。我在处理分布式追踪系统时,曾因Context使用不当导致整个调用链断裂,那…

2026/10/14 11:45:48 阅读更多 →
大学生如何根据专业需求选择Mac设备:场景优先于参数

大学生如何根据专业需求选择Mac设备:场景优先于参数

去年帮亲戚家刚上大学的孩子选电脑,我花了整整一个下午对比参数、跑实体店、查教育优惠,最后发现一个扎心的事实:大多数人纠结的“选哪个型号”,其实问错了问题。真正关键的不是比较MacBook、Mac mini和iMac的纸面参数&#xff0c…

2026/10/14 18:03:25 阅读更多 →
泰戈尔的诗歌20

泰戈尔的诗歌20

十二点钟妈妈,我真想现在不做功课了,我整个早晨都在念书呢。你说,现在还不过是十二点钟。假定不会晚过十二点吧,难道你不能把不过是十二点钟想象成下午么?我能够容容易易地想象:现在太阳已经到了那片稻田的…

2026/10/14 13:57:22 阅读更多 →

最新新闻

MySQL数据库进阶应用(1)—事务管理

MySQL数据库进阶应用(1)—事务管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 6:28:53 阅读更多 →
软件硬件:个人PC核心硬件全景深度分析

软件硬件:个人PC核心硬件全景深度分析

前言一台完整个人电脑(台式机/笔记本)由核心计算硬件、存储硬件、输入输出硬件、供电散热硬件、板载互联硬件五大模块构成。硬件之间通过总线、接口协同工作,任何一个部件瓶颈都会限制整机性能。本文使用多表格拆解各硬件定位、核心参数、性能…

2026/10/16 6:28:53 阅读更多 →
跳表Skip List原理与C语言实现:构建高效有序映射结构

跳表Skip List原理与C语言实现:构建高效有序映射结构

如果你接手过一个需要常驻内存、按键有序、还要支持范围查询的映射结构,大概率会在选型上纠结一阵:哈希表查询快但无序,有序数组遍历好但插入删除要搬数据,红黑树理论完美但实现起来总让人头皮发麻。跳表 Skip List 是这类场景里性…

2026/10/16 6:28:53 阅读更多 →
灵珠AI好用吗?2026年零代码AI应用平台功能与适用人群分析

灵珠AI好用吗?2026年零代码AI应用平台功能与适用人群分析

CSDN 博客|原创|AI 应用|零代码|AI 创作 标签:灵珠 AI、零代码 AI 应用平台、AI 应用创作、魔改、AI 互动内容前言大模型快速普及之后,AI 工具赛道分化明显:一类面向程序员,帮助写代…

2026/10/16 6:28:53 阅读更多 →
题解:洛谷 P1672 [USACO05FEB] Feed Accounting S

题解:洛谷 P1672 [USACO05FEB] Feed Accounting S

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大…

2026/10/16 6:28:53 阅读更多 →
Lunar 6.7.11 修复说明:Sync Mode 在 MacBook Air 与 iMac 上改用 nits 同步

Lunar 6.7.11 修复说明:Sync Mode 在 MacBook Air 与 iMac 上改用 nits 同步

桌面应用 【免费下载链接】Lunar Intelligent adaptive brightness for your external monitors 项目地址: https://gitcode.com/gh_mirrors/lu/Lunar 点击查看 免费下载 Lunar 6.7.11 是一个针对 Sync Mode(同步模式) 的定向修复版本。此次…

2026/10/16 6:27:53 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课,其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置,通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测,本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章,跟着敲完了投票应用,大概会有一种"我好像会了,又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型,下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →