增量游戏一直是休闲玩法里很有代表性的一个品类:玩家只需要做少量操作,剩下的交给时间,资源就会不断积累,从而逐步解锁更多内容。很多开发者第一次接触这类游戏时,都会好奇“看上去只是点一点、等一等,背后的数值和逻辑到底是怎么设计的”。这篇文章我们就用 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", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False, indent=2) @classmethod def load(cls): if not os.path.exists(SAVE_FILE): return cls() with open(SAVE_FILE, "r", encoding="utf-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()会检查存档文件是否存在,存在则读取并恢复,不存在则创建一个新文明。这种存档方案足够应对单机小游戏,但如果要发布到更大平台,建议改成“自动存档 + 手动存档”双通道。
为了方便后续排查问题,存档文件使用了indent=2格式化输出,直接用文本编辑器打开就能看到每一个字段的值,调试时非常方便。
5. 常见问题与排查思路
5.1 高频问题速查
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
游戏启动报FileNotFoundError | 工作目录不对,找不到存档文件 | 先进入evolve目录再执行python game.py |
| 存档解析失败或乱码 | 手动编辑过evolve_save.json导致 JSON 格式错误 | 删除存档文件重新开始,或从备份中恢复 |
| 人口迟迟不增长 | 人口已达到上限,或食物不足 20 | 建造小屋提升上限,建造农田保证食物产出 |
| 科技长期保持 0 | 还没进入农业时代,无法建造图书馆 | 先把文明推进到农业时代,再建造图书馆 |
| 黄金一直不够 | 市场未解锁,或市场数量不足 | 推进到城邦时代后建造市场,前期可通过劳作补充 |
| 回合推进后资源没有变化 | 对应建筑数量为 0 | 村民没有产出建筑时,被动收益就是 0,这是正常现象,先建对应建筑 |
| 中文显示乱码 | 终端编码不是 UTF-8 | Windows 下可先执行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()编写单元测试,用测试验证数值逻辑是否符合预期。第二,尝试调整数值,感受不同参数对游戏节奏的影响。增量游戏真正难的地方不在代码,而在数值手感,这一点只有亲手改过参数才能体会到。
如果这篇实战笔记对你有帮助,可以收藏备用,也欢迎在评论区聊聊你的文明进展到哪个时代了。