【制作100个unity游戏之24】unity制作一个3D动物AI生态系统游戏2(附项目源码)

最终效果

在这里插入图片描述

文章目录

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

系列目录

前言

欢迎来到【制作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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/460242.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习1一knn算法

1.基础知识点介绍 曼哈顿距离一般是比欧式距离长的除非在一维空间 拐弯的就是曼哈顿距离 Knn查看前5行数据head()&#xff0c;info看空非空 查看特征对应的类型 Head()默认前5行&#xff0c;head&#xff08;3&#xff09;就是前3行数据 Unique()可以查看分类后的结果 csv的…

18:蜂鸣器

蜂鸣器 1、蜂鸣器的介绍2、编程让蜂鸣器响起来3、通过定时控制蜂鸣器4、蜂鸣器发出滴滴声&#xff08;间歇性鸣叫&#xff09; 1、蜂鸣器的介绍 蜂鸣器内部其实是2个金属片&#xff0c;当一个金属片接正电&#xff0c;一个金属片接负电时&#xff0c;2个金属片将合拢&#xff…

多 split 窗口 in Gtkmm4

文章目录 效果预览实现概要源代码 效果预览 实现概要 使用Gtk::Paned虽然 Paned 只能装两个子控件, 但是我可以嵌套 paned1 装 box1 和 box2 paned2 装 paned1 和 box3 源代码 #include <gtkmm.h> class ExampleWindow : public Gtk::Window { public:ExampleWindow()…

蓝桥杯(Web大学组)2022国赛真题:水果消消乐

思路&#xff1a; 记录点击次数&#xff0c;点击次数为1时&#xff0c;记录点击下标&#xff08;用于隐藏or消除&#xff09;、点击种类&#xff0c;点击次数为2时&#xff0c;判断该下标所对应种类与第一次是否相同 相同&#xff1a;两个都visibility:hidden &#xff08;占…

汽车控制臂的拓扑优化

前言 本示例使用优化模块通过减小控制臂的体积同时最大化其刚度来优化汽车控制臂的设计。 本页讨论 前言应用描述Abaqus建模方法和仿真技术文件参考 应用描述 本例说明了汽车控制臂的拓扑优化&#xff0c;在拓扑优化过程中&#xff0c;修改设计区域中单元的材料特性(有效地从…

第62讲商品搜索动态实现以及性能优化

商品搜索后端动态获取数据 后端动态获取数据&#xff1a; /*** 商品搜索* param q* return*/GetMapping("/search")public R search(String q){List<Product> productList productService.list(new QueryWrapper<Product>().like("name", q)…

四.Linux实用操作 8-11.网络请求和下载.端口进程管理主机状态监控

目录 四.Linux实用操作 8.网络请求和下载 ping命令 wget命令--下载网络文件 curl命令--发送网络请求/下载文件 四.Linux实用操作 9.端口 端口 端口&#xff08;虚拟&#xff09; 端口&#xff08;号&#xff09; 查看端口占用 四.Linux实用操作 10.进程管理 查看进程…

位运算01 插入[C++]

图源&#xff1a;文心一言 上机题目练习整理&#xff0c;位运算&#xff0c;供小伙伴们参考~&#x1f95d;&#x1f95d; 网页版目录在页面的右上角↗~&#x1f95d;&#x1f95d; 第1版&#xff1a;在力扣新手村刷题的记录~&#x1f9e9;&#x1f9e9; 编辑&#xff1a;梅…

C语言特殊指针

1 野指针 概念&#xff1a;指向一块未知区域的指针&#xff0c;被称为野指针。野指针是危险的。 危害&#xff1a; 引用野指针&#xff0c;相当于访问了非法的内存&#xff0c;常常会导致段错误&#xff08;segmentation fault&#xff09;引用野指针&#xff0c;可能会破坏系…

恒流源方案对比

1、双运放恒流源 2、运放三极管放大电路组成的恒流源 5A 3、运放三极管组成的恒流源 200uA 4、运放MOS管组成的恒流源 100mA 5、电源模块并联输出100A恒流

百面嵌入式专栏(面试题)C语言面试题22道

沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将介绍C语言相关面试题 。 宏定义是在编译的哪个阶段被处理的?答案:宏定义是在编译预处理阶段被处理的。 解读:编译预处理:头文件包含、宏替换、条件编译、去除注释、添加行号。 写一个“标准”宏MIN,这个…

极限的反问题【高数笔记】

1. 什么是极限反问题&#xff1f; 2. 极限反问题分为几类&#xff1f; 3. 每一类极限反问题的具体做法是什么&#xff1f; 4. 每一类极限反问题具体做法是否有前提条件&#xff1f; 5. 例题&#xff1f;