【问题标题】:GameObject.FindGameObjectsWithTag("Enemy").Length off by one for some reason?GameObject.FindGameObjectsWithTag("Enemy").Length 因某种原因减一?
【发布时间】:2021-06-24 01:14:56
【问题描述】:

所以我想在敌人数量达到 0 时暂停游戏。所以我使用 GameObject.FindGameObjectsWithTag("Enemy").Length 来查找敌人的数量。我把它放在一个在敌人被实例化时被正确调用的函数中,所以我可以看到长度变为 4,因为有 4 个敌人产卵。当敌人被杀死时,再次调用该函数,其中长度再次打印到控制台。出于某种原因,尽管只有 3 个敌人,但在杀死第一个敌人时,计数再次以 4 重复。一旦另一个敌人被杀死,当实际上有 2 个敌人时它会报告 3,依此类推,直到当有 0 个敌人时我达到 1。

这是第一个sn-p代码:

    public class EnemyList : MonoBehaviour
{
    public List<GameObject> weakMobs = new List<GameObject>();
    public List<GameObject> mediumMobs = new List<GameObject>();
    public List<GameObject> bossMobs = new List<GameObject>();
    public List<Transform> spawningChildren = new List<Transform>();
    public static int mobCount;

    void Start()
    {
        for (int i = 0; i < spawningChildren.Count; i++)
        {
            GameObject newWeakMob = Instantiate(weakMobs[0], spawningChildren[Random.Range(0, 4)]) as GameObject;
        }
        
        CheckMobCount();
    }
    public void CheckMobCount()
    {
        mobCount = GameObject.FindGameObjectsWithTag("Enemy").Length;
            print(mobCount);

}

下一段代码是杀死敌人并再次调用 CheckMobCount()。

public void TakeDamage()

{
    enemyCurrentHealth -= 25;
    enemyHealthBar.SetHealth(enemyCurrentHealth);
    if (enemyCurrentHealth == 0)
    {
        Destroy(this.gameObject);

        enemyList.CheckMobCount();
        //needs death animations
    }
}

以下是控制台消息: Console of printed lengths

我是自学的,所以如果这是初级的,我深表歉意。我尝试了几种不同的方法,这是我最接近的方法,但我也乐于接受新的想法。

谢谢!!

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    this answer 中所述,该对象实际上并未在当前帧中销毁。

    来自the documentation

    对象 obj 在当前 Update 循环之后立即销毁……实际对象销毁总是延迟到当前 Update 循环之后,但总是在渲染之前完成。

    我也同意使用DestroyImmediate() 是个坏主意。

    最终,您的问题似乎真的是关于当敌人数量达到 0 时暂停游戏,不幸的是,这实际上还没有得到回答。

    事实上,除了将敌人计数的检查移到Update() 方法的开始处,如果它为0,则暂停游戏。然后你会发现此时敌人的组件已经被破坏了。

    大概敌人是在更新循环开始之前(即第一帧之前)产生的,但如果不是,那么你可以使用你已经使用的任何逻辑来决定需要产生新的敌人,以检测你的事实还没有产生任何敌人,避免在敌人产生之前暂停

    【讨论】:

      【解决方案2】:

      在这里,您已将脚本附加到您的敌人实例。当您查询剩余的敌人数量时,它们仍然活着。

      您应该执行以下操作:

      public class Enemy: MonoBehaviour
      {
             public static int EnemyCount = 0;
             
             private void Start()
             {
                 EnemyCount++;
             }
             private void OnDestroy()
             {
                EnemyCount--;
             }
      }
      

      然后你可以从任何地方查询敌人的数量,但只是超出 Enemy.EnemyCount 的 EnemyCount。

      如果您想获得更难的示例,可以查看此游戏开发教程:https://www.youtube.com/watch?v=LPBRLg4c5F8&t=134s

      【讨论】:

      • 我确实最终编辑了我的静态变量以正常工作,类似于你在这里的工作方式,我第一次尝试它时过于复杂,你的评论帮助我意识到它只需要被简化有点太谢谢你了!!
      【解决方案3】:

