ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python从零实现增量游戏:文明演化项目实战

用Python从零实现增量游戏:文明演化项目实战 增量游戏一直是休闲玩法里很有代表性的一个品类玩家只需要做少量操作剩下的交给时间资源就会不断积累从而逐步解锁更多内容。很多开发者第一次接触这类游戏时都会好奇“看上去只是点一点、等一等背后的数值和逻辑到底是怎么设计的”。这篇文章我们就用 Python 从零实现一个完整的增量游戏项目主题是文明演化项目名就叫Evolve。文章会从玩法设计讲起再给出完整可运行的代码、运行演示和常见问题排查。不管你是刚学 Python 不久还是想了解增量游戏数值设计思路都可以照着这份教程一步步做出来。1. 项目背景增量游戏与文明演化1.1 什么是增量游戏增量游戏Incremental Game通常也被称为放置游戏Idle Game或点击游戏Clicker Game。这类游戏的核心特征很统一玩家通过一个非常简单的操作获取初始资源然后用资源购买能够自动产出的建筑或升级让资源增长速度不断提升再解锁更高级的内容。它的关键体验可以概括为三条上手门槛低几乎不需要教程就能明白“点一下、拿资源”。数值成长非常直观玩家能清楚看到产出从 1 变成 10、100、1000 的反馈。允许玩家离线或挂机过一段时间回来收取产出这种“等待然后收获”的节奏很容易让人上瘾。增量游戏看似简单但真正要做好核心难点不在操作而在数值模型设计和成长节奏控制。这也是我们选择手动实现一个增量游戏的原因代码量不大却能完整练习资源管理、状态更新、事件系统、存档读写等常见开发能力。1.2 为什么选择文明演化作为主题把“文明演化”作为增量游戏主题非常契合放置玩法的节奏。从一个小部落开始逐步发展出农业、城邦、帝国最后进入黄金时代这个过程和增量游戏“从弱到强、从简单到复杂”的成长感天然匹配。在 Evolve 中玩家的目标是让一个原始部落不断成长先保证食物供给让人口能够增长。人口增长后才能支撑更多建筑和劳作。通过建设祭坛、图书馆、市场等建筑产出文化、科技、黄金。当资源达到一定条件后文明阶段会升级解锁更多建筑和玩法。这种设计的好处是玩家的每一步操作都有明确目标同时所有系统之间是相互依赖的不会出现某个资源完全没用的情况。1.3 本文适合的读者与最终目标本文适合以下几类读者正在学习 Python、想通过小项目练手的人。对增量游戏或放置类玩法感兴趣想了解背后实现原理的开发者。想做个人独立游戏原型但暂时不打算引入 Pygame 等图形库的人。学完本文后你会得到以下成果一个完全用 Python 标准库实现的文本界面增量游戏。一套包含资源、建筑、科技、事件、存档的完整项目结构。对增量游戏数值平衡的初步认知以及后续扩展成 GUI 或 Web 项目的思路。2. 游戏设计拆解2.1 核心循环Evolve 的核心循环可以拆成下面几步玩家启动游戏看到一个处于“部落时代”的文明拥有基础人口和初始资源。玩家可以消耗食物建造农田让每回合被动产出食物。食物足够后人口会自动增长进入农业时代。农业时代解锁祭坛和图书馆可以产出文化和科技。资源和人口继续积累依次进入城邦时代、帝国时代、黄金时代。每一步的操作入口都非常简单输入指令、推进回合、查看资源变化。这个循环可以用一句话概括用已有资源创造更高产出再用更高产出解锁新的成长阶段。2.2 资源与文明阶段Evolve 里有五种资源它们各司其职资源主要来源主要用途食物农田被动产出建造建筑、人口增长、劳作消耗文化祭坛被动产出、手动劳作建造高级建筑、研究科技、阶段进化科技图书馆被动产出研究科技、阶段进化、建造市场黄金市场被动产出、手动劳作建造高级建筑、研究科技、阶段进化人口食物充足时自动增长所有手动活动的基础文明阶段则是整个游戏的成长主线。Evolve 一共设计了五个阶段部落时代农业时代城邦时代帝国时代黄金时代每个阶段都有不同的基础人口上限和可建造建筑。当人口、文化、科技、黄金满足对应条件时文明就会进化到下一阶段。阶段提升后不仅会解锁新建筑还会提高整体产出倍率让玩家的努力得到明显反馈。2.3 建筑、科技与事件系统建筑是玩家发展文明的主要手段。每种建筑都有明确的定位小屋提高人口上限。农田提高每回合食物产出。祭坛提高每回合文化产出。图书馆提高每回合科技产出。市场提高每回合黄金产出。科技是对建筑产出的进一步放大。玩家消耗科技点研究科技后可以提升食物产出、文化产出或人口上限。为了增加游戏随机性和趣味性Evolve 还加入了简单的事件系统。每个回合推进时有概率触发随机事件可能是丰收、商队、知识崛起也可能是小型瘟疫。事件系统不需要很复杂但能让游戏过程不那么“机械”。2.4 数值平衡思路增量游戏最容易出问题的地方就是数值平衡。如果资源产出太慢玩家会觉得无聊如果太快游戏又会迅速失去挑战。Evolve 的第一版数值设计遵循了几个简单原则食物是前期最重要的资源玩家必须优先建农田否则人口无法增长。文化是中前期资源用于进入农业时代和城邦时代。科技依赖图书馆进入城邦时代后成为主要研究资源。黄金在城邦时代之后才成为核心资源对应市场的解锁时期。文明阶段越高产出倍率越高但有阶段条件卡着不会让玩家瞬间跳级。这套数值不一定是最终答案但它能让游戏从头到尾保持一个相对舒适的发展节奏。具体的调整方法我会在第 6 章展开讨论。3. 环境准备与项目结构3.1 运行环境Evolve 使用 Python 标准库实现不依赖任何第三方库因此环境准备非常简单Python 3.8 及以上版本。一个文本编辑器推荐 VS Code 或 PyCharm。能在终端运行 Python 命令即可。检查 Python 版本python --version只要输出类似Python 3.10.x、Python 3.11.x这样的版本号就可以继续。3.2 项目结构项目只需要一个 Python 文件非常轻量evolve/ └── game.py运行后会生成一个存档文件evolve/ ├── game.py └── evolve_save.jsongame.py是游戏全部逻辑包括数据定义、核心类、事件系统、存档系统和主循环。3.3 核心文件职责虽然只有一个文件但代码内部是分层清晰的数据定义部分阶段、建筑、科技、事件的静态配置。Civilization 类负责资源变化、建筑建造、科技研究、事件触发、阶段进化、存档读写。辅助函数状态展示、帮助菜单。main 函数主循环和指令解析。这种组织方式可以保证代码结构清晰。后面如果要扩展成网页版或图形界面版只需要替换掉最外层的主循环即可。4. 完整实战案例4.1 创建项目结构先在本地创建项目文件夹mkdir evolve cd evolve然后在目录下新建game.py把下面的完整代码复制进去。代码较长建议直接整体复制保存后运行。4.2 完整代码game.py# 文件路径evolve/game.py import json import os import random SAVE_FILE evolve_save.json RES_NAMES { pop: 人口, food: 食物, culture: 文化, tech: 科技, gold: 黄金, } PHASES [ { name: 部落时代, base_pop: 20, buildings: [hut, farm], }, { name: 农业时代, base_pop: 50, buildings: [hut, farm, altar, library], need_pop: 30, need_culture: 20, }, { name: 城邦时代, base_pop: 80, buildings: [hut, farm, altar, library, market], need_pop: 60, need_culture: 100, need_tech: 20, }, { name: 帝国时代, base_pop: 100, buildings: [hut, farm, altar, library, market], need_pop: 100, need_gold: 60, need_tech: 80, }, { name: 黄金时代, base_pop: 200, buildings: [hut, farm, altar, library, market], need_pop: 160, need_culture: 300, need_tech: 150, }, ] BUILDINGS [ { id: hut, name: 小屋, desc: 提供更多居住空间人口上限 5, cost: {food: 30}, }, { id: farm, name: 农田, desc: 每回合产出食物 8, cost: {food: 40}, }, { id: altar, name: 祭坛, desc: 每回合产出文化 3, cost: {food: 60, culture: 20}, }, { id: library, name: 图书馆, desc: 每回合产出科技 3, cost: {food: 50, culture: 30}, }, { id: market, name: 市场, desc: 每回合产出黄金 5, cost: {culture: 60, tech: 30, gold: 40}, }, ] TECHS [ { id: farm_tech, name: 农耕技术, desc: 食物产出 50%, cost: {tech: 20, culture: 20}, era: 1, }, { id: culture_tech, name: 艺术发展, desc: 文化产出 50%, cost: {tech: 50, culture: 40}, era: 2, }, { id: city_tech, name: 城市管理, desc: 人口上限提升 20%, cost: {tech: 80, gold: 50}, era: 2, }, ] EVENTS [ { name: 丰收之年, text: 风调雨顺当年作物收成不错。, effect: {food: 50}, }, { name: 知识崛起, text: 先民在岩壁上记录星象科技点增加。, effect: {tech: 10}, }, { name: 贸易商队, text: 远方的商队带来了黄金。, effect: {gold: 15}, }, { name: 小型瘟疫, text: 疾病来袭部分人口损失。, effect: {pop: -2}, }, { name: 文化繁荣, text: 节日庆典提升了文化。, effect: {culture: 12}, }, ] class Civilization: def __init__(self): self.phase_index 0 self.pop 10 self.food 120 self.culture 0 self.tech 0 self.gold 0 self.tick_count 0 self.buildings {hut: 1, farm: 0, altar: 0, library: 0, market: 0} self.researched [] self.total_food 120 self.total_culture 0 property def phase(self): return PHASES[self.phase_index] def era_multiplier(self): return 1 self.phase_index // 2 def max_pop(self): base self.phase[base_pop] self.buildings[hut] * 5 if 城市管理 in self.researched: base int(base * 1.2) return base def rates(self): era self.era_multiplier() food_rate self.buildings[farm] * 8 * era culture_rate self.buildings[altar] * 3 * era tech_rate self.buildings[library] * 3 * era gold_rate self.buildings[market] * 5 * era if 农耕技术 in self.researched: food_rate food_rate food_rate // 2 if 艺术发展 in self.researched: culture_rate culture_rate culture_rate // 2 return { food: food_rate, culture: culture_rate, tech: tech_rate, gold: gold_rate, } def tick(self): self.tick_count 1 rates self.rates() self.food rates[food] self.culture rates[culture] self.tech rates[tech] self.gold rates[gold] self.total_food rates[food] self.total_culture rates[culture] if self.pop self.max_pop() and self.food 20: self.pop 1 self.food - 20 print(【人口增长】新生儿加入部落人口 1。) if random.random() 0.15: self.trigger_event() self.check_phase_up() def trigger_event(self): event random.choice(EVENTS) print(f【事件】{event[name]}{event[text]}) for key, amount in event[effect].items(): old getattr(self, key, 0) new max(0, old amount) setattr(self, key, new) print(f - {RES_NAMES.get(key, key)} 变化 {int(new - old):d}) def check_phase_up(self): while self.phase_index len(PHASES) - 1: nxt PHASES[self.phase_index 1] conditions { pop: nxt.get(need_pop, 0), culture: nxt.get(need_culture, 0), tech: nxt.get(need_tech, 0), gold: nxt.get(need_gold, 0), } ok True for key, need in conditions.items(): if getattr(self, key) need: ok False break if not ok: break self.phase_index 1 print(f【文明进化】你的文明进入了「{self.phase[name]}」) def perform(self): if self.pop 0: print(已经没有人口进行劳作。) return if self.food 1: print(食物不足无法组织劳作。) return self.food - 1 gain_culture 2 * self.era_multiplier() gain_gold 1 self.phase_index self.culture gain_culture self.gold gain_gold self.total_culture gain_culture print(f部落众人开展劳作与祭祀文化 {gain_culture}黄金 {gain_gold}。) def build_menu(self): available [b for b in BUILDINGS if b[id] in self.phase[buildings]] print(可建造的建筑) for i, b in enumerate(available, 1): cost_str .join( f{int(v)} {RES_NAMES[k]} for k, v in b[cost].items() ) print(f{i}. {b[name]}{cost_str}—— {b[desc]}) print(0. 返回) choice input(请选择编号).strip() if choice 0: return try: idx int(choice) - 1 if 0 idx len(available): self.build(available[idx][id]) else: print(编号超出范围。) except ValueError: print(请输入数字。) def build(self, building_id): b next(x for x in BUILDINGS if x[id] building_id) cost b[cost] for res, amount in cost.items(): if getattr(self, res) amount: print( f资源不足需要 {int(amount)} {RES_NAMES[res]} f当前只有 {int(getattr(self, res))}。 ) return for res, amount in cost.items(): setattr(self, res, getattr(self, res) - amount) self.buildings[building_id] 1 print(f建造完成{b[name]}。) def tech_menu(self): available [ t for t in TECHS if t[era] self.phase_index and t[name] not in self.researched ] if not available: print(当前没有可以研究的科技。) return print(可研究的科技) for i, t in enumerate(available, 1): cost_str .join( f{int(v)} {RES_NAMES[k]} for k, v in t[cost].items() ) print(f{i}. {t[name]}{cost_str}—— {t[desc]}) print(0. 返回) choice input(请选择编号).strip() if choice 0: return try: idx int(choice) - 1 if 0 idx len(available): self.research(available[idx][id]) else: print(编号超出范围。) except ValueError: print(请输入数字。) def research(self, tech_id): t next(x for x in TECHS if x[id] tech_id) if t[name] in self.researched: print(该科技已经研究完成。) return cost t[cost] for res, amount in cost.items(): if getattr(self, res) amount: print(f资源不足需要 {int(amount)} {RES_NAMES[res]}。) return for res, amount in cost.items(): setattr(self, res, getattr(self, res) - amount) self.researched.append(t[name]) print(f研究完成{t[name]}效果{t[desc]}) def save(self): payload { phase_index: self.phase_index, pop: self.pop, food: self.food, culture: self.culture, tech: self.tech, gold: self.gold, tick_count: self.tick_count, buildings: self.buildings, researched: self.researched, total_food: self.total_food, total_culture: self.total_culture, } with open(SAVE_FILE, w, encodingutf-8) as f: json.dump(payload, f, ensure_asciiFalse, indent2) classmethod def load(cls): if not os.path.exists(SAVE_FILE): return cls() with open(SAVE_FILE, r, encodingutf-8) as f: data json.load(f) game cls() for key, value in data.items(): if hasattr(game, key): setattr(game, key, value) return game def print_status(game): rates game.rates() print( * 56) print(f回合 {game.tick_count} | {game.phase[name]} | 已研究 {len(game.researched)} 项科技) print(f人口: {int(game.pop)} / {game.max_pop()}) print(f食物: {int(game.food)}每回合 {int(rates[food])}) print(f文化: {int(game.culture)}每回合 {int(rates[culture])}) print(f科技: {int(game.tech)}每回合 {int(rates[tech])}) print(f黄金: {int(game.gold)}每回合 {int(rates[gold])}) print( * 56) def print_help(): print( 操作说明 [回车] 推进一个回合获得建筑被动产出 f 组织劳作消耗 1 食物获得文化与黄金 b 打开建造菜单 t 打开科技研究菜单 s 保存游戏 q 保存并退出 ) def main(): game Civilization.load() print(欢迎来到 Evolve文明演化增量游戏) print(输入 help 查看指令直接回车推进回合。) while True: print_status(game) print(指令: [回车]推进 | [f]劳作 | [b]建造 | [t]研究 | [s]保存 | [help]帮助 | [q]退出) cmd input( ).strip().lower() if cmd q: game.save() print(已保存并退出。再见) break elif cmd : game.tick() elif cmd f: game.perform() elif cmd b: game.build_menu() elif cmd t: game.tech_menu() elif cmd s: game.save() print(保存成功。) elif cmd help: print_help() else: print(未知指令输入 help 查看说明。) if __name__ __main__: main()4.3 核心逻辑拆解先看数据定义部分。PHASES、BUILDINGS、TECHS、EVENTS四个列表集中管理了游戏的静态配置。把配置和逻辑分离是值得保持的好习惯。以后想加新建筑只需要往BUILDINGS里追加一条记录不需要改动其他代码。Civilization类承担了所有运行时状态。类属性phase_index表示当前处于哪个文明阶段pop、food、culture、tech、gold是五种核心资源buildings是每种建筑的数量researched保存已经研究完成的科技名称。类的核心方法是tick()它代表“推进一个回合”。每次执行tick()时游戏会做四件事根据建筑数量计算本回合的被动产出。判断人口是否可以增长。以一定概率触发随机事件。检测是否满足文明进化条件。其中rates()方法把建筑数量和产出倍率转换成具体的资源增量。这里的设计重点在于只要新增了建筑或研究了科技rates()的返回结果就会立即变化所以tick()本身不需要知道建筑的细节降低了耦合度。check_phase_up()负责阶段进化。它从当前阶段的下一个阶段开始检查如果人口、文化、科技、黄金全部达到要求就提升phase_index。这里的逻辑用了while循环允许一次回合内连续跨越多个阶段不过由于阶段条件的资源要求总体递增实际游戏中很少会出现连跳两阶的情况。build()和research()是操作入口它们的逻辑非常像先检查资源是否足够够了就扣除资源、执行效果不够就给出提示。这种“先校验后扣费再生效”的顺序在写任何涉及资源消耗的系统时都值得参考。如果先扣费再校验一旦校验失败资源就白白丢失了。事件系统放在trigger_event()里。事件从EVENTS列表中随机抽取然后逐条应用effect字典。因为effect使用资源名作为键所以写新的随机事件非常容易不需要改动事件处理逻辑。4.4 运行与验证在项目目录下执行python game.py看到欢迎信息后游戏就会进入主循环。下面是一个完整的早期体验流程欢迎来到 Evolve文明演化增量游戏 输入 help 查看指令直接回车推进回合。 回合 0 | 部落时代 | 已研究 0 项科技 人口: 10 / 25 食物: 120每回合 0 文化: 0每回合 0 科技: 0每回合 0 黄金: 0每回合 0 指令: [回车]推进 | [f]劳作 | [b]建造 | [t]研究 | [s]保存 | [help]帮助 | [q]退出 b 可建造的建筑 1. 小屋30 食物—— 提供更多居住空间人口上限 5 2. 农田40 食物—— 每回合产出食物 8 0. 返回 请选择编号2 建造完成农田。紧接着再推进几个回合就能看到被动产出和人口增长 回合 3 | 部落时代 | 已研究 0 项科技 人口: 12 / 25 食物: 95每回合 8 文化: 0每回合 0 科技: 0每回合 0 黄金: 0每回合 0 指令: [回车]推进 | [f]劳作 | [b]建造 | [t]研究 | [s]保存 | [help]帮助 | [q]退出 f 部落众人开展劳作与祭祀文化 2黄金 1。当食物、文化、人口满足条件后游戏会提示【文明进化】你的文明进入了「农业时代」到这里核心玩法已经完整跑通。4.5 存档说明游戏会把进度保存到同目录下的evolve_save.json。存档内容包含当前阶段、资源数量、建筑数量、科技列表等所有关键状态。手动存档可以输入s退出时程序也会自动保存。再次启动游戏时Civilization.load()会检查存档文件是否存在存在则读取并恢复不存在则创建一个新文明。这种存档方案足够应对单机小游戏但如果要发布到更大平台建议改成“自动存档 手动存档”双通道。为了方便后续排查问题存档文件使用了indent2格式化输出直接用文本编辑器打开就能看到每一个字段的值调试时非常方便。5. 常见问题与排查思路5.1 高频问题速查问题现象常见原因解决思路游戏启动报FileNotFoundError工作目录不对找不到存档文件先进入evolve目录再执行python game.py存档解析失败或乱码手动编辑过evolve_save.json导致 JSON 格式错误删除存档文件重新开始或从备份中恢复人口迟迟不增长人口已达到上限或食物不足 20建造小屋提升上限建造农田保证食物产出科技长期保持 0还没进入农业时代无法建造图书馆先把文明推进到农业时代再建造图书馆黄金一直不够市场未解锁或市场数量不足推进到城邦时代后建造市场前期可通过劳作补充回合推进后资源没有变化对应建筑数量为 0村民没有产出建筑时被动收益就是 0这是正常现象先建对应建筑中文显示乱码终端编码不是 UTF-8Windows 下可先执行chcp 65001或更换终端字体5.2 一个排查示例科技点不增长假设你现在处于农业时代但科技点一直是 0。按照下面的顺序排查先看print_status输出的“科技”一行。如果每回合加成为 0说明图书馆数量为 0。打开建造菜单确认当前阶段是否包含图书馆。农业时代开始才解锁图书馆部落时代看不到它是正常的。检查食物和文化是否足够建造图书馆。图书馆需要 50 食物和 30 文化如果文化不够需要先建祭坛或手动劳作积累。如果以上都正常再看是否已经研究过与科技相关的科技。科技研究需要消耗科技点但研究的目的是放大产出不会直接给科技点。这种排查思路同样适用于黄金、文化、人口等任何资源先看产出再看建筑数量再看阶段解锁最后看成本是否满足。6. 最佳实践与工程建议6.1 从脚本到工程的改造game.py是适合学习的最小结构但如果想长期维护我建议尽早把它拆成多个模块evolve/ ├── main.py # 主循环与交互 ├── data.py # 阶段、建筑、科技、事件配置 ├── civilization.py # Civilization 核心类 ├── events.py # 事件系统 └── storage.py # 存档与读档这样每个文件的职责更清晰也方便为不同模块写单元测试。比如civilization.py不依赖input()输入测试时可以直接构造一个Civilization对象不断调用tick()并断言资源变化是否符合预期。6.2 数值调优经验当前版本的建筑价格是固定值想建多少就建多少这在长线养成中很容易让数值膨胀失控。真实的增量游戏通常会采用两个手段递增价格建筑数量越多下一次建造的消耗越高常见公式是价格 × 1.15^当前数量。这样玩家前期能快速买几个但后期必须权衡先提升产出还是先扩充数量。解锁阈值某些建筑或科技要求上一类建筑达到指定数量才解锁比如“需要 5 个农田才能建造磨坊”。调数值时可以用一个简单公式帮助评估回本回合数 建筑成本 ÷ 每回合新增产出。比如农田成本 40 食物每回合产出 8 食物回本需要 5 回合。如果回本回合数过长玩家会觉得收益太低如果太短游戏又失去积累感。随着阶段推进可以把回本周期从 3 回合慢慢拉长到 10 回合以上用来控制节奏。6.3 存档与异常处理当前存档写入方式足够简单但在生产项目中建议做两步改进先写入临时文件再用os.replace()替换正式存档。这样即使写入中途程序崩溃也不会破坏原存档。读取存档时用try-except包住json.load()解析失败时给出友好提示而不是直接让程序崩溃。另外涉及资源扣减时一定要坚持“先校验、后扣费、再生效”的顺序并且把校验逻辑抽成方法。这样以后增加新建筑或新科技时不需要重复写判断逻辑。6.4 扩展方向Evolve 目前的交互是终端命令行扩展空间很大离线收益保存时记录时间戳下次启动时按离线时长补发部分收益。图形界面改用pygame-ce或Arcade实现界面资源状态变成进度条和图标。Web 版本用NiceGUI、Flet或Flask WebSocket做成浏览器游戏多人排行榜会更方便。随机事件权重把事件从等概率随机改成带权重的稀有度机制比如“金色传说”事件概率低但收益高。科技树可视化把线性科技列表扩展成树状结构让玩家看到更长远的养成路径。7. 总结与下一步学习建议Evolve 这个项目虽然很小但已经覆盖了增量游戏最核心的几个模块资源模型、建筑产出、科技研究、随机事件、阶段进化、存档读写。它的代码量不大绝大多数逻辑都集中在一个类里非常适合作为第一次尝试游戏开发的练手项目。如果你从头到尾把代码敲了一遍我建议下一步做两件事。第一把game.py拆分成多文件并为Civilization.tick()、build()、research()编写单元测试用测试验证数值逻辑是否符合预期。第二尝试调整数值感受不同参数对游戏节奏的影响。增量游戏真正难的地方不在代码而在数值手感这一点只有亲手改过参数才能体会到。如果这篇实战笔记对你有帮助可以收藏备用也欢迎在评论区聊聊你的文明进展到哪个时代了。
返回列表