脚本仍在Destroy()调用后继续执行。

huangapple go评论73阅读模式
英文:

The script is still being executed even after Destroy() was called

问题

在以下代码中,当玩家的生命值降至0时,游戏对象将使用Destroy()方法销毁。但在游戏进行时,有时print()语句会产生-1。我测试了好几次,但从未显示过-2。这意味着即使调用了Destroy()方法,脚本仍然会执行1次。我还尝试了通过将enabled标志设置为false来测试,但结果是相同的。销毁游戏对象或禁用/启用脚本不会立即生效?

上面的代码仅在CollisionEnter回调中使用。

private void CheckDamageCollision(Collision2D other)
{
    if (!other.gameObject.CompareTag(Constants.TAG_ENEMY) && !other.gameObject.CompareTag(Constants.TAG_OBSTACLE) && !other.gameObject.CompareTag(Constants.TAG_ENEMY_WEAPON)) return;
    if (justDamage) return;
    --health;
    PlayerDamage?.Invoke(health);
    if (health < 1)
    {
        print("Health : " + health);
        PlayerDead?.Invoke();
        IgnoreLayerMatrix(false);
        Destroy(gameObject);
        return;
    }
    StartCoroutine(WaitNextDamageCollide());
}

private void OnCollisionEnter2D(Collision2D other)
{
    CheckDamageCollision(other);
}
英文:

In the following code, when the player health reach 0 the game object is destroyed by using Destroy() method. But when the game play, the print() statement sometimes produced -1. I tested several times but it never show -2. It means the script is still being executed 1 time even after calling Destroy() method. I also tested with enabled flag by setting false and it has the same result. Destroying game object or Disabling/Enabling script doesn't effect immediately?

private void CheckDamageCollision(Collision2D other)
{
    if (!other.gameObject.CompareTag(Constants.TAG_ENEMY) &amp;&amp; !other.gameObject.CompareTag(Constants.TAG_OBSTACLE) &amp;&amp; !other.gameObject.CompareTag(Constants.TAG_ENEMY_WEAPON)) return;
    if (justDamage) return;
    --health;
    PlayerDamage?.Invoke(health);
    if (health &lt; 1)
    {
        print(&quot;Health : &quot; + health);
        PlayerDead?.Invoke();
        IgnoreLayerMatrix(false);
        Destroy(gameObject);
        return;
    }
    StartCoroutine(WaitNextDamageCollide());
}

The code above is used only inside the CollisionEnter callback.

private void OnCollisionEnter2D(Collision2D other)
{
    CheckDamageCollision(other);
}

答案1

得分: 1

你提供的代码看起来还不错。但是,很难确定,因为我看不到诸如 PlayerDamage?.Invoke(health); 这样的函数内部发生了什么。

如果这种情况只是偶尔发生,可能是因为 OnCollisionEnter2D 会在一帧内发生多次,而 Destroy() 只会在该帧结束后的停用阶段调用:
> "如果固定时间步长小于实际帧更新时间,则物理循环可能会在一帧内发生多次" - https://docs.unity3d.com/Manual/ExecutionOrder.html

为了避免这种情况,你可以引入一个标志 isDamagedThisFrame,检查玩家是否没有受到伤害,然后调用所有伤害相关的代码,并将 isDamagedThisFrame 设置为 true。在帧结束时,当所有物理更新都完成时,将标志重置为 false

private bool isDamagedThisFrame = false;

private void CheckDamageCollision(Collision2D other)
{
    if (!other.gameObject.CompareTag(Constants.TAG_ENEMY) && !other.gameObject.CompareTag(Constants.TAG_OBSTACLE) && !other.gameObject.CompareTag(Constants.TAG_ENEMY_WEAPON)) return;
    if (justDamage) return;

    if (isDamagedThisFrame) return;
    isDamagedThisFrame = true;

    --health;
    PlayerDamage?.Invoke(health);
    if (health < 1)
    {
        print("Health : " + health);
        PlayerDead?.Invoke();
        IgnoreLayerMatrix(false);
        Destroy(gameObject);
        return;
    }
    StartCoroutine(WaitNextDamageCollide());
}

在你的 WaitNextDamageCollide() 函数中,你可以使用 yield WaitForEndOfFrame

IEnumerator WaitNextDamageCollide() {
   // ... 你的代码
   yield return new WaitForEndOfFrame();
   isDamagedThisFrame = false;
}
英文:

The code you provided seems ok. However, it's hard to tell for certain because I can't see what's inside functions such as PlayerDamage?.Invoke(health);

If this happens only sometimes, it could be because OnCollisionEnter2D occurs more than once per frame and Destroy() only gets invoked after the end of that frame in the decommissioning phase:
> "The physics cycle may happen more than once per frame if the fixed time step is less than the actual frame update time" - https://docs.unity3d.com/Manual/ExecutionOrder.html

To avoid this, you could introduce a flag isDamagedThisFrame and check if the player is not being damaged, then invoke all of your damage stuff, and set isDamagedThisFrame to true. Set the flag back to false at the end of the frame, when all physics updates have finished.

private bool isDamagedThisFrame = false;

private void CheckDamageCollision(Collision2D other)
{
    if (!other.gameObject.CompareTag(Constants.TAG_ENEMY) &amp;&amp; !other.gameObject.CompareTag(Constants.TAG_OBSTACLE) &amp;&amp; !other.gameObject.CompareTag(Constants.TAG_ENEMY_WEAPON)) return;
    if (justDamage) return;

    if (isDamagedThisFrame) return;
    isDamagedThisFrame = true;

    --health;
    PlayerDamage?.Invoke(health);
    if (health &lt; 1)
    {
        print(&quot;Health : &quot; + health);
        PlayerDead?.Invoke();
        IgnoreLayerMatrix(false);
        Destroy(gameObject);
        return;
    }
    StartCoroutine(WaitNextDamageCollide());
}

in your WaitNextDamageCollide() you can yield WaitForEndOfFrame

IEnumerator WaitNextDamageCollide() {
   // ... your code
   yield return new WaitForEndOfFrame();
   isDamagedThisFrame = false;
}

答案2

得分: 0

在帧末调用Destroy。你可以使用立即销毁函数,但会有一些性能开销。如果是一个小游戏,可以放心使用。否则,你可以在碰撞时将碰撞器设置为禁用,这样下一个OnCollisionEnter2D就不会被调用,脚本将在帧末被销毁。

英文:

Destroy is called at the end of the frame. You can use destroy immediate function but it has some performance overhead. If it's a small game then go ahead and use it. Otherwise you can set the collider to disabled on collision, so that the next OnCollisionEnter2D is not called and the script will be destroyed by end of the frame.

huangapple
  • 本文由 发表于 2023年5月30日 01:13:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76359195.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定