
简介这是一款由英国《金融时报》制作的叙事性新闻游戏源码围绕优步司机的经济处境与零工经济体验展开玩家需在一周内尝试赚取1000美元并在真实司机访谈面前做出抉择。资源面向对数据新闻、交互叙事与前端开发感兴趣的开发者与学习者可用于研究新闻游戏的设计思路与实现方式。压缩包共78个文件约517KB以JavaScript为主28个辅以HTML页面、JSON配置、YML流程文件及TypeScript、SCSS样式等涵盖客户端组件、服务端逻辑、测试脚本与构建配置目录结构清晰。项目基于npm或yarn安装依赖支持本地开发与CI自动构建、测试和部署并附带MIT许可说明。目前已有300人学习下载适合希望借鉴叙事游戏架构、了解新闻交互产品工程化实践的前端开发者参考。1. 叙事性新闻游戏到底怎么做从优步司机经济困境到可交互系统把「优步司机的经济和生活」做成一款叙事性新闻游戏核心不是复刻一个打车模拟器而是用系统规则把司机每天面对的真实取舍翻译成玩家能亲手操作的决策。玩家接单、算油费、还车贷、被平台抽成最后发现跑满十二小时仍然剩不下钱——这种「算完账才懂」的体验比读一篇长报道更有说服力。这类作品属于严肃游戏与新闻叙事的交叉地带适合独立开发者、数据新闻团队、交互设计学生以及想用游戏讲清楚一个经济结构问题的从业者。它不需要 3A 预算但需要你把真实数据、系统循环和叙事节奏三样东西拧在一起否则很容易做成一个既不好玩也不可信的半成品。2. 先立住设计骨架系统循环、数据来源与叙事节奏怎么定做这类游戏最容易翻车的地方是一上来就写代码结果做到一半发现「经济模型算不平」或者「玩家根本感受不到压力」。我一般会先把三件事定死核心循环、数据锚点、叙事触发点。这三样决定了后面所有代码和内容的边界。2.1 核心循环一天的时间预算怎么变成可玩机制优步司机的真实处境可以抽象成一个资源分配问题每天有限的时间、有限的油量、不断变化的订单价格、固定的车辆成本。玩家每个决策都在消耗或换取资源。常见做法是把一天切成若干时间段每个时间段玩家选择接单、空驶、休息或收车。这个循环的关键参数是「时间粒度」。粒度太粗玩家感受不到取舍太细操作会变得琐碎。我的经验是每 15 分钟游戏内时间作为一个决策点一天 24 小时就是 96 个决策点但实际玩家只会做 20 到 40 次有效选择其余时间自动推进。下面是一个最小循环的状态定义# 司机每日状态核心资源与约束 driver_state { cash: 120.0, # 手头现金单位元 fuel: 0.75, # 油箱剩余比例 0-1 fatigue: 0.0, # 疲劳值 0-1超过 0.8 触发强制休息 time_left: 16, # 剩余可运营小时 car_payment_due: 7, # 距离车贷扣款还有几天 rating: 4.85, # 平台评分低于 4.6 减少派单 } # 每个决策点的收益计算 def take_order(state, order): fuel_cost order[distance] * 0.6 # 每公里油费约 0.6 元 platform_cut order[fare] * 0.25 # 平台抽成 25% net order[fare] - platform_cut - fuel_cost state[cash] net state[fuel] - order[distance] * 0.08 state[fatigue] order[duration] * 0.02 return state这段代码里每个参数都不是随便写的。油费 0.6 元每公里对应的是普通燃油车在城市工况下的实际开销平台抽成 25% 是行业里常见的区间中值疲劳值每小时涨 0.02 意味着连续跑 10 小时才会接近强制休息线。这些数字你可以按自己调研到的数据调整但必须保证它们之间是自洽的——如果油费太高而订单价格太低玩家第一天就会破产游戏直接失去可玩性。2.2 数据锚点让经济模型可信的三个来源新闻游戏和纯虚构游戏最大的区别是玩家会下意识判断「这数字是真的吗」。所以经济模型不能拍脑袋。我一般从三个地方找锚点公开的行业报告里的收入区间、司机社区里讨论的真实成本结构、以及平台公开的定价规则说明。具体做法是建一张参数表把每个数字的来源和调整空间标清楚参数基准值可调范围说明平台抽成比例25%20%-30%不同城市和时段有差异每公里油费0.6 元0.45-0.75受车型和油价影响日均订单数18 单12-25取决于城市和在线时长车辆日折旧45 元30-60按车价和年限折算平均时薪22 元15-30扣除成本后的净值这张表的作用不是让你精确复刻现实而是让你在调整难度时有据可依。比如你想让玩家感受到「跑得越多单位时间赚得越少」的边际递减效应就可以把疲劳值对收入的影响做成非线性曲线而不是简单的线性扣减。2.3 叙事节奏把新闻信息拆进决策反馈里叙事性新闻游戏最怕做成「弹窗读文章」。玩家在操作中获得的反馈本身就是叙事。比如玩家连续接了三个短途低价单之后系统弹出一句「你今天已经跑了 4 小时收入 68 元油费花了 31 元」——这句话不需要任何文学修饰数字本身就是故事。我通常会把叙事内容分成三层即时反馈每单结束后的收支明细、日终结算当天总收入、总成本、净收入对比、周级事件车贷扣款、车辆维修、平台规则变化。每层的信息密度不同但都指向同一个核心问题这份工作到底能不能养活一个人。提示叙事文本不要写成说教。把事实摆出来让玩家自己算。玩家算出来的结论比你写出来的更有冲击力。3. 把系统跑起来最小可玩版本的实现路径设计骨架定完之后下一步是做一个能跑通的最小版本。这个版本不需要美术资源不需要完整剧情只需要验证核心循环是否成立。我一般用 Python 加一个简单的命令行界面就能跑等数值调稳了再考虑换引擎做表现层。3.1 用 Python 搭一个命令行原型最小版本的目标是玩家能接单、能看到收支、能感受到一天结束后的结果。下面是一个可运行的骨架import random class DriverGame: def __init__(self): self.cash 120.0 self.fuel 0.75 self.fatigue 0.0 self.hours_left 16 self.total_earned 0.0 self.total_cost 0.0 def generate_order(self): # 订单价格和距离呈弱相关短途单价低但周转快 distance random.uniform(2, 15) base_fare 8 distance * 2.2 surge random.choice([1.0, 1.0, 1.0, 1.2, 1.5]) fare round(base_fare * surge, 1) duration round(distance * 3 random.uniform(2, 6), 1) return {distance: distance, fare: fare, duration: duration} def take_order(self, order): fuel_cost order[distance] * 0.6 platform_cut order[fare] * 0.25 net order[fare] - platform_cut - fuel_cost self.cash net self.total_earned order[fare] self.total_cost platform_cut fuel_cost self.fuel - order[distance] * 0.08 self.fatigue order[duration] * 0.02 self.hours_left - order[duration] / 60 return net def daily_report(self): print(f今日总收入: {self.total_earned:.1f} 元) print(f今日总成本: {self.total_cost:.1f} 元) print(f净收入: {self.total_earned - self.total_cost:.1f} 元) print(f剩余现金: {self.cash:.1f} 元) print(f疲劳值: {self.fatigue:.2f}) def run(self): while self.hours_left 0 and self.fatigue 0.9: order self.generate_order() print(f\n新订单: {order[distance]:.1f} 公里, f车费 {order[fare]} 元, 预计 {order[duration]} 分钟) choice input(接单? (y/n): ) if choice.lower() y: net self.take_order(order) print(f本单净收入: {net:.1f} 元) else: self.hours_left - 0.1 # 空驶等待消耗时间 self.daily_report() if __name__ __main__: game DriverGame() game.run()这段代码的逻辑很直白生成订单、玩家选择、更新状态、循环直到时间耗尽或疲劳过高。关键在generate_order里的参数——base_fare 8 distance * 2.2决定了起步价和每公里价格的关系surge的随机分布决定了高峰溢价的出现频率。你可以把surge改成按时段触发的确定性逻辑比如早晚高峰必出 1.5 倍这样玩家就能学会「等高峰再出车」的策略。3.2 数值调参让「跑满一天仍然亏钱」变得可信原型跑通之后最重要的工作是调参。目标是让玩家在正常操作下一天结束后的净收入在 50 到 150 元之间波动偶尔出现亏损。这个区间既能让人感到压力又不至于让人立刻放弃。调参的顺序我一般是这样先固定订单价格分布再调油费和抽成最后调疲劳和时间的约束。如果发现玩家总是能轻松赚钱就提高空驶概率或降低高峰溢价如果发现玩家总是破产就降低车辆固定成本或提高短途订单的周转率。# 调参用的模拟脚本跑 1000 次一天看净收入分布 import statistics def simulate_day(): game DriverGame() while game.hours_left 0 and game.fatigue 0.9: order game.generate_order() # 模拟一个理性玩家净收入为正就接 fuel_cost order[distance] * 0.6 platform_cut order[fare] * 0.25 if order[fare] - platform_cut - fuel_cost 0: game.take_order(order) else: game.hours_left - 0.1 return game.total_earned - game.total_cost results [simulate_day() for _ in range(1000)] print(f平均净收入: {statistics.mean(results):.1f}) print(f中位数: {statistics.median(results):.1f}) print(f亏损天数占比: {sum(1 for r in results if r 0) / 10:.1f}%)跑完这个脚本你就能看到当前参数下司机的收入分布。如果亏损天数占比低于 10%说明压力不够高于 40%说明太苛刻。我一般会把目标定在 20% 到 30% 之间让玩家偶尔翻车但不会一直翻车。3.3 从命令行到可交互界面什么时候该换引擎命令行原型验证的是数值和循环不是体验。当你确认核心循环成立之后就可以考虑换到 Godot 或 Unity 做表现层。换引擎的时机很关键太早你会在美术和动画上浪费时间太晚命令行版本的交互方式会限制你的叙事设计。我的判断标准是当你能用一句话说清楚「玩家在什么情况下会感到焦虑」并且这个焦虑来自数值而不是来自操作不便时就可以换引擎了。比如「玩家发现车贷还有三天到期但现金不够」这种焦虑在命令行里也能感受到那就说明数值设计到位了可以进入表现层开发。4. 避坑指南叙事新闻游戏最容易翻车的五个地方这类项目看起来简单实际上坑很密集。我踩过的和见别人踩过的主要集中在下面五个地方。4.1 经济模型算不平玩家第一天就破产现象玩家按照正常策略接单一天跑下来净收入是负数连续几天后直接放弃。原因固定成本车贷、保险、折旧设得太高或者订单价格设得太低导致边际收益无法覆盖边际成本。常见于直接照搬现实中的极端案例忽略了游戏需要给玩家留出学习空间。解决先跑 1000 次模拟看净收入分布。如果中位数是负数就把固定成本砍掉 30% 到 50%或者把订单均价提高 20%。记住游戏的可玩性优先于数据的精确性你可以用「这是普通城市的一天」来解释偏差。4.2 叙事文本变成说教玩家直接跳过现象玩家不读弹窗里的文字快速点击继续完全没接收到你想传达的信息。原因叙事文本写成了新闻报道或评论文章信息密度太高和玩家的操作节奏脱节。解决把叙事拆成短句绑定在操作反馈上。比如玩家接完一单后不弹窗而是在收支明细里加一行「本单耗时 22 分钟净收入 6.3 元」。玩家自己会算时薪。日终结算时再给一句总结性的话比如「你今天工作了 11 小时净收入 87 元相当于时薪 7.9 元」。不要加任何形容词。4.3 疲劳系统做成惩罚玩家觉得被针对现象玩家连续接单后触发强制休息导致当天收入骤降玩家感到挫败而不是理解。原因疲劳值的增长曲线太陡或者强制休息的惩罚太重让玩家觉得是系统在阻止自己赚钱而不是在模拟真实约束。解决把疲劳设计成渐进式影响而不是硬性开关。比如疲劳值超过 0.6 后每单的净收入打九折超过 0.8 后订单生成频率降低。这样玩家会自己权衡「再跑一单值不值」而不是被系统强行打断。4.4 平台抽成比例写死失去讨论空间现象玩家发现抽成永远是 25%觉得这个数字是开发者随便定的不信任整个模型。原因抽成比例没有变化也没有任何解释玩家无法判断这个数字是否合理。解决让抽成比例随时段和订单类型浮动比如高峰时段抽成 20%平峰 28%长途单抽成 22%。同时在游戏内提供一个「平台规则」页面用中性语言说明抽成的计算方式。玩家可以不同意但至少知道你不是瞎写的。4.5 只做收入侧忽略支出侧的叙事潜力现象游戏只关注司机赚了多少玩家感受不到「为什么赚了还是没钱」。原因支出项目太少或太隐蔽玩家看不到钱花在哪里。解决把支出做成可见的、有节奏的事件。比如每天结束时列出「今日支出油费 62 元、平台抽成 48 元、车辆折旧 45 元、保险均摊 12 元」。玩家看到这些数字自然会理解「毛收入和净收入是两回事」。这比任何文字说明都有效。5. 进阶技巧用真实数据驱动事件系统与验证方法当核心循环和数值都稳定之后你可以开始做真正让这类游戏出彩的部分用真实数据驱动的事件系统。这不是简单地往游戏里塞新闻而是让数据本身成为叙事引擎。5.1 把公开数据变成游戏内事件我一般会建一个事件表每个事件绑定一个触发条件和一组数值影响。触发条件可以是游戏内状态比如现金低于 50 元、疲劳值高于 0.7、连续三天净收入下降也可以是外部数据比如油价上涨、平台调整抽成规则。# 事件系统根据游戏状态触发叙事事件 events [ { id: fuel_price_hike, condition: lambda s: s[days_played] % 7 0, effect: {fuel_cost_multiplier: 1.15}, text: 本周油价上涨 15%每公里油费相应增加。 }, { id: car_repair, condition: lambda s: s[mileage] 500 and random.random() 0.3, effect: {cash: -180}, text: 车辆需要保养支出 180 元。 }, { id: platform_bonus, condition: lambda s: s[rating] 4.9 and s[weekly_orders] 80, effect: {cash: 200}, text: 本周完成 80 单以上且评分优秀获得平台奖励 200 元。 } ] def check_events(state): for event in events: if event[condition](state): apply_effect(state, event[effect]) show_narrative(event[text])这个事件系统的关键不是事件本身有多复杂而是每个事件都能让玩家重新评估自己的策略。油价上涨后玩家会考虑是否少跑长途车辆维修后玩家会意识到折旧是真实存在的成本。这些事件不需要频繁触发一周一次就足够让玩家保持警觉。5.2 用玩家行为数据验证叙事效果游戏做出来之后怎么知道玩家有没有理解你想传达的东西我一般会埋几个行为指标平均每日在线时长、接单率、空驶时间占比、日终结算页面的停留时间。如果玩家在结算页面停留时间很短说明他们不关心收支明细叙事就没到位如果接单率一直很高但净收入很低说明玩家没有学会筛选订单可能需要调整订单信息的展示方式。更直接的验证方法是做 A/B 测试一组玩家看到完整的收支明细另一组只看到净收入。然后对比两组玩家在后续游戏中的策略差异。如果看到明细的玩家更倾向于减少空驶、避开低价单那就说明数据展示确实影响了决策。5.3 一个具体技巧用「时薪」作为核心反馈指标在所有可能的反馈指标里我认为最有效的是「时薪」。不是总收入不是总单数而是净收入除以在线时间。这个数字直接回答了「这份工作值不值得做」的问题。我通常会在日终结算时把时薪放在最显眼的位置并且和历史数据对比。比如「今日时薪 8.2 元过去七天平均时薪 11.5 元」。玩家看到这个数字下降自然会想「是不是今天接的单太便宜了」或者「是不是空驶时间太长了」。这种自我反思比任何教程都有效。def calculate_hourly_wage(total_net, hours_online): if hours_online 0: return 0 return total_net / hours_online # 日终结算时展示 hourly calculate_hourly_wage(net_income, hours_online) print(f今日时薪: {hourly:.1f} 元) print(f过去七天平均时薪: {seven_day_avg:.1f} 元) if hourly seven_day_avg * 0.8: print(今天效率明显低于平均水平检查一下接单策略。)这个技巧的好处是它不需要任何额外的美术或叙事资源只需要一个简单的除法。但它的信息量极大玩家会自己从中读出「这份工作的经济现实」。我自己做这类项目最大的教训是不要试图在游戏里给出答案。把数据摆出来把系统跑起来让玩家自己算。玩家算出来的结论比你写一万字都管用。希望帮到你。本文还有配套的精品资源点击获取