【问题标题】:Get a float variable given a generic array c#给定一个泛型数组c#获取一个浮点变量
【发布时间】:2020-11-01 15:56:09
【问题描述】:

我正在创建一个经常使用加权生成的 RPG。许多类,如怪物、战利品或地牢生成都有一个名为 spawnChance 的变量,它是一个 0-100 的值,用于确定生成率。

我以前一直使用这种选择方式从数组中获取随机加权成员

 var weights = enemy.Select(e => e.spawnChance);
 var index = weights.GetRandomWeightedIndex();
 return enemy[index];

这是扩展名

 public static int GetRandomWeightedIndex(this IEnumerable<float> weights)
{
    int count = weights.Count();
    if (weights == null || count == 0)
    {
        Debug.LogWarning($"Weights are invalid");

        return -1;
    }
    else if (count == 1)
        return 0;
    float w;
    float t = 0;
    int i;
    for (i = 0; i < count; i++)
    {
        w = weights.ElementAt(i);

        if (float.IsPositiveInfinity(w))
        {
            return i;
        }
        else if (w >= 0f && !float.IsNaN(w))
        {
            t += weights.ElementAt(i); ;
        }
    }

    float r = UnityEngine.Random.value;
    float s = 0f;

    for (i = 0; i < count; i++)
    {
        w = weights.ElementAt(i); ;
        if (float.IsNaN(w) || w <= 0f) continue;

        s += w / t;
        if (s >= r) return i;
    }

    return -1;
}

但是,我想减少这段代码,因为通过 linq 选择所有浮点数然后将其传递到扩展程序然后返回的方法相当繁琐并且使用了数十次。

我曾想过使用反射在泛型类中找到一个名为“spawnChance”的变量,但由于所有内容都是在运行时生成的,所以速度会很慢。

我是否可以将 spawnChance 从一个简单的浮点数转换为一个类,然后以某种方式编写一个 linq 方法,该方法能够高效地提取该类的值?由于项目设置,这些类不能从 spawnChance 类继承。 想法?感谢您的宝贵时间!

【问题讨论】:

  • 如果我是你,我不会将生成机会实现为浮点数,而是整数百分比。请记住,C# 中的 floats 不是小数,而是 base-2 数字(特别是 binary32 数字,例如二进制中的 0.010101)。像小数一样处理二进制数有时会导致令人惊讶的结果。
  • 也可以使用C#的System.Decimal (decimal),它十进制数格式。
  • 如果您正确处理浮点数就可以了,即将它用作“距离”的总和而不是离散的度量。

标签: c# unity3d random


【解决方案1】:

您可以定义一个接口,然后让暴露生成机会的类实现它。然后很容易测试一个对象是否实现了它。

public interface ISpawnable
{
    float SpawnChance { get; }
}

SpawnChance 必须是一个属性。不需要为这个用例声明setter(因为你只需要阅读它);但是,实现仍然可以提供一个。

然后就可以测试了

if (obj is ISpawnable spawnable) {
    float w = spawnable.SpawnChance;
    ...
}

如果您有混合对象列表,则使用 LINQ

var weights = mixedTypeObjects
    .OfType<ISpawnable>()         // Filters objects implementing it and casts to it.
    .Select(x => x.SpawnChance);

如果你有泛型类型或方法,你可以使用这个接口作为泛型类型约束。

public class SpawnService<T>
    where T : ISpawnable
{ ... }

这将 T 限制为实现 ISpawnable 的类型,并允许您访问 SpawnChance 而无需在此类中进行任何强制转换。


另一种选择是从公开生成机会的公共基类派生可生成对象。该接口更加灵活,因为它适用于派生自不同基类的类。如果您已经有一个类层次结构并且您希望类独立于该层次结构来实现它,这是一个优势。


您也可以简单地将游戏对象传递给方法并在其中进行过滤和强制转换:

public static int GetRandomWeightedIndex(this IEnumerable<GameObjects> gameObjects)
{
    var weights = gameObjects
        .OfType<ISpawnable>()
        .Select(g => g.SpawnChance);
    ...
}

【讨论】:

  • 接口可能不理想,但泛型会。
【解决方案2】:

看看你是怎么玩的

public class Spawner : MonoBehaviour
{
    [System.Serializable]
    public class WeightedItem
    {
        public GameObject Prefab; // Or whatever you're randomising
        public float Weight;
    }

    public WeightedItem[] Items;

    private float TotalWeight;

    public void Start() {
        // If your lists don't change, pre-calc TotalWeight here.
        // If they do change, you'll have to recalculate it when it does.
        TotalWeight = Items.Sum(m => m.Weight);
    }

    public object RandomItem() 
    {
        // Get your 'random' selection.
        var selectionWeight = Random.Range(0, TotalWeight);

        // Parse over the array to find it
        for (var i = 0; i < Items.Length; i++) {
            selectionWeight -= Items[i].Weight;

            if (selectionWeight <= 0)
                return Items[i].Prefab;
        }

        // Should never reach here unless your TotalWeight is wrong
        throw new System.ApplicationException("Randomised Weight exceeded Total Weight!");
    }
}

【讨论】:

  • 谢谢!修改这种方法有效。接口似乎可以工作,但它们不能很好地与 Unity 配合使用,因为它们不会在没有变通方法的情况下暴露在检查器中并且不会序列化。
  • @SkylarStickley 我一直推荐的只有两个“资产”,DOTween 和 Odin Inspector。 Odin 为 Inspector 显示和序列化添加了大量的生活质量改进。绝对值得一试。 (虽然不会改变这个答案=)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-27
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多