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

news2024/11/15 17:36:18

最终效果

在这里插入图片描述

文章目录

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

系列目录

前言

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

在这里插入图片描述

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

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

相关文章

内网渗透靶场02----Weblogic反序列化+域渗透

网络拓扑&#xff1a; 攻击机&#xff1a; Kali: 192.168.111.129 Win10: 192.168.111.128 靶场基本配置&#xff1a;web服务器双网卡机器&#xff1a; 192.168.111.80&#xff08;模拟外网&#xff09;10.10.10.80&#xff08;模拟内网&#xff09;域成员机器 WIN7PC192.168.…

AI嵌入式K210项目(26)-二维码识别

文章目录 前言一、什么是二维码&#xff1f;二、实验准备三、实验过程四、API接口总结 前言 本章介绍基于机器视觉实现二维码识别&#xff0c;主要包含两个过程&#xff0c;首先检测图像中是否有二维码&#xff0c;如果有则框出并打印二维码信息&#xff1b; 一、什么是二维码…

吉他学习:右手拨弦方法,右手拨弦训练 左手按弦方法

第六课 右手拨弦方法https://m.lizhiweike.com/lecture2/29362775 第七课 右手拨弦训练https://m.lizhiweike.com/lecture2/29362708

Redis——缓存设计与优化

讲解Redis的缓存设计与优化&#xff0c;以及在生产环境中遇到的Redis常见问题&#xff0c;例如缓存雪崩和缓存穿透&#xff0c;还讲解了相关问题的解决方案。 1、Redis缓存的优点和缺点 1.1、缓存优点&#xff1a; 高速读写&#xff1a;Redis可以帮助解决由于数据库压力造成…

高斯伪谱C++封装库开源!

Windows x64/86 C无依赖运行高斯伪谱法求解最优控制问题&#xff0c;你只需要ElegantGP! Author: Y. F. Zhang His Github: https://github.com/ZYunfeii 写在前面 这个库在你下载它的那一时刻起不再依赖任何其他代码&#xff0c;直接可用来构建C的最优控制问题并进行求解。…

vscode配置wsl ubuntu c++的环境

在ubuntu安装llvm/clang sudo apt install llvm clang clangd lldb vscode的调试器接口是按GDB开发的&#xff0c;所以需要一个适配器&#xff0c;lldb-mi就是这个适配器。lldb-mi原来是llvm项目的一部分&#xff0c;后面成为了一个单独的项目https://github.com/lldb-tools/…

编译原理实验1——词法分析(python实现)

文章目录 实验目的实现定义单词对应的种别码定义输出形式&#xff1a;三元式python代码实现运行结果检错处理 总结 实验目的 输入一个C语言代码串&#xff0c;输出单词流&#xff0c;识别对象包含关键字、标识符、整型浮点型字符串型常数、科学计数法、操作符和标点、注释等等。…

一条 SQL 更新语句是如何执行的?

之前你可能经常听 DBA 同事说&#xff0c;MySQL 可以恢复到半个月内任意一秒的状态&#xff0c;惊叹的同时&#xff0c;你是不是心中也会不免会好奇&#xff0c;这是怎样做到的呢&#xff1f; 我们先从一条更新语句讲起&#xff0c;首先创建一个表&#xff0c;这个表有一个主键…

百卓Smart管理平台 uploadfile.php 文件上传漏洞【CVE-2024-0939】

百卓Smart管理平台 uploadfile.php 文件上传漏洞【CVE-2024-0939】 一、 产品简介二、 漏洞概述三、 影响范围四、 复现环境五、 漏洞复现手动复现小龙验证Goby验证 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工…

Python 线性回归可视化 并将回归函数放置到图像上

import matplotlib.pyplot as plt import scipy import seaborn as sns# 加载内置的数据集 df sns.load_dataset(tips)#create regplot p sns.regplot(xtotal_bill, ytip, datadf)#calculate slope and intercept of regression equation slope, intercept, r, p, sterr sci…

