14.1 产能度量:人天与吞吐
13.3 工具的采纳率与维护成本 把工具的价值写成「节省人天×100 / 维护人天×100」的整数账。这一节把同一套记账法压到最底层的资源上:人天。既有 game/industry 的生产与外包短文 与 《美术风格指南与 Style Bible》 讲的是方向、协作与管理的道理;本节不重复它们的框架,只做一件它们不做的事——把「这支团队一个迭代到底能产出多少资产」写成可跑、可复算、可对账的整数。
先把口径摆明:本节所有产能数字都是示例参数(出勤率 850‰、专注率 700‰、单件人天成本都是本套构造的),不代表任何真实团队或公司的产能;唯一来自公开可观察信息的是「人天(person-day / man-day)」这个单位本身——它是游戏与外包行业公开使用的计价口径。真实团队的真实产能需真实环境(真实人员、真实项目),命令行不可复现,本节只演示怎么把这样一份口径算成整数账。
14.1.1 两种人天:日历人天与有效人天
排期表上最容易犯的错,是把「一个人干一天」当成一个整人天。实际上一个人一天里只有一部分时间真正落在产出上:请假、会议、等待反馈、被打断都在吃掉它。于是要把一个数拆成两个:
| 量 | 定义 | 示例值 |
|---|---|---|
| 日历人天 | 人数 × 工作日数 | 6 人 × 10 天 = 60 人天 |
| 有效系数 | 出勤率‰ × 专注率‰ // 1000 | 850 × 700 // 1000 = 595‰ |
| 有效人天 | 日历人天 × 有效系数‰ // 1000 | 60 × 595 // 1000 = 35.70 人天 |
60 个日历人天只换回 35.70 个有效人天。 这不是悲观,而是把「可计划的工作量」和「发工资的天数」分开——排期要按有效人天排,否则计划永远比现实宽松。两个系数都用千分比整数表示,乘法后整数地板除,跨语言逐位一致。
为什么要把出勤率与专注率分成两个系数?因为它们各自对应不同的管理动作。出勤率(示例 850‰)反映「人在不在」——请假、轮休、跨项目借调;专注率(示例 700‰)反映「在的时候有多少时间真的在产出」——会议、评审、等待反馈、被临时插活打断。合成一个 595‰ 的系数,排期只需乘一次;但真要改善产能,得先知道该动哪一个:出勤率靠排班,专注率靠减少打断,抓手完全不同。
14.1.2 配置:一份示例产能模型
下面是 D 层的一份示例产能模型:四个角色(场景与家具、角色与头像、UI 与图标、特效)各有人数,右侧是这一迭代的资产需求(件数 × 单件人天×100)。需求按角色归属,因为产能是分角色的,不能跨角色平移。
{
"project": "stardust-inn",
"chapter": 14,
"note": "示例参数:本套构造的产能模型,非任何真实团队或公司的数字。",
"caliber": "出勤率与专注率为示例口径;人天成本为示例参数。人天(person-day)是游戏与外包行业公开使用的计价单位。",
"workdays": 10,
"availabilityPermille": 850,
"focusPermille": 700,
"roles": [
{ "id": "env", "name": "场景与家具", "people": 2 },
{ "id": "char", "name": "角色与头像", "people": 1 },
{ "id": "ui", "name": "UI 与图标", "people": 2 },
{ "id": "vfx", "name": "特效", "people": 1 }
],
"demand": [
{ "id": "furn_icon", "role": "env", "units": 12, "costPerUnit100": 60 },
{ "id": "furn_prefab", "role": "env", "units": 4, "costPerUnit100": 250 },
{ "id": "portrait", "role": "char", "units": 2, "costPerUnit100": 400 },
{ "id": "ui_icon", "role": "ui", "units": 24, "costPerUnit100": 40 },
{ "id": "vfx_burst", "role": "vfx", "units": 1, "costPerUnit100": 300 }
]
}
单件成本 costPerUnit100 是「人天 ×100」:家具图标 60 即 0.60 人天、角色头像 400 即 4.00 人天。用整数而不是小数,是为了让所有除法都走整数地板除,产出可逐位对拍。
14.1.3 S 层:人天账、利用率与吞吐
下面这段是 S 层脚本,读上面的配置,算三笔账:日历人天 → 有效人天、每个角色的产能对需求、以及整体利用率与吞吐:
#!/usr/bin/env python3
# file: sim/ta14_capacity.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ta14_capacity.py
"""14.1 产能度量:人天与吞吐(S 层,卷二第 14 章)。
读 ``config/ta14_capacity.json`` 的示例产能模型,算三笔整数账:
1. **日历人天 → 有效人天**:有效系数 = 出勤率‰ × 专注率‰ // 1000(整数地板除);
2. **角色产能 vs 需求**:``calendar100 = people × workdays × 100``,
``effective100 = calendar100 × factor‰ // 1000``;需求 ``demand100 = Σ units × costPerUnit100``;
3. **利用率与吞吐**:``util‰ = demand100 × 1000 // effective100``(>1000 即超产能),
``throughput100 = units × 10000 // effective100``(每有效人天的资产数 ×100)。
全整数、无浮点。数字是**示例参数**(本套构造),非任何真实团队。落 ``out/ta14_capacity.json``。
"""
import json
import os
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CFG = os.path.join(ROOT, "config/ta14_capacity.json")
OUT = os.path.join(ROOT, "out")
def build_report(cfg):
"""把示例产能模型算成整数账。"""
workdays = cfg["workdays"]
factor = cfg["availabilityPermille"] * cfg["focusPermille"] // 1000
roles = {r["id"]: r for r in cfg["roles"]}
calendar, capacity = {}, {}
for rid, r in roles.items():
c = r["people"] * workdays * 100
calendar[rid] = c
capacity[rid] = c * factor // 1000
demand = {rid: 0 for rid in roles}
units = {rid: 0 for rid in roles}
for d in cfg["demand"]:
demand[d["role"]] += d["units"] * d["costPerUnit100"]
units[d["role"]] += d["units"]
rows = []
for rid in sorted(roles):
eff, dem = capacity[rid], demand[rid]
rows.append({
"role": rid, "people": roles[rid]["people"],
"calendar100": calendar[rid], "effective100": eff,
"demand100": dem, "units": units[rid],
"utilPermille": dem * 1000 // eff if eff else 0,
})
cal_total = sum(calendar.values())
eff_total = sum(capacity.values())
dem_total = sum(demand.values())
unit_total = sum(units.values())
return {
"project": cfg["project"], "workdays": workdays,
"availabilityPermille": cfg["availabilityPermille"],
"focusPermille": cfg["focusPermille"], "effectiveFactorPermille": factor,
"calendarTotal100": cal_total, "effectiveTotal100": eff_total,
"demandTotal100": dem_total, "unitTotal": unit_total,
"gap100": dem_total - eff_total,
"utilPermille": dem_total * 1000 // eff_total if eff_total else 0,
"naiveUtilPermille": dem_total * 1000 // cal_total if cal_total else 0,
"throughput100": unit_total * 10000 // eff_total if eff_total else 0,
"avgCost100": dem_total // unit_total if unit_total else 0,
"roles": rows,
}
def main() -> int:
cfg = json.load(open(CFG, encoding="utf-8"))
rep = build_report(cfg)
os.makedirs(OUT, exist_ok=True)
with open(os.path.join(OUT, "ta14_capacity.json"), "w", encoding="utf-8") as f:
json.dump(rep, f, ensure_ascii=False, indent=2, sort_keys=True)
f.write("\n")
print(f"factor={rep['effectiveFactorPermille']}permille "
f"calendar100={rep['calendarTotal100']} "
f"effective100={rep['effectiveTotal100']} demand100={rep['demandTotal100']}")
print(f"util={rep['utilPermille']}permille naive={rep['naiveUtilPermille']}permille "
f"gap100={rep['gap100']} throughput100={rep['throughput100']} "
f"avgCost100={rep['avgCost100']}")
for r in rep["roles"]:
print(f" {r['role']:5s} ppl={r['people']} effective100={r['effective100']} "
f"demand100={r['demand100']} util={r['utilPermille']}permille units={r['units']}")
print("wrote out/ta14_capacity.json")
return 0
if __name__ == "__main__":
sys.exit(main())
实跑 /opt/homebrew/bin/python3 sim/ta14_capacity.py:
factor=595permille calendar100=6000 effective100=3570 demand100=3780
util=1058permille naive=630permille gap100=210 throughput100=120 avgCost100=87
char ppl=1 effective100=595 demand100=800 util=1344permille units=2
env ppl=2 effective100=1190 demand100=1720 util=1445permille units=16
ui ppl=2 effective100=1190 demand100=960 util=806permille units=24
vfx ppl=1 effective100=595 demand100=300 util=504permille units=1
wrote out/ta14_capacity.json
14.1.4 读账:利用率 1058‰ 意味着什么
三笔账读下来,结论很直接:
- 整体利用率 = 1058‰,也就是需求 113.60 人天(
demand100=3780)对产能 35.70 有效人天(effective100=3570)——超产能 1058 - 1000 = 58‰。这份计划排不下。 - 如果按日历人天算,利用率只有 630‰(
naive=630),看起来「还有 37% 余量」。这正是日历人天的陷阱:它把不产出的时间也算成了产能。同一个计划,换个分母就从「宽裕」变成「超标」,这就是为什么必须先做 14.1.1 那一步拆解。 - 角色之间严重不均衡:场景与家具 1445‰、角色与头像 1344‰ 双双爆表,而 UI 与图标只有 806‰、特效只有 504‰ 反而有余量。产能不能跨角色平移——UI 的余量救不了场景,因为技能不通用。
- 吞吐 = 1.20 资产/有效人天(
throughput100=120),平均单件成本 0.87 人天(avgCost100=87)。这两个数把「产能」翻译成了「件数」,是 15.1 计价模型的直接输入。
把四行摊开,每个角色的账目是(单位:人天×100):
| 角色 | 人数 | 日历人天×100 | 有效人天×100 | 需求人天×100 | 利用率 |
|---|---|---|---|---|---|
场景与家具 env | 2 | 2000 | 1190 | 1720 | 1445‰ |
角色与头像 char | 1 | 1000 | 595 | 800 | 1344‰ |
UI 与图标 ui | 2 | 2000 | 1190 | 960 | 806‰ |
特效 vfx | 1 | 1000 | 595 | 300 | 504‰ |
| 合计 | 6 | 6000 | 3570 | 3780 | 1058‰ |
一句话:利用率的分母选错,整张排期表都会骗你。 1058‰ 与 630‰ 是同一个计划的两种读法,差的就是那 595‰ 的有效系数。
14.1.5 反推:这份产能最多能出多少件
利用率是「需求占产能的比例」,反过来问同样有用:把一个角色的产能全投到某一类资产上,最多能出多少件? 答案就是 有效人天×100 // 单件成本×100(整数地板除):
| 角色 | 有效人天×100 | 目标资产 | 单件成本×100 | 最多件数 |
|---|---|---|---|---|
场景与家具 env | 1190 | 家具图标 | 60 | 19 |
场景与家具 env | 1190 | 家具 prefab | 250 | 4 |
角色与头像 char | 595 | 角色头像 | 400 | 1 |
UI 与图标 ui | 1190 | UI 图标 | 40 | 29 |
特效 vfx | 595 | 特效 | 300 | 1 |
这张表是 15.1 计价模型的直接输入:「一个迭代能做多少件」不是拍脑袋,而是 有效人天×100 // 单件成本×100。 注意 1190 // 250 = 4(不是 4.76)——整数地板除把「做不完的那 0.76 件」直接砍掉,这正是排期要保守的地方:产能账宁可少算一件,也不要在交付日发现差半件。
利用率超过 1000‰ 时,能动的只有三条杠杆,它们在账面上都只是「改一个数」,代价却完全不同:
| 杠杆 | 改动 | 有效人天×100 | 整体利用率 |
|---|---|---|---|
| 加人 | env 从 2 人加到 3 人 | 4165 | 907‰ |
| 砍范围 | 总需求砍 20% | 3570 | 847‰ |
| 提系数 | 专注率 700‰ → 750‰ | 3822 | 989‰ |
加人涨成本、砍范围降交付、提系数通常要改流程。三条杠杆都能把 1058‰ 压到 1000‰ 以下,但只有先算清「是哪一条在起作用」,才能决定动哪一条——这正是本节把产能拆成显式整数的意义。
14.1.6 有效系数是敏感参数
effectiveFactorPermille = 595‰ 不是常量,它是两个示例参数的乘积。把专注率从 700‰ 调到 650‰(多留一点会议与协作时间),有效系数变成 850 × 650 // 1000 = 552‰,有效人天从 35.70 掉到 33.12,整体利用率从 1058‰ 升到 1141‰:
| 专注率‰ | 有效系数‰ | 有效人天×100 | 整体利用率 |
|---|---|---|---|
| 700 | 595 | 3570 | 1058‰ |
| 650 | 552 | 3312 | 1141‰ |
同一份需求,专注率降 50‰,利用率就涨 83‰。这说明产能账对「有效系数」这个输入非常敏感——所以它必须先被显式声明,而不是藏在某个人的直觉里。本节的 595‰ 是示例口径,真实团队要用自己的实测值替换;替换之后整张表会同步变化,但算法与对账口径不变。
14.1.7 四条口径边界
- 数字全是示例参数。出勤率、专注率、单件成本都是本套构造的,目的是演示算法而非给出可移植的产能基准。任何真实团队的产能需真实环境(真实人员与项目数据),命令行不可复现。
- 不做「提升 N 倍」的断言。本节只给同机、同口径的相对账,绝对数值不可移植;把
focusPermille从 700 改成 800 会让所有利用率同步变化,但那只说明「参数敏感」,不说明任何真实收益。 - 人天是最小颗粒,不是最优颗粒。真实排期还要处理任务可分割、多人协作、评审往返,这些会进 14.2 的依赖 DAG;本节先把「一支团队一个迭代有多少有效产能」这件事算成可复算的整数。
- 利用率不是越低越好。1058‰ 说明计划排不下,但 600‰ 也未必健康——产能长期闲置意味着人力在烧钱。真正要看的是角色间的均衡:env 1445‰ 与 vfx 504‰ 并存,说明瓶颈在特定角色,而 14.2 的关键路径正是处理这种不均衡的工具。
阅读导航:上一节:13.3 工具的采纳率与维护成本 · 下一节:14.2 排期与美术依赖 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。