使用 OnTriggerStay 在 C# 和 Unity3D 中查找每个碰撞对象

Find every colliding object in C# and Unity3D using OnTriggerStay

作为游戏玩家,我会这样表述它:AOE-Stun,击晕所有击中的人然后消失。

我有附有 class "EnemyMovement" 的敌人对象。 此 class 包含一个函数 "Slow"。我有一个附有 class "StunSpell" 的圆圈。现在我想为每个与之碰撞的敌人对象调用一次 "Slow"。

void OnTriggerStay2D(Collider2D other){ 
    if (other.gameObject.tag == "Enemy") {  //Here i want to find every gameobject (by tag "Enemy")
        other.GetComponent<EnemyMovement> ().Slow (3, 0f);  //Call this function once per gameobject

     //And as soon as every object that was found executed "Slow" once:
     // -> Destroy(gameObject); to destroy the AOE-Object
    }
}

在 EnemyMovement class 中有一个 OnTriggerEnter 方法。标记法术并在其上放置一个 2dCollider。当 OntriggerEnter 被标记 == "slow spell" 击中时,调用慢速。

基本上我会按照您目前正在做的相反的方式来做。以另一种方式进行操作很容易让你与任何击中敌人的东西互动(因为这是预期的行为)

(代表OP发表).

我使用列表解决了这个问题,以检查哪些敌人与圆圈相撞:

void CallStun(Collider2D coll){
    if (coll.gameObject.tag == "Enemy") {
        coll.GetComponent<EnemyHealth> ().TakeDamage (damage);
        coll.GetComponent<EnemyMovement> ().Slow (3, 0f);
    }
}

void OnTriggerStay2D(Collider2D other){

    if (stun == false) {
        return;
    }

    if (!collList.Contains (other)) {    //if the object is not already in the list
        collList.Add (other);    //add the object to the list
        CallStun (other);    //call the functions on the specific object
    } else {
        Destroy (gameObject);    //if no(more) collisions occur -> destroy the circle object
    }
}

这个 QA 有点乱,但在视频游戏中完全正常且司空见惯...

"...检查到每个字符的距离..."

private void GrenadeTimer()
    {
    rb.isKinematic = true;  
    
    // here is our small explosion...
    Gp.explosions.MakeExplosion("explosionA",transform.position);
    
    float radius = splashMeasuredInEnemyHeight *
       Gp.markers.GeneralExampleEnemyWidth();
    
    List<Enemy> hits = new List<Enemy>();
    foreach(Enemy e in Gp.enemies.all)
        {
        if (e.gameObject.layer != Grid.layerEnemies) continue;
        if ( transform.DistanceTo(e) < radius ) hits.Add(e);
        }
    
    hits.SortByDistanceFrom( this.transform );
    boss.SequentialHits(hits,damage);
    boss.Done(this);
    }

很难想象还有比这更简单的事了。

请注意,我们决定

radius

以米为单位,比方说“4.2米”,我们要在其中对敌人造成伤害。 (或者,对它们进行抛光,或其他任何情况。)

这东西

Gp.enemies.all

是一个 List<Enemy> ... 它容纳了当前游戏中的所有敌人。简单吧?

如果你实际上没有 List<> 所有敌人(或玩家、NPC - 任何相关的) - 你就###ed了。重新开始您的学习项目。一旦你有了一个经过单元测试的实时列表,回到这里。

这行代码

Grid.layerEnemies

与Unity中的系统有关。这通常会给新的爱好者带来问题...

实际上,您可以在 Unity 中什么都做不了,而无需对每一件事都使用图层系统。

让您开始使用图层超出了本文的范围,因此我们将其放在一边。如果您愿意,只需在您的学习项目中省略该行代码即可。

下一步。所以 - 我们 运行 通过并找到我们想要影响的所有敌人。假设有十五个。

注意...

代码在循环中收集它们。他们最终进入“点击”列表。

无论如何,当你刚开始学习时,你可以简单地在循环中应用buff/damage/etc:

    foreach(Enemy e in Gp.enemies.all)
        {
        if (e.gameObject.layer != Grid.layerEnemies) continue;
        if ( transform.DistanceTo(e) < radius )
            e.Slow(3f, 0f);
        }

