如何在 Unity 中有效地使用继承?

How can I use inheritance in Unity effectively?

我对继承有基本的了解,但由于我想要作为游戏对象组件的每个脚本都必须继承自 MonoBehaviour,因此事情变得棘手。

假设我想要一个名为 Character 的基础 class,并且我希望 Player 和 Enemy 派生自 Character。我应该如何将 Player 和 Enemy 脚本添加到 Player 和 Enemy 对象而不会出现错误?

我还尝试创建空游戏对象并向其添加 CharacterStats,然后用具有不同统计数据的不同角色对象填充此 class。你猜怎么着,如果脚本派生自 Monobehaviour,则不能使用 new 关键字来创建对象。

是否有一些关于这个主题的教程可以使它更清楚?

用好继承来自于规划和迭代。也许你的角色 class 不能很好地作为你的敌人的 class。我倾向于让玩家在他们自己的 class 中(除非是多人游戏),而敌人使用他们自己的基地 class。下面是我在我正在开发的 2D 游戏中使用的一些继承的示例。

基地Class: Enemy.cs

public abstract class Enemy : MonoBehaviour
{
  public Vector2 flyInPos;
  public Vector2 startPos;

  public bool isFlyingIn;

  public float flyInDuration = 5000f;
  public float flyInTime = 0f;

  public abstract void Destroy();

  public abstract void DestroyWithoutScore();

  public abstract void Attack();

  public abstract void Attack2();

  public abstract void Attack3();

  public abstract void FlyOut();

  public virtual void FlyIn()
  {
    isFlyingIn = true;
  }
}

每个敌人都需要有这些元素。你会注意到我的基础 class 中的大多数方法都被标记为 abstract 这样每个 class 继承的方法都可以有一组独特的攻击和飞行模式.需要注意的一件事是基础 class 继承自 MonoBehaviour。以下是我如何在我的一个敌人身上实现继承。抱歉,它仍然是 WIP。

继承Class: ShootingDrone.cs

public class ShootingDrone : Enemy
{
  public GameObject projectileSpawner;
  public GameObject projectile;

  public void Start()
  {
    startPos = transform.position;
    Scheduler.Invoke(Attack, 5f, gameObject);
  }

  public void Update()
  {
    if(isFlyingIn)
    {
      transform.position = Vector3.Lerp(startPos, flyInPos, flyInTime / flyInDuration);
      flyInTime += Time.deltaTime;
    }
  }

  public override void Attack()
  {
    Shoot();
    Scheduler.Invoke(Shoot, 0.25f, gameObject);
    Scheduler.Invoke(Shoot, 0.5f, gameObject);
  }

  public override void Attack2()
  {
    Attack();
  }

  public override void Attack3()
  {
    Attack();
  }

  public void Shoot()
  {
    GameObject newProjectile = Instantiate(projectile, projectileSpawner.transform);
    newProjectile.transform.parent = null;
  }

  public override void FlyOut()
  {
    throw new System.NotImplementedException();
  }

  public override void DestroyWithoutScore()
  {
    throw new System.NotImplementedException();
  }

  public override void Destroy()
  {
    Destroy(gameObject);
  }
}

Enemy 的基础 class 继承自 MonoBehaviour。因此,当 Shooting Drone 继承自 Enemy 时,它也继承自 MonoBehaviour。

针对您尝试添加新的 MonoBehaviour 的问题。这样做的原因是 MonoBehavior 必须附加到某些东西上。想象一个没有 GameObject 的刚体,它是行不通的。如果你想制作一个“新的”CharacterStats,你会这样做:

CharacterStats stats = gameObject.AddComponent<CharacterStats>();

如果您不想将 CharacterStats 作为一个组件,那么只需从 class 中删除 MonoBehavior 继承并将其实例化为新的 CharacterStats。

有多种教程介绍了继承,但是根据您对这个主题的了解程度,我会从 Unity official inheritance tutorial. I do think this tutorial is too brief but I also like CircutStreams tutorial 开始,因为它还提到了实现接口,这可以更好地解决继承问题许多案例。

大概要理解的是MonoBehaviour本身继承了Component - MonoBehaviour IS a Component.

当您将其视为组件是事物的一部分时,至少在某种程度上您不能 new 一个组件 - 我可以制作一个 new Arm 吗?如果我这样做了,它会从哪里得到?但是我可以做一个 new Body body 然后做 body.AddComponent<Arm>,对吧?因为这样就很清楚 Arm 发生了什么 - 它 附加到 body.

同样,您不能只是 new 组件,因为它们应该是游戏对象的 部分。您可以 做的是制作一个新的游戏对象,然后.AddComponent<>() object。同样,现在很清楚 组件的去向。

我大体上同意 UnholySheep's comment 你应该更喜欢组合而不是继承,因为这通常会使你的代码更加模块化。也就是说,我肯定也使用继承。

当你概述你的 class 时,你应该问自己的问题是,“这个新的 class 是 _____ 的一种还是这个新的 class 有一个 _____。”我想说玩家是角色,敌人是角色很容易,但是你需要 subclass吗?或者 Player 只是一个也有 PlayerController 的 Character 吗?也许你可以有一个像 IPlayerController 这样的界面,并为用户提供一个 LocalPlayerController,为敌人提供一个 AIPlayerController? problem-solve 编程的很多方法。

或者,也许所有角色都有一个 PlayerController,而 non-player 个角色只有 .activeControl 位为假。然后你可以做ghost/spectate NPC之类的事情

但具体针对您的问题:

Let's say I want to have a base class called Character and I want Player and Enemy to derive from Character. How am I suppose to add Player and Enemy script to Player and Enemy objects without getting an error?

您将从一个 Character 脚本开始,该脚本继承 MonoBehaviour 并实现两个 classes:

通用的任何逻辑
public class Character : MonoBehaviour
{
    // Your common character logic
}

那么你会让子class继承Character:

public class Player : Character
{
    // Your Player-specific logic here
}

public class Enemy : Character
{
    // Your Enemy-specific logic here
}

然后您可以将 EnemyPlayer 脚本添加到编辑器中的游戏对象,或者您可以通过获取对目标游戏对象的引用(通过在编辑器中设置引用或通过在脚本中创建一个新的 GameObject)然后调用 targetGameObject.AddComponent<YourComponentToAdd>();.