Unity C#面向对象编程:继承与多态在游戏开发中的实战应用

发布时间:2026/7/25 9:54:41
Unity C#面向对象编程:继承与多态在游戏开发中的实战应用 1. 项目概述为什么Unity开发者必须掌握继承与多态如果你刚开始用Unity做游戏或者从其他语言转过来学C#可能会觉得“继承”和“多态”这两个词听起来很学术离实际的游戏开发有点远。我以前带新人时也常听到这样的疑问“我写个脚本让角色移动、跳跃不就行了吗为什么要学这些” 但我要告诉你一旦你开始构建稍微复杂一点的系统比如一个有十几种不同敌人的关卡、一个包含多种交互道具的背包或者一套可扩展的技能树你就会发现不理解继承和多态你的代码会迅速变成一团难以维护的“意大利面条”。简单来说继承让你能建立清晰的“家族关系”。比如你可以创建一个Enemy敌人基类定义所有敌人都有的生命值、移动速度等属性和TakeDamage受到伤害方法。然后Goblin哥布林和Dragon龙这两个类去继承Enemy。哥布林有自己偷窃的行为龙有喷火的能力但它们都“是”一种敌人共享基础的敌人逻辑。这避免了你在哥布林和龙的脚本里重复写生命值管理的代码。多态则赋予了代码“灵活应变”的能力。它允许你通过一个统一的“接口”比如基类引用来操作不同的子类对象而程序在运行时能自动识别出具体是哪个子类并调用其正确的方法。想象一下你有一个Enemy类型的数组里面既装了哥布林对象也装了龙对象。当你遍历数组对每个元素调用Attack()方法时哥布林会上去砍一刀而龙则会喷出火焰。你不需要写if (enemy is Goblin) ... else if (enemy is Dragon) ...这样冗长的判断系统会自动处理。这就是多态的魅力它让增加新的敌人类型变得异常简单只需新建一个类并继承Enemy旧的攻击循环代码完全不用修改。这个“Unity C#入门继承与多态的简单应用”项目就是带你绕过枯燥的理论直接动手在Unity编辑器里构建一个能直观看到效果的迷你游戏原型。我们将创建一个简单的战斗模拟你会亲手写出基类、子类体验多态如何让代码简洁而强大。这不仅是面试常考的基础更是你日后设计复杂游戏系统的基石。无论你是完全的编程新手还是有一定基础想巩固面向对象思想的开发者跟着做一遍你会有种“原来如此”的顿悟感。2. 核心概念拆解从“是什么”到“为什么用”在直接敲代码之前花几分钟把核心概念理清楚能让你后续的实操事半功倍。很多初学者卡住不是因为语法不会而是没理解这些概念要解决的实际问题。2.1 继承构建清晰的代码家族树继承的核心思想是“派生”与“扩展”。在C#中使用冒号:来表示继承关系。public class Enemy // 基类父类 { public string name; public int health; public void TakeDamage(int damage) { health - damage; Debug.Log(name 受到了 damage 点伤害剩余生命 health); } } public class Goblin : Enemy // Goblin继承自Enemy { public int stealPower; // 哥布林特有的属性偷窃能力 public void Steal() { Debug.Log(name 发动偷窃); } }在这里Goblin类自动获得了Enemy类的name、health字段和TakeDamage方法。这就是代码复用是继承最直接的好处。你可以创建几十种敌人而它们共同的基础逻辑只需要在Enemy类里维护一份。注意继承代表一种“是一个is-a”的关系。Goblin是一个Enemy这逻辑上是成立的。不要为了复用代码而滥用继承比如让Car汽车继承Engine发动机这就不符合“是一个”的关系汽车不是发动机这种情况下使用组合将发动机作为汽车的一个部件更合适。2.2 多态同一接口不同实现多态建立在继承之上。它通常通过“重写”基类的虚方法或抽象方法来实现。public class Enemy { // virtual关键字表示该方法可以被子类重写 public virtual void Attack() { Debug.Log(敌人发动了普通攻击); } } public class Goblin : Enemy { // override关键字表示重写父类的Attack方法 public override void Attack() { Debug.Log(哥布林挥舞短刀砍击); } } public class Dragon : Enemy { public override void Attack() { Debug.Log(巨龙喷吐出灼热的火焰); } }多态的魔力在以下场景中展现ListEnemy enemies new ListEnemy(); enemies.Add(new Goblin() { name 卑鄙哥布林 }); enemies.Add(new Dragon() { name 炎狱巨龙 }); foreach (Enemy enemy in enemies) { // 这里enemy的编译时类型是Enemy但运行时类型分别是Goblin和Dragon enemy.Attack(); // 输出“哥布林挥舞短刀砍击” 和 “巨龙喷吐出灼热的火焰” }编译器在编译时只知道enemy是Enemy类型但它允许调用Attack方法。到了运行时CLR公共语言运行时会查找对象的实际类型Goblin或Dragon并调用该类型下重写的Attack方法。这就是运行时多态或动态绑定。为什么这如此重要假设你没有用多态你的代码可能长这样foreach (var unit in enemies) { if (unit is Goblin) ((Goblin)unit).GoblinAttack(); else if (unit is Dragon) ((Dragon)unit).DragonBreath(); // ...每增加一种新敌人这里就要加一个else if }这种代码极其脆弱难以维护。而使用多态增加一个新的Archer弓箭手敌人你只需要新建Archer : Enemy类并重写Attack上面的foreach循环一行都不用改这极大地提高了系统的可扩展性和可维护性。2.3 关键关键字virtual, override, base 与 abstract理解这些关键字是正确运用多态的关键virtual虚方法在基类中声明表示该方法可以被派生类重写。它本身可以有实现方法体。override重写在派生类中声明用于提供基类虚方法或抽象方法的新实现。签名必须与基类方法完全一致。base在派生类中用于调用基类中被重写的方法或构造函数。例如在Goblin的Attack方法中你可以先调用base.Attack()执行敌人的基础攻击逻辑再追加哥布林的特殊效果。abstract抽象方法与抽象类这是更严格的约定。abstract class抽象类不能实例化你不能new AbstractEnemy()它存在的意义就是被继承。abstract method抽象方法只有声明没有实现没有方法体。任何继承自抽象类的非抽象子类都必须用override实现所有抽象方法。这强制了子类必须提供某些关键行为。public abstract class Enemy // 抽象类 { public int health; public abstract void Attack(); // 抽象方法无实现 public void TakeDamage(int damage) // 普通方法有实现 { health - damage; } } public class Goblin : Enemy { public override void Attack() // 必须实现Attack { Debug.Log(哥布林攻击); } } // 如果Goblin类不实现Attack编译会报错。使用abstract相当于和所有子类签订了一份“契约”确保它们都具备某种能力。在游戏开发中对于核心行为如攻击、死亡使用抽象类来定义接口是非常好的实践。3. 项目实战构建一个简易战斗模拟系统理论说得再多不如动手做一遍。我们将在Unity中创建一个简单的场景通过代码直观地感受继承与多态如何工作。3.1 环境准备与项目设置打开Unity Hub创建一个新的3D核心项目命名为“InheritancePolymorphismDemo”。在场景中简单创建几个3D物体作为视觉参考即可比如几个Cube或Sphere分别命名为Player、EnemySpawnPoint等。因为我们主要关注脚本逻辑所以美术资源从简。你也可以从Asset Store导入一些免费的低多边形模型让场景更好看。在Project窗口的Assets文件夹下创建一个名为“Scripts”的文件夹来存放我们即将编写的所有C#脚本。3.2 设计基类抽象敌人Enemy我们的系统将从设计一个稳固的基类开始。考虑到敌人必须能攻击和受伤但具体攻击方式未知我们将Enemy设计为抽象类。在Scripts文件夹下右键创建新的C#脚本命名为Enemy。using UnityEngine; // 声明为抽象类无法直接创建Enemy实例 public abstract class Enemy : MonoBehaviour { // 公共字段可在Inspector中编辑 public string enemyName 默认敌人; public int maxHealth 100; public int attackPower 10; // 当前生命值对外只读内部可修改 [SerializeField] // 序列化字段在Inspector可见可调但其他脚本不能直接修改 private int currentHealth; // 属性用于安全地访问和设置当前生命值并触发死亡事件 public int CurrentHealth { get { return currentHealth; } set { currentHealth Mathf.Clamp(value, 0, maxHealth); // 确保生命值在0到最大值之间 Debug.Log(${enemyName} 生命值变为{currentHealth}); if (currentHealth 0) { Die(); } } } // 初始化 protected virtual void Start() { CurrentHealth maxHealth; // 使用属性赋值触发Clamp和日志 Debug.Log(${enemyName} 已生成最大生命{maxHealth}攻击力{attackPower}); } // 抽象方法攻击行为。每个具体敌人都必须实现自己的攻击逻辑。 public abstract void Attack(); // 虚方法受到伤害。子类可以重写此方法来增加特殊效果如格挡、反弹。 public virtual void TakeDamage(int damage) { Debug.Log(${enemyName} 受到了 {damage} 点伤害); CurrentHealth - damage; // 通过属性减少生命值 } // 受保护的虚方法死亡逻辑。子类可以重写以播放独特的死亡动画或特效。 protected virtual void Die() { Debug.Log(${enemyName} 被击败了); // 通常这里会播放死亡动画、产生掉落物、销毁对象等。 Destroy(gameObject, 0.5f); // 0.5秒后销毁游戏对象 } }代码解析与设计思路抽象类abstract classEnemy被声明为抽象类因为它代表一个不完整的“敌人”概念。你永远不会创建一个纯粹的“敌人”你创建的是哥布林、龙等具体敌人。这从设计上防止了误用。抽象方法abstract void Attack()这是本设计的核心“契约”。它强制所有子类必须定义“攻击”这个行为但具体怎么攻击由子类决定。虚方法virtual void TakeDamage()和virtual void Die()它们提供了默认实现但子类可以根据需要重写。例如一个Boss敌人可能在死亡时触发关卡胜利事件。属性CurrentHealth使用属性而非公共字段来封装currentHealth。这允许我们在设置生命值时加入逻辑如数值钳制、触发死亡这是面向对象中“封装”思想的体现。[SerializeField]让私有字段在Unity编辑器中可见便于调试。protected virtual void Start()标记为protected和virtual允许子类在需要时扩展初始化逻辑通过override同时也能调用基类的Start通过base.Start()。3.3 实现具体子类哥布林Goblin与龙Dragon现在我们来创建两种具体的敌人它们将继承Enemy类并实现其抽象方法。创建Goblin脚本using UnityEngine; public class Goblin : Enemy { // 哥布林特有的属性偷窃成功率 [Range(0f, 1f)] public float stealChance 0.3f; public int stealAmount 5; // 重写实现抽象攻击方法 public override void Attack() { // 哥布林的攻击普通攻击并概率偷窃 Debug.Log(${enemyName} 哥布林挥舞生锈的短刀造成 {attackPower} 点伤害); // 这里可以调用一个假设的玩家受伤方法Player.Instance.TakeDamage(attackPower); // 尝试偷窃 TrySteal(); } // 哥布林特有的方法 private void TrySteal() { if (Random.value stealChance) // Random.value 返回0-1之间的随机数 { Debug.Log($coloryellow{enemyName} 在攻击中顺手牵羊偷走了 {stealAmount} 枚金币/color); // 这里可以修改游戏的金币数值GameManager.Instance.Gold - stealAmount; } } // 可选重写受伤方法增加哥布林敏捷闪避的设定 public override void TakeDamage(int damage) { float dodgeChance 0.1f; // 10%闪避几率 if (Random.value dodgeChance) { Debug.Log($colorcyan{enemyName} 敏捷地躲开了攻击/color); return; // 闪避成功不受伤害 } base.TakeDamage(damage); // 闪避失败调用基类的受伤逻辑 } }创建Dragon脚本using UnityEngine; public class Dragon : Enemy { // 龙特有的属性火焰伤害和范围 public int flameDamage 25; public float flameRange 5f; private bool isBreathingFire false; // 重写实现抽象攻击方法 public override void Attack() { // 龙有两种攻击模式普通爪击或火焰喷射 if (CurrentHealth maxHealth * 0.5f !isBreathingFire) // 生命低于50%且未在喷火 { BreatheFire(); } else { ClawAttack(); } } private void ClawAttack() { Debug.Log($colorred{enemyName} 巨龙用利爪猛击造成 {attackPower} 点伤害/color); } private void BreatheFire() { isBreathingFire true; Debug.Log($colororange{enemyName} 愤怒地喷出火焰对范围内所有目标造成 {flameDamage} 点伤害/color); // 这里可以实现范围伤害逻辑例如通过Physics.OverlapSphere查找范围内的玩家或物体 Invoke(nameof(ResetBreath), 3f); // 3秒后重置喷火状态 } private void ResetBreath() { isBreathingFire false; Debug.Log(${enemyName} 的火焰吐息冷却完毕。); } // 重写死亡方法增加巨龙死亡的震撼效果 protected override void Die() { Debug.Log($colorred轰隆{enemyName} 庞大的身躯轰然倒地大地为之震颤/color); // 可以在这里触发屏幕震动、播放爆炸特效等 base.Die(); // 调用基类的销毁逻辑 } }实操要点override关键字在子类中重写基类的抽象或虚方法时必须使用override。这是编译器检查多态正确性的关键。base关键字的使用在Goblin.TakeDamage中我们首先尝试闪避如果失败则通过base.TakeDamage(damage)调用基类Enemy的受伤逻辑。这避免了重复编写扣血和死亡检查的代码。子类特有逻辑Goblin的TrySteal和Dragon的BreatheFire、ClawAttack都是子类独有的行为它们丰富了敌人的差异性。状态管理Dragon中使用isBreathingFire布尔值来管理喷火状态这是一个简单的状态标志防止攻击逻辑混乱。3.4 创建测试控制器与场景组装为了在游戏中看到效果我们需要一个简单的脚本来生成敌人并模拟战斗。创建GameManager脚本using UnityEngine; using System.Collections.Generic; public class GameManager : MonoBehaviour { // 单例模式便于其他脚本访问 public static GameManager Instance; // 敌人生成点 public Transform[] spawnPoints; // 敌人预制体列表在Inspector中拖拽赋值 public GameObject[] enemyPrefabs; // 生成的敌人列表 private ListEnemy activeEnemies new ListEnemy(); void Awake() { if (Instance null) Instance this; else Destroy(gameObject); } void Start() { SpawnEnemies(); StartCoroutine(SimulateBattle()); // 开始模拟战斗 } void SpawnEnemies() { if (spawnPoints.Length 0 || enemyPrefabs.Length 0) { Debug.LogError(请设置生成点或敌人预制体); return; } for (int i 0; i spawnPoints.Length; i) { // 随机选择一个敌人预制体 GameObject prefabToSpawn enemyPrefabs[Random.Range(0, enemyPrefabs.Length)]; GameObject enemyObj Instantiate(prefabToSpawn, spawnPoints[i].position, Quaternion.identity); // 获取Enemy组件并添加到列表 Enemy enemy enemyObj.GetComponentEnemy(); if (enemy ! null) { activeEnemies.Add(enemy); // 可以在这里设置敌人独特名字 enemy.enemyName ${enemy.GetType().Name}_{i}; } } Debug.Log($已生成 {activeEnemies.Count} 个敌人。); } System.Collections.IEnumerator SimulateBattle() { Debug.Log( 战斗模拟开始 ); yield return new WaitForSeconds(1f); // 等待1秒 while (activeEnemies.Count 0) { // 1. 所有敌人行动攻击 foreach (Enemy enemy in activeEnemies.ToArray()) // 使用.ToArray()避免在遍历时修改集合 { if (enemy ! null) { enemy.Attack(); // 多态的核心体现这里调用的是具体子类Goblin或Dragon的Attack方法。 yield return new WaitForSeconds(0.5f); // 每个攻击间隔0.5秒 } } // 2. 模拟玩家反击对每个敌人造成随机伤害 Debug.Log(--- 玩家回合 ---); foreach (Enemy enemy in activeEnemies.ToArray()) { if (enemy ! null) { int playerDamage Random.Range(15, 35); enemy.TakeDamage(playerDamage); // 多态调用的是可能被重写的TakeDamage方法如Goblin的闪避。 yield return new WaitForSeconds(0.3f); } } // 3. 清理已死亡的敌人 activeEnemies.RemoveAll(enemy enemy null || enemy.CurrentHealth 0); yield return new WaitForSeconds(1f); // 回合间隔 Debug.Log($剩余敌人{activeEnemies.Count}); } Debug.Log( 战斗结束所有敌人被击败 ); } }场景组装步骤在场景中创建一个空物体命名为GameManager将GameManager脚本挂载上去。创建几个空物体作为SpawnPoints摆放在场景中不同位置拖拽给GameManager脚本的Spawn Points数组。在Project窗口中分别用Goblin和Dragon脚本创建两个预制体Prefab。可以给它们挂上不同的3D模型或颜色材质以便区分。将这两个预制体拖拽到GameManager脚本的Enemy Prefabs数组中。运行游戏。你将在Console控制台中看到完整的战斗日志清晰地展示出不同敌人执行其独特的攻击和行为哥布林偷窃、龙喷火并且它们对受伤的反应也不同哥布林可能闪避。4. 深入解析多态在Unity游戏架构中的应用通过上面的小项目你已经看到了多态在具体类型行为差异上的威力。但在实际的大型Unity项目中继承与多态的应用更加深入和广泛它们构成了许多核心设计模式的基础。4.1 状态模式用多态管理复杂状态机游戏中的角色玩家或AI经常有多个状态如闲置、移动、攻击、受伤、死亡等。使用一堆bool标志和冗长的if-else或switch语句来管理会非常混乱。// 不使用多态的混乱写法伪代码 void Update() { if (isDead) { /* 死亡处理 */ return; } if (isAttacking) { /* 攻击处理 */ return; } if (isTakingDamage) { /* 受伤处理 */ return; } // ... 更多的if }使用多态的状态模式则清晰得多public abstract class PlayerState { protected PlayerController player; public PlayerState(PlayerController player) { this.player player; } public abstract void EnterState(); public abstract void UpdateState(); public abstract void ExitState(); } public class IdleState : PlayerState { public override void EnterState() { player.animator.Play(Idle); } public override void UpdateState() { if (Input.GetKeyDown(KeyCode.Space)) player.ChangeState(new JumpState(player)); if (Input.GetMouseButtonDown(0)) player.ChangeState(new AttackState(player)); } public override void ExitState() { } } public class AttackState : PlayerState { /* ... */ } public class JumpState : PlayerState { /* ... */ } // 在PlayerController中 public class PlayerController : MonoBehaviour { private PlayerState currentState; public void ChangeState(PlayerState newState) { currentState?.ExitState(); currentState newState; currentState.EnterState(); } void Update() { currentState?.UpdateState(); } }这样每个状态都是一个独立的类通过多态PlayerController只需要调用currentState.UpdateState()具体执行哪个状态的逻辑由运行时对象决定。增加新状态只需新建一个类符合开闭原则。4.2 策略模式灵活配置AI或技能行为假设你的游戏有不同类型的敌人AI追击型、巡逻型、远程狙击型。你可以定义一个IAIStrategy接口或AIStrategy基类。public abstract class AIStrategy { public abstract void Execute(Enemy enemy); } public class ChaseStrategy : AIStrategy { public override void Execute(Enemy enemy) { // 寻找玩家并移动向玩家 Vector3 direction (Player.Instance.transform.position - enemy.transform.position).normalized; enemy.transform.Translate(direction * enemy.moveSpeed * Time.deltaTime); } } public class PatrolStrategy : AIStrategy { /* 沿路径点巡逻 */ } public class SniperStrategy : AIStrategy { /* 保持距离远程攻击 */ } // 在Enemy类中 public class Enemy : MonoBehaviour { public AIStrategy currentStrategy; void Update() { currentStrategy?.Execute(this); // 多态调用 } public void SetStrategy(AIStrategy newStrategy) { currentStrategy newStrategy; } }你可以在运行时动态改变敌人的AI策略例如受伤后从“巡逻”切换到“追击”而Enemy类的Update代码无需任何修改。这极大地提升了AI行为的可配置性和可扩展性。4.3 工厂模式用多态创建对象当你需要根据条件创建不同类型的对象时工厂模式配合多态可以消除复杂的创建逻辑。public abstract class EnemyFactory { public abstract Enemy CreateEnemy(); } public class GoblinFactory : EnemyFactory { public override Enemy CreateEnemy() { GameObject go new GameObject(Goblin); var goblin go.AddComponentGoblin(); goblin.enemyName 工厂生产的哥布林; // ... 其他初始化 return goblin; } } public class DragonFactory : EnemyFactory { /* 类似 */ } // 使用工厂 public class Spawner : MonoBehaviour { public EnemyFactory enemyFactory; // 在Inspector中分配具体的工厂如GoblinFactory void Spawn() { Enemy enemy enemyFactory.CreateEnemy(); // 多态不知道具体创建哪种但知道创建的是Enemy // 使用enemy... } }如果要增加新的敌人类型你只需要新建一个对应的工厂类而负责生成敌人的Spawner代码完全不用动。5. 常见陷阱、性能考量与最佳实践即使理解了概念在实际编码中也会踩坑。下面是一些我总结的常见问题和经验。5.1 继承的误用与“组合优于继承”原则问题场景你想让FlyingEnemy飞行敌人和GroundEnemy地面敌人都具备FireAttack火焰攻击的能力。新手可能会让FlyingEnemy和GroundEnemy都继承自一个FireEnemy类。// 不推荐的设计使用继承来共享“能力” public class FireEnemy : Enemy { public void FireAttack() { /* 火焰攻击逻辑 */ } } public class FlyingEnemy : FireEnemy { /* 现在FlyingEnemy有了FireAttack但它可能根本不需要 */ } public class GroundEnemy : FireEnemy { /* 同上 */ }这导致了僵化的类层次。如果后来需要一种会IceAttack冰霜攻击的FlyingEnemy怎么办难道要再创建一个IceFlyingEnemy类这会引发“类爆炸”。解决方案使用组合将“攻击能力”抽象为组件。public interface IAttackBehavior { void Attack(); } public class FireAttack : IAttackBehavior { public void Attack() { Debug.Log(火焰攻击); } } public class IceAttack : IAttackBehavior { public void Attack() { Debug.Log(冰霜攻击); } } public class Enemy : MonoBehaviour { public IAttackBehavior attackBehavior; // 持有攻击行为组件 void Update() { if (Input.GetKeyDown(KeyCode.Space)) { attackBehavior?.Attack(); // 委托给具体的行为组件 } } } // 配置敌人时可以动态组合 Enemy dragon new Dragon(); dragon.attackBehavior new FireAttack(); // 龙使用火焰攻击 Enemy yeti new Yeti(); yeti.attackBehavior new IceAttack(); // 雪人使用冰霜攻击这就是著名的“组合优于继承”原则。它通过将变化的部分攻击方式封装成独立的、可互换的组件使得类更加灵活更容易应对需求变化。Unity自身的GetComponent系统就是组合模式的典范。5.2 多态与性能虚方法调用的开销虚方法virtual和接口方法调用由于需要在运行时查找方法表vtable其性能开销略高于非虚方法普通方法的直接调用。对于在Update等每帧调用的高频函数这个开销需要关注。优化建议不要过度使用虚方法只在确实需要子类改变行为的地方使用virtual。对于内部辅助方法尽量使用非虚方法或private方法。考虑使用策略模式组合如上所述将行为委托给组件。组件的方法可以是非虚的通过持有不同的组件引用来改变行为有时比深层次的继承树更高效、更清晰。对于性能极度敏感的代码如渲染循环、物理计算可以考虑使用其他模式如数据导向设计DOD但这属于高级优化范畴在大部分游戏逻辑中多态的开销是可接受的。5.3 设计陷阱脆弱的基类问题问题当你修改一个被广泛继承的基类如Enemy时可能会无意中破坏所有子类的功能。例如你在Enemy.TakeDamage方法中增加了一个参数那么所有重写了该方法的子类如Goblin.TakeDamage都必须修改签名否则会导致编译错误或行为异常。缓解策略对基类的修改保持谨慎特别是对公共和受保护成员的修改。尽量通过添加新方法而非修改现有方法来扩展功能。使用密封类sealed如果你确定某个类不会被继承或者其继承会带来问题可以将其标记为sealed。这也能带来微小的性能提升因为运行时知道它没有子类可以进行某些优化。优先使用组合这能减少类之间的深度耦合基类的变化影响范围会更小。5.4 Unity特有的注意事项MonoBehaviour生命周期方法的重写Start(),Update(),Awake()等是Unity引擎调用的特殊方法。在子类中重写它们时必须调用基类方法base.Start()除非你明确知道不需要基类的初始化逻辑。忘记调用base.Awake()是导致组件初始化失败的常见原因。public class SpecialEnemy : Enemy { protected override void Start() { base.Start(); // 务必先调用基类Start确保基类初始化完成 // ... 子类特有的初始化 } }序列化与Inspector公共字段和标记了[SerializeField]的私有字段会显示在Unity Inspector中。当使用继承时子类的Inspector会同时显示自身和父类的可序列化字段。注意如果修改了基类中某个序列化字段的名称可能会破坏已有预制体或场景中对该字段的序列化引用。GetComponent与多态GetComponentEnemy()会返回挂载在GameObject上的Enemy组件或其任何派生类Goblin,Dragon的引用。这在需要统一处理所有敌人类型时非常方便。掌握继承与多态就像是拿到了编写可维护、可扩展游戏代码的钥匙。从今天这个简单的战斗模拟开始尝试在你自己的项目里应用这些思想。下次当你发现自己在复制粘贴大段代码或者写满一屏幕的if-else判断类型时停下来想一想“这里是不是该用多态了” 思考和实践得越多你的代码设计能力就会越强。