Code 【Unity3D】C#事件广播的实例 编程 13次访问 01-21 17:30 这几天初识了一下C#的事件 先复习一下事件的特性: 首先**事件**(Event)是基于**委托**(Delegate)的,是委托的包装器,类似是受到了限制的委托。但它并不是一种特殊的委托。 事件又五个部分组成 - 事件源 - 事件 - 响应对象 - 响应方法 - 订阅 事件可以通知订阅的对象执行对应方法,我们可以通过这个例子了解事件的特点和用处。 以一个小游戏作为例子。  Player:操作对象,我们已经写好了该对象的移动转向; 子弹:可以根据点击,向前方发射,一定时间或碰撞到敌人销毁; Enemy:一个有自动追击能力的AI; 我们现在需要程序化生成Enemy,类似塔防游戏一样,根据波数自动生成敌人Enemy。 [========] ##### 首先有一个波类型Wave ```csharp [System.Serializable] public class Wave { public int enmeyNum;//该波敌人数量 public float timeBtwSpawn;//生成时间间隔 } ``` ##### 再有一个Spawner类,以波(Wave)为单位程序化生成Enemy ```csharp using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Spawner : MonoBehaviour { public GameObject enemyPrefab;//敌人预制体 public List waves; private Wave currentWave;//当前波对象 private int currentIndex;//当前波数(1 —— +∞) public int waitSpawnNum;//还未生成的敌人数,决定生成 public int spanAliveNum;//当前存活的敌人数,决定下一波 public float nextSpawnTime;//生成的间隔时间 private void Start() { NextWave(); } //间隔生成一波的敌人 private void Update() { if (waitSpawnNum>0&&Time.time>nextSpawnTime)//生成时机是还有未生成的对象且超过规定间隔 { waitSpawnNum--; GameObject spawnEnemy = Instantiate(enemyPrefab, transform.position, Quaternion.identity); nextSpawnTime = Time.time + currentWave.timeBtwSpawn; } } //下一波 private void NextWave() { currentIndex++; Debug.Log(string.Format("Current Wave : {0}", currentIndex)); if (currentIndex - 1 < waves.Count) { currentWave = waves[currentIndex - 1]; } } } ``` ##### 这是子弹得到碰撞方法,在子弹接触Enemy后触发TakeDamage减少Enemy血量。 ```csharp private void HitEnemy(RaycastHit hitInfo) { IDamageable damageable = hitInfo.collider.GetComponent();//获取Enemy对象接口 if (damageable!=null) { damageable.TakeDamage(damage);//调用接口的扣血方法,减少Enemy的血量。 Destroy(gameObject); } } ``` #####对脚本进行赋值  现在,我们可以自动生成第一波敌人了,但是问题来了,如何确定下一波生成的时机呢? 当然,我们可以根据所剩敌人的数量([spanAliveNum](#Spawner_Update)),当敌人的数量为0时,我们就可以进行下一波。触发NextWave()方法; spanAliveNum的减少可以通过[HitEnemy()](#hit1)中的TakeDamage()方法实现; 即:当游戏中的子弹击中Enemy时,会触发判断,当其血量低于0时,触发死亡。spanAliveNum自减。 按照以上逻辑来,我们有一个思路: 我们在[HitEnemy()](#hit1)方法中加入一个单例模式中的变量,记录已经死亡的Enemy数量。就可以在Spawner中的[Update()](#Spawner_Update)方法通过判断单例模式方法中的该变量,决定是否可以开启下一波。 单例方法↓ ```csharp using UnityEngine; public class EnemyManage : MonoBehaviour { public static EnemyManage instance; public int enemyNum; //我们所需要的变量 private void Awake() { if (instance==null) { instance = this; } else { if (instance!=this) { Destroy(gameObject); } } DontDestroyOnLoad(gameObject); } } ``` 但是缺点也很明显,我们需要在Spawner类中的[Update()](#Spawner2)方法中不断的去判断[单例模式](#单例模式)中的变量"enemyNum"是否小于0; ```csharp public class Spawner : MonoBehaviour { ...... private void Update() { ...... spanAliveNum = EnemyManage.instance.enemyNum; if (spanAliveNum < 0) { NextWave(); } } ...... } ``` 但如果有不同的敌人类型呢?毫无疑问,这样是低效的。 面对成倍增长的需求,我们需要一个响应式的方法,当[Enemy触发死亡](#hit1)后,可以通知到[Spawner类](#Spawner_Update),并执行Spawner的某一方法,该方法可以减少Spawner类实现定义好的spanAliveNum变量,根据变量决定是否开启下一波。 那么**事件**的好处就体现出来了,我们可以在Enemy(**拥有者/事件源**)中添加一个**事件**OnDeath,并且Spawner作为**响应者**,EnemyDeath()作为**响应方法**。 逻辑如下↓ 当Enemy死亡时触发Enemy的事件OnDeath(),同时会通知到Spawner对象,并执行Spawner对象**订阅**的方法[EnemyDeath()](#Spawner_EnemyDeath); 1. 首先在Enemy的父类LivingEntity中加入事件,以及调用该事件的内部方法TakeDamage ```csharp using UnityEngine; using System; public class LivingEntity : MonoBehaviour,IDamageable { public float maxHealth;//其他成员变量这里省略 //事件的定义 public event Action OnDeath; //内部逻辑,事件不能直接调用,需要内部逻辑才能调用 public void Die() { isDead = true; Destroy(gameObject); if (OnDeath!=null) { OnDeath(); } } //当Enemy被子弹触碰时会触发该方法 public void TakeDamage(float _damageAmout) { health -= _damageAmout; if (health<=0&&isDead==false) { Die(); } } } ``` 1. 我们将Spawner的[Update()](#Spawner_Update)方法添加一条注册的语句; ```csharp private void Update() { if (waitSpawnNum>0&&Time.time>nextSpawnTime) { waitSpawnNum--; GameObject spawnEnemy = Instantiate(enemyPrefab, transform.position, Quaternion.identity); //添加新的一条注册语句,即是吧EnemyDeath()方法注册到Enemy的OnDeath方法中。 spawnEnemy.GetComponent().OnDeath += EnemyDeath; nextSpawnTime = Time.time + currentWave.timeBtwSpawn; } } ``` 1.实现注册的方法 ```csharp private void EnemyDeath() { spanAliveNum--; if (spanAliveNum<=0) { NextWave(); } } ``` 于是:Enemy受击 ——> [HitEnemy()](#hit2) ——> [TakeDamage()](#TakeDamage) ——> [Die()](#TakeDamage) ——>通知Spawner类 ——> [EnemyDeath()](#Spawner_EnemyDeath) ——> [NextWave()](#Spawner_Update); 通过使用事件,我们可以响应式的触发逻辑了。触发事件将会广播到Spawner类,并将其时间响应方法触发。 < 「焦虑将我绑架」 博客优化方案 > 让浏览器记住我!