FindObjectOfType返回null

4
我遇到的问题是,我拾取掉落物品时会向枪械中添加弹药。
我建立了一个Gun类,包含所有方法和变量。还建立了一个Rifle类,从Gun类派生而来。步枪运行良好,没有任何问题。
现在,我正在添加一个“拾取”系统,在这个系统中,x个敌人会掉落拾取物品。
以下是拾取物品的脚本:
public class AddARAmmo : MonoBehaviour
{
    private Rifle rifle;
    private void Awake()
    {
        rifle = FindObjectOfType<Rifle>();
    }
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == string.Format("Player"))
        {
           rifle.AddAmmo(30);
            Destroy(gameObject);
        }

    }
}

步枪和枪械脚本有点长,但这里是来自 Gun 基类的相关内容,它是公共抽象类......
public int bulletsInStock;
 public void AddAmmo(int ammoToAdd)
{
    bulletsInStock += ammoToAdd; 
    UpdateAmmo();// updates on screen Ammo
}
......

然后,在步枪课程中。
 public override void Modifiers() // This is where the guns starts are stored
{
    bulletSpeed = 2777f;
    bulletsInStock = 200;
    bulletsInMag = 30;
    bulletPoolSize = 40;
    desiredRPS = 15;
    muzzleFlashPoolSize = 10;
}

我遇到了一个“对象引用未设置为对象实例”的问题。

在游戏层级结构中,步枪脚本已经附加在步枪上,所以它应该能够找到它。 有人看出有什么问题吗?

以下是完整的枪械脚本。

public abstract class Gun : MonoBehaviour
{
    [SerializeField] protected GameObject muzzleFlash;// spawns on barrelEnd
    [SerializeField] protected Transform muzzleFlashFolder;
    [SerializeField] protected Transform bulletFolder;// is the parent of bullets
    [SerializeField] protected Transform barrelEnd;// Gameobject at the end of barrel
    [SerializeField] protected Rigidbody bullet; // The bullet Prefab
    [SerializeField] protected Text ammo; // OSD
    [SerializeField] protected Text weaponType; // OSD
    [HideInInspector] protected float bulletSpeed;
    [HideInInspector] public int bulletsInStock;
    [HideInInspector] protected int bulletsInMag;
    [HideInInspector] protected float desiredRPS;// Rounds Per Second
    [HideInInspector] protected List<Rigidbody> poolOfBullets; // Make pool for bullets
    [HideInInspector] protected int bulletPoolSize; // The size off the buletpool 10 works really well
    [HideInInspector] protected List<GameObject> muzzleFlashPool;// pool for muzzleflash
    [HideInInspector] protected int muzzleFlashPoolSize; // size of the muzzle pool
    [HideInInspector] protected int bulletsLeft; // In mag
    [HideInInspector] protected bool isReloading = false;
    [HideInInspector] protected float timeLeft;// for fire speed
    [HideInInspector] protected float fireSpeedTimer;
    [HideInInspector] protected Weapons weaponsScript;
    [HideInInspector] protected PlayerController playerController;
    protected void FixedUpdateStuff()
    {
        if (playerController.canMove && Input.GetAxisRaw(string.Format("Fire1")) > 0)
        {
            FireSpeedControl();// call the fire timer controller
        }
        if (playerController.canMove && Input.GetAxisRaw(string.Format("Fire1")) == 0)
        {
            timeLeft = 0f;
        }
        if (playerController.canMove && Input.GetKeyDown(KeyCode.R) && !isReloading)
        {
            Reload();
        }
        UpdateAmmoOnInput();
    }
    protected void UpdateStuff()
    {
        if (gameObject.activeInHierarchy)// when a gun become active it updates OSD
        {
            UpdateWeaponType();// With its Name
        }
    }
    protected void RPSFinder()//  finds the Rounds Per Second the gun will fire
    {
        fireSpeedTimer = (100 / desiredRPS) / 100;
        timeLeft = fireSpeedTimer;
    }

