最终效果

文章目录

  • 最终效果
  • 系列目录
  • 前言
  • 添加捕食者
  • 动画控制
  • 源码
  • 完结

系列目录

前言

欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第24篇中,我们将探索如何用unity制作一个3D动物AI生态系统游戏,我会附带项目源码,以便你们更好理解它。

添加捕食者

修改AnimalState,为了方便我就直接贴出全部代码吧,大概就是在原基础上添加了追逐状态和事件处理

using System.Collections;using UnityEngine;using UnityEngine.AI;// 定义动物状态的枚举类型public enum AnimalState{Idle, // 空闲状态Moving,// 移动状态Chase// 追逐状态}// 必须附加到具有 NavMeshAgent 组件的游戏对象上[RequireComponent(typeof(NavMeshAgent))]public class Animal : MonoBehaviour{[Header("动物一次移动的距离"), SerializeField]private float wanderDistance = 50f;[Header("动物的行走速度"), SerializeField]private float walkSpeed = 5f;[Header("动物行走的最大时间"), SerializeField]private float maxWalkTime = 6f;[Header("动物休息的时间"), SerializeField]private float idleTime = 5f;[Header("奔跑速度"), SerializeField]private float runSpeed = 8f;[Header("生命值"), SerializeField]private int health = 10;protected NavMeshAgent navMeshAgent;// 存储 NavMeshAgent 组件的引用protected AnimalState currentState = AnimalState.Idle;// 存储当前动物的状态,默认为 Idleprivate void Start(){InitialiseAnimal();// 初始化动物}// 初始化动物protected virtual void InitialiseAnimal(){navMeshAgent = GetComponent<NavMeshAgent>();// 获取 NavMeshAgent 组件的引用navMeshAgent.speed = walkSpeed;// 设置动物的行走速度currentState = AnimalState.Idle;// 将当前状态设置为 IdleUpdateState();// 更新动物的状态}// 更新动物的状态protected virtual void UpdateState(){switch (currentState){case AnimalState.Idle:// 如果当前状态是 IdleHandleIdleState();// 处理空闲状态break;case AnimalState.Moving:// 如果当前状态是 MovingHandleMovingState();// 处理移动状态break;case AnimalState.Chase:// 如果当前状态是 ChaseHandleChaseState();// 处理追逐状态break;}}// 获取距离起点一定距离内的随机 NavMesh 位置protected Vector3 GetRandomNavMeshPosition(Vector3 origin, float distance){for (int i = 0; i < 5; i++){Vector3 randomDirection = Random.insideUnitSphere * distance;// 在球形区域内生成一个随机方向randomDirection += origin;// 将随机方向与起点相加,计算出随机点的位置NavMeshHit navMeshHit;if (NavMesh.SamplePosition(randomDirection, out navMeshHit, distance, NavMesh.AllAreas)){// 如果找到了 NavMesh 上的可行走位置,返回该位置return navMeshHit.position;}}return origin;}protected virtual void HandleChaseState(){StopAllCoroutines();}// 处理空闲状态protected virtual void HandleIdleState(){StartCoroutine(WaitToMove());// 等待一段时间后转换到移动状态}// 等待一段时间后转换到移动状态private IEnumerator WaitToMove(){float waitTime = Random.Range(idleTime / 2, idleTime * 2);// 随机生成等待时间yield return new WaitForSeconds(waitTime);// 等待一段时间Vector3 randomDestination = GetRandomNavMeshPosition(transform.position, wanderDistance);// 获取随机位置navMeshAgent.SetDestination(randomDestination);// 设置 NavMeshAgent 的目标位置SetState(AnimalState.Moving);// 将状态设置为 Moving}// 处理移动状态protected virtual void HandleMovingState(){StartCoroutine(WaitToReachDestination());// 等待到达目的地后转换到空闲状态}// 等待到达目的地后转换到空闲状态private IEnumerator WaitToReachDestination(){float startTime = Time.time;// 记录开始移动的时间while (navMeshAgent.pathPending || navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance && navMeshAgent.isActiveAndEnabled)// 当还未到达目的地时循环{if (Time.time - startTime >= maxWalkTime)// 如果超过了最大行走时间{navMeshAgent.ResetPath();// 重置路径SetState(AnimalState.Idle);// 将状态设置为 Idleyield break;// 结束函数的执行}CheckChaseConditions();// 检查是否满足追逐条件yield return null;// 等待下一帧}// 到达目的地后将状态设置为 IdleSetState(AnimalState.Idle);}// 将动物的状态设置为指定的状态protected void SetState(AnimalState newState){if (currentState == newState)// 如果新状态与当前状态相同,直接返回{return;}currentState = newState;// 更新当前状态OnStateChanged(newState);// 触发状态改变事件}// 状态改变事件的处理函数protected virtual void OnStateChanged(AnimalState newState){if (newState == AnimalState.Moving)navMeshAgent.speed = walkSpeed;if (newState == AnimalState.Chase)navMeshAgent.speed = runSpeed;UpdateState();}// 接收到伤害时的处理函数public virtual void RecieveDamage(int damage){health -= damage;if (health <= 0)Die();}// 死亡时的处理函数protected virtual void Die(){StopAllCoroutines();Destroy(gameObject);}// 检查是否满足追逐条件的函数protected virtual void CheckChaseConditions() { }}

新增Prey,基础Animal,定义猎物的行为脚本

