【问题标题】:How to wrap around the index when reaching the end or beginning of an array in c#?在c#中到达数组的末尾或开头时如何环绕索引?
【发布时间】:2021-10-30 01:39:32
【问题描述】:

我目前正在为 FPS 编写武器脚本,我想用鼠标滚轮切换武器。我创建了一个包含武器的数组,每次我用鼠标滚轮向上滚动时,武器的索引都会增加一。我的问题是,当我使用最后一个武器时,我收到 IndexOutOfBounds 错误消息。如果它位于数组的末尾,我尝试将武器索引重置为 0,但由于某种原因不起作用。我也尝试过使用 while 循环而不是 if 语句来做到这一点,但效果不佳。代码如下:

public class WeaponManager : MonoBehaviour
{

    [SerializeField]
    private WeaponHandler[] weapons;

    private int current_weapon_index;

    void Start()
    {
        current_weapon_index = 0;
        weapons[current_weapon_index].gameObject.SetActive(true);
    }

    
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            TurnOnSelectedWeapon(0);
        }

        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            TurnOnSelectedWeapon(1);
        }

        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            TurnOnSelectedWeapon(2);
        }

        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            TurnOnSelectedWeapon(3);
        }

        if (Input.GetKeyDown(KeyCode.Alpha5))
        {
            TurnOnSelectedWeapon(4);
        }

        if (Input.GetKeyDown(KeyCode.Alpha6))
        {
            TurnOnSelectedWeapon(5);
        }

        if(Input.mouseScrollDelta.y > 0)
        {
            SwitchToNextWeapon();
        }

        if (Input.mouseScrollDelta.y < 0)
        {
            SwitchToPreviousWeapon();
        }
    }

    void TurnOnSelectedWeapon(int weaponIndex)
    {
        weapons[current_weapon_index].gameObject.SetActive(false);

        weapons[weaponIndex].gameObject.SetActive(true);

        current_weapon_index = weaponIndex;
    }

    void SwitchToNextWeapon()
    {
        weapons[current_weapon_index].gameObject.SetActive(false);

        current_weapon_index++;
       
        weapons[current_weapon_index].gameObject.SetActive(true);

        if (current_weapon_index >= weapons.Length)
        {
            current_weapon_index = 0;
        }
    }

    void SwitchToPreviousWeapon()
    {
        weapons[current_weapon_index].gameObject.SetActive(false);

        current_weapon_index--;

        weapons[current_weapon_index].gameObject.SetActive(true);
    }
}

【问题讨论】:

  • current_weapon_index = current_weapon_index % weapons.Length - 类似这样。
  • @GuruStron 可以很好地增加,但是当你减少到 0 以下时会发生什么?

标签: c# arrays unity3d


【解决方案1】:
void SwitchToNextWeapon()
{
    weapons[current_weapon_index].gameObject.SetActive(false);
    var temp = current_weapon_index + 1;
    current_weapon_index = temp >= weapons.Count() ? 0 : temp;
    
    weapons[current_weapon_index].gameObject.SetActive(true);
}

void SwitchToPreviousWeapon()
{
    weapons[current_weapon_index].gameObject.SetActive(false);
    var temp = current_weapon_index - 1;
    current_weapon_index = temp < 0 ? weapons.Count() - 1 : temp;

    weapons[current_weapon_index].gameObject.SetActive(true);
}

在增加或减少当前武器索引之前添加一个检查。如果达到最大值,则恢复为 0,如果达到最小值 (0),则将索引设置为最大值。

【讨论】:

  • 由于weapons 是一个数组,我会说最好使用Length
  • 成功了!谢谢!你能解释一下为什么它不像我那样工作吗?我只是想知道,这样我就可以避免类似的错误。当然,您不必这样做。
  • @L1NTHALO 很高兴它有帮助!在您的代码中,您试图在检查其有效性之前使用新的增加和减少的索引来访问该项目。
【解决方案2】:

实现一个为您处理此问题的类将非常简单,只需维护一个内部的List&lt;&gt; 项目。添加 Current 属性以读取当前选定的项目,以及从鼠标滚轮处理程序调用的 MoveNext/MovePrevious 方法

public class ContinuousList<T>
{
    private List<T> internalList = new List<T>();
    private int currentIndex = 0;

    public void Add(T item) => internalList.Add(item);

    public T Current { get => internalList[currentIndex]; }

    public void MoveNext()
    {
       currentIndex++;
       if(currentIndex >= internalList.Count) currentIndex = 0;
    }

    public void MovePrevious()
    {
       currentIndex--;
       if(currentIndex <= 0) currentIndex = internalList.Count - 1;
    }
}

假设你可能有一些具有基类Weapon的武器:

