You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

MMORPG游戏中经验收集罐(Exp Collector Pot)经验分配逻辑实现求助

嘿,我来帮你搞定这个经验收集罐的分配逻辑!其实核心就是分阶段循环处理:先搞定那些快满的罐子,再把剩余经验平均分给剩下的,直到经验分完或者罐子用完。下面我一步步给你拆解思路和代码实现。

核心思路拆解

首先,我们的分配逻辑需要处理两种关键场景:

  • 某个罐子快满了,只能吸收少量经验就达到上限,之后要销毁它
  • 剩余经验需要动态重新分配给剩下的罐子

可以把整个流程拆成这几步:

  1. 循环处理直到经验分完或无罐可用:每次有罐子销毁后,剩余经验的分配规则会变化,所以必须用循环反复处理
  2. 先算总剩余容量:先统计所有罐子还能装多少经验,判断当前经验能不能直接填满所有罐子(能的话直接清空所有罐子,经验减去总容量即可)
  3. 平均分配+余数处理:如果经验不够填满所有罐子,先算平均每个罐子分多少,把余数分给前几个罐子(保证分配尽可能平均)
  4. 逐个处理罐子:给每个罐子加经验,如果加满了就销毁它,同时扣减剩余经验
数学逻辑细节

这里的关键是处理「部分罐子快满」的情况:

  • 对于每个罐子,它能接收的最大经验是 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

火山引擎 最新活动