Unity 对象池:同时释放多个对象时出现 IndexOutOfRangeException

问题描述 投票:0回答:1

当同时从对象池中释放多个对象时,我在 Unity 项目中遇到 IndexOutOfRangeException。

就上下文而言,我正在开发一个 Unity 项目,其中坦克使用对象池系统来管理性能来发射炮弹。当坦克靠近时,它们会快速发射炮弹,导致池中的物体被快速连续地激活和释放。这种快速的交互似乎引发了异常。

这是我的对象池实现:

using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEditor.EditorTools;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    private int poolSize;
    //drag the object to pool in the game menu to this slot
    public GameObject ObjectToPool;
    public static GameManager manager;
    public GameObject[] pools;
    public short poolIndex;
    public GameObject activePlayer;
    public Camera activeCamera;
    void Awake()
    {
        //if the static instance of the class does not exist, create one
        if(manager==null){
            manager=this;
        }

        //tag is hardcoded but i will change that later
        poolSize=3*GetPooledObjectUserCount("Tank");
        poolIndex=0;
        InitializePool();
    }

    private int GetPooledObjectUserCount(string tag){
        return GameObject.FindGameObjectsWithTag(tag).Length;
    }
    
    private void InitializePool(){
        pools=new GameObject[poolSize];
        GameObject tmp;
        for(short i=0; i<poolSize; i++){
            tmp=Instantiate(ObjectToPool);
            tmp.SetActive(false);
            tmp.AddComponent<PooledObject>().PoolIndex = i;
            pools[i]=tmp;
        }
    }

    public GameObject GetObjectFromPool(){
        GameObject gameObject=pools[poolIndex];
        poolIndex++;
        return gameObject;
    }

    public void ReleaseObject(GameObject gameObject){
        gameObject.SetActive(false);
        GameObject latestActive=pools[poolIndex-1];

        //getting release and lastest active object index in pool by getting their component
        PooledObject releaseComponent=gameObject.GetComponent<PooledObject>();
        PooledObject activeComponent=latestActive.GetComponent<PooledObject>();
        int releaseIndex=releaseComponent.PoolIndex;
        int activeIndex=activeComponent.PoolIndex;

        //swap their index component value before swapping them in the array
        releaseComponent.PoolIndex=activeIndex;
        activeComponent.PoolIndex=releaseIndex;

        //switch their place in the pool
        pools[activeIndex]=gameObject;
        pools[releaseIndex]=latestActive;
        poolIndex--;
    }
}

这是 PooledObject 类的实现,它作为组件附加到每个池化 shell,因为我不想在 shell.cs 脚本中声明变量来跟踪其池索引:


using UnityEngine;  

public class PooledObject : MonoBehaviour  
{  
    public int PoolIndex;  
}  

最后,附加到 shell 的 shell.cs 脚本的实现:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Shell : MonoBehaviour
{
    public float speed=40;
    public Rigidbody rb;
    public GameObject explosion;
    public Transform shellTransform;

    // Start is called before the first frame update
    void Awake()
    {
        rb=gameObject.GetComponent<Rigidbody>();
        shellTransform=gameObject.GetComponent<Transform>();
    }

    // Update is called once per frame

    void OnCollisionEnter(Collision collision) 
    {
        GameManager.manager.ReleaseObject(gameObject);
        GameObject shockwave=Instantiate(explosion, shellTransform.position, shellTransform.rotation);
        Destroy(shockwave, 0.5f);
    }
    void Update()
    {
        shellTransform.forward=rb.velocity.normalized;
    }
}

错误信息:

Exception caught: Index was outside the bounds of the array.
StackTrace:   at GameManager.ReleaseObject (UnityEngine.GameObject gameObject) [0x0000a] in /Users/tripham/Desktop/Unity/AI Projects/AI Tank prototype/Assets/Scripts/GameManager.cs:56 
UnityEngine.Debug:LogError (object)
GameManager:ReleaseObject (UnityEngine.GameObject) (at Assets/Scripts/GameManager.cs:73)
Shell:OnCollisionEnter (UnityEngine.Collision) (at Assets/Scripts/Shell.cs:23)
UnityEngine.Physics:OnSceneContact (UnityEngine.PhysicsScene,intptr,int) (at /Users/bokken/build/output/unity/unity/Modules/Physics/ScriptBindings/PhysicsContact.bindings.cs:49)

我注意到,即使有大量调用者(场景中的坦克),这种池化实现也没有问题。出现错误时的poolIndex也始终为0,因此我已经排除了poolIndex超过poolSize的可能性。仅当坦克彼此非常接近地分组时才会出现错误,因此由于炮弹相互撞击且坦克接近,炮弹被设置为激活并非常快速地释放。

我已经考虑了逻辑,但我确实找不到任何边缘情况。这让我怀疑这可能是因为线程的竞争条件导致 ReleaseObject() 的多个调用者同时访问 poolIndex。`

c# arrays race-condition object-pooling
1个回答
0
投票

在函数“GetObjectFromPool”中,您可以增加 poolIndex 而不检查是否超过初始化数组的长度。您需要检查索引是否已过去并将其重置为 0

  public GameObject GetObjectFromPool(){
    GameObject gameObject=pools[poolIndex];
    if (poolIndex + 1 >= pools.length)
        poolIndex=0;
    else
        poolIndex++;

    return gameObject;
}
© www.soinside.com 2019 - 2024. All rights reserved.