最终效果
文章目录
- 最终效果
- 系列目录
- 前言
- 添加捕食者
- 动画控制
- 源码
- 完结
系列目录
前言
欢迎来到【制作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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~