【制作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.mzph.cn/news/674213.shtml

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

相关文章

如何在 emacs 上开始使用 Tree-Sitter (archlinux)

文章目录 如何在emacs上开始使用Tree-Sitter&#xff08;archlinux&#xff09; 如何在emacs上开始使用Tree-Sitter&#xff08;archlinux&#xff09; 在archlinux上使用比windows上不知道要方便多少倍&#xff01; $ sudo pacman -S emacs $ sudo pacman -S tree-sitter这里…

第6章——深度学习入门(鱼书)

第6章 与学习相关的技巧 本章将介绍神经网络的学习中的一些重要观点&#xff0c;主题涉及 寻找最优权重参数的最优化方法、权重参数的初始值、超参数的设定方法 等。此外&#xff0c;为了应对过拟合&#xff0c;本章还将介绍 权值衰减、Dropout等正则化方法&#xff0c;并进行实…

Redis(02)——事务管理

事务概念 Redis事务的本质是一组命令的集合。事务支持一次执行多个命令&#xff0c;一个事务中所有命令都会被序列化&#xff0c;在事务执行过程中&#xff0c;会按照顺序串行化执行队列中的命令&#xff0c;其他客户端提交的命令请求不会插入到事务执行命令序列中 Redis事务…

Windows权限维持

注册表类&#xff1a; 普通注册表后门 在一般用户权限下&#xff0c;通常是将要执行的后门程序或脚本路径填写到如下注册表的键值中HKCU\Software\Microsoft\Windows\CurrentVersion\Run&#xff0c;键名任意。普通权限即可运行 cmd下操作&#xff1a; reg add "HKEY_…

我差一点就中了Magniber勒索病毒

前言 勒索攻击已经成为了全球最大的网络威胁&#xff0c;越来越多的黑客组织开始加入到勒索病毒攻击活动&#xff0c;目前勒索病毒黑客组织大致可以分为两类&#xff0c;一类攻击目标主要以TO C(个人)为主&#xff0c;一类攻击目标主要以TO B(企业)为主&#xff0c;前者的勒索…

C# CAD交互界面-自定义面板集(四)

运行环境 vs2022 c# cad2016 调试成功 一、引用 using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Windows; using System.Windows.Forms; 二、程序说明 创建自定义面板集&#xff08;PaletteSet&#xff09;的C#命令方法实现。该方法名为CreatePalette&#xff…

Redis篇之持久化

一、为什么要进行持久化 Redis是一个基于内存的键值存储系统&#xff0c;但为了保证数据在服务器重启、故障等情况下不丢失。 二、应该怎么持久化 1.RDB持久化 &#xff08;1&#xff09;RDB是什么 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff…

流程引擎activiti、flowable、camunda简单介绍

市场上比较有名的开源流程引擎有osworkflow、jbpm、activiti、flowable、camunda。 其中&#xff1a;Jbpm4、Activiti、Flowable、camunda四个框架同宗同源&#xff0c;祖先都是Jbpm4&#xff0c;开发者只要用过其中一个框架&#xff0c;基本上就会用其它三个。 推荐使用camu…

C语言带颜色输出

我们在做函数API功能测试时或其他数据解析时&#xff0c;为了区分信息内容&#xff0c;可以给不同级别的输出加上不同的颜色&#xff0c;以方便查看。 如&#xff0c;我自己的一个项目中的显示效果&#xff1a; 这样做可以更加清晰的反应数据信息&#xff01; 实现原理&#x…

07:Kubectl 命令详解|K8S资源对象管理|K8S集群管理(重难点)

Kubectl 命令详解&#xff5c;K8S资源对象管理&#xff5c;K8S集群管理 kubectl管理命令kubectl get 查询资源常用的排错命令kubectl run 创建容器 POD原理pod的生命周期 k8s资源对象管理资源文件使用资源文件管理对象Pod资源文件deploy资源文件 集群调度的规则扩容与缩减集群更…

网络分析仪的防护技巧

VNA的一些使用防护技巧&#xff0c;虽不全面&#xff0c;但非常实用&#xff1a; [1] 一定要使用正规接地的三相交流电源线缆进行供电&#xff0c;地线不可悬浮&#xff0c;并且&#xff0c;火线和零线不可反接&#xff1b; [2] 交流供电必须稳定&#xff0c;如220V供电&#x…

【Git版本控制 03】远程操作

目录 一、克隆远程仓库 二、推送远程仓库 三、拉取远程仓库 四、忽略特殊文件 五、命令配置别名 一、克隆远程仓库 Git是分布式版本控制系统&#xff0c;同⼀个Git仓库&#xff0c;可以分布到不同的机器上。怎么分布呢&#xff1f; 找⼀台电脑充当服务器的⻆⾊&#xff…

Elementplus报错 [ElOnlyChild] no valid child node found

报错描述&#xff1a;ElementPlusError: [ElOnlyChild] no valid child node found 问题复现&#xff08;随机例子&#xff09;&#xff1a; <el-popover placement"right" :width"400" trigger"click"><template #reference><e…

Spring Cloud使用ZooKeeper作为注册中心的示例

简单的Spring Cloud应用程序使用ZooKeeper作为注册中心的示例&#xff1a; 1.新建模块&#xff1a; 2.勾选依赖&#xff1a; 3.在pom.xml文件中做出部分修改及添加Spring Cloud Zookeeper 依赖版本&#xff1a; 完整pom文件 <?xml version"1.0" encoding&q…

SpringBoot之事务源码解析

首先事务是基于aop的&#xff0c;如果不了解aop的&#xff0c;建议先去看下我关于aop的文章: Spring之aop源码解析  先说结论&#xff0c;带着结论看源码。首先&#xff0c;在bean的生命周期中&#xff0c; 执行实例化前置增强&#xff0c;会加载所有切面并放入缓存&#xff0…

Centos 7.5 安装 NVM 详细步骤

NVM&#xff08;Node Version Manager&#xff09;是一个用于管理Node.js版本的工具&#xff0c;它可以让你轻松地在多个版本之间切换。NVM 通过下载和管理 Node.js 的多个版本&#xff0c;为用户提供了一种灵活的方式来使用不同版本的 Node.js。如果你需要更多关于NVM的信息&a…

1 月 Web3 游戏行业概览:市场实现空前增长

作者&#xff1a;lesleyfootprint.network 今年一月&#xff0c;区块链游戏领域迎来了爆发式增长&#xff0c;活跃用户的数量大幅提升。 区块链游戏不断融合 AI 技术&#xff0c;旨在提升玩家体验并扩大其服务范围&#xff0c;公链与游戏的兼容性问题也日渐受到重视。技术革新…

Python进阶--爬取下载人生格言(基于格言网的Python3爬虫)

目录 一、此处需要安装第三方库: 二、抓包分析及Python代码 1、打开人生格言网&#xff08;人生格言-人生格言大全_格言网&#xff09;进行抓包分析 2、请求模块的代码 3、抓包分析人生格言界面 4、获取各种类型的人生格言链接 5、获取下一页的链接 6、获取人生格言的…

canvas实现涂鸦画板功能

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

路由引入路由过滤排错

目录 排错网络拓扑图 排错需求 故障排错 故障一 故障二 故障三 排错网络拓扑图 排错需求 按照图示配置 IP 地址&#xff0c;总部和分支 A、分支 B 各自使用 loopback 口模拟业务网段公司业务流分为 A 流和 B 流&#xff0c;网段如图所示总部内部配置 OSPF 互通&#xff0…