最终效果
文章目录
- 最终效果
- 烘培导航地图
- 配置敌人导航数据
- 简单配置敌人动画
- 敌人AI脚本
- 完结
想了解导航的其他内容可以看我这篇文章:【Unity游戏开发教程】零基础带你从小白到超神29——导航系统
烘培导航地图
选中地面,设置为静态导航
点击烘培,可行走区域
选中一些障碍物,也设置为静态导航
选择为不适合步行
重新点击烘培,一些障碍物区域就不可以行走了
配置敌人导航数据
给敌人加导航组件
注意:
增大角速度,是为了让敌人旋转更加快
配置停止距离和关闭自动刹车是为了防止敌人切换为待机动画,不会滑动
简单配置敌人动画
动画基础使用:新版角色动画的具体使用
敌人AI脚本
零基础带你从小白到超神29——导航系统
using UnityEngine;
using UnityEngine.AI;
public class EnemyAiTutorial : MonoBehaviour
{
NavMeshAgent agent; // 导航代理
Animator animator;
Transform player; // 玩家的Transform对象
public LayerMask whatIsPlayer; // 碰撞检测所需的LayerMask
[Header("敌人的生命值")]
public float health;
[Space]
[Header("巡逻相关")]
[Header("巡逻范围")]
public float walkPointRange;
bool walkPointSet;//是否设置了巡逻点
Vector3 walkPoint;//巡逻点
[Space]
[Header("攻击相关")]
[Header("攻击间隔时间")]
public float timeBetweenAttacks;
[Header("每次攻击时间")]
public float timeAttack;
[Header("攻击所使用的物体(子弹)")]
public GameObject projectile;
bool isAttack;
bool alreadyAttacked;//是否已经攻击过
[Space]
[Header("状态相关")]
[Header("是否是近战攻击")]
public bool isMeleeAttack;
[Header("远程攻击点")]
public Transform remoteAttackPoint;
[Header("视野范围和攻击范围")]
public float sightRange;
public float attackRange;
//玩家是否在视野范围和攻击范围内
public bool playerInSightRange;
public bool playerInAttackRange;
[Header("速度")]
public float walkSpeed = 1.2f;
public float runSpeed = 5f;
bool isIdle;
private void Awake()
{
player = GameObject.Find("Player").transform; // 查找到名为"PlayerObj"的游戏物体,并获取其Transform组件
agent = GetComponent<NavMeshAgent>(); // 获取自身的导航代理组件
animator = GetComponent<Animator>();
}
private void Update()
{
// 检查玩家是否在视野范围或攻击范围内
playerInSightRange = Physics.CheckSphere(transform.position, sightRange, whatIsPlayer);
playerInAttackRange = Physics.CheckSphere(transform.position, attackRange, whatIsPlayer);
// 根据玩家位置和状态进行相应的行为
if (!playerInSightRange && !playerInAttackRange) Patroling();
if (playerInSightRange && !playerInAttackRange) ChasePlayer();
if (playerInAttackRange && playerInSightRange) AttackPlayer();
}
private void Patroling()
{
if (!walkPointSet) SearchWalkPoint(); // 如果没有设置巡逻点,则搜索巡逻点
if (walkPointSet)
{
agent.speed = walkSpeed; // 将导航代理的速度设置为5
//TODO设置巡逻速度,播放走路动画
animator.SetBool("Walk", true);
animator.SetBool("Run", false);
agent.SetDestination(walkPoint); // 设置巡逻点为目标点
}
Vector3 distanceToWalkPoint = transform.position - walkPoint;
// 已到达巡逻点
if (distanceToWalkPoint.magnitude < 1f)
{
//TODO:播放待机动画,休息3-5秒,也可以等待机动画执行完再进行下一步
animator.SetBool("Walk", false);
animator.SetBool("Run", false);
if (!isIdle)
{
Invoke(nameof(SetWalkPointSet), Random.Range(3, 6));
}
isIdle = true;
}
}
void SetWalkPointSet()
{
isIdle = false;
walkPointSet = false;
}
private void SearchWalkPoint()
{
// 在指定范围内随机生成一个巡逻点
float randomZ = Random.Range(-walkPointRange, walkPointRange);
float randomX = Random.Range(-walkPointRange, walkPointRange);
walkPoint = new Vector3(transform.position.x + randomX, transform.position.y, transform.position.z + randomZ);
// SamplePosition表示判断该点是不是导航允许到达的地方
NavMeshHit hit;
if (NavMesh.SamplePosition(walkPoint, out hit, walkPointRange, 1))
{
walkPoint = hit.position;
walkPointSet = true;
}
}
private void ChasePlayer()
{
//判断是否在攻击
if(isAttack) return;
//TODO设置追击速度,播放奔跑动画
agent.speed = runSpeed; // 将导航代理的速度设置为5
animator.SetBool("Walk", false);
animator.SetBool("Run", true);
agent.SetDestination(player.position); // 设置导航代理的目标点为玩家位置
}
private void AttackPlayer()
{
// 已到达目标点附近
if ((transform.position - player.position).magnitude < 1.5f)
{
// 停止移动
agent.SetDestination(transform.position);
}
if (!alreadyAttacked)
{
//面向玩家
transform.LookAt(player);
//是否是近战攻击
if (isMeleeAttack)
{
animator.SetBool("Walk", false);
animator.SetBool("Run", false);
//TODO播放近战攻击动画
animator.SetTrigger("Punch");
}
else
{
///攻击代码
Rigidbody rb = Instantiate(projectile, remoteAttackPoint.position, Quaternion.identity).GetComponent<Rigidbody>();
rb.AddForce(transform.forward * 32f, ForceMode.Impulse);
rb.AddForce(transform.up * 8f, ForceMode.Impulse);
///攻击代码结束
}
alreadyAttacked = true;
Invoke(nameof(ResetAttack), timeBetweenAttacks);
//攻击时间
isAttack = true;
Invoke(nameof(ResetIsAttack), timeAttack);
}
}
private void ResetIsAttack()
{
isAttack = false;
}
private void ResetAttack()
{
alreadyAttacked = false;
}
public void TakeDamage(int damage)
{
health -= damage;
// 如果生命值小于等于0,则摧毁自身
//TODO: 播放死亡动画
if (health <= 0) Invoke(nameof(DestroyEnemy), 0.5f);
}
private void DestroyEnemy()
{
Destroy(gameObject);
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange); // 在编辑器中绘制攻击范围的圆
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, sightRange); // 在编辑器中绘制视野范围的圆形
}
}
挂载脚本,配置参数,这里以一个近战敌人为例子
效果
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~