然而,在任何真正的游戏中,您必须首先列出项目,然后最典型的是有一个管理器(比方说,您的“爆炸管理器!” - 随便什么)处理这些命中/增益/伤害/随便。

原因是您很少能将所有事件都放在同一帧中。想象一下当我快速引爆 15 个敌人时的声音/视觉效果。几乎可以肯定你的创意总监/任何人都希望他们发生“rat-a-tat-tat”你知道吗?无论哪种方式,它都会比“全部触发”复杂得多。 (此外,在性能方面,您可能不得不错开它们 - 显然,这可能是一个涉及大量代码库的大问题;甚至不要提及游戏是否联网。)请注意,在给出的实际示例中,它们最终是交错,确实是从手榴弹向外的距离,看起来很棒。

(出于好奇,该特定代码已被用于爆炸 十亿 手榴弹!)

下一期:

看看你的代码,你只是“GetComponent”。其他对象是“哑巴”。实际上,您永远不会这样做。请注意,在此处的示例代码中,有一个实际的 c# class Enemy

我会在底部粘贴一些Enemy来增加味道。

事实上,您几乎总是保留一个“附加到 players/enemies/etc 的主要 c# Class”的列表。您通常不会太在意 GameObject 本身。

(如果您确实需要访问游戏对象,请对它说 Destroy,您只需 enemy.gameObject。)

所以在这里,因为我们只是检查距离,所以您立即得到 Enemy class。 (如果你使用物理,你必须“GetComponent”才能到达敌人class;当然你也经常这样做。)

话虽这么说 - 请牢记 Unity 的组件行为本质:

话虽这么说。我的讨论有点含糊,有一个“敌人”class(确实有特定的 class 敌人,例如“恐龙”、“杀手机器人”、“攻击鹦鹉”等等)。

请记住,在 Unity 中您确实需要“行为明智”。

真的不应该有“攻击鹦鹉”class。真的,应该只有组件——行为——比如

  • 苍蝇
  • 扔石头
  • 有亮色
  • 承受伤害
  • 激光眼球
  • 绿树成荫

从概念上讲,“AttackParrot”只是一个游戏对象,恰好具有所有这六种行为。相比之下,它就不会说“BreathesFire”和“CanHyperjump”了。

这里详细讨论了所有内容:

说“哦,不应该有 'Enemy' class,只有行为”有点“纯粹主义者”——但要记住一些事情。

接下来,

您必须在 Unity 游戏中拥有“通用”组件,这些组件随处可用。音效、计分等等。

Unity 只是忘了做这件事(他们以后会加上)。

幸运的是,这非常容易做到。注意上面有一个“boss”通用组件和一个“soundEffects”通用组件被调用。

在你的项目中任何需要使用通用“boss”组件或通用“声音”组件的脚本中,它只是...

Boss boss = Object.FindObjectOfType<Boss>();
Sound sound = Object.FindObjectOfType<Sound>();

仅此而已...

老大老大=Object.FindObjectOfType();

这已经解释了很多次了,我们只需要 link 就可以了:


请注意,如果您愿意,使用 PhysX 执行此操作的替代方法是:

如果您想使用内置物理:Physics2D.CircleCastNonAlloc

如果你愿意,花几天时间来掌握它。

请注意,这里的示例是针对 2D 游戏的,在 3D 中是相同的。

(当您在 3D 中测量“距离”时,如果您的游戏仅在平面上进行,您可能只想测量这两个轴上的距离 - 但是老实说,这无关紧要。)


你可能会问,什么是SortByDistanceFrom

SortByDistanceFrom 事实上.......按距离排序

为了节省您的输入时间,这里是扩展名:

public static void SortByDistanceFrom<T>(
         this List<T> things, Transform t) where T:Component
    {
    Vector3 p = t.position;
    
    things.Sort(delegate(T a, T b)
        {
        return Vector2.Distance(p, a.transform.position)
            .CompareTo(Vector2.Distance(p, b.transform.position));
        });
    }

这给新爱好者带来了另一个问题。


示例敌人class

示例 - 上面提到的敌人 class ...包括以添加背景。

因此,所有实际的敌人组件(恐龙、袋熊、XFighters 等等)都将派生自这个组件,并酌情覆盖(运动等概念)。