    protected void Fire()// Instatiates a clone of the desired bullet and fires it at bulletSpeed
    {
        if (!Empty())
        {
            Rigidbody bulletClones = GetPooledBullet();
            if (bulletClones != null)
            {

bulletClones.transform.SetPositionAndRotation(barrelEnd.position, barrelEnd.rotation);
                bulletClones.gameObject.SetActive(true);
            }
            GameObject muzzleFlashClone = GetMuzzleFlash();
            if (muzzleFlashClone != null)
            {
                muzzleFlashClone.transform.position = barrelEnd.position;
                muzzleFlashClone.gameObject.SetActive(true);
            }
            bulletClones.AddForce(-bulletClones.transform.up * bulletSpeed * .304f); //add the force in FPS * .304 = MPS
            bulletsLeft--;// the holder to know how many bullets are left in the magazine
            isReloading = false;// Gun cannot reload unless it has been fired
            UpdateAmmo();// Updates the on screen ammo count and the stock usage
            return;
        }
    }

    protected void Reload()
    {// this removes full magazine from the stock and the stock can still go negitive FIX FIX FIX FIX FIX FIX FIX
        if (bulletsInStock > 0)
        {
            isReloading = true;
            bulletsInStock -= bulletsInMag;
            bulletsLeft = bulletsInMag;
            UpdateAmmo();
        }
    }

    protected bool Empty()// Checks the magazine to see if there are bullets in it
    {
        if (bulletsLeft == 0)
            return true;
        else
            return false;
}

    protected void FireSpeedControl()// controls the RPS fired by the gun Controled by Update() Input
    {
        if (timeLeft > 0f)
        {
            timeLeft -= Time.deltaTime;
        }
        else if (timeLeft <= 0f)
        {
            Fire();
            timeLeft = fireSpeedTimer;
        }
    }

    protected Rigidbody GetPooledBullet()// retrieve a preInstatiated bullet from the pool to use when shooting
    {
        for (int i = 0; i < poolOfBullets.Count; i++)
        {
            if (!poolOfBullets[i].gameObject.activeInHierarchy)
            {
                return poolOfBullets[i];
            }
        }
        return null;
    }

    protected GameObject GetMuzzleFlash()
    {
        for (int i = 0; i < muzzleFlashPool.Count; i++)
        {
            if (!muzzleFlashPool[i].gameObject.activeInHierarchy)
            {
                return muzzleFlashPool[i];
            }
        }
        return null;
    }

    protected void UpdateAmmo()// Update the on screen ammo information
    {
        ammo.text = bulletsLeft + string.Format( " : ") + bulletsInStock;
    }

    protected abstract void UpdateWeaponType();

    protected void UpdateAmmoOnInput()
    {
        if (weaponsScript.updateAmmo)
        {
            UpdateAmmo();
            weaponsScript.updateAmmo = false;
        }
    }


    public abstract void Modifiers();

    protected void StartStuff()
    {
        Modifiers();// Call first to store indvidual gun stats
        playerController = FindObjectOfType<PlayerController>();
        weaponsScript = FindObjectOfType<Weapons>();
        poolOfBullets = new List<Rigidbody>();
        for (int i = 0; i < bulletPoolSize; i++)
        {
            Rigidbody bulletClone = (Rigidbody)Instantiate(bullet);
            bulletClone.gameObject.SetActive(false);// Builds the Inspector list 
            poolOfBullets.Add(bulletClone); //and populates the elements with clones
            bulletClone.transform.parent = bulletFolder.transform;
        }
        muzzleFlashPool = new List<GameObject>();
        for (int i = 0; i < muzzleFlashPoolSize; i++)
        {
            GameObject muzzleFlashClone = (GameObject)Instantiate(muzzleFlash);
            muzzleFlashClone.gameObject.SetActive(false);
            muzzleFlashPool.Add(muzzleFlashClone);
            muzzleFlashClone.transform.parent = muzzleFlashFolder.transform;
        }
            bulletsLeft = bulletsInMag;
            ammo.text = string.Format( " 0 : 0 ");
            RPSFinder();// Run last to set the RPS of the gun
        }
        public void AddAmmo(int ammoToAdd)
        {
            bulletsInStock += ammoToAdd;
            UpdateAmmo();
        }

    }
}

以下是完整的步枪脚本:

public class Rifle : Gun
{
    //All variables are stored in the "Gun" Script 
    //Copy this onto guns 

    void Start()
    {
        StartStuff();
    }
    private void FixedUpdate()
    {
        FixedUpdateStuff();
    }