using System.Collections;using UnityEngine;// 表示一种猎物动物public class Prey : Animal{[SerializeField, Header("检测范围")] private float detectionRange = 10f;[SerializeField, Header("逃离的最大距离")] private float escapeMaxDistance = 80f;private Predator currentPredator = null; // 当前追逐的捕食者// 警报猎物,传入捕食者对象public void AlertPrey(Predator predator){SetState(AnimalState.Chase); // 设置状态为追逐currentPredator = predator; // 设置当前捕食者StartCoroutine(RunFromPredator()); // 开始逃离捕食者}// 逃离捕食者的协程private IEnumerator RunFromPredator(){// 等待捕食者进入检测范围while (currentPredator == null || Vector3.Distance(transform.position, currentPredator.transform.position) > detectionRange){yield return null;}// 捕食者进入检测范围,开始逃离while (currentPredator != null && Vector3.Distance(transform.position, currentPredator.transform.position) <= detectionRange){RunAwayFromPredator(); // 逃离捕食者yield return null;}// 捕食者超出范围,向最终位置逃离并回到空闲状态 if (!navMeshAgent.pathPending && navMeshAgent.remainingDistance > navMeshAgent.stoppingDistance){yield return null;}SetState(AnimalState.Idle); // 设置状态为空闲}// 逃离捕食者的方法private void RunAwayFromPredator(){if (navMeshAgent != null && navMeshAgent.isActiveAndEnabled){if (!navMeshAgent.pathPending && navMeshAgent.remainingDistance < navMeshAgent.stoppingDistance){// 计算逃离方向,即当前位置与捕食者位置的反向向量Vector3 runDirection = transform.position - currentPredator.transform.position;// 根据逃离方向和逃离的最大距离,计算逃离的目标位置Vector3 escapeDestination = transform.position + runDirection.normalized * (escapeMaxDistance * 2);// 设置导航代理的目标位置为随机的逃离目标位置navMeshAgent.SetDestination(GetRandomNavMeshPosition(escapeDestination, escapeMaxDistance));}}}// 处理猎物死亡的方法protected override void Die(){StopAllCoroutines();base.Die();}// 在场景视图中绘制检测范围的方法private void OnDrawGizmos(){Gizmos.color = Color.green;Gizmos.DrawWireSphere(transform.position, detectionRange);}}

新增Predator,同样继承Animal,定义捕食者的行为脚本

using System.Collections;using UnityEngine;// 表示一种捕食者动物public class Predator : Animal{[SerializeField, Header("检测范围,用于检测猎物")] private float detectionRange = 20f;[SerializeField, Header("追逐的最长时间")] private float maxChaseTime = 10f;[SerializeField, Header("咬伤伤害值")] private int biteDamage = 3;[SerializeField, Header("咬伤冷却时间")] private float biteCooldown = 1f;private Prey currentChaseTarget; // 当前追逐的猎物// 检查追逐条件protected override void CheckChaseConditions(){if (currentChaseTarget)return;// 获取检测范围内的所有 collider,存储到数组中Collider[] colliders = new Collider[10];int numColliders = Physics.OverlapSphereNonAlloc(transform.position, detectionRange, colliders);for (int i = 0; i < numColliders; i++){// 如果该 collider 绑定的游戏物体上有 Prey 组件,则该游戏物体为猎物Prey prey = colliders[i].GetComponent<Prey>();if (prey != null){StartChase(prey); // 开始追逐该猎物return;}}currentChaseTarget = null;}// 开始追逐指定的猎物private void StartChase(Prey prey){currentChaseTarget = prey;SetState(AnimalState.Chase); // 设置状态为追逐状态}// 处理追逐状态protected override void HandleChaseState(){if (currentChaseTarget != null){currentChaseTarget.AlertPrey(this); // 提醒猎物有捕食者正在追逐它StartCoroutine(ChasePrey()); // 开始追逐猎物的协程}else{SetState(AnimalState.Idle); // 如果没有猎物,则回到空闲状态}}// 追逐猎物的协程private IEnumerator ChasePrey(){float startTime = Time.time;while (currentChaseTarget != null && Vector3.Distance(transform.position, currentChaseTarget.transform.position) > navMeshAgent.stoppingDistance){if (Time.time - startTime >= maxChaseTime || currentChaseTarget == null){StopChase(); // 如果超时或者目标不存在,则停止追逐yield break;}SetState(AnimalState.Chase); // 设置状态为追逐状态navMeshAgent.SetDestination(currentChaseTarget.transform.position); // 设置目标位置为猎物的位置yield return null;}// 如果猎物还存在,则对猎物造成伤害if (currentChaseTarget){currentChaseTarget.RecieveDamage(biteDamage);}yield return new WaitForSeconds(biteCooldown); // 等待咬伤冷却时间currentChaseTarget = null;HandleChaseState(); // 继续处理追逐状态CheckChaseConditions(); // 检查是否可以继续追逐其他猎物}// 停止追逐private void StopChase(){navMeshAgent.ResetPath();currentChaseTarget = null;SetState(AnimalState.Moving); // 设置状态为移动状态}// 绘制检测范围的 gizmosprivate void OnDrawGizmos(){Gizmos.color = Color.red;Gizmos.DrawWireSphere(transform.position, detectionRange);}}

挂载新脚本,并配置参数,这里可以设置捕猎者速度大于动物,确保可以狩猎成功
记得给动物添加碰撞体,不然捕猎者无法检测到动物发起攻击
同样修改动物和捕猎者导航网格停止距离的值,这里我设置为3
注意:导航网格停止距离的值默认为0,如果不设置会影响动物的逃跑功能和捕猎者的咬伤功能

羊参数配置

狼参数配置

效果

动画控制

修改Animal

private Animator animator;animator = GetComponentInChildren<Animator>();// 状态改变事件的处理函数protected virtual void OnStateChanged(AnimalState newState){animator" />CrossFadeInFixedTime(newState.ToString(), 0.5f);//...}

看看CrossFadeInFixedTime官方文档解释,实现一个淡入淡出的动画效果

效果

源码

源码在最后一期

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~