Unity中使用委托 代理 实现敌人自动检测目标并攻击
2016-12-15 17:53
786 查看
假如有一个类控制着游戏中某个关卡的敌人。所有敌人都有一个特点:只要敌人发现玩家了,它就会追赶玩家。最重要的是其他敌人会被通知到玩家的位置,并且也开始追赶玩家。
所以实现这个类应该向下面这样写。
using UnityEngine;
using System.Collections;
public class ReactiveEnemy : MonoBehaviour
{
//a variable to store this game object's Transform
private Transform myTransform;
//a variable to store the player's character Transform
private Transform playerTransform;
/*a static boolean variable to tell if the player has collided with
any enemy trigger*/
public static bool hasCollided = false;
//Initialization code
void Awake()
{
//get the game object the script is attached to
myTransform = this.GetComponent<Transform>();
//get the player's Transform
playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>();
}
//runs every game cycle
void Update()
{
//checks if the player has collided with any trigger
if(hasCollided)
{
//follow the player
Follow();
}
}
//this method makes the enemy follow the player
private void Follow()
{
//look at the player
myTransform.LookAt(playerTransform);
/*only follow the player if this enemy is
4.5 units away from the player*/
if(Vector3.Distance(myTransform.position,playerTransform.position)>=4.5f)
{
//move the enemy
myTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f);
}
}
}
下面解释下这段代码是如何工作的。在Awake()函数中找到player并且获得transform。在Update函数中监测是否看到玩家。如果看到玩家,就执行Follow函数。
需要在每个敌人物体上添加一个触发器。触发器可以让敌人在某范围内检测到玩家。下面的触发脚本只是在碰到玩家后将上面ReactiveEnemy 脚本中的静态变量 hasColliderd设为真。
脚本上的注释已经很清楚了,不做过多解释。这个脚本能正常运行但对于每个挂着这个脚本的敌人来说,他们需要在每次更新(Update)中监测是否有敌人看到了玩家(上面脚本中的第十行)。更多的敌人意味着更多的相同的情况,再次检查只是重复之前已经完成的操作。
还有一个问题:假如我们想为敌人增加另外一个动作,例如这次不是追赶了,要飞怎么办?当然,我们可以创建另一个类甚至可以还用之前的那个类传递一个布尔值来确定敌人是一个追赶者还是一个飞翔者,但这样又需要在每个游戏循环中添加一个if语句来判断。
在这种类似的情况下,我们可以使用一个委托来封装这些有相同特征的方法,并且只需判断一次玩家是否被敌人监测到,接着去执行根据引用而来的各种预期的动作。下面是用委托来编码同一个ReactiveEnemy类。这个类分为两部分:一部分设置委托监测玩家是否被发现(AllEnemiesClass),另一部分根据玩家做出实际处理动作(EnemyActions)。
下面是AllEnemies 类。
开始,我们定义委托有哪些特性(第十七行)。接着,当委托被定义后,我们在Awake()中第40行对它初始化。初始化需要添加一个函数引用,所以我们从allEnemies 数组中选一个加上。然后我们将其他的方法添加上(设置委托,43-47行)。
Update函数中,我们要做的只是调用aaaDelegate的委托,并将playerTransform作为参数传递进去。完成了!所有被添加进委托的函数都会被调用。变量hasCollided 只会被判断一次,玩家的transform 也只需获取一次,不再是场景中的每个敌人都在Awake()函数中获取一次。
那FollowPlayer()方法来自哪里呢?还记着我们将ReactiveEnemy 分成两部分吗?敌人FollowPlayer方法在另一个叫做EnemyActions脚本中。
如果需要添加敌人的另外一种动作,只需做一件事就是添加一个新的方法即可,像这样:
//...
//this method makes the enemy fly
public void Fly(Transform pTransform)
{
//if the player is at 4.5f units away
if(Vector3.Distance(myTransform.position,pTransform.position)<=4.5f)
{
//make it fly
myTransform.Translate(Vector3.up * Time.deltaTime);
}
else
{
//make it land
if(myTransform.position.y >= 0.525528f)
{
myTransform.Translate(-Vector3.up * Time.deltaTime);
}
}
}
//...
之后,我们只需将这个方法添加到AllEnemies脚本的委托中
而且我们可以更改任何事情,因为委托只是对一个或多个方法的引用-这跟它被称为委托是一样的。
对于触发器的代码和上面展示的是相同的。委托为代码带来了极大的灵活性,尤其是需要重构时。它允许在游戏规则上多做思考,并且在不需要了解具体是如何实施的情况下执行。考虑将委托作为运行时可执行和不可执行的接口。可以通过-= 运算符移除这些方法,也可以通过添加方法合并委托。
所以实现这个类应该向下面这样写。
using UnityEngine;
using System.Collections;
public class ReactiveEnemy : MonoBehaviour
{
//a variable to store this game object's Transform
private Transform myTransform;
//a variable to store the player's character Transform
private Transform playerTransform;
/*a static boolean variable to tell if the player has collided with
any enemy trigger*/
public static bool hasCollided = false;
//Initialization code
void Awake()
{
//get the game object the script is attached to
myTransform = this.GetComponent<Transform>();
//get the player's Transform
playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>();
}
//runs every game cycle
void Update()
{
//checks if the player has collided with any trigger
if(hasCollided)
{
//follow the player
Follow();
}
}
//this method makes the enemy follow the player
private void Follow()
{
//look at the player
myTransform.LookAt(playerTransform);
/*only follow the player if this enemy is
4.5 units away from the player*/
if(Vector3.Distance(myTransform.position,playerTransform.position)>=4.5f)
{
//move the enemy
myTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f);
}
}
}
下面解释下这段代码是如何工作的。在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 类。
using UnityEngine; using System.Collections; public class AllEnemies : MonoBehaviour { //a variable to store the player's character Transform private Transform playerTransform; /* a static boolean variable to tell if the player has collided with any enemy trigger*/ public static bool hasCollided = false; //an array of game objects, to store every single enemy in the scene private GameObject[] allEnemies; /*sets a delegate called 'AllEnemyActions', that returns void and takes a Transform as a parameter*/ private delegate void AllEnemyActions(Transform pTransform); /*now that the 'AllEnemyActions' delegate signature is set, we instantiate a delegate, naming it as 'aaaDelegate'*/ private AllEnemyActions aaaDelegate; void Awake() { //get the player's character Transform playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>(); //get all enemies that are in this scene, and have the 'Enemy' tag allEnemies = GameObject.FindGameObjectsWithTag("Enemy"); /* here, the delegate is instantiated. It takes a method as * a parameter, meaning that the FollowPlayer method from * the first enemy of the array 'allEnemies' is now being wrapped * by the aaaDelegate. So if we call the delegate right now, * by writing: * aaaDelegate(playerTransform); * it would be the same as writing: * allEnemies[0].GetComponent<EnemyActions>().FollowPlayer(); */ aaaDelegate = new AllEnemyActions(allEnemies[0].GetComponent<EnemyActions>().FollowPlayer); /* now, we add other methods that have the same signature, so * the delegate can reference them, when it's called.*/ aaaDelegate += allEnemies[1].GetComponent<EnemyActions>().FollowPlayer; aaaDelegate += allEnemies[2].GetComponent<EnemyActions>().FollowPlayer; aaaDelegate += allEnemies[3].GetComponent<EnemyActions>().FollowPlayer; aaaDelegate += allEnemies[4].GetComponent<EnemyActions>().FollowPlayer; aaaDelegate += allEnemies[5].GetComponent<EnemyActions>().FollowPlayer; /*NOTE: It is possible to use a loop to add these methods as * references to the delegate. To remove a reference to a method, you will need to use the '-=' operator. */ } void Update() { //checks if the player has collided with any trigger if(hasCollided) { //call the delegate aaaDelegate(playerTransform); /*The above line is the same as calling every FollowPlayer() * method from every GameObject has the 'Enemy' tag. */ } } }
开始,我们定义委托有哪些特性(第十七行)。接着,当委托被定义后,我们在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 Transform private Transform myTransform; void Awake() { //get the game object the script is attached to myTransform = this.GetComponent<Transform>(); } //this method makes the enemy follow the player public void FollowPlayer(Transform playerTransform) { //look at the player myTransform.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 enemy myTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f); } } }
如果需要添加敌人的另外一种动作,只需做一件事就是添加一个新的方法即可,像这样:
//...
//this method makes the enemy fly
public void Fly(Transform pTransform)
{
//if the player is at 4.5f units away
if(Vector3.Distance(myTransform.position,pTransform.position)<=4.5f)
{
//make it fly
myTransform.Translate(Vector3.up * Time.deltaTime);
}
else
{
//make it land
if(myTransform.position.y >= 0.525528f)
{
myTransform.Translate(-Vector3.up * Time.deltaTime);
}
}
}
//...
之后,我们只需将这个方法添加到AllEnemies脚本的委托中
/*Instead of (line 47): aaaDelegate += allEnemies[5].GetComponent<enemyactions>().Follow;*/ //Write: aaaDelegate += allEnemies[5].GetComponent<enemyactions>().Fly;
而且我们可以更改任何事情,因为委托只是对一个或多个方法的引用-这跟它被称为委托是一样的。
对于触发器的代码和上面展示的是相同的。委托为代码带来了极大的灵活性,尤其是需要重构时。它允许在游戏规则上多做思考,并且在不需要了解具体是如何实施的情况下执行。考虑将委托作为运行时可执行和不可执行的接口。可以通过-= 运算符移除这些方法,也可以通过添加方法合并委托。
相关文章推荐
- 【Unity】使用C#中的委托类型实现代理设计模式
- 使用DefaultAdvisorAutoProxyCreator实现spring的自动代理
- MVC3使用Unity实现依赖注入接口与于实现类自动注册
- 使用BeanNameAutoProxyCreator实现spring的自动代理
- 使用opencv3+python实现视频运动目标检测
- JAVA动态代理和方法拦截(使用CGLib实现AOP、方法拦截、委托)
- 使用BeanNameAutoProxyCreator实现spring的自动代理
- 使用shell脚本检测lvs中realserver状态,并实现自动删除已挂机主机
- 使用unity + Vuforia实现脱卡识别,多目标情况
- 使用TaskManager爬取2万条代理IP实现自动投票功能
- Unity中使用委托/事件实现GameObject之间的通信
- 使用自动配置、自动代理和自动检测
- Unity检测目标点是否在扇形攻击范围内
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架
- 使用DefaultAdvisorAutoProxyCreator实现spring的自动代理
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架
- Internet Explorer® 8 之后,您可使用自动代理和自动检测功能,全局更改一些浏览器设置,而无需更改每个用户的计算机
- 【设计模式】使用unity实现代理模式(Proxy mode)
- JAVA动态代理和方法拦截(使用CGLib实现AOP、方法拦截、委托)