从控制台到策略深度:我用Java手搓了一个深渊RPG
《从控制台到策略深度:我用 Java 设计了一个 30 层地牢 RPG》
1. 项目概述
这是一个基于 Java 控制台的回合制地牢探险游戏。玩家从第 1 层一路挑战到第 30 层,通过战斗、掉落与策略选择逐步成长,最终迎战深渊破坏神。
游戏包含以下核心内容:
- 角色创建与属性分配
- 回合制战斗(玩家选技能,敌人随机行动)
- Buff 系统(中毒、流血、眩晕、狂暴、圣光免疫等)
- 掉落与背包(消耗品、装备、技能书)
- 装备与套装加成(冒险者 / 狂战士 / 圣骑士)
- 奇遇房间(泉水、商店、洞穴随机事件)
- 存档 / 读档继续冒险
2. 项目结构
为了便于后续平滑升级到 2D 图形界面(GUI),项目采用“流程控制”和“核心实体”分离的组织方式,包结构如下:
1 | com.LuoHe |
2.1 com.LuoHe.domain(核心实体与底层规则)
Character:角色基类,包含血蓝、攻防、Buff 容器与回合结算方法HeroCharacter/EnemyCharacter:玩家与敌人,继承自CharacterItem:物品基类Equipment:装备(武器 / 防具 / 饰品),提供属性加成与套装信息Consumable:消耗品(回血 / 回蓝)SkillBook:技能书(学习技能)
Skill:技能对象(名称、MP 消耗、描述)User:账号对象(用于登录注册与账号持久化)
2.2 com.LuoHe.ui(控制层与展示层)
App:程序入口Login:登录注册模块(账号校验与读写)FightingGame:关卡推进、战斗循环、控制台输出与交互入口
2.3 简单工厂模式:EnemyFactory
深渊共有 30 层,如果把怪物生成逻辑直接写在主循环中,会导致流程类过度膨胀。项目将敌人生成抽离为:
EnemyFactory.createEnemy(stage)
主流程只传入层数,工厂内部负责:
- 普通怪随机抽取与数值强化
- Boss/精英的定点投放
- 对应的提示信息输出(如“区域霸主”“终极警告”等)
3. 战斗系统
本项目采用经典的回合制战斗:每层生成一个敌人,玩家与敌人轮流行动,直到一方生命值归零。
战斗流程为:开战展示 → 回合循环 → 玩家行动 → 敌人行动 → 胜负结算 → 战后处理。
3.1 开战:生成敌人并进入战斗回合
进入某一层后:
- 普通层:随机普通怪,并按层数强化属性
- Boss/精英层(5 的倍数):生成固定敌人并输出警告提示
同时输出关卡与敌人信息,例如:
- “第 N 层战斗开始!对手:xxx”
- 敌我双方血条 / 蓝条展示
3.2 回合循环:直到一方死亡
每层战斗内部使用:
while (player.isAlive() && enemy.isAlive())
并用 round++ 推进回合数。每一回合分为两段:
(1)玩家回合
- 回合前状态结算:
player.updateBuffs() - 若玩家存在
buffs.containsKey("眩晕"):跳过行动 - 否则执行玩家行动:
playerRound(player, enemy)
(2)敌人回合
- 回合前状态结算:
enemy.updateBuffs() - 若敌人眩晕:跳过行动
- 否则敌人行动:
enemyRound(player, enemy)
每次行动后都会做胜负检查,任何一方死亡则退出战斗循环。
3.3 状态结算:回合开始统一处理 Buff
每个角色在自己回合开始时都会调用:
updateBuffs():触发中毒、流血、生命回溯、专注等效果- Buff 回合数减少,为 0 时移除并提示“状态消失”
3.4 玩家行动:技能菜单 + 输入校验 + 执行效果
玩家回合由 playerRound(player, enemy) 实现,流程如下:
- 动态打印技能菜单(遍历
player.skillList) - 输入技能编号并校验范围
- 校验 MP 是否足够
- 扣除 MP:
player.useMP(skill.getMpCost()) - 根据技能名称执行效果(
switch(skill.getName()))
技能一览(示例):
| 技能 | 消耗 | 效果 |
|---|---|---|
| — | —: | — |
| 普通攻击 | 0 MP | 造成 100% 物理伤害 |
| 碎甲重击 | 15 MP | 150% 伤害,30% 概率附加【眩晕】 |
| 嗜血打击 | 20 MP | 120% 伤害,吸取伤害的一部分生命 |
| 狂暴姿态 | 25 MP | 附加【狂暴】3 回合(攻 +50%,防 -30%) |
| 圣光术 | 30 MP | 净化负面状态,并附加【圣光免疫】2 回合 |
| 雷霆万钧 | 40 MP | 200% 伤害,50% 概率附加【眩晕】 |
| 绝境反击 | 50 MP | 血量越低伤害越高,附加【狂暴】1 回合 |
3.5 敌人行动:普通攻击或技能随机选择
敌人回合由 enemyRound(player, enemy) 实现:
- 随机执行普通攻击或技能攻击
- 技能由敌人
skill字段决定,并可能附加中毒、流血、眩晕等状态
3.6 伤害计算:统一公式入口
伤害计算封装为统一方法:
calculateDamage(attack, defence)
所有技能最终都走该入口,保证伤害结算规则一致。
3.7 胜利结算:金币、掉落、战后恢复、进入营地
当敌人被击败后会进行:
- 胜场 +1(用于统计)
- 掉落金币(随机 + 层数加成)
- 概率掉落道具(Boss 层或随机触发)
- 战后恢复生命值(当前版本为固定区间恢复)
- 输出战报与当前胜场
- 进入营地
enterCamp(player, stage)进行整理、存档或继续下一层
4. Buff 系统
项目引入了 中毒、流血、眩晕、狂暴、专注 等异常状态。所有状态保存在:
Map<String, Integer> buffs(状态名 → 剩余回合数)
并在每回合开始前调用 updateBuffs() 结算。
核心结算框架如下:
1 | Iterator<Map.Entry<String,Integer>> iterator = buffs.entrySet().iterator(); |
5. 掉落、背包与装备
5.1 基于多态的背包交互
Equipment、Consumable、SkillBook 都继承自 Item,背包只需要:
List<Item> inventory
交互时通过 instanceof 分流:
- 消耗品:直接使用(回血 / 回蓝)
- 装备:替换装备栏并处理属性变化
- 技能书:学习新技能并加入
skillList
5.2 套装共鸣系统
使用 HashMap 动态统计当前装备栏中的套装件数:
1 | Map<String, Integer> counts = new HashMap<>(); |
随后根据件数触发对应的套装加成(攻击、生命、魔法、防御等)。
5.3 血量过渡算法(换装时的细节处理)
当装备改变 MaxHP/MaxMP 时:
- 上限增加:当前 HP/MP 等额增加
- 上限减少:对溢出部分进行截断,保证当前值不超过上限
6. 奇遇系统
为了避免连续战斗的单调感,项目用取模规则:
stage % 5 == 4
在每个 Boss 关卡前一层(4、9、14、19、24、29)设置“安全屋奇遇”。玩家三选一:
- 治愈泉水:回满生命与魔法
- 地精商店:花金币购买道具/装备
- 未知探险:随机触发陷阱扣血、捡金币、或获得高价值装备
7. 存档读档
项目实现了双重存档机制:
7.1 账号密码持久化
注册、锁定等账号数据通过 IO 将 ArrayList<User> 写入 users.dat,下次启动可直接登录。
7.2 游戏进度持久化
让核心实体(如 Character、Item、Skill 等)实现 Serializable 接口。玩家在营地选择“存档并退出”时:
ObjectOutputStream序列化玩家对象(包含背包、装备、Buff、技能等完整状态)
再次登录时通过反序列化读取并继续冒险。