MMORPG游戏中经验收集罐(Exp Collector Pot)经验分配逻辑实现求助
嘿,我来帮你搞定这个经验收集罐的分配逻辑!其实核心就是分阶段循环处理:先搞定那些快满的罐子,再把剩余经验平均分给剩下的,直到经验分完或者罐子用完。下面我一步步给你拆解思路和代码实现。
核心思路拆解
首先,我们的分配逻辑需要处理两种关键场景:
- 某个罐子快满了,只能吸收少量经验就达到上限,之后要销毁它
- 剩余经验需要动态重新分配给剩下的罐子
可以把整个流程拆成这几步:
- 循环处理直到经验分完或无罐可用:每次有罐子销毁后,剩余经验的分配规则会变化,所以必须用循环反复处理
- 先算总剩余容量:先统计所有罐子还能装多少经验,判断当前经验能不能直接填满所有罐子(能的话直接清空所有罐子,经验减去总容量即可)
- 平均分配+余数处理:如果经验不够填满所有罐子,先算平均每个罐子分多少,把余数分给前几个罐子(保证分配尽可能平均)
- 逐个处理罐子:给每个罐子加经验,如果加满了就销毁它,同时扣减剩余经验
数学逻辑细节
这里的关键是处理「部分罐子快满」的情况:
- 对于每个罐子,它能接收的最大经验是
MAX_EXP - 当前存储经验 - 如果我们计算的平均分配量大于这个值,说明这个罐子只能先被填满,销毁后剩余经验再重新分配给其他罐子
- 当经验能平均分配时,用
总经验 / 罐子数得到基础分配量,总经验 % 罐子数得到余数,前「余数」个罐子多拿1点,保证经验分完
代码实现(Java)
结合你的现有代码,我写了完整的实现,注释里标了关键点:
private static final int MAX_EXP_PER_POT = 10000; private static final List<ExperienceCollectorPot> COLLECTORS = new CopyOnWriteArrayList<>(); public void distributeExperience(long expGained) { long expToDistribute = expGained; // 循环处理,直到经验分完或者没有收集罐可用 while (expToDistribute > 0 && !COLLECTORS.isEmpty()) { int remainingPotCount = COLLECTORS.size(); // 计算所有罐子还能容纳的总经验 long totalRemainingCapacity = COLLECTORS.stream() .mapToLong(pot -> MAX_EXP_PER_POT - pot.getExp()) .sum(); // 如果剩余经验足够填满所有罐子,直接清空所有罐子,经验扣减总容量 if (expToDistribute >= totalRemainingCapacity) { COLLECTORS.clear(); expToDistribute -= totalRemainingCapacity; break; } // 计算基础分配量和余数,保证分配尽可能平均 long baseExpPerPot = expToDistribute / remainingPotCount; long remainder = expToDistribute % remainingPotCount; // 用迭代器遍历,方便删除满了的罐子 Iterator<ExperienceCollectorPot> iterator = COLLECTORS.iterator(); long distributed = 0; while (iterator.hasNext() && distributed < expGained) { ExperienceCollectorPot currentPot = iterator.next(); long potRemainingCapacity = MAX_EXP_PER_POT - currentPot.getExp(); // 确定当前罐子能分到的经验:如果还有余数就多给1点,但不超过罐子剩余容量 long addExp; if (remainder > 0) { addExp = Math.min(baseExpPerPot + 1, potRemainingCapacity); remainder--; } else { addExp = Math.min(baseExpPerPot, potRemainingCapacity); } // 更新罐子经验(record不可变,所以创建新对象替换) ExperienceCollectorPot updatedPot = new ExperienceCollectorPot( currentPot.getId(), currentPot.getExp() + addExp ); int potIndex = COLLECTORS.indexOf(currentPot); if (potIndex != -1) { COLLECTORS.set(potIndex, updatedPot); } // 更新已分配经验和剩余待分配经验 distributed += addExp; expToDistribute -= addExp; // 如果罐子满了,销毁它 if (updatedPot.getExp() == MAX_EXP_PER_POT) { iterator.remove(); } } } // 这里可以加剩余经验的处理,比如直接给玩家自身 // player.gainExperience(expToDistribute); }
关键细节说明
- CopyOnWriteArrayList的使用:因为我们需要在遍历中删除元素,这个列表支持安全的并发修改,适合游戏场景(可能有其他线程操作罐子)
- Record的不可变性:因为Java record是只读的,所以更新经验必须创建新的对象替换旧的,代码里用
COLLECTORS.set来实现 - 边界场景覆盖:比如经验刚好填满所有罐子、单个罐子快满、经验为0、无罐子可用等情况都处理到了
- 效率优化:先判断总剩余容量,避免不必要的逐个处理,适合大量经验的场景
内容的提问来源于stack exchange,提问作者Girls are beautiful




