2016-12-02 10 views
1

Как геймер, я бы назвал это следующим: AOE-Stun, который оглушает всех, кто попадает, а затем исчезает.Найти каждый сталкивающийся объект в C# и Unity3D с помощью OnTriggerStay

У меня есть объекты противника с прикрепленным к нему классом «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 
    } 
} 
+1

Что вы хотите сказать? – Programmer

+0

Я хочу найти каждый объект игры с тегом враг, который сталкивается и вызывает функцию «Slow» в каждом вражеском-игровом объекте один раз, а затем уничтожить объект. – Csharpest

+0

Я думал о создании цикла for, который находит каждый сталкивающийся вражеский объект, но я не знаю как к этому – Csharpest

ответ

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<> всех врагов (или игроков, НИПы - независимо от того, что уместно) - вы ### ed. Начните свой учебный проект. После того, как у вас есть живой список, который тестируется с помощью модуля, вернитесь к этому.

Эта строка кода

Grid.layerEnemies 

относится к слоя системы в Unity. Это часто вызывает у новых любителей проблемы ...

В действительности вы можете сделать абсолютно ничего в Unity без использования системы слоев для каждой вещи.

За пределами этой статьи вы можете начать использовать слои, поэтому мы оставим это в стороне. Если вы предпочитаете, просто оставьте строку кода в своем учебном проекте.

Дальше. Итак - мы убегаем и находим всех врагов, на которые хотим повлиять. Скажем, их пятнадцать.

Обратите внимание, что ...

код собирает их в петле. Они попадают в список «хитов».

Непременно, когда вы только учитесь, вы можете просто применить бафф/повреждения/и т.д. insinde петля:

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

Однако, в любой реальной игре, вы должны сначала сделать список предметов, а затем, как правило, у менеджеров (скажем, ваш «диспетчер взрывов!» - независимо) обрабатывают эти удары/баффы/убытки/что угодно.

Причина в том, что вы редко можете просто бросать события в одном и том же кадре. Представьте себе звуковые/визуальные эффекты, когда я быстро взорвусь, скажу пятнадцать врагов. Почти наверняка ваш креативный директор/тот, кто захочет, чтобы они произошли «rat-a-tat-tat», вы знаете? Так или иначе, это будет намного сложнее, чем просто «вызвать их всех». (Кроме того, с точки зрения производительности вам вполне может понадобиться пошатнуть их - очевидно, это может быть огромной проблемой, связанной с массивными базами кода, не упоминайте, если игра в сети.) Обратите внимание, что в приведенном фактическом примере они заканчиваются тем, что пошатнувшись, и действительно на расстоянии от гранаты, которая выглядит великолепно.

(В качестве курьеза, что конкретный код был использован, чтобы взорвать порядка один миллиард гранат!)

Следующий выпуск:

глядя на свой код, вы просто «GetComponent». Другие объекты «тупые». На самом деле вы никогда этого не делаете. Обратите внимание, что в приведенном здесь примере кода есть фактический класс C# Enemy

Вставьте часть из Enemy внизу, чтобы получить аромат.

Фактически, вы почти всегда держите Список «основного класса C#, прикрепленного к игрокам/врагам/etc». Обычно вы не сильно разбираетесь в GameObject как таковой.

(Если вам не нужно, чтобы добраться до GameObject, сказать Destroy, вы просто enemy.gameObject.)

Так вот, так как мы просто проверить расстояние, вы сразу же иметь Enemy класс. (Если вы используете физику, вам нужно «GetComponent», чтобы попасть в класс Enemy, конечно, вы тоже часто это делаете.)

Это, как говорится, следует учитывать природу компонента-поведения Unity:

Это считается. Моя дискуссия немного скользкая, есть класс «Враг» (и действительно есть специальные классы для врагов, такие как «Динозавр», «KillerRobot», «AttackParrot» и т. Д.).

Попытайтесь иметь в виду, однако, вам действительно нужно верить «поведением» в Единстве.

На самом деле не должно быть класса «AttackParrot».На самом деле, должно быть просто компоненты - поведение - такие, как

  • Мухи
  • ThrowsRocks
  • HasBrightColors
  • TakesDamage
  • LaserEyeballs
  • LandOnTrees

Концептуально "AttackParrot" будет просто игровым объектом, который, как оказалось, 11 из этих шести поведений. Напротив, он не сказал бы «BreathesFire» и «CanHyperjump».

Это все обсуждается подробно здесь:

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

Это немного «пуристов», чтобы сказать: «О, не должно быть класса„Враг“, только поведение» - но что-то имей в виду.

Далее,

Вы должны иметь «общие» компоненты в игре Unity, которые доступны везде. Такие вещи, как звуковые эффекты, оценка и так далее.

Unity просто забыл, чтобы сделать это (они добавят его в будущем).

К счастью, это невероятно легко сделать. Обратите внимание, что в приведенном выше разделе присутствует общий компонент «босс» и общий компонент «soundEffects», к которому обращаются.

