Appearance
会打架的虚拟形象:环境展示屏的间距工程与共享资源设计
第二部 · Agent 实践 · 第25章
撰写日期:2026-07-08
结论
我在一块团队环境展示屏(用虚拟形象代表团队成员、在场景里自主移动的那类"活地图",设计取舍见半公开大屏)上改一个具体问题:几个形象的头像和名字牌互相挡住了。第一版方案是加大一个"最小间距"常数,改完过阵子又挡上了。
视觉占地(footprint)不是一个可以写死的常数,它随内容长度、字号、屏幕宽高比变化。间距工程的正确起点是"量出当前渲染结果",不是"猜一个看起来够用的数字"。
这篇记的是这一类问题的通用解法,以及在同一次迭代里顺带处理的两个相关问题:如何在"每人固定位置"和"画面要有生活感"之间取舍,以及如何把用户提供的真实素材接入展示屏而不把二进制文件塞进代码库。
1. 从"写死间距"到"测量占地"
固定间距常数在两种情况下必然失效:内容变长(名字、状态文案长度不一)、渲染环境变化(窗口尺寸、全屏切换、字号随分辨率缩放)。这两种变化在设计时都无法穷举,唯一稳定的做法是在运行时量出真实结果:
| 做法 | 问题 |
|---|---|
| 设计时预估一个间距常数 | 常数只对预估时的内容长度和屏幕尺寸成立,换内容或换屏幕就失效 |
| 运行时读取每个元素的真实渲染尺寸,据此计算间距 | 天然适配任意内容长度和任意屏幕尺寸,无需为每种情况单独调参 |
具体做法:初始化时清空临时变换,读一次每个元素的真实渲染框,取"头像 + 文字牌"合并后的外接尺寸作为该元素的占地;这份占地随后驱动布局和碰撞检测两处计算。屏幕宽高比变化(比如进入全屏)时重新测量一次——这一步很容易漏掉,漏掉的后果是"平时正常、一全屏就重叠"。
2. 从"距离阈值"到"包围盒相交"
形象的占地是矩形(头像上、文字牌下,整体偏高瘦),用圆形距离判断"是否太近",在瘦高矩形上会系统性判断失误:横向明明没挤,纵向已经叠了,圆形阈值却因为斜向距离"看起来够远"而放过。
| 检测方式 | 适用形状 | 本例是否适用 |
|---|---|---|
| 圆形距离阈值 | 占地接近圆形/正方形的元素 | 不适用:形象是瘦高矩形,圆形判定会漏判纵向重叠 |
| 轴对齐包围盒(AABB)相交 | 矩形占地,且矩形边基本与坐标轴平行 | 适用:直接按真实宽高做矩形相交检测 |
修复方式是改成矩形相交检测:两个元素的包围盒如果在横纵两个方向都有重叠,就沿重叠更小的那个方向把两者分开;对多个元素同时存在时,单轮修正不够(推开 A 和 B 可能又把 B 推进了 C),要跑多轮直到某一轮没有任何一对再重叠,或达到轮数上限。
3. 产品目标冲突的折中:固定领地 + 有上限的共享设施轮换
这一轮迭代还遇到一个真实的需求冲突:此前的设计原则是"形象的移动要承载真实数据含义"(谁活跃、谁负责什么,见半公开大屏第 4 节),但后续反馈希望"每个人固定在自己的一小块区域里,别到处乱飘"。这两个目标字面上矛盾——一个要"移动有意义",一个要"别移动"。
拆开看,两个目标其实分别在管两件事:大多数时刻的位置该不该随机漂移,和画面要不要有生活感。折中方案是把二者分层:
| 目标 | 落地方式 |
|---|---|
| 每人有稳定、可预期的位置 | 大多数时间只在自己的固定小区域内做幅度很小的随机游走,不出这个区域 |
| 画面仍然"活着",且不破坏"结构扁平、没有专属席位"的原则 | 场景里放一个所有人都可能用到的共享设施,设一个同时容纳人数上限;每次决定"下一步去哪"时,有一个较小概率去体验一下,用完自动回到自己的固定区域,不会赖着不走 |
这个模式的价值在于:共享设施的名额不是分配给某个人的,谁都可能被抽中去用一次,画面因此始终有变化,但"个人固定领地"这条约束在统计上依然成立(多数形象在多数时间里确实待在自己的格子里)。验证这类设计有没有生效,不能只看一眼当前帧,要跑一段时间采样,确认"同时在用共享设施的人数从未超过上限"“每个人的位置离家距离长期有界"这类不变量始终成立。
4. 用户素材接入展示屏:引用云端凭证,而不是把文件放进代码库
展示屏最后接入了一批用户自己提供的真实图片素材(团队活动照片)。这类需求常见的两种错误做法:
| 做法 | 问题 |
|---|---|
| 把原始文件直接提交进代码仓库 | 仓库体积膨胀且难以瘦身;原始素材可能不适合公开可见;文件本身没有代码变更历史的意义,却占用同一套版本管理 |
| 只存一个本地文件路径 | 只在准备素材的那台机器上能显示,换一台部署环境就是死链 |
更稳的模式是"云端引用":把素材上传到团队已有的云端文档/存储系统里(不新增一套存储方案),代码里只保存一个引用凭证(文件在云端的 token/ID)。每次展示屏的数据生成流程跑一遍时,用这个凭证向云端换一个短时效的签名下载链接,前端直接用这个链接加载图片;下一轮生成时链接自动刷新,永不过期,也不需要考虑权限同步——素材的访问控制完全交给已有的云端系统。代码仓库自始至终不包含任何二进制文件,换素材只需要换一个凭证配置,不需要改代码。
5. 组合型模糊需求:先给选项,再动手改
这一轮还有一个小但值得记的协作细节:用户先后提出"要一整面更大的共享设施"和"每个人要固定在自己的一小块领地",两句话字面上可以有好几种组合方式(共享设施优先、固定领地优先、二者按比例穿插……),直接挑一种理解就动手,返工概率不低。
更稳的做法是把可能的组合方式列成 2–4 个具体选项,写清楚每个选项对最终效果的影响,请提需求的人选一个,再动手实现。这不是"不会拆解需求",而是组合型约束的正确解法本来就不是靠推测,而是靠澄清——尤其当选项之间的实现成本差不多、但产品效果差异很大的时候,问一句比做完再改便宜得多。
我的判断
这一类"看起来是小 bug"的间距问题,根子几乎都在同一个地方:把运行时才能确定的量(内容长度、渲染尺寸、屏幕比例)当成了设计时的常数。我现在的默认做法是——凡是布局相关的常数,先问一句"这个数字会不会因为内容变化或者环境变化而失效",会失效就必须在运行时测量,不能靠调大一点蒙混过去。
共享资源轮换那部分则是一个更通用的启发:两个看似冲突的产品目标,很多时候不需要二选一,把其中一个降级成"大多数情况成立的统计规律"、另一个做成"小概率但明确上限的例外通道",往往就能同时满足。