我这次制作的是狼这个敌人:unity中有免费的资源,我选择的资源如下图所示,望读者下载方便动手以便更好地学习和理解:
Wolf Animated | 3D Animals | Unity Asset Store
将狼引入资源包后,首先把狼的一个实例拖入场景,
在检测器中新增两个组件:分别为Rigidbody和Boxcollider:
注意细节!记得冻结y轴旋转。
接下来拖入animator的控制器,加入nav mesh agent:
狼有很多动画之间的转换,我设置了两个Layer,分别为:BaseLayer和AttackLayer
BaseLayer中狼执行正常的移动巡逻动画:
AttackLayer中狼执行发现攻击目标(玩家)后的追击功能;
接下来设置四个bool值,分别为:walk chase follow hit
接下来为动作切换时bool值得转换,鼠标单击动画之间的箭头:
AttakLayer中:
Base State到breathes之间的切换为:bool Chase ,Chase为1切换到breathes。,Chase为0代表breathes返回Base State
以此类推:
breathes与run之间为:follow;attack2和run之间为:hit;
BaseLayer中:
breathes和walk之间的bool为:walk。
完成好animator的设置之后我们便来到最后一步:代码:
首先放上完整代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum EnemyStates { GUARD,PATROL,CHASE,DEAD } //守卫、巡逻、追击、死亡状态
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
{
private EnemyStates enemyStates;
private NavMeshAgent agent; //确保变量组件一定存在
private Animator anim;
[Header("Basic Setting")]
public float sightRadius;
public GameObject attackTarget;
public bool isGuard;
private float speed; //追玩家的时候是一个速度,自己回去的时候又是一个速度
public float lookAtTime;
private float AttackWaitTime;
private float remainLookAtTime;
[Header("Patrol State")]
public float patrolRange;
private Vector3 waypoint;
private Vector3 guardPos; //获取最开始怪物的坐标
//怪物受伤
private float attackTimer;
public float attackTime;
public float HP = 15;
//bool值配合动画
bool isWalk;
bool isChase;
bool isFollow;
bool isHit;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
anim = GetComponent<Animator>();
speed = agent.speed;
guardPos = transform.position;
remainLookAtTime = lookAtTime;
}
private void Start()
{
attackTime = 2;
attackTimer = attackTime;
if (isGuard)
{
enemyStates = EnemyStates.GUARD;
}
else
{
enemyStates = EnemyStates.PATROL;
GetNewWayPoint();//最开始给一个点
}
}
private void Update()
{
Attacktimer();
SwitchStates();
SwitchAnimation();
}
void SwitchAnimation()
{
anim.SetBool("Walk", isWalk);
anim.SetBool("Chase", isChase);
anim.SetBool("Follow", isFollow);
anim.SetBool("Hit", isHit); //bool值关联到一起
}
void SwitchStates()
{
//如果发现敌人切换为chase
if (FoundPlayer())
{
enemyStates = EnemyStates.CHASE;
Debug.Log("找到players");
}
switch(enemyStates)
{
case EnemyStates.GUARD:
break;
case EnemyStates.PATROL:
isChase = false;
agent.speed = speed * 0.5f; //巡逻状态下移动速度更低
if(Vector3.Distance(waypoint,transform.position)<=agent.stoppingDistance)//nav mesh agent 中有效帮助我们控制怪物应该停下的距离 ,判断是否走到该点。
{
isWalk = false;
if(remainLookAtTime > 0)
{
remainLookAtTime -= Time.deltaTime;
}
else
{
GetNewWayPoint();
}
}
else
{
isWalk = true;
agent.destination = waypoint;
}
break;
case EnemyStates.CHASE:
//配合动画
isWalk = false;
isChase = true;
agent.speed = speed;
if(!FoundPlayer())
{
//回到上一个状态
isFollow = false;
if (remainLookAtTime > 0)
{
agent.destination = transform.position;//脱离目标后,追击动画失效然而动物仍然会以idle状态移动至脱离目标时角色位置;
remainLookAtTime -= Time.deltaTime;
}
else if(isGuard)
{
enemyStates = EnemyStates.GUARD;
}
else
{
enemyStates = EnemyStates.PATROL;
}
}
else
{
isFollow = true;
agent.SetDestination(attackTarget.transform.position);
if(Vector3.Distance(attackTarget.transform.position, transform.position) <= 3)
{
isHit = true;
}
else
{
isHit = false;
}
}
//追到玩家后攻击
break;
case EnemyStates.DEAD:
break;
}
}
bool FoundPlayer()
{
var colliders = Physics.OverlapSphere(transform.position, sightRadius);//以敌人为中心和当前半径查找周围所有的碰撞体;
foreach (var target in colliders)
{
if(target.CompareTag("Player"))
{
attackTarget = target.gameObject;
return true;
}
}
attackTarget = null;
return false;
}
void GetNewWayPoint()
{
remainLookAtTime = lookAtTime; //重新还原时间
float randomX = Random.Range(-patrolRange, patrolRange);
float randomZ = Random.Range(-patrolRange, patrolRange);
Vector3 randomPoint = new Vector3(guardPos.x + randomX, transform.position.y, guardPos.z + randomZ); //在初始位置的基础上获取新的坐标 ,地形有坑坑洼洼,y不变
//Vector3 randomPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ); //在当前位置的基础上获取新的坐标
// waypoint = randomPoint; //选取到不能行走的点是会卡bug
NavMeshHit hit;
waypoint = NavMesh.SamplePosition(randomPoint, out hit, patrolRange, 1) ? hit.position : transform.position; //获取一个点后先判断是否可移动,不可移动则保持当前坐标再次获取新的坐标点。
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, sightRadius); //方便调节敌人追击范围。。。。
}
private void OnTriggerStay(Collider other)
{
if (other.tag == "Player" && Input.GetKey(KeyCode.Q) && attackTimer<=0)
{
attackTimer = attackTime;
Debug.Log("kill");
HP -= 5;
if (HP == 0)
{
gameObject.SetActive(false);
}
}
}
void Attacktimer()
{
if (attackTimer > 0)
{
attackTimer -= Time.deltaTime * 2;
}
}
}
代码中有部分备注,值得注意的一个地方是:
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, sightRadius); //方便调节敌人追击范围。。。。
}
这个可以显示出敌人发现目标玩家的范围,方便我们调整合适的距离。寻找玩家是在一定范围内寻找other.tag == "Player"的物体,因此记得把玩家的tag更改为Player,把狼的tag更改为Enemy。
调整一下狼身上挂载的脚本相关信息:
最后还需要找到地形,手动烘培一下地形,让navmesh生成自动寻路的路径!!!否则会报错