using UnityEngine;
using System.Collections;

public class Enemy:BaseFrite
    {
    public tk2dSpriteAnimator animMain;
    public string usualAnimName;
    
    [System.NonSerialized] public Enemies boss;
    
    [Header("For this particular enemy class...")]
    public float typeSpeedFactor;
    public int typeStrength;
    public int value;
    
    // could be changed at any time during existence of an item
    
    [System.NonSerialized] public FourLimits offscreen; // must be set by our boss
    
    [System.NonSerialized] public int hitCount;         // that's ATOMIC through all integers
    [System.NonSerialized] public int strength;         // just as atomic!
    
    [System.NonSerialized] public float beginsOnRight;
    
    private bool inPlay;    // ie, not still in runup
    
    void Awake()
        {
        boss = Gp.enemies;
        }
    
    void Start()
        {
        }
    
    public void ChangeClipTo(string clipName)
        {
        if (animMain == null)
            {
            return;
            }
        
        animMain.StopAndResetFrame();
        animMain.Play(clipName);
        }
    
    public virtual void ResetAndBegin() // call from the boss, to kick-off sprite
        {
        hitCount = 0;
        strength = typeStrength;
        beginsOnRight = Gp.markers.HitsBeginOnRight();
        Prepare();
        Gp.run.runLevel.EnemyAvailable();
        }
            
    protected virtual void Prepare()    // write it for this type of sprite
        {
        ChangeClipTo(bn);
        // so, for the most basic enemy, you just do that
        // for other enemy, that will be custom (example, swap damage sprites, etc)
        }
    
    void OnTriggerEnter2D(Collider2D c)
        {
        
        GameObject cgo = c.gameObject;
        
        // huge amount of code like this .......
        if (cgo.layer == Grid.layerPeeps)   // we ran in to a "Peep"
            {
            Projectile p = c.GetComponent<Projectile>();
            if (p == null)
                {
                Debug.Log("WOE!!! " +cgo.name);
                return;
                }
            int damageNow = p.damage;
            Hit(damageNow);
            return;
            }
        
        }
    
    public void _stepHit()
        {
        if ( transform.position.x > beginsOnRight ) return;
        
        ++hitCount;
        --strength;
        ChangeAnimationsBasedOnHitCountIncrease();
        // derived classes write that one.
        
        // todo, actually should the next passage only be after all the steps?
        // is after all value is deducted? (just as with the _bashSound)...
        
        if (strength==0)    // enemy done for!
            {
            Gp.coins.CreateCoinBunch(value, transform.position);
            FinalEffect();
            
            if ( Gp.skillsTest.on )
                {
                Gp.skillsTest.EnemyGottedInSkillsTest(gameObject);
                boss.Done(this);
                return;
                }
            
            Grid.pops.GotEnemy(Gp.run.RunDistance);     // basically re meters/achvmts
            EnemyDestroyedTypeSpecificStatsEtc();       // basically re achvments
            Gp.run.runLevel.EnemyGotted();              // basically run/level stats
            
            boss.Done(this);                            // basically removes it
            }
        }
    
    protected virtual void EnemyDestroyedTypeSpecificStatsEtc()
        {
        // you would use this in derives, to mark/etc class specifics
        // most typically to alert achievements system if the enemy type needs to.
        }
    
    private void _bashSound()
        {
        if (Gp.bil.ExplodishWeapon)
            Grid.sfx.Play("Hit_Enemy_Explosive_A", "Hit_Enemy_Explosive_B");
        else
            Grid.sfx.Play("Hit_Enemy_Non_Explosive_A", "Hit_Enemy_Non_Explosive_B");
        }
    
    public void Hit(int n)  // note that hitCount is atomic - hence strength, too
        {
        for (int i=1; i<=n; ++i) _stepHit();
        
        if (strength > 0) // bil hit the enemy, but enemy is still going.
            _bashSound();
        }
    
    protected virtual void ChangeAnimationsBasedOnHitCountIncrease()
        {
        // you may prefer to look at either "strength" or "hitCount"
        }
    
    protected virtual void FinalEffect()
        {
        // so, for most derived it is this standard explosion...
        Gp.explosions.MakeExplosion("explosionC", transform.position);
        }
    
    public void Update()
        {
        if (!holdMovement) Movement();
        
        // note don't forget Translate is in Space.Self,
        // so you are already heading transform.right - cool.
        
        if (offscreen.Outside(transform))
            {
            if (inPlay)
                {
                boss.Done(this);
                return;
                }
            }
        else
            {
            inPlay = true;
            }
        }
    
    protected virtual void Movement() // override for parabolas, etc etc
        {
        transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
        }
    
    }

