# b2d **Repository Path**: haart/b2d ## Basic Information - **Project Name**: b2d - **Description**: 2d游戏引擎 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-24 - **Last Updated**: 2026-09-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # B2D B2D 是使用 Free Pascal 编写的 DOS/街机风格 2D 像素游戏引擎。当前平台后端支持 Windows 10/11 x64,生成 `b2d.dll`,通过稳定的 `cdecl` C ABI 供 C、C++、Free Pascal 和其它支持 C ABI 的语言调用。项目不依赖 C/C++ 工具链、SDL、GLFW、现成游戏引擎或 FCL 图片类。 当前运行时和公共接口固定为 API 1.0(`0x00010000`),共导出 121 个函数。项目仍处于开发阶段,暂不通过版本号表达兼容性或接口演进;Demo 不在启动时检查版本。公共声明见 `include/b2d.h`,Pascal SDK 见 `sdk/pascal/b2d_api.pas`。 完整设计见 `docs/b2d-design.md`;固定 Tick、追赶和暂停规则见 `docs/next-tick-game-loop-design.md`;Sprite/Move 与资源格式见 `docs/simple-sprite-temporary-rules.md`;类型化文本 ZIP 存档见 `docs/b2d-save-format.md`;已实现的推箱子 Demo2 见 `docs/demo2-roadmap.md`。 ## 核心规格 - 固定逻辑画面 `640×480`,窗口支持 `1×/2×/3×/4×` 整数放大和无边框全屏;不能整数铺满时使用黑边。 - 背景瓦片为 `16×16`,每层 `40×30`;碰撞网格独立保持 `8×8`、`80×60`。 - TileID `0` 表示空瓦片,`1..255` 是内置字形,`256..65535` 用于自定义图片、汉字和卡通瓦片。 - 瓦片与软合成使用 8 位索引色;索引 `0` 透明,最终画面映射为 `640×480` RGB888。 - OpenGL 3.3 Core 只负责上传、整数缩放和显示 RGB888,不处理调色板、透明或图层。 - 显示顺序固定为:底背景 → 背景3 → 卡通后层 → 背景2 → 卡通中层 → 背景1 → 卡通前层 → 背景0;每个相邻位置都提供固定的 Overlay 插入点。 - 固定保留四个背景层和三个卡通层,不增加通用 UI 层;最多 16 个屏幕空间纯色矩形 Overlay 可分别设置坐标、RGB、Alpha 和固定插入点。 - 同一卡通层内 SpriteID 越小越靠前,渲染顺序不受创建顺序、Y 坐标或运行顺序影响。 ## Tick 与线程 成功调用 `b2d_initialize` 的应用线程固定为 GameScriptThread,也是可变场景状态的唯一拥有者。引擎内部另有: - UIThread:管理 Win32 窗口、菜单、焦点和输入事件队列; - RenderThread:永久独占 OpenGL Context,只读取不可变 RenderSnapshot、转换 RGB888 并 Present; - 音频后端线程:持续消费短音混音数据。 API 1.0 使用固定 Tick 架构,不使用 Fiber、AsyncTask、`b2d_delay`、SceneTickThread、GameCommandQueue 或 SceneQuerySnapshot。游戏以固定 60 Hz 主循环运行: ```c b2d_tick_info tick = {0}; tick.size = sizeof(tick); while (!b2d_quit_requested()) { /* 查询输入、碰撞和 Sprite/Move 状态 */ /* 修改场景、启动 Move、播放声音 */ if (!b2d_next_tick(&tick)) { break; } } ``` `b2d_next_tick` 会先消费平台事件并优先处理退出请求,再软合成当前候选场景、发布 RenderSnapshot,随后等待或追赶下一个绝对截止时间;等待期间到达的事件也会立即唤醒并再次处理。到达截止时间后只推进恰好一个 Move/动画逻辑 Tick,然后返回下一候选 Tick。一次调用永远不会批量推进多个逻辑 Tick。 `b2d_tick_info` 提供: - `tick_index`:已经完成的逻辑 Tick 总数;首次成功调用返回 1; - `elapsed_wall_ticks`:本次新跨过的墙钟 Tick 边界数;正常为 1,逐 Tick 追赶时通常为 0; - `ticks_behind`:仍需由后续游戏循环逐次追赶的 Tick 数,最大 5; - `dropped_wall_ticks`:为防止死亡螺旋而丢弃的墙钟债务,不代表跳过游戏逻辑; - flags:时钟重定位、自动暂停恢复、追赶、丢时钟、输入重置和手动逻辑暂停。 初始化、资源准备和场景定义不会形成 Tick 债务。资源包/地图切换、取消退出、系统超长时间跳跃或显式调用 `b2d_reset_tick_clock` 后,下一个 Tick 会重新建立时钟。 ## 自动暂停 可见窗口默认在失焦、原生菜单、最小化和模态界面期间自动暂停;隐藏测试窗口默认不自动暂停。`SIZE_MOVE` 支持但默认关闭。 ```c b2d_set_auto_pause( B2D_AUTO_PAUSE_FOCUS_LOST | B2D_AUTO_PAUSE_MENU_LOOP | B2D_AUTO_PAUSE_MINIMIZED | B2D_AUTO_PAUSE_MODAL_UI ); ``` 自动暂停只在安全 Tick 边界阻塞 `b2d_next_tick`,不停止 UIThread 或 RenderThread,也不累计暂停期间的 Tick 债务。恢复时清除不可信输入并重定位时钟。默认音频策略是 `B2D_AUDIO_PAUSE_MUTE`;`CONTINUE` 已支持,当前 Windows 后端不支持精确 `FREEZE`,请求会明确失败。 游戏自己的暂停菜单使用 `b2d_set_logic_paused`:`b2d_next_tick` 仍以 60 Hz 返回并处理输入和 UI,但 Move/动画不推进。 ## 背景输出 `b2d_put_tile`、`b2d_print` 等函数只修改保留式内存场景,不直接调用 OpenGL: ```c b2d_use_bg(B2D_BACKGROUND_1); b2d_locate(2, 3); b2d_print("Hello 中文"); ``` `b2d_print` 严格解码 UTF-8,并把 BMP Unicode 码点数值作为 TileID 写入背景。尚未加载的 TileID 仍保留在地图中,加载同编号瓦片后会自动显示。 游戏可以把背景0约定为界面和覆盖层,通过清空、定位、输出文字或放置自定义 Tile 实现状态栏、对话框、打字机、闪烁提示和字符画等效果。背景0和卡通前层仍属于场景并共同受全局 Camera 影响;固定视口游戏通常保持 Camera 为零,滚动场景则由游戏在 Camera 改变时同步补偿或重绘界面。 `b2d_set_overlay_rect` 是兼容入口,设置 ID 0、位于卡通前层之后和背景0之前的纯色矩形。`b2d_set_overlay_rect_ex` 可使用 ID `0..15` 和八个固定插入点创建多个矩形;每个矩形都有独立坐标、RGB 和 Alpha `1..255`,且不受 Camera 影响。同一插入点按 ID 从小到大合成: ```c b2d_set_overlay_rect_ex( 0, B2D_OVERLAY_BEFORE_BACKGROUND_0, 128, 176, 384, 208, 0, 0, 0, 77 ); /* 在背景0绘制的按钮和文字不会被压暗。 */ ``` `b2d_clear_overlay_rect(id)` 单独清除一个矩形,`b2d_clear_overlays()` 清除全部矩形;旧的 `b2d_clear_overlay()` 只清除兼容槽 ID 0。 再次使用同一个 ID 调用 `b2d_set_overlay_rect_ex` 会原地更新该 Overlay 的插入点、坐标、尺寸、颜色和 Alpha,不影响其它 ID。游戏可以在每个逻辑 Tick 计算新参数并再次设置;变化会进入下一张 RenderSnapshot,用于淡入淡出、移动、缩放或颜色渐变,不需要额外的 Overlay 动画对象。 ## Sprite 与 Move 静态 Sprite 无动画、无自动运动;定义时复制 TileID 网格: ```c uint16_t tiles[] = {256, 257, 258, 259}; b2d_def_sprite( 10, 2, 2, tiles, B2D_SPRITE_LAYER_FRONT, B2D_SPRITE_FLIP_X ); b2d_show_sprite(10, 100, 80); ``` Move 是固定方向、速度、距离、原地动画重复次数和图层的脚本式运动,并引用资源包内只读 MovePic: ```c b2d_load_pack("game.zip"); b2d_def_move(20, 100, 3, 60, 128, 0, B2D_SPRITE_LAYER_MIDDLE); b2d_move_sprite(20, 32, 200); while (b2d_is_sprite_moving(20)) { if (!b2d_next_tick(NULL)) { break; } } ``` Speed 单位为逻辑像素/秒,范围 `0..255`;Distance 为沿方向的逻辑像素,范围 `0..65535`。当 `Distance>0` 时,动画在路径期间循环、`RepeatCount` 不参与完成,路径到达终点时强制显示方向序列最后一帧。当 `Distance=0` 时,`RepeatCount=0` 表示无限原地动画,`1..255` 表示完整播放次数,有限动画自然结束后同样保持最后一帧。`b2d_cut_sprite` 始终保留切断当时的帧。动画默认每帧 4 Tick,即 15 FPS;`b2d_set_move_duration_ticks(1..255)` 可全局调整。 同一套静态 Sprite 或 MovePic 动画可以使用 Palette Map 切换配色,不需要复制 Sprite、Move 或 Tile: ```c uint8_t energy_map[B2D_PALETTE_MAP_SIZE]; for (uint32_t i = 0; i < B2D_PALETTE_MAP_SIZE; ++i) { energy_map[i] = (uint8_t)i; } energy_map[32] = 80; energy_map[33] = 81; b2d_define_palette_map(1, energy_map); b2d_set_sprite_palette_map(20, 1); /* 获得能量 */ b2d_set_sprite_palette_map(20, 0); /* 恢复原色 */ ``` 映射 ID 0 固定为原色,1..255 可定义和实时重定义。索引 0 必须保持 0,非零索引不能映射到 0,因此换色不会改变透明区域。切换或重定义映射不会重置位置、可见性、Move 当前帧、移动状态或 `CUT` 结果;只影响 Sprite/Move,不影响背景、文字、Overlay、碰撞和 Tile 原始像素。换包会连同 Sprite/Move 一起清空映射定义。完整规则见 `docs/simple-sprite-temporary-rules.md`。 ## PLAY 音乐 `b2d_play` 使用 QuickBASIC 核心 MML 规则异步播放乐谱,支持 `T32..T255`、`O0..O6`、`L1..L64`、`A..G`、后缀升降音 `C#`/`C+`/`D-`、`P`、兼容休止符 `R`、`N0..N84`、`MN`/`MS`/`ML`、附点和 `<`/`>`。`MB`、`MF` 都会接受,但 B2D 始终异步;冒号可组成最多八音的和声,例如 `O4L4 C:E:G`。`b2d_is_music_playing` 查询乐谱或 WAV/MP3 是否仍在播放,`b2d_set_master_volume(0..1000)` 控制全局音量。默认原生菜单的 `Settings -> Volume...` 提供同一主音量设置。完整规则见 `docs/b2d-play-mml.md`。 ## 资源包 正式资源使用 UTF-8 `.b2drc` 描述: ```text b2dpack build game.b2drc game.zip ``` 完整游戏包包含: - `meta/b2d-index.bin`:`B2DRIDX1` Tileset 和包级调色板索引; - 可选 `sprites/movepics.bin`:`B2DMVP01` 只读 MovePic 表; - 可选 `meta/manifest.ini`、地图、声音、音乐和普通资源。 `b2d_load_pack` 先建立并完整验证独立候选包。失败时旧包和场景保持不变;成功时一次性重建包对象、包拥有 Tile、MovePic、背景、碰撞、Sprite 和 Move,并执行 InputReset 与 TickClockRebase。运行时只激活一个资源包。 API 1.0 提供相对路径原始用户文件 API 和类型化存档文档 API,并支持零复制的 PE/ELF 资源包视图加载。`.b2dsave` 是标准 ZIP,核心文件为 UTF-8 `save.txt`;运行时自动解压/压缩、检查 CRC 和类型,并通过临时文件与原子替换写入。存档支持 Section、UTF-8 Key、整数、浮点、布尔、UTF-8、字节、日期、UTC 时间和一维同类型数组。资源嵌入方式见 `docs/b2d-embedded-resources.md`,存档规则见 `docs/b2d-save-format.md`。 低级调试命令包括: ```text b2dpack palette output.pal b2dpack pcx input.png output.pcx b2dpack pcx input.tga output.pcx --palette game.pal --transparent FF00FF b2dpack tile-table output.png font.fon first-tile-id tile-count input.pcx [...] b2dpack map input.txt output.b2dmap b2dpack pack output.zip archive/path=source/path [...] b2dpack resource input.zip output.res [resource-id] b2dsave dump input.b2dsave output.txt b2dsave build input.txt output.b2dsave b2dsave verify input.b2dsave b2dsave info input.b2dsave ``` 低级 `pack` 生成的普通 ZIP 没有 `meta/b2d-index.bin`,不能作为完整游戏包传给 `b2d_load_pack`。 ## 图片转换 `b2dpack` 直接读取未压缩 TGA。PNG、GIF、WebP 等输入由官方 `ffwebp full` 转换为临时 TGA,查找顺序为当前目录、`b2dpack` 所在目录、`PATH`。Windows 使用 `CreateProcessW`;已设计的 Linux 隔离层使用 `fork/exec/wait`。不需要 C 编译器。 - 接受 8 位灰度和带有效 Alpha 属性的 32 位真彩色 TGA; - 预乘 Alpha 会先反预乘;Alpha 0 透明,Alpha `1..255` 按完全不透明处理; - 可选 `--transparent RRGGBB` 在反预乘后精确匹配透明色; - 不抖动;可见颜色在调色板索引 `1..255` 中搜索感知最近色;算法在加权 RGB 距离上保留最低源彩度并约束环形色相,避免低饱和颜色不必要地退化为灰阶; - 图片按 `16×16` 切分,边缘补透明,并删除全部全透明瓦片;删除项不占 TileID。 默认调色板是内置 `B2D Aurora-255`:索引 0 保留透明,索引 1 保存纯黑,移除 Aurora 原始索引 1 的 `17,17,17`,原索引 `2..255` 保持不变。JASC 文件位于 `assets/palettes/b2d-aurora-255.pal`。 ## 通用素材包 `assets/common/classic-roguelike/` 提供可由多个 Demo 共用的经典 Roguelike 资源源包: - TileID `1024..2727` 保存 Kenney Roguelike Pack 的 1704 个非空 Tile; - 地板、石墙、目标和箱子不再复制到项目自绘的语义核心区, `semantic-tile-map.csv` 直接把稳定名称映射到 Kenney TileID; - Demo2 直接使用公共 Kenney TileID:`0x04ED` 地板、`0x06A3..0x06A9`/`0x06DC..0x06E2` 墙顶和 `0x074C..0x074F` 墙面 墙顶/墙面组件、四块目标区域以及 `0x064F/0x0650` 木箱; - TileID `4096..4479` 保存 12 个四方向双帧原始 `32×32` 角色(每帧四个引擎瓦片); - MovePicID `1000..1011` 对应 `man1..4`、`mnt1..4`、`mnv1..4`, `1012` 对应 Demo2 统一普通木箱的移动图; - TileID `8192..8298` 是单独维护的项目原创键帽图集,包含数字、字母、精简标点、空白键帽、常用功能键、小号字形 `F1..F9`、四个独立方向键和 `WASD`;后续只从 `8299` 起追加; - `common-resources.b2drc.inc` 可拼接到更多 Demo 的资源配方中; - 构建输出 `b2d-common-resources.zip/.res`,Demo2 的最终包复用同一声明并追加 自己的关卡资源。 环境素材采用 Kenney 的 CC0;角色由 Philipp Lenssen 绘制,采用 CC BY 3.0, 分发时必须保留作者署名。完整映射、重新生成方式和授权说明见 `assets/common/classic-roguelike/README.md`。 ## 构建与测试 固定使用 Free Pascal 3.2.2: ```powershell .\build.ps1 -Target Debug .\build.ps1 -Target Release .\build.ps1 -Target Tests ``` Windows 下,Debug 版的 Demo1/Demo2 使用 Console 子系统并保留控制台,便于查看诊断输出;Release 版使用 GUI 子系统,正常启动时不会创建控制台窗口。`b2dpack`、`b2dsave` 和素材生成器在两种配置下都保持命令行程序。Linux 不使用 Windows 子系统设置。 主要输出: ```text build/debug/b2d.dll build/debug/demo1.exe build/debug/demo2.exe build/debug/b2d-common-resources.zip build/debug/b2d-common-resources.res build/debug/demo2-resources.res build/debug/b2dpack.exe build/debug/b2dsave.exe build/release/b2d.dll build/release/demo1.exe build/release/demo2.exe build/release/b2d-common-resources.zip build/release/b2d-common-resources.res build/release/demo2-resources.res build/release/b2dpack.exe build/release/b2dsave.exe ``` Demo1 正常启动会创建可见窗口;自动化连通性测试可使用: ```powershell .\build\debug\demo1.exe --smoke-test ``` 该模式使用隐藏窗口运行 3 个逻辑 Tick,验证 DLL 加载、场景定义、RenderThread 和 `b2d_next_tick` 后正常关闭。 Demo2 是一款包含 50 个可解关卡的推箱子游戏,使用通用经典 Roguelike 素材包、Demo2 专用键帽 TGA、`b2dpack build` 资源包、包内 UTF-8 关卡、背景 TileMap、静态 Sprite、Action 输入、暂停、选关和用户进度存档。关卡每 5 关提升一个难度档,通过尺寸、隔板、凹槽、箱子数和推动次数递增难度;游戏不提供 Undo,卡死时按 `R` 重开本关: ```powershell .\build\debug\demo2.exe .\build\debug\demo2.exe --smoke-test .\build\debug\demo2.exe --smoke-test-title .\build\debug\demo2.exe --smoke-test-progress .\build\debug\demo2.exe --smoke-test-move ``` 标题 Smoke Test 会核对完整 RGB888 标题帧哈希;进度 Smoke Test 会通过公共存档 ABI 完成、保存并重新加载全部 50 关。关卡基准解法用于证明可解和稳定回归,不声明为最优解;发布前人工试玩清单见 `docs/demo2-playtest.md`。 `Tests` 会核对 C 头、Pascal SDK、DEF、Pascal exports 和 DLL 实际导出一致,并覆盖窗口/UI/Render 线程、固定 Tick、追赶和丢时钟、线程归属与线程局部 LastError、RenderSnapshot、Sprite/Move、资源换包、通用素材包的 Tile/MovePic/许可证索引、用户文件、类型化存档、文本/ZIP 往返、`b2dsave`、输入重置、PCX/TGA、ZIP、音频混音、Demo2 关卡/存档和 DLL 集成。 ## 编码约束 - Pascal 源码使用 `{$mode fpc}`、`{$H+}`、`{$modeswitch result}`;需要 `out` 时启用 `{$modeswitch out}`。 - 所有 Pascal 源文件统一使用 `.pas` 扩展名,包括 program/library 入口;项目不使用 `.lpr` 或 Lazarus 工程文件。 - 仅 Win32 Unicode API 边界使用 `UnicodeString`,其它文本统一使用 `String`。 - 不显式声明 `AnsiString`,不使用 Pascal 异常作为正常错误通道。 - 正常错误通过返回值和调用线程自己的 UTF-8 LastError 处理。 - 场景、资源、输入消费、Sprite/Move、音频和窗口公共 API 由 GameScriptThread 调用;自动暂停配置/查询、退出只读查询、帧缓冲复制和 LastError 是明确的线程安全例外。 - 引擎和工具只引入实际需要的单元,不依赖 C/C++ 工具链。