java编写的多态项目,宠物小精灵图形化界面,第三节
Adventure(冒险家)是游戏核心控制类,负责管理玩家资源(装备、药品、宠物);
驱动游戏流程(关卡切换、地图探索、事件交互)
处理用户输入与反馈(移动、战斗、物品获取)
三、核心方法解析
1. start()
:游戏入口方法
功能:初始化游戏,启动主循环处理用户输入与事件。
流程:
关键逻辑:
- 关卡切换:通过传送门(
Portar
)实现,通往下一关时动态生成新关卡(Level
),并更新currentLevel
; - 地图刷新:每次操作后调用
LevelMap.show()
重新绘制地图,确保用户看到最新状态; - 输入校验:使用
Tools.getInputChar()
和Tools.getInputNumber()
限制用户输入范围,避免非法操作。 -
/** * 开始闯关 * start():游戏入口, * 初始化关卡和地图,进入主循环处理用户输入和事件。 * */ public void start(){ currentLevel = new Level(null,1,null); LevelMap map = currentLevel.getMap(); //冒险家进入地图 map.addAdventure(this); while(true){ currentLevel.getMap().show(); System.out.println("请选择移动方向:W(上)、A(左)、S(下)、D(右)、E(退出)"); char direct = Tools.getInputChar(); if(direct == 'E')//退出 { System.out.println("确定要退出吗?Y/N"); char quit = Tools.getInputChar(); if(Character.toUpperCase(quit) == 'Y'){ System.out.println("感谢使用宠物小精灵闯关"); break; } } else { Item item = discovery(direct); if(item != null){ //物品被发现 item.setDiscovery(true); currentLevel.getMap().show(); } if(item instanceof Treasure){//宝箱 processTreasure((Treasure) item,direct); }else if(item instanceof Monster){//怪物 processMonster((Monster) item,direct); } else if (item instanceof Portar) {//传送门 System.out.println("发现传送门是否通过? Y/N"); char pass = Tools.getInputChar(); if(Character.toUpperCase(pass) == 'Y'){ if(((Portar) item).isNext()){//是否是通往下一关卡的传送门 //获取当前关卡的下一关卡 Level nextLevel = currentLevel.getNextLevel(); if(nextLevel == null) { nextLevel = new Level(currentLevel,currentLevel.getNumber()+1,null); //将冒险家加载到地图中 nextLevel.getMap().addAdventure(this); //当前关卡的下一关卡即为新创建的关卡 currentLevel.setNextLevel(nextLevel); } //经过传送门后,下一关卡即为当前关卡 currentLevel = nextLevel; }else{//通往上一关卡的传送门 Level prevLevel = currentLevel.getPrevLevel(); if(prevLevel == null){ System.out.println("非法操作"); }else{ currentLevel = prevLevel; } } } } else {//其他情况 move(direct); } } } }
2.
processMonster(Monster monster, char direct)
:处理怪物战斗功能:与怪物交互,触发战斗逻辑,处理药品使用、宠物选择及战斗结果。
关键步骤:
- 确认战斗:用户选择是否清除怪物(输入Y/N);
- 选择宠物:展示所有宠物信息,用户选择出战宠物;
- 战斗循环:
- 宠物与怪物交替攻击(
Pokemon.attackMonster()
和Monster.attackPokemon()
); - 宠物生命值低于50%时,询问是否使用药品(调用
getCurrentLevelHP()
查找可用药品); - 药品使用后更新背包(若药品数量耗尽则移除,否则减少数量);
- 宠物与怪物交替攻击(
- 战斗结果:
- 怪物被击败:掉落物品(
Monster.drop()
),物品添加至背包(processItem()
); - 宠物被击败:怪物恢复满血(
Monster.resume()
)。
- 怪物被击败:掉落物品(
/**
* 处理怪物方法
* @param monster
* @param direct
*
*/
private void processMonster(Monster monster,char direct){
//询问是否清除怪物
System.out.println("发现" + monster.getName() + ",是否清除?Y/N");
char clear = Tools.getInputChar();//交互
if(Character.toUpperCase(clear) == 'Y'){
for (int i=0;i<pokemons.length;i++)
{//展示怪物信息
System.out.println((i+1) + "\t" + pokemons[i].getItemInformation());
}
//选择出战宠物:
System.out.println("请选择出站宠物小精灵:");
/**
* 调用Tools.getInputNumber(1, pokemons.length)方法
* 来获取用户的输入。该方法会保证用户输入的数字在 1 到pokemons.length这个范围之内。
*/
int number = Tools.getInputNumber(1,pokemons.length);
//由于用户输入的序号是从 1 开始的,而数组的索引是从 0 开始的,所以要使用number-1来获取对应的宠物小精灵
Pokemon pokemon = pokemons[number-1];
//循环条件:怪物和宠物都存活
//当任一生命值≤0 时,跳出循环,进入结果处理
while (monster.getCurrentHealth() > 0 && pokemon.getCurrentHealth() > 0)
{
//获取宠物小精灵的剩余生命值的比例
double rate = pokemon.getHealthPercent();
/**
* 这个方法返回当前生命值相对于最大生命值的百分比,用小数形式表示(范围从 0.0 到 1.0)。例如:
* 满血状态返回 1.0
* 半血状态返回 0.5
* 空血状态返回 0.0
*
* return currentHealth * 1.0 / getHealth();
* // 代入具体数值:
* return 100 * 1.0 / 100;
* // 计算步骤:
* 1. 100 * 1.0 = 100.0(转换为double类型)
* 2. 100.0 / 100 = 1.0(浮点数除法,结果为1.0)
*
* 关键点 1:为什么要乘以 1.0?
* 这涉及 Java 的运算规则:
* 整数除法:如果两个操作数都是整数,结果会被截断为整数(如50/100结果是 0)
* 浮点数除法:只要有一个操作数是浮点数,结果就会保留小数(如50.0/100结果是 0.5)
* 通过乘以1.0(这是一个 double 类型的常量),将整个表达式转换为浮点数运算,确保结果的准确性
*/
if(rate < 0.5){//生命值低于50%,询问是否使用药品
System.out.println(pokemon.getName() + "生命值低于50%,是否使用药品?Y/N");
char eatHp = Tools.getInputChar();
if(Character.toUpperCase(eatHp) == 'Y')
{
HP hp = getCurrentLevelHP(currentLevel.getNumber());
if(hp == null){
System.out.println("背包中没有药品。请探索其他地图");
break;
}else {
//如果药品可以被销毁,说明没有可用数量
if(hp.canDestroy()){
int index = -1;
for (int i = 0;i<medicines.length;i++){
//目的:找到与当前药品等级相同的药品在数组中的位置
//假设当前使用的是 2 级药品,遍历数组找到所有 2 级药品中的第一个(索引 i)
if(hp.getLevelNumber() == medicines[i].getLevelNumber()){
index = i;
break;
}
}
/**
* 作用:将数组中index位置后的元素整体前移一位,覆盖要移除的元素
* 参数解析:
* medicines:源数组(要复制的数组)
* index + 1:从源数组的index+1位置开始复制
* medicines:目标数组(复制到哪个数组)
* index:目标数组的起始位置
* medicines.length - index - 1:复制的元素数量
*
*
* 4. arraycopy数组操作可视化示例
* 假设medicines数组存储了 4 个药品,索引 0~3:
* plaintext
* [1级药品, 2级药品, 3级药品, 2级药品]
*
* 现在要移除索引 1 的 2 级药品(index=1)
* System.arraycopy 的操作是:
* 从源数组的index+1=2位置(3 级药品)开始
* 复制到目标数组的index=1位置
* 复制长度为 4-1-1=2 个元素(3 级药品和 2 级药品)
* 操作后数组变为:
* plaintext
* [1级药品, 3级药品, 2级药品, 2级药品]
*
* 注意:数组长度并未改变,只是索引 1 的元素被覆盖了
*
*/
System.arraycopy(medicines,index + 1,medicines,index,medicines.length - index - 1);
System.out.println("药品已经使用完毕");
}else {
int health = hp.use();
pokemon.setCurrentHealth(pokemon.getCurrentHealth() + health);
}
}
}
}
Tools.lazy(300L);//延迟300ms,控制战斗节奏
pokemon.attackMonster(monster);//宠物攻击怪物
Tools.lazy(300L);
monster.attackPokemon(pokemon);//怪物攻击宠物
Tools.lazy(300L);
}
//怪物已被击败
if(monster.getCurrentHealth() == 0) {
System.out.println("怪物已被击败");
//怪物掉落物品
Item dropItem = monster.drop();//drop方法里面有Tools.getRandomItem(levelNumber);
//展示获取的物品信息
System.out.println("怪物已被击败,掉落" + dropItem.getItemInformation());
processItem(dropItem);//处理获得的物品(如添加到背包)
//怪物被击败后
move(direct);//继续移动,避免卡在战斗位置
}else {//宠物小精灵被击败
monster.resume();//怪物回血
System.out.println(pokemon.getName() + "已被击败");
}
}
}
3. processTreasure(Treasure treasure, char direct)
:处理宝箱
功能:开启宝箱,获取随机物品(药品、装备、宠物),并将物品添加至背包。
关键逻辑:
- 用户确认开启宝箱(输入Y/N);
- 调用
Treasure.open()
生成随机物品(通过Tools.getRandomItem()
); - 调用
processItem()
将物品分类存入对应背包(药品合并、装备更换、宠物融合或新增); - 开启后冒险家移动至宝箱位置(
move(direct)
)。
/**
* processTreasure():
* 处理宝箱事件(打开、获取物品、合并资源)。
* @param treasure
*/
private void processTreasure(Treasure treasure,char direct){
System.out.println("发现宝箱,是否打开? Y/N");
char open = Tools.getInputChar();
//将输入字符转为大写(Character.toUpperCase),如果输入是 Y,则进入开箱逻辑。
if(Character.toUpperCase(open) == 'Y'){
/**
* 开启宝箱获得一个物品
* 调用宝箱的 open() 方法,随机生成一个物品
* (可能是药品、装备或精灵)。
*/
Item item = treasure.open();
//展示获取的物品信息
System.out.println("获得" + item.getItemInformation());
processItem(item);
//宝箱处理后,冒险家移动至宝箱的位置
move(direct);
}
}
4. processItem(Item item)
:处理获得的物品
功能:根据物品类型(药品、装备、宠物),将其添加至对应背包或执行特殊操作(如融合)。
分支逻辑:
- 药品(HP):遍历
medicines
背包,若存在同等级药品则合并数量(HP.addCount()
); - 装备(Equipment):询问用户是否更换宠物装备(
Pokemon.changeEquipment()
),换下的旧装备存入equipments
背包; - 宠物(Pokemon):
- 若背包已有同类型宠物(
getClass()
比较),询问是否融合(Pokemon.merge()
); - 若无同类型宠物,直接扩容
pokemons
背包并添加新宠物。
- 若背包已有同类型宠物(
/**
* 处理获得的物品
* @param item
*/
private void processItem(Item item){
if(item instanceof HP)//药品
{
/**
* 宝箱开出的 item(HP类型)
* ↓
* 强制转换为 HP 类型:((HP) item)
* ↓
* 获取它的数量:((HP) item).getCount()
* ↓
* 调用背包中同等级药品的 addCount() 方法:hp.addCount(数量)
* ↓
* 背包中药水数量增加 ✅
*/
//循环遍历当前背包中的药品列表 medicines
for(HP hp:medicines){
//检查背包中是否存在和当前药品 相同等级(levelNumber)的药品
if(hp.getLevelNumber() == item.getLevelNumber()){
//强制类型转换为了使用HP中getCount方法,getCount方法:获取药品数量
hp.addCount(((HP) item).getCount());
break;
}
}
}
else if(item instanceof Equipment)
{//装备
System.out.println("发现新的装备,是否给宠物小精灵更换? Y/N");
char change = Tools.getInputChar();
if(Character.toUpperCase(change) == 'Y'){
Equipment old = null;
//遍历玩家拥有的所有小精灵
for(Pokemon pokemon: pokemons)
{
//小精灵更换装备
old = pokemon.changeEquipment((Equipment) item);
//如果换下来的装备为空,说明后面的小精灵不需要再看
//如果小精灵 原本没有装备,会直接装备新装备,返回 null
if(old == null) break;
}
//如果换下来的装备不为空,则直接放入背包中
//如果小精灵 已有装备,会脱下旧装备,返回旧装备对象
if(old != null){
/**
* 检查旧装备:如果 old != null,说明有小精灵换下了旧装备。
*
* 扩容背包:使用 Arrays.copyOf 将装备背包 equipments 扩容(长度+1)。
*
* 存入旧装备:将旧装备放入背包最后一个位置。
*/
equipments = Arrays.copyOf(equipments,equipments.length+1);
equipments[equipments.length-1] = old;
}
}
}
else {//宠物小精灵
int index = -1;
for(int i=0;i<pokemons.length;i++)
{
//getClass() == 严格比较类对象,要求精灵类型完全一致
//(例如 Bulbasaur.class vs Bulbasaur.class)。
if(item.getClass() == pokemons[i].getClass()){
//若存在同类型精灵,记录其位置 index;否则 index 保持为 -1
index = i;
break;
}
}
//不存在同类型宠物小精灵
/**
* 扩容背包:通过 Arrays.copyOf 将精灵背包扩容 1 个位置。
*
* 存入新精灵:将宝箱中获得的新精灵放入扩容后的最后一个位置。
*/
if(index == -1){
pokemons = Arrays.copyOf(pokemons,pokemons.length + 1);
pokemons[pokemons.length - 1] = (Pokemon) item;
}
else {//存在同类型宠物小精灵
System.out.println("发现可融合宠物小精灵,是否融合? Y/N");
char merge = Tools.getInputChar();
if(Character.toUpperCase(merge) == 'Y'){
//用户选择融合(Y):调用 merge() 方法,将新精灵融合到已有的同类型精灵中。
pokemons[index].merge((Pokemon) item);
}else{//用户拒绝融合(N):将新精灵直接存入背包,不做其他操作。
pokemons = Arrays.copyOf(pokemons,pokemons.length + 1);
pokemons[pokemons.length - 1] = (Pokemon) item;
}
}
}
}
5. getCurrentLevelHP(int levelNumber)
:查找可用药品
功能:递归查找当前关卡或更低关卡的可用药品(优先使用当前关卡,逐级回退)。
逻辑:
- 从当前关卡
levelNumber
开始遍历medicines
背包,查找同等级药品; - 若未找到,递归调用
levelNumber-1
继续查找; - 若到第0关仍未找到,返回
null
(无可用药品)。
/**
* 获取当前关卡使用的药品,如果当前关卡的药品已经使用完,那么可以使用上一关卡的药品,依次类推
* @param levelNumber
*
* 假如目前是第3关,调用getCurrentLevelHP传入3,首先看关卡是否为0,为0就代表关卡为0,没有要
* 之后hp赋值null,for循环遍历数组长度,获取该药品对应的关卡等级,和当前关卡比较,能使用直接退出走else,return hp
* 如果没有的话为null,接着调用getCurrentLevelHP方法,参数是关卡-1,刚开始是第三关,之后变成第二关判断第二关是否有药品能使用
*
* @return
*/
private HP getCurrentLevelHP(int levelNumber){
if(levelNumber == 0)return null;
HP hp = null;
for (int i= 0;i<medicines.length;i++)
{
//getLevelNumber():获取该药品对应的关卡等级(例如 1 级药品、2 级药品)
//levelNumber 获取当前游戏所处的关卡编号(例如第 1 关、第 2 关)
if(medicines[i].getLevelNumber() == levelNumber){
hp = medicines[i];
break;
}
}
if(hp == null){
return getCurrentLevelHP(levelNumber - 1);
}else {
return hp;
}
}
6. discovery(char direct)
与move(char direct)
:探索与移动
discovery
:调用LevelMap.getPositionItem()
获取目标方向的物品,并标记为可见(item.setDiscovery(true)
);move
:调用LevelMap.move()
移动冒险家位置,更新地图中冒险家坐标。
/**
* 探索给定方向地图位置
* @param direct 方向
* @return
*/
private Item discovery(char direct){
return (Item) currentLevel.getMap().getPositionItem(Character.toUpperCase(direct));
}
/**
* 向给定方向地图位置移动
* @param direct
*/
private void move(char direct){
currentLevel.getMap().move(Character.toUpperCase(direct));
}
最后创建一个启动类调用游戏入口方法就能运行了
以上就是冒险家的实现,感谢大家观看!