本文为个人翻译的,感谢原作者!
原文链接:
当unity3d 3.0发布时,这个版本不仅修复了许多bug,添加了很多新特性,而且也提升了Mono这个开发工具的使用,包括支持了命名空间,LINQ,委托等。本文下面将讨论什么是委托以及在unity3d中使用委托进行开发的优势。
通常C#中的委托是指对某一方法或者某一些有相同特性方法(返回值类型相同并且有相同的参数)的引用。更好的解释见于;
A delegate is a type that safely encapsulates a method, similar to a function pointer in C and C++. Unlike C function pointers, delegates are object-oriented, type safe, and secure. The type of a delegate is defined by the name of the delegate.
如上所说,我们从一个例子中来看如何使用委托。假如有一个类控制着游戏中某个关卡的敌人。所有敌人都有一个特点:只要敌人发现玩家了,它就会追赶玩家。最重要的是其他敌人会被通知到玩家的位置,并且也开始追赶玩家。
所以实现这个类应该向下面这样写。
1 using UnityEngine; 2 3 using System.Collections; 4 5 6 7 public class ReactiveEnemy : MonoBehaviour 8 9 {10 11 //a variable to store this game object's Transform12 13 private Transform myTransform;14 15 //a variable to store the player's character Transform16 17 private Transform playerTransform;18 19 /*a static boolean variable to tell if the player has collided with20 21 any enemy trigger*/22 23 public static bool hasCollided = false;24 25 26 27 //Initialization code28 29 void Awake()30 31 {32 33 //get the game object the script is attached to34 35 myTransform = this.GetComponent();36 37 //get the player's Transform38 39 playerTransform = GameObject.FindWithTag("Player").GetComponent ();40 41 }42 43 44 45 //runs every game cycle46 47 void Update()48 49 {50 51 //checks if the player has collided with any trigger52 53 if(hasCollided)54 55 {56 57 //follow the player58 59 Follow();60 61 }62 63 }64 65 66 67 //this method makes the enemy follow the player68 69 private void Follow()70 71 {72 73 //look at the player74 75 myTransform.LookAt(playerTransform);76 77 78 79 /*only follow the player if this enemy is80 81 4.5 units away from the player*/82 83 if(Vector3.Distance(myTransform.position,playerTransform.position)>=4.5f)84 85 {86 87 //move the enemy88 89 myTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f);90 91 }92 93 }94 95 }
下面解释下这段代码是如何工作的。在Awake()函数中找到player并且获得transform。在Update函数中监测是否看到玩家。如果看到玩家,就执行Follow函数。
需要在每个敌人物体上添加一个触发器。触发器可以让敌人在某范围内检测到玩家。下面的触发脚本只是在碰到玩家后将上面ReactiveEnemy 脚本中的静态变量 hasColliderd设为真。
using UnityEngine; using System.Collections; public class EnemyTrigger : MonoBehaviour { //if something collided with the trigger void OnTriggerEnter(Collider col) { //if the player collided with the trigger if(col.gameObject.tag=="Player") { //set hasCollided static variable to true ReactiveEnemy.hasCollided = true; } } }
脚本上的注释已经很清楚了,不做过多解释。这个脚本能正常运行但对于每个挂着这个脚本的敌人来说,他们需要在每次更新(Update)中监测是否有敌人看到了玩家(上面脚本中的第十行)。更多的敌人意味着更多的相同的情况,再次检查只是重复之前已经完成的操作。
还有一个问题:假如我们想为敌人增加另外一个动作,例如这次不是追赶了,要飞怎么办?当然,我们可以创建另一个类甚至可以还用之前的那个类传递一个布尔值来确定敌人是一个追赶者还是一个飞翔者,但这样又需要在每个游戏循环中添加一个if语句来判断。
在这种类似的情况下,我们可以使用一个委托来封装这些有相同特征的方法,并且只需判断一次玩家是否被敌人监测到,接着去执行根据引用而来的各种预期的动作。下面是用委托来编码同一个ReactiveEnemy类。这个类分为两部分:一部分设置委托监测玩家是否被发现(AllEnemiesClass),另一部分根据玩家做出实际处理动作(EnemyActions)。
下面是AllEnemies 类。
1 using UnityEngine; 2 3 using System.Collections; 4 5 6 7 public class AllEnemies : MonoBehaviour 8 9 { 10 11 //a variable to store the player's character Transform 12 13 private Transform playerTransform; 14 15 /* a static boolean variable to tell if the player has collided with 16 17 any enemy trigger*/ 18 19 public static bool hasCollided = false; 20 21 22 23 //an array of game objects, to store every single enemy in the scene 24 25 private GameObject[] allEnemies; 26 27 28 29 /*sets a delegate called 'AllEnemyActions', that returns void and takes 30 31 a Transform as a parameter*/ 32 33 private delegate void AllEnemyActions(Transform pTransform); 34 35 36 37 /*now that the 'AllEnemyActions' delegate signature is set, we 38 39 instantiate a delegate, naming it as 'aaaDelegate'*/ 40 41 private AllEnemyActions aaaDelegate; 42 43 44 45 void Awake() 46 47 { 48 49 //get the player's character Transform 50 51 playerTransform = GameObject.FindWithTag("Player").GetComponent(); 52 53 54 55 //get all enemies that are in this scene, and have the 'Enemy' tag 56 57 allEnemies = GameObject.FindGameObjectsWithTag("Enemy"); 58 59 60 61 /* here, the delegate is instantiated. It takes a method as 62 63 * a parameter, meaning that the FollowPlayer method from 64 65 * the first enemy of the array 'allEnemies' is now being wrapped 66 67 * by the aaaDelegate. So if we call the delegate right now, 68 69 * by writing: 70 71 * aaaDelegate(playerTransform); 72 73 * it would be the same as writing: 74 75 * allEnemies[0].GetComponent ().FollowPlayer(); 76 77 */ 78 79 aaaDelegate = new AllEnemyActions(allEnemies[0].GetComponent ().FollowPlayer); 80 81 /* now, we add other methods that have the same signature, so 82 83 * the delegate can reference them, when it's called.*/ 84 85 aaaDelegate += allEnemies[1].GetComponent ().FollowPlayer; 86 87 aaaDelegate += allEnemies[2].GetComponent ().FollowPlayer; 88 89 aaaDelegate += allEnemies[3].GetComponent ().FollowPlayer; 90 91 aaaDelegate += allEnemies[4].GetComponent ().FollowPlayer; 92 93 aaaDelegate += allEnemies[5].GetComponent ().FollowPlayer; 94 95 96 97 /*NOTE: It is possible to use a loop to add these methods as 98 99 * references to the delegate. To remove a reference to a method,100 101 you will need to use the '-=' operator. */102 103 104 105 }106 107 108 109 void Update()110 111 {112 113 //checks if the player has collided with any trigger114 115 if(hasCollided)116 117 {118 119 //call the delegate120 121 aaaDelegate(playerTransform);122 123 124 125 /*The above line is the same as calling every FollowPlayer()126 127 * method from every GameObject has the 'Enemy' tag. */128 129 }130 131 132 133 }134 135 }
开始,我们定义委托有哪些特性(第十七行)。接着,当委托被定义后,我们在Awake()中第40行对它初始化。初始化需要添加一个函数引用,所以我们从allEnemies 数组中选一个加上。然后我们将其他的方法添加上(设置委托,43-47行)。
Update函数中,我们要做的只是调用aaaDelegate的委托,并将playerTransform作为参数传递进去。完成了!所有被添加进委托的函数都会被调用。变量hasCollided 只会被判断一次,玩家的transform 也只需获取一次,不再是场景中的每个敌人都在Awake()函数中获取一次。
那FollowPlayer()方法来自哪里呢?还记着我们将ReactiveEnemy 分成两部分吗?敌人FollowPlayer方法在另一个叫做EnemyActions脚本中。
using UnityEngine;using System.Collections; public class EnemyActions : MonoBehaviour{//a variable to store this game object's Transformprivate Transform myTransform; void Awake(){//get the game object the script is attached tomyTransform = this.GetComponent();} //this method makes the enemy follow the playerpublic void FollowPlayer(Transform playerTransform){//look at the playermyTransform.LookAt(playerTransform); /*only follow the player if this enemy is 4.5 units away from the * the player*/if(Vector3.Distance(myTransform.position,playerTransform.position)>=4.5f){//move the enemymyTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f);}}}
如果需要添加敌人的另外一种动作,只需做一件事就是添加一个新的方法即可,像这样:
1 //... 2 3 4 5 //this method makes the enemy fly 6 7 public void Fly(Transform pTransform) 8 9 {10 11 //if the player is at 4.5f units away12 13 if(Vector3.Distance(myTransform.position,pTransform.position)<=4.5f)14 15 {16 17 //make it fly18 19 myTransform.Translate(Vector3.up * Time.deltaTime);20 21 }22 23 else24 25 {26 27 //make it land28 29 if(myTransform.position.y >= 0.525528f)30 31 {32 33 myTransform.Translate(-Vector3.up * Time.deltaTime);34 35 }36 37 }38 39 }40 41 42 43 //...
之后,我们只需将这个方法添加到AllEnemies脚本的委托中
1 /*Instead of (line 47): 2 3 aaaDelegate += allEnemies[5].GetComponent().Follow;*/ 4 5 //Write: 6 7 aaaDelegate += allEnemies[5].GetComponent ().Fly;
而且我们可以更改任何事情,因为委托只是对一个或多个方法的引用-这跟它被称为委托是一样的。
对于触发器的代码和上面展示的是相同的。委托为代码带来了极大的灵活性,尤其是需要重构时。它允许在游戏规则上多做思考,并且在不需要了解具体是如何实施的情况下执行。考虑将委托作为运行时可执行和不可执行的接口。可以通过-= 运算符移除这些方法,也可以通过添加方法合并委托。