# drawbridge.textwar **Repository Path**: lightsever/drawbridge.textwar ## Basic Information - **Project Name**: drawbridge.textwar - **Description**: No description available - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-08 - **Last Updated**: 2026-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Drawbridge TextWar 使用 Drawbridge 作为渲染接口的汉字 RTS 小游戏。泛黄宣纸上的水墨军略图,黑色「兵」对抗朱红色「兵」,圆圈内的「营」是双方兵营。 ## 游戏流程与操作 页面状态机:`Logo → Menu → Battle → Result → Menu`。Logo 在约 2.9 秒内完成淡入、停留、淡出;主菜单只有「开始游戏」按钮。摧毁敌营胜利,我营被摧毁则失败;结算显示用时、出兵、歼敌与损失,可返回菜单重新开始。 - 左键点击单选,左键拖动框选黑色士兵,选中状态显示虚线圈。 - 右键地面:按小队阵型移动;右键敌营:进攻敌营。 - `Ctrl+A` 全选我军,`Esc` 清除选择。进入枪械射程(110)自动开火,敌军自动进攻我营。 - 中键按住拖动地图,松开即停;`WASD` / 方向键连续滚动,略带加减速。鼠标移到边缘不会自动滚屏,适合窗口化游戏。 - `Space` 在约 150ms 内聚焦选中部队的中心;未选兵时聚焦我营。右下角小地图显示双方兵营、士兵和当前视野,点击或左键拖动可定位。 - 镜头停在地图边界,切出窗口时取消按键滚动和拖动;重新开始重置镜头。 - 初始我军 6 人、敌军 4 人。我方每 1.6 秒出兵,敌方每 3.2 秒出兵;每方最多同时存在 60 人。 - 兵营 100 生命,士兵 3 生命。营下方显示血量及出兵进度;士兵满血隐藏血条,受伤时在头顶显示,选中与否一致。 - 士兵脚底有椭圆阴影,走路时「兵」字四个顶点轻微扭动,停下后恢复。 - 士兵身旁持一把机枪,待机时枪身朝本方面向;开火时枪口指向目标 360° 旋转,朝左镜像,并在开火点播放 5 帧 0.13 秒枪口焰序列,同时在目标点播放 6 帧 0.26 秒击中迸裂效果。射程 110,每 0.45 秒一发,对士兵 1 伤害、对营 2 伤害,命中即时结算。 - 士兵字形显示为 16×16 逻辑像素(原 28×28),机枪、枪口焰、击中效果、阴影与虚线圈同步缩小;720p 下字形约 18 像素,受伤血条保留 2 像素厚度。 战场大小 1920×1080,固定缩放;界面使用 960×640 逻辑坐标,战场视野为 920×432。窗口缩放时等比显示,鼠标先换算至界面坐标,再按镜头换算为战场坐标。HUD 固定在屏幕上;框选、指令标记、血条、机枪和开火/击中效果随地图移动。当前只有一个关卡,无地形障碍和资源采集。 山川与河道是背景画面,不参与碰撞或寻路。士兵使用独立 PNG 字形,默认楷体;UI 文字仍用原来的点阵字体。 ## 替换士兵字体和地图 - `src/TextWar.Demo/assets/units/soldier.png`:透明底字形,默认 28×28。只使用 alpha 覆盖率,黑色或白色字形均可;我军黑、敌军红由渲染时染色。 - `src/TextWar.Demo/assets/backgrounds/ink-map.png`:1536×1024 泛黄宣纸地图,运行时缩放至战场及小地图。 - `assets/units/shadow.png`、`assets/weapons/jiqiang-idle.png`:独立阴影与待机机枪资源(28×20,pivot=(12,11),朝右为基准)。 - `assets/weapons/jiqiang-fire.png`:5 个横排 64×64 枪口焰帧;`assets/weapons/jiqiang-hit.png`:6 个横排 64×64 击中迸裂帧,中心 pivot=(32,32),分别播放 0.13 / 0.26 秒。 - 圆圈、虚线圈和基础色块也从 `assets/ui/*.png` 加载。运行时只读取资源并做顶点动画;离线资源导出脚本在 `scripts/`,可直接替换 PNG/GIF,无需运行导出脚本。 - 替换 PNG 后重新运行项目即可。运行时不依赖 Windows 字体或 System.Drawing,PNG 解码直接复用 drawbridge 示例中的 StbImageSharp 源码,无新增 NuGet 包。 Windows 下可用脚本从本机字体重新导出字形: ```powershell .\scripts\Export-SoldierGlyph.ps1 -FontName 'KaiTi' -Glyph '兵' ``` 机枪与开火/击中序列由 `scripts/Export-MachineGun.ps1` 导出(System.Drawing 白色剪影,运行时按阵营染色);旧的 `dao-*.png` 小刀资源已不再加载,保留供参考。 生成来源、背景提示词见 [assets/ASSETS.md](src/TextWar.Demo/assets/ASSETS.md)。 `src/TextWar.Demo/Game/GameApp.cs` 管理各页面;`BattleWorld.cs` 是不依赖渲染的战斗模拟;`GameCanvas.cs` 通过现有 batcher 绘制全部图形与文字。输入事件进入队列,在游戏线程处理;模拟固定 60Hz 更新。 ## 运行 需要 .NET 10 SDK 或兼容的更新 SDK。Windows 后端使用 Drawbridge 自带的 ANGLE 原生库。 ```powershell git submodule update --init --recursive dotnet build TextWar.slnx # Windows 窗口:从 Logo 开始完整游戏流程 dotnet run --project src/TextWar.Demo # 浏览器后端,使用 localhost:9911,自动打开浏览器 dotnet run --project src/TextWar.Demo -- --web # Windows 无窗口 GPU 回归验证,失败返回非零退出码 dotnet run --project src/TextWar.Demo -- --smoke # 1280×720 GPU 回读,检查士兵在 720p 下的显示效果 dotnet run --project src/TextWar.Demo -- --smoke --720p # Windows 窗口后端回归,额外覆盖窗口覆盖层与 batcher 的状态隔离 dotnet run --project src/TextWar.Demo -- --smoke --window # 鸿蒙实机离屏 EGL Pbuffer 回归(在鸿蒙设备上运行,走系统 libEGL/libGLESv2) dotnet run --project src/TextWar.Demo -- --smoke --harmony # 鸿蒙 HAP 真窗口:.NET 起 TCP server,直接 /system/bin/aa start 拉起 HAP 前端连入(本机即设备,无需 hdc/fport;--bundle 可换 HAP 包名,默认 light.hx.dotnet) dotnet run --project src/TextWar.Demo -- --hap # Drawbridge 窗口按钮回归:GL 状态隔离、连续帧像素稳定、输入空帧不交换缓冲 dotnet run --project drawbridge/tests/Drawbridge.SmokeTest -- --chrome-test # 浏览器 RT 绘制状态与连续像素回归(需要已安装 Playwright Chromium) dotnet run --project drawbridge/tests/Drawbridge.BrowserTest -- --rt-test # 只验证页面切换、框选、移动、出兵、战斗、结算和重新开始 dotnet run --project src/TextWar.Demo -- --logic-test ``` `--smoke` 输出 `artifacts/game-logo.png`、`game-menu.png`、`game-selection.png`、`game-battle.png`、`game-victory.png` 和 `batcher-checks.png`。已验证 Windows ANGLE 无窗口和窗口后端;浏览器入口已编译,尚未做浏览器端回归。 窗口三按钮默认隐藏,鼠标进入整个顶部标题区时淡入,离开后淡出。按钮由 Drawbridge 在游戏帧末绘制;覆盖层使用独立 VAO,并恢复混合、颜色写入、深度、模板、裁剪、光栅化和 framebuffer/viewport 状态;单独输入 ACK 不交换未经重绘的缓冲,悬停按实际鼠标位置计算。`--chrome-test` 在真实 ANGLE 上下文中验证显示/隐藏、首次初始化及 24 个连续帧,检查按钮像素和绘制前后的 GL 状态。 Drawbridge 的完整无窗口测试目前另有既存的 `SystemUiAckRouting` 失败:UI 请求返回业务 ID,测试及 ACK 来源使用命令序号。窗口按钮专项及游戏窗口/无窗口回归可单独运行。 跨平台检查:Web 的 DOM 覆盖层没有自绘三按钮,RT 显示路径的 GL 状态泄露已修复,并通过真实 Chromium 回归。Harmony 的空输入 present 同类问题也已修复,平台输入独立轮询;鸿蒙实机离屏 `--smoke --harmony` 与 HAP 真窗口 `--hap` 均已在本机(鸿蒙 PC)验证通过。HAP 前端 Up 事件不带按键,输入映射层用最近一次 Down 的按键补齐。系统标题栏仍以 Windows 实测为准。 ## 渲染系统 `src/TextWar.Rendering` 从 webcs 的 `PackedTextureInstanceBatcher` 路径适配而来,所有 GPU 操作经 `IGLESContext.Req*` 发出: - `PackTexture`:RGBA8 / R8 Texture2DArray,MaxRects 装箱,最近邻采样,图层按需上传。 - `PackedTextureGroup`:彩色与灰度图集、命名元素、512×512 RGBA32F 元素查询纹理;每元素 16 个 float,上限 65536 个元素。只上传发生变化的元数据行。 - `PackedTextureInstanceBatcher`:保留 webcs 的 36 字节实例布局、元素 ID、旋转、缩放、颜色、effect 和 reserved。固定六顶点四边形 + divisor=1 实例属性 + `ReqDrawArraysInstanced`。支持实例增删清空、按索引改写和保留批次重复绘制;删除当前仅提供整批 `Clear`。 - `Batcher`:`Begin / DrawSprite / DrawInstance / DrawText / End`。精灵和字形走同一实例路径;切换纹理组时先提交上一组,保持透明绘制顺序。 - 字体复用 Drawbridge `Drawbridge.TestFunc/Font12` 中的 `TTFont12`、内置字体数据和基础图像/装箱类型,通过 `IPackedTextureGroup` 接入新图集。支持中英文、空格、换行、tab、缩放和字距。未知字符沿用原字体的空白占位行为。 本次移植聚焦实例化 + 纹理数组这一条 batcher 路径,未搬入 webcs 的浏览器运行时、完整材质/相机系统、SingleTextureBatcher、任意三角形 PackedTextureBatcher 或 Scale9。普通精灵和文本统一由实例化路径处理。 ## 接入方式 构造、添加图集内容、绘制、释放资源都会产生 Drawbridge 请求,必须位于调用方的 `BeginFrameReqBatcher / EndFrameReqBatcher` 内,并在同一渲染线程串行执行。渲染器不自行打开或关闭帧。 ```csharp using DrawBridge.Font12; using TextWar.Rendering; using Group = TextWar.Rendering.PackedTextureGroup; // bridge 已 Start,并收到 WindowInit。调用方预先订阅 shader compile/link ACK。 bridge.BeginFrameReqBatcher(); var group = new Group(bridge.GLES); var batcher = new Batcher(bridge.GLES); var font = new TTFont12(group, new MemoryStream(ttfont12_bytes.Data)); bridge.EndFrameReqBatcher(); // 等待初始化帧完成,检查 shader ACK,具体见 Demo/Program.cs。 bridge.BeginFrameReqBatcher(); bridge.GLES.ReqViewport(0, 0, 960, 640); batcher.Begin(960, 640); batcher.DrawText(font, "你好 Drawbridge", 20, 20, new Color32(255, 255, 255, 255), scale: 2); batcher.End(); bridge.EndFrameReqBatcher(); ``` 坐标以视口左上角为原点、Y 向下;旋转使用弧度,围绕 SpriteData 的 pivot 旋转。像素字体建议使用整数坐标与整数倍缩放。`DrawSprite` 的坐标是精灵 pivot 位置;字体 pivot 为零。 低层 `PackedTextureInstanceBatcher.Render` 不清空实例,适合保留批次后局部改写;门面 `Batcher.End` 提交并清空。图集由调用方持有,多个 batcher 可以共享;先释放 batcher,再释放不再被使用的图集。批渲染会设置 program、VAO、纹理单元 0–2、blend、depth、stencil 和 cull 状态,不恢复之前的 GL 状态;viewport、framebuffer、scissor 由调用方管理。 ## 验证 `--smoke` 使用真实 Drawbridge ANGLE GLES 上下文,检查编译 / 链接 ACK、GL 错误和像素回读,覆盖: - 1500+ 实例扩容后原有数据不丢失。 - 三个 RGBA 图层、元素表跨 128 元素行边界、命名元素替换。 - 图集满 / 超大精灵报错后已有内容可继续绘制。 - A/B/A 纹理组切换顺序、alpha 混合和实例改写后重绘。 - `A`、`中`、`B` 的两倍放大与换行,与原始 1-bit 字体资源逐像素比较。 - 低透明度淡入淡出、连续帧绘制与资源释放无 GL 错误。战斗页按背景 / 裁剪战场 / 固定 UI 分成 3 个实例批次,其余页面仍为 1 个批次。 - 镜头转换、拖图停止、键盘加减速、失焦取消、聚焦、地图边界、滚动中框选、重开重置;GPU 回读验证地图随镜头变化且 HUD 像素不变。 - 通过实际页面输入函数执行框选、移动、全选进攻,模拟至战斗胜利,验证失败分支和重新开始。 移植来源与改动记录见 [third_party/webcs/NOTICE.md](third_party/webcs/NOTICE.md)。