1

作为一个游戏玩家,我会这样说:一个 AOE-Stun,它击中的每个人都会昏迷然后消失。

我有一个附加了“EnemyMovement”类的敌人对象。这个类包含一个函数“Slow”。我有一个附有“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
    }
}
4

3 回答 3

3

这个 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);
        }

然而,在任何真正的游戏中,您必须首先列出物品清单,然后最常见的情况是让一个经理(比如说,您的“爆炸经理!” - 随便)处理这些命中/增益/损坏/其他任何东西。

原因是你很少能把发生的事情都放在同一个框架中。想象一下当我快速爆炸十五个敌人时的声音/视觉效果。几乎可以肯定你的创意总监/谁会希望他们发生“老鼠屎”你知道吗?一种或另一种方式将远比仅仅“触发它们”复杂得多。(此外,在性能方面,您可能不得不错开它们 - 显然这可能是一个涉及大量代码库的大问题;甚至不要提及游戏是否联网。)请注意,在给出的实际示例中,它们最终是交错,实际上是从手榴弹向外的距离,看起来很棒。

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

下一期:

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

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

实际上,您几乎总是保留“附加到玩家/敌人/等的主要 c# 类”的列表。您通常不会对 GameObject 这样的问题感到困扰。

(如果您确实需要到达 GameObject,请告诉Destroy它,您只需enemy.gameObject。)

所以在这里,因为我们只是检查距离,你马上就可以Enemy上课了。(如果你使用物理,你必须“GetComponent”才能进入 Enemy 类;当然你也经常这样做。)

话虽如此 - 请记住 Unity 的组件行为性质:

话虽如此。我的讨论有点滑,有一个“Enemy”类(确实有针对敌人的特定类,例如“Dinosaur”、“KillerRobot”、“AttackParrot”等)。

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

真的不应该有一个“AttackParrot”类。真的,应该只有组件 - 行为 - 例如

  • 苍蝇
  • 扔石头
  • 有明亮的颜色
  • 受到伤害
  • 激光眼球
  • 陆地上的树

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

这一切都在这里详细讨论:

https://stackoverflow.com/a/372​​43035/294884

说“哦,不应该有‘敌人’类,只有行为”有点“纯粹主义”——但要记住一些事情。

接下来,

您必须在 Unity 游戏中拥有“通用”组件,这些组件在任何地方都可以访问。诸如音效、评分等。

Unity只是忘记了这样做(他们将来会添加它)。

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

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

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

这里的所有都是它的...

Boss boss = Object.FindObjectOfType();

这已经解释了很多次了,我们只需要链接到它:

https://stackoverflow.com/a/35891919/294884


请注意,如果您愿意,使用 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));
        });
    }

这为新的爱好者提出了另一个问题。


示例敌人类

示例 - 上面提到的 Enemy 类......包括添加背景。

因此,所有实际的敌人组件(恐龙、袋熊、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 );
        }
    
    }

所以这是一个普通的敌人类。然后你就有了它的衍生品,比如 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");
        }
    
    }

让我们考虑一下“在 Enemy 类中覆盖”的想法。

在示例 Enemy 类中覆盖......

许多敌人有不同类型的运动,对吧?游戏中的一般范例是物体在 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#中处理的。


静态“全局”类...比通用组件更简单,用于简单地“保存”某些变量

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

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、Scores、AI、Networking、Social、InAppPurchase 等,确实会通过“预加载”类型的对象来解释。(即,您在Boss boss = Object.FindObjectOfType();需要它们的任何脚本的顶部、任何场景等中使用。)

但是对于简单的变量,那些只需要在任何地方访问的东西,你可以使用这样一个简单的静态类。通常只有一个静态类(称为“Gameplay”或“Gp”)完成整个项目的工作。

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

请注意,静态类当然不是真正的 MonoBehavior - 您“实际上不能在 Unity 中“做”任何事情”。它仅适用于您想在任何地方轻松访问的“保存变量”(最常见的是列表)。

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

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

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

于 2016-12-03T15:33:54.187 回答
0

(代表 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
    }
}
于 2016-12-03T10:15:45.147 回答
-2

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

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

于 2016-12-02T20:08:50.193 回答