FlinkSql 窗口函数

Windowing TVF 以前用的是Grouped Window Functions&#xff08;分组窗口函数&#xff09;&#xff0c;但是分组窗口函数只支持窗口聚合 现在FlinkSql统一都是用的是Windowing TVFs&#xff08;窗口表值函数&#xff09;&#xff0c;Windowing TVFs更符合 SQL 标准且更加强大…

[项目管理] 如何使用git客户端管理gitee的私有仓库

最近发现即使翻墙也无法g使用ithub了&#xff0c;需要把本地的项目搬迁到新的git托管平台。 gitee 是一个国内开源项目托管平台&#xff0c;是开源开发者、团队、个人进行 git 代码管理和协作的首选平台之一。本文将详细介绍如何向 gitee 提交私有项目。 注册 Gitee 账号并创建…

AD域国产替代方案,助力某金融企业麒麟信创电脑实现“真替真用”

近期收到不少企业客户反馈采购的信创PC电脑用不起来&#xff0c;影响信创改造的进度。例如&#xff0c;某金融企业积极响应国产化信创替代战略&#xff0c;购置了一批麒麟操作系统电脑。分发使用中发现了如下问题&#xff1a; • 当前麒麟操作系统电脑无法做到统一身份认证&…

K8S系列文章之 [使用 Alpine 搭建 k3s]

官方文档&#xff1a;K3s - 轻量级 Kubernetes | K3s 官方描述&#xff0c;可运行在 systemd 或者 openrc 环境上&#xff0c;那就往精简方向走&#xff0c;使用 alpine 做系统。与 RHEL、Debian 的区别&#xff0c;主要在防火墙侧&#xff1b;其他基础配置需求类似&#xff0…

jmeter的简单使用

1、打开jmeter 打开Jmeter 安装包&#xff0c;进入\bin 中&#xff0c;找到“ApacheJMeter.jar”或"jmeter.bat", 双击打开即可 2、建立线程组 如下图所示&#xff0c;右击TestPlan&#xff0c;点击ADD->Threads(Users)->ThreadGroup 线程组页面分析&#xf…

IS-IS 接口认证密码平滑更换

拓扑图 配置 AR1、AR2建立ISIS level-2邻居关系&#xff0c;并配置接口认证密码为huawei sysname AR1 # isis 1is-level level-2network-entity 49.0000.0000.0000.0001.00 # interface GigabitEthernet0/0/0ip address 12.1.1.1 255.255.255.0 isis enable 1isis authentica…

ThinkPHP 中使用Redis

环境.env [app] app_debug "1" app_trace ""[database] database "" hostname "127.0.0.1" hostport "" password "" prefix "ls_" username ""[redis] hostname "127.0.0.1…

【C语言】通过socket看系统调用过程

一、通过socket看系统调用过程 在Linux操作系统中&#xff0c;系统调用是用户空间与内核空间之间交互的一种方式。当一个应用程序需要执行操作系统级别的任务时&#xff0c;比如创建一个网络套接字&#xff08;socket&#xff09;&#xff0c;它必须通过系统调用请求内核来执行…

SQL在云计算中的新角色:重新定义数据分析

文章目录 1. 云计算与数据分析的融合2. SQL在云计算中的新角色3. 分布式SQL查询引擎4. SQL-on-Hadoop解决方案5. SQL与其他数据分析工具的集成6. 实时数据分析与SQL7. SQL在云数据仓库中的角色8. 安全性与隐私保护9. SQL的未来展望《SQL数据分析实战&#xff08;第2版&#xff…

Snipaste使用

今天推荐一款好用的截图、贴图软件工具&#xff0c;名字叫Snipaste&#xff0c;以下是官方介绍的截图 软件官方下载地址&#xff1a; Snipaste 下载 1、截图功能 2、标注 3、开发中的使用 有时候在开发中需要临时把一些任务规则信息&#xff0c;放在代码编辑器旁边进行参考&am…