原来是一般敌人class。然后你有 派生,例如 Ufo、Dinosaur、Tank、XWingFighter 等。这是 Ufo ...

请注意,它覆盖 很多东西。它似乎覆盖了“准备”(评论表明它“开始更高”,你可以看到它覆盖了其他东西。

using UnityEngine;
using System.Collections;

public class Ufo:Enemy
    {
    public Transform projectilePosition;
    
    protected override void Prepare()
        {
        // ufo always start up high (and then zip up and down)
        transform.ForceY(Gp.markers.StartHeightHighArea());
        
        animMain.StopAndResetFrame();
        animMain.Play(bn + "A");
        animMain.StopAndResetFrame();
        
        Invoke("ZipDown", Random.Range(0.6f,0.8f));
        }
    
    protected override void OnGamePause()
        {
        CancelInvoke();
        StopAllCoroutines();
        }
    protected override void OnGameUnpause()
        {
        Attack();
        
        if(transform.position.y<0f)
            ZipUp();
        else
            ZipDown();
        }
    
    private float fZip = 3.3f;
    
    private void ZipDown() { StartCoroutine(_zipdown()); }
    private void ZipUp() { StartCoroutine(_zipup()); }
    
    private IEnumerator _zipdown()
        {
        Grid.sfx.Play("Enemy_UFO_Move_Down");
        
        float tLow = Gp.markers.StartHeightLowArea();
        while (transform.position.y > tLow)
            {
            transform.Translate(0f,
                fZip * -Time.deltaTime * mpsNow, 0f,Space.Self );
            yield return null;
            }
        Attack();
        Invoke("ZipUp", Random.Range(0.7f,1.4f));
        }
    
    private IEnumerator _zipup()
        {
        Grid.sfx.Play("Enemy_UFO_Move_Up");
        
        float tHigh = Gp.markers.StartHeightHighArea();
        while (transform.position.y < tHigh)
            {
            transform.Translate(0f,
                fZip * Time.deltaTime * mpsNow, 0f,Space.Self );
            yield return null;
            }
        Attack();
        Invoke("ZipDown", Random.Range(0.7f,1.4f));
        }
    
    private void Attack()
        {
        Grid.sfx.Play("Enemy_UFO_Shoot");
        animMain.Play();
        Invoke("_syncShoot", .1f);
        }
    
    private void _syncShoot()
        {
        Gp.eeps.MakeEepUfo(projectilePosition.position);
        }
    
    protected override void ChangeAnimationsBasedOnHitCountIncrease()
        {
        // ufo just goes 4,2,out
        
        if (strength == 2)
            {
            // if any attack, cancel it
            CancelInvoke("ShootGreenPea");
            CancelInvoke("Attack");
            
            // on the ufo, anim only plays with attack
            animMain.StopAndResetFrame();
            animMain.Play(bn + "B");
            animMain.StopAndResetFrame();
            
            Invoke("Attack", 1.5f.Jiggle());
            }
        }
    
    protected override void EnemyDestroyedTypeSpecificStatsEtc()
        {
        Grid.pops.AddToEnemyCount("ufo");
        }
    
    }

让我们考虑一下“在敌人中覆盖 class”的想法。

覆盖示例中的敌人class......

很多敌人都有不同类型的移动,对吧?游戏中的一般范例是物体在 2D 中运动(即,我们“每帧将它们移动一定距离”——这里不使用 PhysX)。所以不同的敌人以完全不同的方式移动。

这是一个以某种方式移动的...(评论对此进行了解释)

protected override void Movement()
    {
    // it enters at about 2x normal speed
    // the slow crossing of the stage is then about 1/2 normal speed
    
    float mpsUse = transform.position.x < changeoverX ? mpsNow*.5f : mpsNow * 2.5f;
    
    transform.Translate( -Time.deltaTime * mpsUse * typeSpeedFactor, 0f, 0f, Space.Self );
    
    // recall that mpsNow was set by enemies when this was created, indeed
    // nu.mpsNow = ordinaryMps * widthSpeedFactor;
    }

这是一个随行的,但有时会“向下漂移……”

protected override void Movement()
    {
    float mm = mpsNow * typeSpeedFactor;
    
    if ( fallingMotion )
        transform.Translate(
            -Time.deltaTime * mm,
                -Time.deltaTime * mm * fallingness, 0f,
            Space.Self );
    else
        transform.Translate(
            -Time.deltaTime * mm, 0f, 0f,
            Space.Self );
    }

这是一个似乎跟随鼻窦的...

protected override void Movement()
    {
    transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
    
    float y = Mathf.Sin( basis-transform.position.x * (2f/length) );
    y *= height;
    transform.transform.ForceY( y );
    }

这是一个复杂的速度变化,放大

protected override void Movement()
    {
    // it enters at about 2x normal speed
    // it appears to then slow crossing of the stage about 1/2 normal speed
    // however it then zooms to about 3x normal speed
    
    float mpsUse = mpsNow;
    
    float angled = 0f;
    
    if ( transform.position.x > changeoverX)    //enter quickly
        mpsUse = mpsNow * 3f;
    
    if ( transform.position.x < thenAngled)     // for bead, angled section
        {
        mpsUse = mpsNow * 1.5f;
        angled = leanVariation;
        }
    
    transform.Translate(
        -Time.deltaTime * mpsUse * typeSpeedFactor,
        -Time.deltaTime * mpsUse * typeSpeedFactor * angled,
        0f, Space.Self );
    }

你可以做任何运动 - 飞行、运行宁、弹跳等等。

这一切都由 protected override 概念在 c# 中处理。


静态“全局变量”class ...比一般组件更简单,只需“保存”某些变量

这是一个静态 class 的简单示例,其中包含您不妨称之为“全局变量”的内容,在游戏工程环境中,将某些东西作为“全局变量”是明智的。

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

static class Gp
    {
    public static Enemies enemies;
    public static Pickups pickups;
    public static Coins coins;
    public static Peeps peeps;
    public static Eeps eeps;
    }

所以 TBC 复杂的“通用”系统,如 SoundEffects、Boss、Scoring、AI、Networking、Social、InAppPurchase 等,如上所述,确实会通过“预加载”类型对象解释。 (即,您在需要它们的任何脚本顶部、任何场景等中使用 Boss boss = Object.FindObjectOfType();。)

但是对于只需要随处访问的简单变量和事物,您可以使用像这样的普通静态 class。通常只有一个静态 class(称为“Gameplay”或“Gp”之类的东西)就可以完成整个项目的工作。

{无论如何,一些团队会说“去他的,不要使用静态 class,将它放在像 Boss 这样的“通用”(“预加载样式”)组件中...... ."}

请注意,当然静态 class 不是真正的 MonoBehavior - 你 “在 Unity 中实际上不能在其中“执行”任何操作”。它仅适用于您想在任何地方轻松访问的“保持变量”(通常是列表)。

同样,请务必记住静态 class 根本不是 Unity 游戏对象或组件 -因此它确实是不是你游戏的一部分;你真的不能在静态class中“做”任何事情。要“做”任何事情,在 Unity 中它必须是一个实际的组件,字面意思是在特定游戏对象上的某个位置。

因此,例如,试图将您的“分数”保持在一个简单的静态 class 中是完全没有用的。不可避免地,与“分数”相关,您会想要做各种事情(更改屏幕显示、奖励积分、保存加密首选项、触发级别……无论如何,有很多事情要做)。你绝对不能在静态中这样做——你根本不能在静态中“做”任何事情——它必须是一个实际的 Unity 游戏对象。 (即,使用“预加载系统”。)再次强调,静态只是用于跟踪一些基本的“全局”变量,通常是事物列表。 (诸如“屏幕标记”之类的东西就是最好的例子。)

顺便说一句,在游戏开发中,“eep”是 敌人的射弹,而“peep”是玩家的射弹,嘿!