    void Update()
    {
        UpdateStuff();
    }

    public override void Modifiers() // This is where the guns starts are stored
    {
        bulletSpeed = 2777f;
        bulletsInStock = 200;
        bulletsInMag = 30;
        bulletPoolSize = 40;
        desiredRPS = 15;
        muzzleFlashPoolSize = 10;
    }

    protected override void UpdateWeaponType()
    {
        weaponType.text = string.Format("Assault Rifle");
    }


}

你能展示一下“Rifle”脚本吗? - Programmer
1
异常抛出的位置在哪里? - Abion47
rifle.AddAmmo(30); 在AddARAmmo脚本中。 - Melsy
1
然后 rifle 是 null。要么该脚本在 Awake 方法运行之前运行,要么 FindObjectOfType 方法返回 null。添加检查 if (rifle != null) rifle.AddAmmo(30); 并找出是哪个问题。 - Abion47
1
可能是什么是NullReferenceException,如何修复它?的重复问题。 - Abion47
显示剩余4条评论
2个回答

8
有三个原因可能导致FindObjectOfType返回null:
假设要查找的脚本名称为“Rifle”:
当FindObjectOfType<Rifle>()返回null时。
1.附加了“Rifle”脚本的GameObject处于非活动状态。您必须确保GameObject处于活动状态。 enter image description here 2.“Rifle”未附加到任何GameObject上。请确保将Rifle脚本附加到GameObject上。 enter image description here
只要所附加的GameObject处于活动状态,无论脚本是启用还是禁用,它都应该被找到。参见#1。
3.加载新场景时:
当您使用诸如SceneManager.LoadSceneApplication.LoadLevel等函数触发场景加载时,FindObjectOfType将无法找到任何内容,直到场景加载完成为止。
请参阅此处了解如何检查场景何时完成加载。
如果你仍然有问题的话,可以简单地找到游戏对象,然后从中获取 Rifle 组件。假设游戏对象的名称也是 "Rifle"
GameObject.Find("Rifle").GetComponent<Rifle>();

游戏对象在场景加载时处于非活动状态。但是当我调用引用时,该对象可以处于活动或非活动状态,并且可能为空。但是我会考虑这一点进行一些测试。 - Melsy
我之前有一个部分,在查找发生之前将所有枪设置为非活动状态,现在已经禁用了它,现在它可以正常工作了。有时候只需要回到基础知识。 - Melsy
是的,不活动是发生这种情况的原因。很高兴你找到了问题。 - Programmer
3
+1 针对清晰简洁的解释和截图。这三个是唯一可能的原因吗?绝对没有其他的机会吗?如果您的信息来自官方文件,请引用。 - Matt
@Matthias 这个回答仅适用于 FindObjectOfType 函数,并且它们在 文档 中没有提到。这个答案基于我之前使用 FindObjectOfType 时的经验。我想不出任何其他可能导致它无法工作的原因。 - Programmer

0
我找到了解决方法,适用于任何遇到类似问题的人。 问题出在敌人掉落物品时,Awake()方法被调用。当它运行时,枪在场景中是不活动的,因此FindObjectOfType没有找到引用脚本,因为如上所述,它必须在场景中处于活动状态才能被找到。
所以我创建了一个Holder脚本,我称之为EnemyDrops,这个脚本调用了findobjectoftypes来查找枪支。这样一来,调用就在初始游戏启动时完成了。
然后我将拾取物品更改为查找EnemyDrops脚本(它位于空游戏对象上)并将调用发送给它。
EnemyDrops脚本
private Handgun handgun;

private void Awake()
{
    handgun = FindObjectOfType<Handgun>();
}

public void AddHandgunAmmo(int x)
{
    handgun.AddAmmo(x);
}  

以及新的拾取脚本

public class AddHandgunAmmo : MonoBehaviour
{
    private EnemyDrops enemyDrops;
    private void Awake()
    {
        enemyDrops = FindObjectOfType<EnemyDrops>();
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == string.Format("Player"))
        {
          enemyDrops.AddHandgunAmmo(50);
            Destroy(gameObject);
        }
    }
} 

正如您所看到的,通过方法传递值仍然可以正常工作,只需要一个“中间人”来中转信息。但这样也完全可行。

感谢大家的反馈,希望这能对某些人有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接