最终效果
文章目录
- 最终效果
- 系列目录
- 前言
- 添加捕食者
- 动画控制
- 源码
- 完结
系列目录
前言
欢迎来到【制作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; // 存储当前动物的状态,默认为 Idle
private void Start()
{
InitialiseAnimal(); // 初始化动物
}
// 初始化动物
protected virtual void InitialiseAnimal()
{
navMeshAgent = GetComponent<NavMeshAgent>(); // 获取 NavMeshAgent 组件的引用
navMeshAgent.speed = walkSpeed; // 设置动物的行走速度
currentState = AnimalState.Idle; // 将当前状态设置为 Idle
UpdateState(); // 更新动物的状态
}
// 更新动物的状态
protected virtual void UpdateState()
{
switch (currentState)
{
case AnimalState.Idle: // 如果当前状态是 Idle
HandleIdleState(); // 处理空闲状态
break;
case AnimalState.Moving: // 如果当前状态是 Moving
HandleMovingState(); // 处理移动状态
break;
case AnimalState.Chase: // 如果当前状态是 Chase
HandleChaseState(); // 处理追逐状态
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); // 将状态设置为 Idle
yield break; // 结束函数的执行
}
CheckChaseConditions(); // 检查是否满足追逐条件
yield return null; // 等待下一帧
}
// 到达目的地后将状态设置为 Idle
SetState(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); // 设置状态为移动状态
}
// 绘制检测范围的 gizmos
private 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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~