使用 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”是玩家的射弹,嘿!
作为游戏玩家,我会这样表述它: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”是玩家的射弹,嘿!