var weaponList = new ContinuousList<Weapon>();
weaponList.Add(new Sword()); 
weaponList.Add(new Axe());
var currentWeapon = weaponList.Current; // gets Sword
weaponList.MoveNext();
var currentWeapon = weaponList.Current; // gets Axe
weaponList.MoveNext();
var currentWeapon = weaponList.Current; // Back to Sword

现场示例:https://dotnetfiddle.net/Ji7rkt

请注意,在此 ContinuousList 上实现 IEnumerable&lt;T&gt; 非常容易,因此它可以用于任何枚举和 LINQ 方法。我不想让一个简单的例子复杂化,而是在这里查看实际操作:https://dotnetfiddle.net/NtdfDi

【讨论】:

  • 作为一个小改进,您可以考虑实现IEnumerable&lt;T&gt;IEnumerator&lt;T&gt;,这使得类可以与foreach 构造和其他一些地方一起使用,就像普通列表一样。
  • @Alejandro 确实如此。我推迟了这样做,以免代码复杂化。
【解决方案3】:

使用模 (%) 运算符可以轻松处理循环值。

int mod = 5;

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(i % mod);
}

您会看到输出循环从 0 到 mod-1:0,1,2,3,4,0,1,2,...

这涵盖了加一的情况:

int index = 4;
int mod = myArray.Length; // assume 5 items in the array

// Increment and cycle
index = ++index % mod;

您会看到索引现在是 0,因为您位于列表的末尾,因此下一项应该位于列表的开头。


但是,递减周期性值存在一些问题。由于我不明白的原因,C# 选择允许负模值,即:

-1 % 5 = -1

... 而不是 4,这是您所期望的。
编辑:在 cmets 中争辩说 4 不是每个人都期望的。根据我第一次解决这个问题时的经验,我在网上发现了很多关于负模结果存在的困惑/烦恼,但我不能反驳这是我的观察偏差。

我过去已经解决过这个问题,解决这个问题的最简单方法是:

  • 取模
  • 添加模数
  • 再次取模

本质上,如果第一步的结果为负数(例如-1),我们只需将模数相加,从而将值推至零以上。但是,如果第一步已经是积极的结果,那么我们现在将值设置得太高了。因此,通过再次取模,我们能够抵消可能过高的值。这涵盖了这两种情况。

Here is a dotnetfiddle to prove that it works.

换句话说:

public int Increment(int current, int mod)
{
    return ((++current % mod) + mod) % mod;
}

public int Decrement(int current, int mod)
{
    return ((--current % mod) + mod) % mod;
}

为了 DRY,你可以重新塑造它,这样你就只使用这个复杂的公式一次

public int Cycle(int current, int mod)
{
    return ((current % mod) + mod) % mod;
}

...但是您必须先手动输入/减少该值。您喜欢哪个版本取决于您。

【讨论】:

  • “这是您所期望的” - 这可能是 所期望的,但它已经按照我所期望的方式运行;期望可能有点主观。
  • @MarcGravell:我希望大多数人期望% x 输出介于0x-1 之间的整数值,而不是介于-x+1x-1 之间的整数值。我的期望是基于我第一次解决这个负模数问题时在网上发现的大量困惑和烦恼。但是,也许如果您从负面结果有意义的特定用例开始,它可能会有所不同并且更直观。我无法证明这一点,因为我是从将它们用于简单的周期性值开始的。
  • 这基本上不是我也做过的here
  • @derHugo:就负模量的具体计算方式而言,是的,公式相同。但答案不仅仅是它的代码 sn-p,我想就底层数学提供更深入的解释。
【解决方案4】:

This answer 用于 swift 但基本上以相同的方式应用于c#

因此,一般来说,您可以通过两次取模将任何给定索引包装到数组长度。

这适用于更一般的情况,不仅适用于上下移动一个步骤:

public static class ArrayUtils
{
    public static void Forward<T>(ref int currentIndex, T[] array, int amount)
    {
        currentIndex = WrapIndex(currentIndex + amount, array);
    }
    
    public static void Backward<T>(ref int currentIndex, T[] array, int amount)
    {
        currentIndex = WrapIndex(currentIndex - amount, array);
    }
    
    public static int WrapIndex<T>(int newIndex, T[] array)
    {
        var length = array.Length;
        return ((newIndex % length) + length) % length;
    }
}

Fiddle

您现在可以将其用于任何数组。

【讨论】:

    猜你喜欢
    • 2017-04-17
    • 2014-12-25
    • 1970-01-01
    • 2011-08-06
    • 1970-01-01
    • 1970-01-01
    • 2017-06-17
    • 2014-10-25
    • 2011-12-27
    相关资源
    最近更新 更多