问题描述
作为游戏玩家,我会这样说:AOE-Stun,使击中的每个人都晕眩,然后消失.
As a gamer I'd phrase it like this: An AOE-Stun that stuns everyone it hits and then disappears.
我有附有"EnemyMovement"类的敌人物体.该类包含一个函数"Slow".我有一个圈子,上面附有"StunSpell"类.现在,我想为每一个与之碰撞的敌人对象调用一次慢".
I have enemy objects with a class "EnemyMovement" attached to it.This class contains a function "Slow". I have a circle that has the class "StunSpell" attached to it. Now I want to call "Slow" once for every enemy object that collides with it.
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
}
}
推荐答案
此质量检查有点混乱,但简单地...
" ...检查到每个字符的距离..."
This QA is a bit messy, but it's totally normal and commonplace in video games to simply ...
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);
}
很难想象有什么比这更简单的了.
It's hard to imagine anything being simpler than that.
请注意,我们决定采用
radius
以米为单位,假设为"4.2米",我们要在其中对敌人造成伤害. (或者,对它们进行抛光,或视情况而定.)
in meters, let's say "4.2 meters", inside which we want to do damage to the enemies. (Or, buff them, or whatever the case may be.)
这东西
Gp.enemies.all
是List<Enemy>
... ...它容纳了当前游戏中的所有敌人.简单吧?
is a List<Enemy>
... it holds all the enemies in the game at the moment. Simple right?
如果您实际上并没有所有敌人(或玩家,NPC-任何相关的东西)的List<>
-您将被### ed.重新开始您的学习项目.有了经过单元测试的实时列表后,请回到此列表.
If you do not actually have a List<>
of all the enemies (or players, NPCs - whatever is relevant) - you are ###ed. Start over on your learning project. Once you have a live List which is unit-tested, come back to this.
这行代码
Grid.layerEnemies
与Unity中的 layer 系统相关.这通常会导致新的业余爱好者出现问题...
relates to the layer system in Unity. This often causes new hobbyists a problem ...
让您开始使用Layers超出了本文的范围,因此我们将其搁置一旁.如果愿意,只需在学习项目中省略代码行即可.
It is beyond the scope of this article to get you started on using Layers, so we'll leave that aside. If you prefer, just leave out the line of code in your learning project.
下一步.所以-我们经过并找到了我们想要影响的所有敌人.我们说其中有十五个.
Next. So - we run through and find all the enemies we want to affect. Let us say there are fifteen of them.
请注意...
总的来说,当您只是学习时,只需在循环中应用buff/damage/etc:
By all means, when you're just learning, you can simply apply the buff/damage/etc insinde the loop:
foreach(Enemy e in Gp.enemies.all)
{
if (e.gameObject.layer != Grid.layerEnemies) continue;
if ( transform.DistanceTo(e) < radius )
e.Slow(3f, 0f);
}
但是,在任何真实游戏中,您都必须首先列出物品清单,然后最典型的是让经理(例如,您的爆炸经理!"-随便什么)处理这些命中/增益/伤害/什么.
However, in any real game, you have to first make a list of the items, and then most typically have a manager (let's say, your "explosions manager!" - whatever) process those hits/ buffs/ damages/ whatever.
原因是您很少能将所有事件都放在同一帧中.想象一下当我迅速爆炸说15个敌人时的声音/视觉效果.几乎可以肯定,您的创意总监/希望他们进行"rat-a-tat-tat"游戏的人你懂?一种或另一种方式将比仅全部触发"要复杂得多. (此外,在性能方面,您可能必须将它们错开-显然,这可能是一个涉及大量代码库的巨大问题;请不要提及游戏是否联网.)请注意,在给出的实际示例中,它们最终是错开了,确实是从手榴弹向外拉开了,看起来很棒.
The reason is that you can rarely just throw in happenings all in the same frame. Imagine the sound/ visual effects when I quickly explode say fifteen enemies. Almost certainly your creative director / whoever will want them to happen "rat-a-tat-tat" you know? One way or another it will be far more complex than just "triggering them all". (Also, performance-wise you may well have to stagger them - obviously this can be a huge issue involving massive code bases; don't eve mention if the game is networked.) Note that in the actual example given, they end up being staggered, and indeed by distance outwards from the grenade, which looks great.
(出于好奇,该特定代码已被用来炸破10亿枚手榴弹!)
(As a curiosity, that particular code has been used to explode on the order of one billion grenades!)
下一期:
查看您的代码,您只需"GetComponent".其他对象是哑巴".实际上,您永远不会这样做.请注意,此处的示例代码中有一个实际的c#类Enemy
looking at your code, you just "GetComponent". The other objects are "dumb". In reality you never do this. Note that in the example code here, there is an actual c# class Enemy
我将在底部的Enemy
中粘贴一些味道.
I will paste in some of Enemy
at the bottom to give a flavour.
(如果确实需要进入GameObject,对Destroy
说,则只需enemy.gameObject
.)
(If you do need to get to the GameObject, say to Destroy
it, you just enemy.gameObject
.)
因此,在这里,由于我们只是在检查距离,因此您立即有了Enemy
类. (如果您使用的是物理学,则必须"GetComponent"才能进入Enemy类;当然,您也经常这样做.)
So here, since we're simply checking the distance, you immediately have the Enemy
class. (If you're using physics, you have to "GetComponent" to get to the Enemy class; of course you often do that, also.)
话虽如此.我的讨论有点溜溜,有一个敌人"的想法.类(确实有针对敌人的特定类,例如恐龙",杀手机器人",攻击鹦鹉"等).
That being said. My discussion is a bit slippery, there's an "Enemy" class (and indeed there's specific classes for enemies, such as "Dinosaur", "KillerRobot", "AttackParrot" and so on).
尽管要牢记,您确实需要行为明智"的做法.在Unity中.
Try to bear in mind though, you really need to thing "behaviour-wise" in Unity.
确实不应该有一个"AttackParrot"班级.确实,应该只有一些组件-行为-例如
There really shouldn't be an "AttackParrot" class. Really, there should just be components - behaviors - such as
- 苍蝇
- ThrowsRocks
- HasBrightColors
- TakesDamage
- LaserEyeballs
- LandOnTrees
从概念上讲,"AttackParrot"只是一个游戏对象,恰好具有所有这六个行为.相反,它不会说"BreathesFire".和"CanHyperjump".
Conceptually an "AttackParrot" would just be a game object, which, happens to have, all of those six behaviors. In contrast, it would not have say "BreathesFire" and "CanHyperjump".
这里将详细讨论:
https://stackoverflow.com/a/37243035/294884
有点纯粹"说哦,不应该有一个'敌人'阶级,只有行为" -但要记住一点.
It's a bit "purist" to say "Oh, there shouldn't be an 'Enemy' class, only behaviors" - but something to bear in mind.
接下来,
团结只是忘记了要做到这一点(将来会添加).
Unity simply forgot to do this (they'll add it in the future).
幸运的是,这非常容易做到.注意,在上面有一个老板".一般组件和"soundEffects"通用组件.
Fortunately it's incredibly easy to do. Notice in the above there's a "boss" general component and a "soundEffects" general component which are called to.
在您项目中需要使用常规老板"脚本的任何脚本中,组件或一般的声音"组件,只是...
In any script in your project that needs to use the general "boss" component or the general "sound" component, it's just...
Boss boss = Object.FindObjectOfType<Boss>();
Sound sound = Object.FindObjectOfType<Sound>();
仅此而已...
对此已作了很多次详尽的解释,我们只需要链接到它即可:
This has been explained at vast length so many times, we need only link to it:
https://stackoverflow.com/a/35891919/294884
请注意,如果您愿意,使用PhysX的另一种方法是:
Note that, if you prefer, the alternate way to do this using PhysX is:
如果需要,请花几天时间来掌握.
If you want, take a couple of days out to master that.
请注意,此处的示例适用于2D游戏,在3D中相同.
Note that the examples here are for a 2D game, it's identical in 3D.
(当您在3D中测量距离"时,如果您的游戏仅发生在平坦的表面上,您可能会可能只打扰测量这两个轴上的距离,但是说实话这是无关紧要的. )
(When you measure "distance" in 3D, if you game happens only on a flat surface, you may want to bother only measuring the distance on those two axis - but honestly it's irrelevant.)
您可能会问,什么是SortByDistanceFrom
?
为节省您的输入,以下是该扩展名:
To save you typing, here is that extension:
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));
});
}
这给新的业余爱好者带来了另一个问题.
This raises another issue for new hobbyists.
示例-上面提到的Enemy类...包括添加背景.
Example - the Enemy class mentioned above ... included to add background.
因此,所有实际的敌方组件(恐龙,袋熊,X战斗机,等等)都将从该组件派生而来,并适当地覆盖(诸如运动之类的概念).
So, all the actual enemy components (Dinosaurs, Wombats, XFighters, whatever) would derive from this one, overriding (concepts like motion, etc) as appropriate.
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 ...
So that's a general enemy class. Then you have derives of that, such as Ufo, Dinosaur, Tank, XWingFighter, etc etc. Here's Ufo ...
请注意,它会覆盖许多内容.似乎覆盖了准备"状态. (注释表明它开始更高",您可以看到它优先于其他内容.
Note that it overrides many things. It seems to override "Prepare" (the comments suggest it "starts higher", and you can see it overrides other things.
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");
}
}
让我们考虑在敌人阶级中压倒一切"的想法.
Let's think about the idea of "overrides in the Enemy class".
许多敌人有不同类型的动作,对吗?游戏中的一般范例是运动以2D运动的事物(即,我们将它们每帧移动一定距离" –在此不使用PhysX).因此,不同的敌人以截然不同的方式运动.
The many enemies have different types of movement, right? The general paradigm in the game is things moving in 2D, kinematically (ie, we "move them a certain amount of distance each frame" - not using PhysX here). So the different enemies move in radically different ways.
这是一种以某种方式运动的...(评论解释了这一点)
Here's one that moves in a certain way ... (the comments explain it)
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;
}
这是一个不断发展的过程,但有时会向下漂移..."
Here's one that goes along, but sometimes "drifts downwards..."
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 );
}
这似乎是一个跟着窦走的人...
Here's one that seems to follow a sinus ...
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 );
}
这里是一个复杂的速度变化,可以放大
Here's one that does complex speed changes, zooming around
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 );
}
您可以使运动成为可能-飞行,奔跑,弹跳等等.
You could make the movement anything - flying, running, bouncing, whatever.
全部由protected override
概念在c#中处理.
It's all handled in c# by the protected override
concept.
这是一个静态类的简单示例,其中包含您可能称为全局变量"的东西.在游戏工程环境中,将某些东西称为全局变量"是明智的.
Here's a trivial example of a static class which holds what you might as well call "globals", In a game engineering milieu, it's sensible to have certain things as "globals".
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();
.)
So TBC complicated "general" systems like SoundEffects, Boss, Scoring, AI, Networking, Social, InAppPurchase etc etc as described above, would indeed by the "preload" type objects as explained. (ie, you're using Boss boss = Object.FindObjectOfType();
at the top of any script, in any scene etc, that needs them.)
但是对于只是需要在任何地方访问的变量之类的东西,您可以使用一个普通的静态类.通常,只有一个静态类(称为游戏性"或"Gp"之类的东西)才能完成整个项目.
But for simply variables, things, that just need to access everywhere, you can use a trivial static class like that. Often just the one static class (called something like "Gameplay" or "Gp") does the job for the whole project.
{总的来说,有些团队会说拧,不要使用静态类,将其放在"一般"类中. ("preload-style")组件,例如Boss...."}
{By all means, some teams would say "screw that, don't use a static class, put it in a "general" ("preload-style") component like Boss...."}
请注意,静态类 当然不是真正的MonoBehavior -您 "实际上不能执行"操作.在Unity中的任何内容". .它仅用于持有变量". (最常见的是列表),您想在任何地方都可以轻松访问.
Note that of course a static class is NOT a real MonoBehavior - you "can NOT actually "DO" anything inside it in Unity". It's only for "holding variables" (most often, Lists) you want to access easily everywhere.
同样,请记住,静态类 根本不是Unity游戏对象或组件 -因此,从字面上看,它不是 游戏的一部分 ;您从字面上不能 做" 静态类中的任何内容.要做"在Unity中,任何东西都必须是一个实际的组件,确切地说是在特定GameObject上的某个位置.
Again, be sure to remember that a static class is quite simply NOT a Unity game object or component - hence it very literally is not part of your game; you literally can not "do" anything whatsoever in a static class. To "do" anything, at all, in Unity it must be an actual Component, literally on a specific GameObject, at some position.
例如,因此试图保持您的得分"是完全没有用的.在一个简单的静态类中.关于分数",不可避免地要涉及到分数".您将需要做各种事情(更改屏幕显示,奖励积分,保存加密的首选项,触发级别……等等,还有很多事情要做).您绝对不能以静态方式执行此操作-您不能静态"执行此操作静态中的所有内容-它必须是实际的Unity游戏对象. (即,使用预加载系统".)静态变量仅用于字面上跟踪一些基本的全局"变量.变量,通常是事物列表. (诸如屏幕标记"之类的例子就是最好的例子.)
Thus for example it's completely useless trying to keep your "score" in a simple static class. Inevitably in relation to the "score" you will want to do all sorts of things (change the display on screen, award points, save encrypted preferences, trigger levels ... whatever, there is a lot of stuff to do). You absolutely cannot do that in a static - you can not "do" anything, at all, in a static - it must be an actual Unity game object. (ie, using the "preload system".) Once again statics are just for literally keeping track of some basically "global" variables, usually lists of things. (Things like "screen markers" are the perfect example.)
在游戏开发中,顺便说一句顺便说一句"是敌人的弹丸和窥视",是玩家弹丸,嘿!
Just BTW in game development an "eep" is an enemy projectile and a "peep" is a player projectile, heh!
这篇关于使用OnTriggerStay查找C#和Unity3D中的每个碰撞对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!