В любом сценарии в проекте, который должен использовать общий «босс» компонент или общий «звук» компонент, это просто ...

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 класс

Пример - класс 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, Динозавр, 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».

Переопределения в примере Класс врага ......

У многих врагов разные виды движения, правда? Общая парадигма в игре - это вещи, движущиеся в 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); 
    } 

Вы можете сделать движение чем угодно - летать, бегать, прыгать, что угодно.

Это все обрабатывается в C# по концепции protected override.


Статические «глобальные» классы ...проще простого компонента, просто «удерживая» определенные переменные

Вот тривиальный пример статического класса, который содержит то, что вы могли бы назвать «глобальными». В среде разработки игр разумно иметь определенные вещи, как «Глобал».

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, сеть, социальный, InAppPurchase и т.д. и т.п., как описано выше, бы действительно по "преднагрузке" объекты типа, как объяснено. (т. е. вы используете Boss boss = Object.FindObjectOfType(); в начале любого скрипта, в любой сцене и т. д., который им нужен.)

Но для простых переменных, вещей, которые просто нужно получить везде, вы можете использовать тривиальный статический класс как это. Часто только один статический класс (называемый «Геймплей» или «ГП») выполняет работу для всего проекта.

{Скорее всего, некоторые команды скажут «винт, что не используйте статический класс, поставьте его в« общий »(« preload-style ») компонент, такой как Boss ....»}

Обратите внимание, что, конечно, статический класс НЕ реальная MonoBehavior - вы «не может на самом деле„делать“что-то внутри него в единстве». Это только для «удерживающих переменных» (чаще всего списков), к которым вы хотите легко получить доступ везде.

Опять же, убедитесь, что помните, что статический класс достаточно просто не является объектом Unity игры или компонент - следовательно, он очень буквально является не является частью вашей игры; вы буквально не можете "do" anything что бы то ни было в статическом классе. Чтобы «делать» что угодно, вообще, в Unity, он должен быть фактическим Компонентом, буквально на определенном GameObject, в некоторой позиции.

Так, например, это совершенно бесполезно, пытаясь сохранить свой «счет» в простом статическом классе. Неизбежно по отношению к «счету» вы захотите делать всевозможные вещи (изменять отображение на экране, очки награды, сохранять зашифрованные настройки, уровни запуска ... что угодно, есть много чего заняться). Вы абсолютно не можете сделать это в статике - вы не можете «ничего» делать, вообще, в статике - это должен быть реальный игровой объект Unity. (т. е. с использованием «системы предварительной загрузки».) Еще раз статика просто для буквального отслеживания некоторых в основном «глобальных» переменных, как правило, списков вещей. (Такие вещи, как «экран маркеры» являются прекрасным примером.)

Просто BTW в разработке игр «ЭСППЗ» является врагом снарядом и «пип» является игроком снарядом, х!

+0

Вау! Сначала я хочу поблагодарить вас за то, что вы нашли время и подробно объяснили мне такие вещи! Я читал ссылки на «предварительную сцену» и отношение к программированию поведения. Несмотря на то, что вы многому научили меня, мне все еще не хватает знаний, чтобы полностью понять пример класса врага. Что означает атомный? Что означает «Gp»? Что включает класс «Враги»? Я не хочу слишком беспокоить вас, мне все равно потребуется много времени, чтобы понять все это. – Csharpest

+0

no prpblem вообще, @Csharpest - Я буду отвечать на все эти вопросы по мере того, как день прогрессирует. – Fattie

+0

Отлично! спасибо, смешное, что я действительно думал о том, что означают «peep» и «eep», пока я не прочитаю ваше последнее предложение hehe – Csharpest

-2

В классе EnemyMovement используется метод OnTriggerEnter. Пометьте заклинание и поставьте на него 2dCollider. Когда OntriggerEnter попадает с тегом == «медленное заклинание», вызовите медленное.

В принципе, я бы сделал это обратным способом, который вы сейчас делаете. В противном случае вам легко будет взаимодействовать со всем, что попадает в противника (так как это предполагаемое поведение)

+0

Но если бы я создал больше заклинаний (которые имеют схожую работу), мне придется пометить каждое заклинание по-другому. Я вроде бы не хочу этого делать – Csharpest

+1

не используют теги по какой-либо причине, когда-либо, в Unity. это всего лишь одна из тех запутанных «идиотских особенностей», которые они добавили, чтобы помочь любителям буквально в первый день. Опять же: никогда не используйте теги, когда-либо. Это так просто. – Fattie

+0

Я никогда не слышал этого совета, но я думаю, что это поможет вам объяснить, почему никогда не использовать теги, а не просто называть его идиотом. –

0

(Отправлено от имени ОП).

я решил его с помощью списка, чтобы проверить, какие враги сталкиваясь с кругом:

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 
    } 
} 
Смежные вопросы