您的位置:首页 > 移动开发 > Unity3D

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设为真。
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;


而且我们可以更改任何事情,因为委托只是对一个或多个方法的引用-这跟它被称为委托是一样的。

 

对于触发器的代码和上面展示的是相同的。委托为代码带来了极大的灵活性,尤其是需要重构时。它允许在游戏规则上多做思考,并且在不需要了解具体是如何实施的情况下执行。考虑将委托作为运行时可执行和不可执行的接口。可以通过-= 运算符移除这些方法,也可以通过添加方法合并委托。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