      Destroy 实际上是在帧结束时执行的。有DestroyImmediate 立即执行,但要使用的是not recommended。我要做的是添加一个字段或一个属性来识别敌人是否还活着,然后检查它。比如:

      class Enemy : MonoBehaviour
      {
          public bool IsAlive { get; set; } = true;
      }
      
      public class EnemyList : MonoBehaviour
      {
        //...
        public void CheckMobCount()
        {
            mobCount = GameObject.FindGameObjectsWithTag("Enemy").Select(x => x.GetComponent<Enemy>()).Count(x => x.IsAlive);
            print(mobCount);
      
        }
      }
      

      然后:

      public void TakeDamage()
      {
        enemyCurrentHealth -= 25;
        enemyHealthBar.SetHealth(enemyCurrentHealth);
        if (enemyCurrentHealth == 0)
        {
            Destroy(this.gameObject);
            this.GetComponent<Enemy>().IsAlive = false;
      
            enemyList.CheckMobCount();
            //needs death animations
        }
      }
      

      这可以进一步优化以将Enemy 存储在某处,而不是每次都使用GetComponent,但你明白了。

      【讨论】:

      • 我的代码已经设置好让我轻松尝试正确使用静态变量,但这将是我的下一次尝试。如果我将来遇到什么事情,我很可能会这样做,因为这很有意义。
      【解决方案4】:

      正如其他人已经提到的,问题是Destroy 被执行延迟

      实际的对象销毁总是延迟到当前更新循环之后,但总是在渲染之前完成。

      您可以只计算仍然活着GameObjects,那些bool operatortruetrue

      对象是否存在?

      在同一帧中销毁的对象将是错误的。

      例如使用Linq Count

      using System.Linq;
      
      ....
      
      mobCount = GameObject.FindGameObjectsWithTag("Enemy").Count(e => e);
      

      基本上等于做

      mobCount = 0;
      foreach(e in GameObject.FindGameObjectsWithTag("Enemy"))
      {
          if(e) mobCount++;
      }
      

      不需要额外的属性或组件。

      【讨论】:

      • .Count(e =&gt; e) 不会编译
      • @bashis 是的,它会......为什么不呢?
      • 假设@bashis 忽略了Unity3d,提供了从Object 类到bool 的隐式转换(坦率地说,这是一个非常奇怪的设计选择)。即便如此,在已经具有完美 Length 属性的集合上调用 Count() 是没有意义的。我看不出上面的内容对这个问题有什么影响。
      • @PeterDuniho 正如所说,它会从列表中过滤掉任何不再存在的对象..在 OP 的情况下就是这种情况,因为使用了Destroy。虽然这个销毁被延迟了,所以 Unity 首先所以对象实际上在帧结束时被删除,而 bool 运算符已经为在同一帧内销毁的对象返回 false .. 它不计算所有项目 .. 这就是全部目的使用带有谓词的Count ;)
      • 感谢您的评论,我可能会在某个时候切换到此方法,但我现在使用静态变量让它工作。
      【解决方案5】:

      我建议你用“DestroyImmediate”代替“Destroy”,然后看看结果。 我有个更好的主意,为什么不在生成敌人时使用静态变量?

       void Start()
        {
          for (int i = 0; i < spawningChildren.Count; i++)
          {
              GameObject newWeakMob = Instantiate(weakMobs[0], 
              spawningChildren[Random.Range(0, 4)]) as GameObject;
              mobCount++;
          }
          
       }
      

      【讨论】:

        【解决方案6】:
        • 不要使用 Linq
        • 不要使用 DestroyImmediate(它可能会冻结并导致您的游戏出现错误)
        • 避免在循环中使用 FindGameObjectsWithTag,仅在初始化时。
        • 在数组或列表中跟踪你的敌人
        • 当你消灭一个敌人时,从列表中删除它的引用
        • 使用列表计数/长度获取真实的实际数字。

        【讨论】:

          猜你喜欢
          • 2013-12-22
          • 2016-05-10
          • 1970-01-01
          • 1970-01-01
          • 2017-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-04
          相关资源
          最近更新 更多