You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity Inspector实现类似骑砍快速战斗的Spawn Chance滑块联动

实现Unity Inspector中Spawn Chance滑块的联动调整(类似骑砍战团快速战斗军队滑块)

我想在Unity Inspector里实现类似《骑马与砍杀:战团》快速战斗模式的滑块联动功能——拖动其中一个grid item的Spawn Chance(byte类型,用Range属性做的滑块)时,其他滑块会自动调整,保证所有值的总和始终是100。

我用OnValidate写了代码,但现在调整值时滑块会直接锁死在100,达不到预期效果,代码如下:

// the sum of all spawn chances should be 100
List<byte> spawnChances = new List<byte>();
foreach (Spawnable item in gridItems) {
    spawnChances.Add(item.spawnChance);
}
if (spawnChances.Count > 0) {
    byte sum = 0;
    foreach (byte chance in spawnChances) {
        sum += chance;
    }
    if (sum != 100) {
        foreach (Spawnable item in gridItems) {
            item.spawnChance = (byte)(item.spawnChance / sum * 100);
            // if i do this, when i change the value
            // the editor snaps it to 100
        }
    }
}

问题分析

原代码的核心问题有两个:

  1. 没有区分用户正在修改的滑块,每次都对所有滑块按比例缩放,导致用户刚调整的值被强制覆盖
  2. byte是整数类型,item.spawnChance / sum会先做整数除法(结果为0或1),再乘以100就会出现直接跳到100或0的情况,完全丢失比例信息

修正方案

核心思路是锁定用户正在修改的滑块值,只调整其他滑块,同时处理整数精度问题,保证总和始终为100。以下是完整代码:

using System.Collections.Generic;
using UnityEngine;

public class SpawnManager : MonoBehaviour
{
    public List<Spawnable> gridItems = new List<Spawnable>();
    // 缓存上一次的Spawn Chance值,用来判断哪个项被修改
    private List<byte> lastSpawnChances = new List<byte>();

    private void OnValidate()
    {
        if (gridItems.Count == 0) return;

        // 初始化缓存列表
        if (lastSpawnChances.Count != gridItems.Count)
        {
            lastSpawnChances.Clear();
            foreach (var item in gridItems)
            {
                lastSpawnChances.Add(item.spawnChance);
            }
            return;
        }

        // 找出用户修改的项
        int modifiedIndex = -1;
        byte modifiedValue = 0;
        for (int i = 0; i < gridItems.Count; i++)
        {
            if (gridItems[i].spawnChance != lastSpawnChances[i])
            {
                modifiedIndex = i;
                modifiedValue = gridItems[i].spawnChance;
                break;
            }
        }

        // 无修改项时,直接统一缩放至总和100
        if (modifiedIndex == -1)
        {
            RescaleAllToSum100();
            UpdateLastCache();
            return;
        }

        // 处理用户输入超过100的情况:强制当前项为100,其他为0
        int remaining = 100 - modifiedValue;
        if (remaining < 0)
        {
            gridItems[modifiedIndex].spawnChance = 100;
            for (int i = 0; i < gridItems.Count; i++)
            {
                if (i != modifiedIndex) gridItems[i].spawnChance = 0;
            }
            UpdateLastCache();
            return;
        }

        // 计算其他项的总权重(上一次的数值总和)
        int otherTotal = 0;
        for (int i = 0; i < gridItems.Count; i++)
        {
            if (i != modifiedIndex) otherTotal += lastSpawnChances[i];
        }

        // 其他项总权重为0时,平均分配剩余值
        if (otherTotal == 0)
        {
            int itemCount = gridItems.Count - 1;
            int perItem = remaining / itemCount;
            int remainder = remaining % itemCount;
            for (int i = 0; i < gridItems.Count; i++)
            {
                if (i != modifiedIndex)
                {
                    gridItems[i].spawnChance = (byte)(perItem + (remainder > 0 ? 1 : 0));
                    if (remainder > 0) remainder--;
                }
            }
        }
        else
        {
            // 按上一次的比例分配剩余值,处理整数精度
            int currentSum = 0;
            for (int i = 0; i < gridItems.Count; i++)
            {
                if (i != modifiedIndex)
                {
                    float ratio = (float)lastSpawnChances[i] / otherTotal;
                    byte newValue = (byte)(remaining * ratio);
                    gridItems[i].spawnChance = newValue;
                    currentSum += newValue;
                }
            }
            // 补全差值,保证总和为100
            int diff = remaining - currentSum;
            if (diff != 0)
            {
                for (int i = 0; i < gridItems.Count; i++)
                {
                    if (i != modifiedIndex && gridItems[i].spawnChance > 0)
                    {
                        gridItems[i].spawnChance += (byte)diff;
                        break;
                    }
                }
            }
        }

        UpdateLastCache();
    }

    // 统一缩放所有项至总和100(初始化或无修改时调用)
    private void RescaleAllToSum100()
    {
        int sum = 0;
        foreach (var item in gridItems) sum += item.spawnChance;

        if (sum == 0)
        {
            // 总和为0时平均分配
            int perItem = 100 / gridItems.Count;
            int remainder = 100 % gridItems.Count;
            for (int i = 0; i < gridItems.Count; i++)
            {
                gridItems[i].spawnChance = (byte)(perItem + (remainder > 0 ? 1 : 0));
                if (remainder > 0) remainder--;
            }
            return;
        }

        // 按比例缩放前n-1项,最后一项补差值
        int currentSum = 0;
        for (int i = 0; i < gridItems.Count - 1; i++)
        {
            float ratio = (float)gridItems[i].spawnChance / sum;
            byte newValue = (byte)(100 * ratio);
            gridItems[i].spawnChance = newValue;
            currentSum += newValue;
        }
        gridItems[gridItems.Count - 1].spawnChance = (byte)(100 - currentSum);
    }

    // 更新缓存的上一次数值
    private void UpdateLastCache()
    {
        lastSpawnChances.Clear();
        foreach (var item in gridItems) lastSpawnChances.Add(item.spawnChance);
    }
}

[System.Serializable]
public class Spawnable
{
    [Range(0, 100)]
    public byte spawnChance;
    // 这里可以添加其他属性,比如预制体、名称等
}

代码说明

  • 用lastSpawnChances记录上一次的数值,精准定位用户修改的滑块
  • 锁定用户修改的滑块值,只调整其他滑块的比例,符合直觉操作
  • 处理了多种边界情况:总和为0、用户输入超过100、整数精度丢失
  • 保证所有滑块的Spawn Chance总和始终为100,和骑砍战团的滑块逻辑一致

内容的提问来源于stack exchange,提问作者Obscure021

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 11:30:54