即使在调用 Destroy() 之后,该脚本仍在执行

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

提问人:Nyein Chan 提问时间:5/30/2023 最后编辑:derHugoNyein Chan 更新时间:6/2/2023 访问量:171

问:

在下面的代码中,当玩家生命值达到 0 时,将使用 Destroy() 方法销毁游戏对象。但是在玩游戏时,print() 语句有时会产生 -1。我测试了几次,但它从未显示 -2。这意味着即使在调用 Destroy() 方法后,脚本仍在执行 1 次。我还通过设置 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;
    --health;
    PlayerDamage?.Invoke(health);
    if (health < 1)
    {
        print("Health : " + health);
        PlayerDead?.Invoke();
        IgnoreLayerMatrix(false);
        Destroy(gameObject);
        return;
    }
    StartCoroutine(WaitNextDamageCollide());
}

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

private void OnCollisionEnter2D(Collision2D other)
{
    CheckDamageCollision(other);
}
C# unity-game-engine 回调 游戏开发 生命周期

评论


答:

1赞 Phillip Zoghbi 5/30/2023 #1

您提供的代码似乎没问题。但是,很难确定,因为我看不到功能内部的内容,例如PlayerDamage?.Invoke(health);

如果这种情况只是偶尔发生,可能是因为每个帧发生不止一次,并且仅在停用阶段的该帧结束后调用:OnCollisionEnter2DDestroy()

“如果固定时间步长小于实际帧更新时间,则物理循环可能每帧发生不止一次” - https://docs.unity3d.com/Manual/ExecutionOrder.html

为了避免这种情况,您可以引入一个标志并检查玩家是否没有受到伤害,然后调用所有伤害的东西,并设置为 .将标志设置回帧末尾,此时所有物理更新都已完成。isDamagedThisFrameisDamagedThisFrametruefalse

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() {
   // ... your code
   yield return new WaitForEndOfFrame();
   isDamagedThisFrame = false;
}

评论

0赞 Nyein Chan 5/31/2023
即使我不使用事件调用等代码,也不会有任何更改。这似乎是因为Unity物理生命周期。但是,当实际帧更新小于物理更新时,AFAIK 物理相关的回调会多次调用。当我在 Unity 编辑器中查看统计数据时,fps 约为 120,物理时间戳为 0.02s。因此,实际的帧更新不仅仅是物理更新。
0赞 Vionix 5/30/2023 #2

Destroy 在帧的末尾调用。您可以使用立即销毁功能,但它有一些性能开销。如果这是一个小游戏,那么请继续使用它。否则,可以将碰撞器设置为“碰撞时禁用”,这样就不会调用下一个 OnCollisionEnter2D,并且脚本将在帧结束时销毁。

评论

0赞 Phillip Zoghbi 5/30/2023
不建议使用。此函数应仅在编写编辑器代码时使用,因为在编辑模式下永远不会调用延迟销毁。在游戏代码中,您应该改用。DestroyImmediateObject.Destroy