1. 项目背景:从“冒险故事”到“可运行的程序”
先问大家一个问题:如果让你用代码实现一段冒险故事,你会怎么设计?
最近整理素材时,我一直在想一个很有意思的题材——“宝可梦:百变怪与喵喵的冒险”。百变怪的特点是能变身成其他宝可梦,喵喵的特点是能学习人类语言、喜欢收集金币,这两个角色凑在一起,天然就是一套“探索 + 切换形态 + 收集资源 + 战斗决策”的游戏机制。
很多新手学 Python 时,最常见的困惑是:语法学了不少,但不知道能做什么。其实像这种“冒险故事”类的文本游戏,是特别适合练手的项目类型。它不需要复杂图形界面,不需要高配环境,只需要 Python 基础语法、面向对象思想、随机事件设计和流程控制,就能做出一个完整可运行的冒险程序。
这篇文章我们就围绕“宝可梦:百变怪与喵喵的冒险”这个主题,从零到一实现一个 Python 文本冒险小游戏。项目会包含角色系统、变身系统、战斗系统、金币收集系统、随机事件系统和多分支剧情系统,最终能在命令行中完整运行。
内容适合以下读者:
- 刚学完 Python 基础语法、想通过项目巩固知识的新手;
- 想了解文本冒险游戏、回合制对战如何设计的学生;
- 需要在课程设计、毕业设计或博客项目中找灵感的后端方向开发者。
读完这篇文章后,你将掌握:
- 如何设计一个轻量级的冒险游戏架构;
- 如何用面向对象的方式管理角色和技能;
- 如何设计随机事件与多分支流程;
- 如何把“变身”“战斗”“收集”三大玩法串起来;
- 如何做防御性编程,避免用户输入导致程序崩溃。
下面我们直接进入技术实现。
2. 环境准备与项目结构
2.1 开发环境说明
这个项目不需要额外安装第三方库,只用 Python 标准库,所以对环境要求非常低。
- 操作系统:Windows 10/11、macOS、Linux 均可;
- Python 版本:建议 Python 3.8 及以上;
- 开发工具:PyCharm、VS Code 或者直接用命令行运行都可以;
- 依赖库:无(仅使用
random、time、os、sys等标准库)。
版本需要根据你的项目实际情况调整,本文示例以 Python 3.10 环境演示,重点展示配置思路和代码逻辑。
2.2 项目结构规划
为了避免所有代码堆在一个文件里,我们把项目拆分成模块。虽然作为入门项目,单文件也能跑,但拆分模块更接近真实工程的写法,后续扩展也方便。
pokemon_adventure/ ├── main.py # 程序入口,负责游戏主循环 ├── models.py # 数据模型:怪物、技能、主角状态 ├── battle.py # 战斗系统:回合制对战 ├── events.py # 事件系统:地图探索和奇遇 └── utils.py # 工具函数:打印延迟、清屏等如果你只是想快速跑通,也可以把所有代码放在main.py一个文件里。但按照上面的模块拆分,你能更直观地看到不同职责的代码如何组织。
2.3 运行方式
进入项目目录后,执行:
python main.py或者用 IDE 直接运行main.py。
程序启动后会在命令行提示你输入选项,输入对应数字后按回车即可。
3. 核心概念与设计思路
3.1 什么是文本冒险游戏
文本冒险游戏(Text Adventure Game)是指通过文字描述场景,玩家通过输入指令来推进剧情的游戏形式。它最大的特点是:所有交互都发生在命令行中,没有图形画面,因此对编程要求相对友好,但非常考验逻辑设计。
“宝可梦:百变怪与喵喵的冒险”这个项目本质上是“文本冒险游戏 + 回合制战斗 + 角色养成”的混合体。
3.2 核心角色设计
先明确两个核心角色:
百变怪(Ditto)
- 核心能力:变身(Transformation),可以在战斗或探索中切换成不同形态;
- 初始属性均衡,适合新手理解“形态切换”机制;
- HP(生命值):初始 100;
- Attack(攻击力):初始 20。
喵喵(Meowth)
- 核心能力:收集金币,探索地图时会随机发现金币;
- 战斗时可以使用“抓”(Scratch)和“聚宝功”(Pay Day);
- HP:初始 80;
- Attack:初始 25。
它们两个组合行动:百变怪负责应对不同敌人时切换属性,喵喵负责在每场战斗后收集金币。
3.3 变身机制的设计
在宝可梦设定中,百变怪最核心的机制是“变身”。放到程序里,我们可以把变身设计成“切换状态”:
- 普通形态(normal):均衡;
- 火焰形态(fire):对草系敌人有额外伤害;
- 水流形态(water):对火系敌人有额外伤害;
- 电击形态(electric):对飞行系敌人有额外伤害。
每一种形态对应不同的攻击加成。系统会根据当前敌人类型,自动或让玩家选择是否变身。
3.4 事件驱动模型
游戏整体采用“事件驱动”模型:主循环不断输出场景描述,等待玩家输入指令,然后根据指令改变游戏状态。
主循环: 显示当前场景 显示可用操作 等待玩家输入 根据输入分发到不同处理函数 更新状态 判断游戏是否结束这种模型也是很多后端服务、GUI 程序的基础逻辑,理解它对后续学习有很大帮助。
4. 数据模型设计:models.py
首先实现数据模型。我们定义三个主要类:Skill(技能)、Creature(宝可梦)、PlayerState(玩家状态)。
4.1 技能类 Skill
# 文件路径:pokemon_adventure/models.py class Skill: """技能类,每个技能拥有效果值、属性和冷却信息""" def __init__(self, name, damage, skill_type, cooldown=0): self.name = name # 技能名称 self.damage = damage # 伤害值 self.skill_type = skill_type # 技能属性:normal/fire/water/electric self.cooldown = cooldown # 冷却回合数,0 表示无冷却 self.current_cd = 0 # 当前剩余冷却 def is_available(self): """技能是否可以使用(冷却判断)""" return self.current_cd == 0 def reduce_cooldown(self): """每回合结束时减少冷却""" if self.current_cd > 0: self.current_cd -= 1 def reset_cooldown(self): """使用技能后重置冷却""" if self.cooldown > 0: self.current_cd = self.cooldown def __str__(self): return f"{self.name}(属性:{self.skill_type}, 伤害:{self.damage})"这里的核心是冷却机制。虽然游戏比较简单,但加入冷却后,玩家不能无限使用强技能,战斗会更有策略性。
4.2 宝可梦类 Creature
Creature类负责管理一个宝可梦的基础属性、技能列表和状态。
class Creature: """宝可梦类,表示一个可战斗的精灵""" def __init__(self, name, hp, attack, defense, skills, creature_type="normal"): self.name = name self.max_hp = hp self.hp = hp self.attack = attack self.defense = defense self.skills = skills self.creature_type = creature_type def take_damage(self, damage): """受到伤害,扣除 HP""" self.hp -= damage if self.hp < 0: self.hp = 0 return damage def heal(self, amount): """恢复 HP,不能超过最大 HP""" self.hp += amount if self.hp > self.max_hp: self.hp = self.max_hp def is_alive(self): """判断是否存活""" return self.hp > 0 def use_skill(self, skill_index, target): """使用技能攻击目标""" if skill_index < 0 or skill_index >= len(self.skills): return None skill = self.skills[skill_index] if not skill.is_available(): return None # 计算伤害:基础攻击 + 技能伤害 - 目标防御 damage = max(1, self.attack + skill.damage - target.defense) # 属性克制逻辑见 4.4 damage = self._apply_type_bonus(damage, skill.skill_type, target.creature_type) target.take_damage(damage) skill.reset_cooldown() return damage def _apply_type_bonus(self, damage, skill_type, target_type): """属性克制:对特定目标属性造成 1.5 倍伤害""" boost_map = { ("fire", "grass"): 1.5, ("water", "fire"): 1.5, ("electric", "flying"): 1.5, } if (skill_type, target_type) in boost_map: return int(damage * boost_map[(skill_type, target_type)]) return damage def __str__(self): return f"{self.name} [HP:{self.hp}/{self.max_hp}]"注意_apply_type_bonus这个方法,它实现了属性克制,让“变身切换属性”变得有意义。
4.3 玩家状态类 PlayerState
PlayerState用来记录游戏进度,包括当前地图位置、金币数量和已收集的形态。
class PlayerState: """记录游戏推进状态""" def __init__(self, start_position="小镇"): self.position = start_position # 当前位置 self.gold = 0 # 金币数量 self.forms = ["normal"] # 已解锁的变身形态 self.current_form = "normal" # 当前形态 self.day = 1 # 时间推进 self.adventure_count = 0 # 探索次数 self.win_flag = False # 是否通关 self.game_over = False # 是否结束 def add_gold(self, amount): """增加金币""" self.gold += amount def add_form(self, form_name): """解锁新的变身形态""" if form_name not in self.forms: self.forms.append(form_name) def change_form(self, form_name): """切换当前形态""" if form_name in self.forms: self.current_form = form_name return True return False def __str__(self): return f"位置:{self.position} 金币:{self.gold} 形态:{self.current_form}"这里需要注意:变身形态的切换不直接影响 Creature 的初始技能,但它会影响后面 combat 中的伤害加成和事件选择,所以单独放在PlayerState中管理是合理的。
5. 战斗系统实现:battle.py
5.1 战斗流程设计
回合制战斗的流程:
1. 玩家选择技能 2. 计算伤害并扣除敌人 HP 3. 判断敌人是否死亡 4. 若未被击败,敌人随机选择技能攻击玩家 5. 每回合结束,技能冷却减 1 6. 回到第 1 步,直到一方 HP 归零5.2 战斗函数
# 文件路径:pokemon_adventure/battle.py import random import time from models import Creature def battle(player_creature: Creature, enemy_creature: Creature): """ 回合制战斗 返回 True 表示玩家胜利,False 表示玩家失败 """ print("=" * 40) print(f"战斗开始!{player_creature.name} VS {enemy_creature.name}") print("=" * 40) while player_creature.is_alive() and enemy_creature.is_alive(): print(f"\n{player_creature}") print(f"{enemy_creature}") # 玩家选择技能 while True: print("\n请选择技能:") for idx, skill in enumerate(player_creature.skills): status = "" if skill.is_available() else " [冷却中]" print(f"{idx + 1}. {skill.name}(伤害:{skill.damage}){status}") print("0. 逃跑") try: choice = int(input("请输入数字: ")) except ValueError: print("输入无效,请输入数字。") continue if choice == 0: # 逃跑有一定概率成功 if random.random() < 0.5: print("你成功逃跑了!") return False else: print("逃跑失败!") # 敌人获得一次攻击机会 enemy_damage = enemy_creature.use_skill( random.randint(0, len(enemy_creature.skills) - 1), player_creature ) if enemy_damage: print(f"{enemy_creature.name} 对 {player_creature.name} 造成 {enemy_damage} 点伤害!") break skill_index = choice - 1 if skill_index < 0 or skill_index >= len(player_creature.skills): print("技能编号不存在,请重新选择。") continue if not player_creature.skills[skill_index].is_available(): print("该技能还在冷却中!") continue break if not player_creature.is_alive(): break if choice == 0: # 逃跑分支已经处理过敌人攻击 continue # 玩家攻击 damage = player_creature.use_skill(skill_index, enemy_creature) if damage: print(f"{player_creature.name} 使用 {player_creature.skills[skill_index].name} 对 {enemy_creature.name} 造成 {damage} 点伤害!") # 判断敌人死亡 if not enemy_creature.is_alive(): print(f"{enemy_creature.name} 倒下了!") break # 敌人攻击 enemy_skill_index = random.randint(0, len(enemy_creature.skills) - 1) enemy_damage = enemy_creature.use_skill(enemy_skill_index, player_creature) if enemy_damage: print(f"{enemy_creature.name} 使用 {enemy_creature.skills[enemy_skill_index].name} 对 {player_creature.name} 造成 {enemy_damage} 点伤害!") # 每回合结束处理冷却 for skill in player_creature.skills: skill.reduce_cooldown() for skill in enemy_creature.skills: skill.reduce_cooldown() time.sleep(1) return player_creature.is_alive()这段代码有几个关键点:
- 输入防御:使用
try...except ValueError处理用户输入非数字的情况,这是命令行程序最常见的崩溃原因。 - 逃跑判定:引入随机数,让逃跑存在失败概率,而不是必成功,增加游戏性。
- 冷却刷新:每个回合结束统一调用
reduce_cooldown,保证技能冷却逻辑正确。
6. 事件系统实现:events.py
事件系统负责“探索地图 - 触发事件 - 获得奖励/遭遇战斗”这条主流程。
6.1 地图区域定义
我们设计 4 个探索区域:
- 小镇边缘(safe)
- 神秘森林(normal)
- 火焰洞穴(fire)
- 湖畔水岸(water)
玩家初始在小镇边缘,随着探索推进,会解锁更多区域和变身形态。
6.2 探索事件函数
# 文件路径:pokemon_adventure/events.py import random from models import Creature, Skill from battle import battle def create_default_skills(): """创建默认技能列表""" return [ Skill("撞击", 10, "normal", cooldown=0), Skill("变身冲击", 20, "normal", cooldown=2), Skill("高速旋转", 15, "normal", cooldown=1), ] def create_fire_skills(): """火焰形态技能""" return [ Skill("撞击", 10, "normal", cooldown=0), Skill("火焰拳", 25, "fire", cooldown=2), Skill("火花", 15, "fire", cooldown=1), ] def create_water_skills(): """水流形态技能""" return [ Skill("撞击", 10, "normal", cooldown=0), Skill("水之波动", 25, "water", cooldown=2), Skill("泡沫光线", 15, "water", cooldown=1), ] def create_electric_skills(): """电击形态技能""" return [ Skill("撞击", 10, "normal", cooldown=0), Skill("十万伏特", 28, "electric", cooldown=3), Skill("电光一闪", 15, "normal", cooldown=1), ] def generate_enemy(area): """根据区域生成敌人""" if area == "神秘森林": return Creature("野生的妙蛙种子", 60, 15, 5, [ Skill("撞击", 8, "normal"), Skill("藤鞭", 12, "grass"), ], creature_type="grass") elif area == "火焰洞穴": return Creature("野生的火恐龙", 80, 20, 8, [ Skill("撞击", 10, "normal"), Skill("火焰牙", 18, "fire"), ], creature_type="fire") elif area == "湖畔水岸": return Creature("野生的杰尼龟", 75, 18, 10, [ Skill("撞击", 10, "normal"), Skill("水枪", 16, "water"), ], creature_type="water") else: return Creature("野生的波波", 40, 10, 3, [ Skill("撞击", 6, "normal"), Skill("烈暴风", 10, "flying"), ], creature_type="flying") def explore_area(player_state, player_creature: Creature): """在指定区域执行探索""" area_events = { "小镇边缘": ["发现少量金币", "遭遇弱小的野生宝可梦", "休息恢复 HP", "遇到路人获得情报"], "神秘森林": ["发现宝可梦蛋", "遭遇草系宝可梦", "发现树果", "遭遇训练家挑战"], "火焰洞穴": ["发现火焰碎片", "遭遇火系宝可梦", "身体变得温暖", "发现隐藏金币"], "湖畔水岸": ["发现水系宝石", "遭遇水系宝可梦", "在湖边休息", "发现沉没的宝藏"], } events = area_events.get(player_state.position, area_events["小镇边缘"]) event = random.choice(events) print(f"\n你来到了{player_state.position},进行探索...") print(f"事件:{event}") if "金币" in event or "宝藏" in event: gold = random.randint(10, 50) player_state.add_gold(gold) print(f"获得 {gold} 金币!当前金币:{player_state.gold}") elif "野生" in event or "训练家" in event: enemy = generate_enemy(player_state.position) print(f"遭遇了{enemy.name}!") victory = battle(player_creature, enemy) if victory: reward = random.randint(20, 60) player_state.add_gold(reward) print(f"战斗胜利!获得 {reward} 金币!") print(f"喵喵使用聚宝功收集了额外金币!") bonus = random.randint(5, 15) player_state.add_gold(bonus) print(f"额外获得 {bonus} 金币!") return True else: player_state.game_over = True print("战斗失败,冒险结束...") return False elif "休息" in event or "树果" in event: heal_amount = 30 player_creature.heal(heal_amount) print(f"{player_creature.name} 恢复了 {heal_amount} 点 HP!") elif "情报" in event: forms = ["fire", "water", "electric"] unlock_form = random.choice(forms) player_state.add_form(unlock_form) print(f"获得重要情报:解锁了 {unlock_form} 形态!") # 增加探索计数和时间推进 player_state.adventure_count += 1 if player_state.adventure_count % 3 == 0: player_state.day += 1 print(f"\n【时间推进】已经是第 {player_state.day} 天了") return True6.3 形态切换与探索流程
在探索前,玩家可以切换形态。对应关系如下:
| 玩家当前形态 | 可用的技能集合 |
|---|---|
| normal | 撞击、变身冲击、高速旋转 |
| fire | 撞击、火焰拳、火花 |
| water | 撞击、水之波动、泡沫光线 |
| electric | 撞击、十万伏特、电光一闪 |
切换形态后,需要更新player_creature.skills,这样战斗时使用的技能会随形态改变。
这一步在主程序中实现。
7. 主程序实现:main.py
7.1 初始化游戏
# 文件路径:pokemon_adventure/main.py import os import sys import time from models import Creature, Skill, PlayerState from events import ( explore_area, create_default_skills, create_fire_skills, create_water_skills, create_electric_skills, ) SKILL_MAP = { "normal": create_default_skills, "fire": create_fire_skills, "water": create_water_skills, "electric": create_electric_skills, } def clear_screen(): """清屏(兼容 Windows 和 Unix)""" os.system('cls' if os.name == 'nt' else 'clear') def print_banner(): """打印游戏标题""" print("=" * 50) print(" 宝可梦:百变怪与喵喵的冒险") print("=" * 50) print("你和搭档百变怪、喵喵一起踏上冒险旅程") print("探索地图、收集金币、解锁形态、击败强敌!") print("=" * 50) time.sleep(1) def create_player(): """创建玩家控制的百变怪""" # 初始形态为普通形态 skills = create_default_skills() player_creature = Creature( name="百变怪", hp=100, attack=20, defense=5, skills=skills, creature_type="normal" ) # 创建喵喵作为搭档(暂不直接参与战斗,但在事件中提供金币加成) meowth = Creature( name="喵喵", hp=80, attack=25, defense=3, skills=[ Skill("抓", 10, "normal"), Skill("聚宝功", 15, "normal", cooldown=2), ], creature_type="normal" ) return player_creature, meowth def change_form_skills(player_creature, form_name): """根据形态切换技能""" if form_name in SKILL_MAP: player_creature.skills = SKILL_MAP[form_name]() player_creature.creature_type = form_name return True return False def show_status(player_creature, meowth, player_state): """显示当前游戏状态""" print("\n" + "=" * 50) print("【当前状态】") print(f"百变怪:{player_creature}") print(f"喵喵:{meowth}") print(str(player_state)) print(f"已解锁形态:{', '.join(player_state.forms)}") print("=" * 50) def show_menu(player_state): """显示主菜单""" print("\n请选择行动:") print("1. 探索当前区域") print("2. 切换变身形态") print("3. 查看状态") print("4. 休息恢复 HP") print("5. 前往其他区域") print("0. 退出冒险") def main(): clear_screen() print_banner() player_creature, meowth = create_player() player_state = PlayerState() while not player_state.game_over: show_menu(player_state) try: choice = int(input("请输入选项:")) except ValueError: print("输入无效,请输入数字。") continue if choice == 0: print("感谢游玩,期待你的下次冒险!") sys.exit(0) elif choice == 1: clear_screen() result = explore_area(player_state, player_creature) if not result: break time.sleep(1) elif choice == 2: clear_screen() print("\n可以切换的形态:") form_list = player_state.forms for idx, form in enumerate(form_list): print(f"{idx + 1}. {form}") print("请输入形态编号:") try: form_choice = int(input(">>> ")) except ValueError: print("输入无效。") continue if 1 <= form_choice <= len(form_list): form_name = form_list[form_choice - 1] # 切换百变怪的形态 ok = change_form_skills(player_creature, form_name) if ok: player_state.change_form(form_name) print(f"百变怪变身成了{form_name}形态!") else: print("形态切换失败,未知形态。") else: print("形态编号不存在。") time.sleep(1) elif choice == 3: clear_screen() show_status(player_creature, meowth, player_state) time.sleep(1) elif choice == 4: clear_screen() heal_amount = 50 player_creature.heal(heal_amount) print(f"百变怪休息了一下,恢复了 {heal_amount} 点 HP!") print(f"当前 HP:{player_creature.hp}/{player_creature.max_hp}") time.sleep(1) elif choice == 5: clear_screen() print("\n可选区域:") areas = ["小镇边缘", "神秘森林", "火焰洞穴", "湖畔水岸"] for idx, area in enumerate(areas): print(f"{idx + 1}. {area}") print("请输入区域编号:") try: area_choice = int(input(">>> ")) except ValueError: print("输入无效。") continue if 1 <= area_choice <= len(areas): new_area = areas[area_choice - 1] # 火焰洞穴和湖畔水岸需要对应形态或足够金币解锁 if new_area == "火焰洞穴" and "fire" not in player_state.forms: print("进入火焰洞穴需要先获得火焰情报!") elif new_area == "湖畔水岸" and "water" not in player_state.forms: print("进入湖畔水岸需要先获得水流情报!") else: player_state.position = new_area print(f"你来到了{new_area}。") else: print("区域编号不存在。") time.sleep(1) else: print("未知选项,请重新输入。") print("\n游戏结束!") print(f"最终金币数量:{player_state.gold}") print(f"探索次数:{player_state.adventure_count}") if __name__ == "__main__": main()7.2 主流程分析
整个主程序是一个典型的循环结构:
- 显示菜单;
- 读取玩家输入;
- 按输入分发到不同功能分支;
- 更新游戏状态;
- 循环直到游戏结束。
这种模式在控制台程序中非常常见。你可以在此基础上继续添加背包系统、道具系统、剧情对话系统等。
8. 运行与验证
8.1 运行过程演示
启动程序后,预期输出如下:
================================================== 宝可梦:百变怪与喵喵的冒险 ================================================== 你和搭档百变怪、喵喵一起踏上冒险旅程 探索地图、收集金币、解锁形态、击败强敌! ================================================== 请选择行动: 1. 探索当前区域 2. 切换变身形态 3. 查看状态 4. 休息恢复 HP 5. 前往其他区域 0. 退出冒险 请输入选项:输入3查看状态:
【当前状态】 百变怪:百变怪 [HP:100/100] 喵喵:喵喵 [HP:80/80] 位置:小镇边缘 金币:0 形态:normal 已解锁形态:normal输入1探索,可能会触发“遭遇野生的波波”,进入战斗界面:
==================================================== 战斗开始!百变怪 VS 野生的波波 ==================================================== 百变怪 [HP:100/100] 野生的波波 [HP:40/40] 请选择技能: 1. 撞击(伤害:10) 2. 变身冲击(伤害:20) 3. 高速旋转(伤害:15) 0. 逃跑 请输入数字:选择技能后,战斗会按照回合制逻辑运行,最终输出胜负结果。
8.2 你可能会遇到的运行问题
如果你在运行过程中遇到了一些小问题,可以对照下面这个表格快速排查:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
运行python main.py提示找不到文件 | 没有进入项目目录 | 先cd pokemon_adventure再执行 |
| 输入字母导致程序崩溃 | 没有对输入做异常捕获 | 本文代码已经用try...except处理,确认代码完整 |
| 切换形态后技能没有变化 | change_form_skills没有被调用 | 检查主程序中选项 2 的分支 |
| 战斗后金币不增加 | 没有将战斗奖励累加到PlayerState | 确认explore_area中调用add_gold() |
| 火焰洞穴无法进入 | 还没有解锁火焰形态 | 先在小镇边缘探索,触发“情报”事件 |
| 游戏无法退出 | 主循环缺少退出分支 | 确认choice == 0时调用了sys.exit(0) |
9. 项目扩展:如何继续完善这个游戏
这个项目的核心框架搭好后,你可以根据自己的兴趣扩展很多功能。
9.1 增加背包系统
目前游戏只有金币和形态,比较单一。可以增加一个Inventory类:
class Inventory: def __init__(self): self.items = {} def add_item(self, item_name, count=1): self.items[item_name] = self.items.get(item_name, 0) + count def use_item(self, item_name): if self.items.get(item_name, 0) > 0: self.items[item_name] -= 1 return True return False然后在地图事件中随机掉落“伤药”“力量强化剂”等道具,战斗中使用后恢复 HP 或提升攻击。
9.2 增加多分支剧情
引入剧本文件,用 JSON 描述剧情节点:
{ "start": { "text": "你站在小镇门口,前方有三条路。", "choices": [ {"text": "去森林", "next": "forest", "condition": "gold>=10"}, {"text": "去洞穴", "next": "cave", "condition": "form==fire"}, {"text": "回镇子", "next": "town"} ] } }再用一个StoryEngine类根据条件加载节点,自动推进剧情。这样游戏就从“事件循环”升级成了“剧情树”。
9.3 增加敌人刷新概率
目前敌人生成是等概率的,可以按区域调整概率:
def generate_enemy(area): encounter_table = { "神秘森林": [ (0.6, "野生的妙蛙种子"), (0.4, "野生的绿毛虫"), ], } # 按概率随机选择一个9.4 增加音效或界面
如果想让游戏更像“真正的游戏”,可以用pygame或curses增加界面。但在学习阶段,建议先把逻辑功能做完,再考虑接口美化。
10. 常见问题与最佳实践
10.1 面向对象设计要点
在这个项目中,我们使用了三个核心类来组织数据。为什么不直接写一堆dict和全局变量?
因为:
- 可维护性好:新增一个宝可梦只需要
Creature(...)一行代码; - 行为内聚:伤害计算、技能冷却、属性克制都和对象绑定,逻辑不会散落各处;
- 易于扩展:后续如需增加“经验值”“等级”“进化”,只需要在类中添加属性和方法。
新手在项目初期容易把所有逻辑写在主函数里,后面发现自己都看不懂代码了。建议学习这种“一个类管理一块职责”的思路。
10.2 输入验证与防御性编程
命令行程序最常见的崩溃原因是“用户输入了不合法值”。本项目中有两类处理:
# 场景一:输入不是数字 try: choice = int(input("请输入选项:")) except ValueError: print("输入无效,请输入数字。") continue # 场景二:输入数字越界 if choice < 0 or choice > 5: print("未知选项,请重新输入。") continue实际工程中,输入验证不只存在于游戏程序,任何面向用户的系统都需要考虑。搜索引擎的搜索框、表单提交、API 请求参数,本质上都在做“输入校验”这件事。
10.3 合理使用随机事件
本项目使用random.choice和random.randint来生成随机事件。需要注意的是:
- 随机事件要有明确反馈,不能让玩家觉得“莫名其妙”;
- 随机事件的影响范围要可控,不能随机到直接结束游戏的极端情况(除非是特殊隐藏事件);
- 调试时可以通过设置固定随机种子来复现问题:
import random random.seed(42) # 调试时使用固定种子,方便复现10.4 代码模块划分
推荐这样的项目结构规范:
models.py:只放数据模型,不包含业务逻辑;battle.py:只放战斗相关逻辑;events.py:只放地图探索和事件策略;main.py:主流程和菜单处理。
这样拆分后,每个文件的行数不超过 200 行,无论是排查问题还是后续重构都更轻松。
10.5 关于文件命名与编码
- Python 文件名使用小写字母和下划线,例如
battle.py而不是Battle.py; - 类名使用大驼峰命名,例如
PlayerState; - 函数和变量名使用小驼峰或下划线,例如
player_state; - 如果代码中包含中文文本,务必在文件头部添加编码声明(Python 3 默认 UTF-8,一般不需要)。
11. 总结与学习建议
这篇文章我们完整实现了“宝可梦:百变怪与喵喵的冒险”这个文本冒险小游戏,核心内容包括:
- 用
Creature、Skill、PlayerState构建游戏数据模型; - 用回合制战斗系统实现技能选择、冷却、伤害计算和属性克制;
- 用事件系统实现探索地图、随机事件、金币收集和形态解锁;
- 用主循环组织整个游戏流程,让玩家通过命令行交互完成冒险。
整体来说,这个项目虽然是一个命令行小程序,但它包含了真实游戏开发的很多核心元素:状态管理、战斗逻辑、事件驱动、数据建模、输入校验。把这些内容吃透,再去学 Pygame 图形化开发或者 Web 后端开发都会更顺利。
下一步你可以尝试:
- 把游戏改成“关卡制”,通关后解锁下一区域;
- 增加存档系统,用 JSON 文件保存进度;
- 增加图鉴系统,记录玩家遇到过的所有宝可梦;
- 把战斗改成实时手动选择,接入键盘事件而不是命令行输入;
- 给游戏增加简单的数据统计,比如“击败敌人数量”“技能使用次数”。
如果这篇文章的代码和思路对你有帮助,可以动手运行一遍,再按自己的想法改造。只有自己动手调试过的代码,才能真正变成你自己的经验。