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

简介: 【制作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;  // 存储当前动物的状态,默认为 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,如果不设置会影响动物的逃跑功能和捕猎者的咬伤功能

羊参数配置

狼参数配置

效果

image.png

动画控制

修改Animal

private Animator animator;

animator = GetComponentInChildren<Animator>();

// 状态改变事件的处理函数
protected virtual void OnStateChanged(AnimalState newState)
{
    animator?.CrossFadeInFixedTime(newState.ToString(), 0.5f);

    //...
}

看看CrossFadeInFixedTime官方文档解释,实现一个淡入淡出的动画效果

效果

image.png

源码

源码在最后一期

目录
相关文章
|
28天前
|
存储 人工智能 运维
【01】做一个精美的打飞机小游戏,浅尝阿里云通义灵码python小游戏开发AI编程-之飞机大战小游戏上手实践-优雅草央千澈-用ai开发小游戏尝试-分享源代码和游戏包
【01】做一个精美的打飞机小游戏,浅尝阿里云通义灵码python小游戏开发AI编程-之飞机大战小游戏上手实践-优雅草央千澈-用ai开发小游戏尝试-分享源代码和游戏包
164 48
【01】做一个精美的打飞机小游戏,浅尝阿里云通义灵码python小游戏开发AI编程-之飞机大战小游戏上手实践-优雅草央千澈-用ai开发小游戏尝试-分享源代码和游戏包
|
1月前
|
人工智能 移动开发 JavaScript
如何用uniapp打包桌面客户端exe包,vue或者uni项目如何打包桌面客户端之electron开发-优雅草央千澈以开源蜻蜓AI工具为例子演示完整教程-开源代码附上
如何用uniapp打包桌面客户端exe包,vue或者uni项目如何打包桌面客户端之electron开发-优雅草央千澈以开源蜻蜓AI工具为例子演示完整教程-开源代码附上
157 18
|
9天前
|
人工智能 算法 计算机视觉
【01】opencv项目实践第一步opencv是什么-opencv项目实践-opencv完整入门以及项目实践介绍-opencv以土壤和水滴分离的项目实践-人工智能AI项目优雅草卓伊凡
【01】opencv项目实践第一步opencv是什么-opencv项目实践-opencv完整入门以及项目实践介绍-opencv以土壤和水滴分离的项目实践-人工智能AI项目优雅草卓伊凡
93 61
【01】opencv项目实践第一步opencv是什么-opencv项目实践-opencv完整入门以及项目实践介绍-opencv以土壤和水滴分离的项目实践-人工智能AI项目优雅草卓伊凡
|
22天前
|
人工智能 数据处理 语音技术
Pipecat实战:5步快速构建语音与AI整合项目,创建你的第一个多模态语音 AI 助手
Pipecat 是一个开源的 Python 框架,专注于构建语音和多模态对话代理,支持与多种 AI 服务集成,提供实时处理能力,适用于语音助手、企业服务等场景。
80 23
Pipecat实战:5步快速构建语音与AI整合项目,创建你的第一个多模态语音 AI 助手
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
MMAudio:开源 AI 音频合成项目,根据视频或文本生成同步的音频
MMAudio 是一个基于多模态联合训练的高质量 AI 音频合成项目,能够根据视频内容或文本描述生成同步的音频。该项目适用于影视制作、游戏开发、虚拟现实等多种场景,提升用户体验。
151 7
MMAudio:开源 AI 音频合成项目,根据视频或文本生成同步的音频
|
2月前
|
存储 人工智能 数据库
Codel:AI代理工具,支持在终端、浏览器、编辑器执行复杂任务和项目
Codel是一款全自主AI代理工具,支持在终端、浏览器和编辑器中执行复杂任务和项目。它运行在沙盒化的Docker环境中,具备自主操作能力,内置浏览器和文本编辑器,所有操作记录存储于PostgreSQL数据库。Codel能够自动完成复杂任务,如创建项目结构、进行网络搜索等,适用于自动化编程、研究与开发、教育与培训以及数据科学与分析等多个领域。
104 11
Codel:AI代理工具,支持在终端、浏览器、编辑器执行复杂任务和项目
|
23天前
|
人工智能 运维 物联网
云大使 X 函数计算 FC 专属活动上线!享返佣,一键打造 AI 应用
如今,AI 技术已经成为推动业务创新和增长的重要力量。但对于许多企业和开发者来说,如何高效、便捷地部署和管理 AI 应用仍然是一个挑战。阿里云函数计算 FC 以其免运维的特点,大大降低了 AI 应用部署的复杂性。用户无需担心底层资源的管理和运维问题,可以专注于应用的创新和开发,并且用户可以通过一键部署功能,迅速将 AI 大模型部署到云端,实现快速上线和迭代。函数计算目前推出了多种规格的云资源优惠套餐,用户可以根据实际需求灵活选择。
|
20天前
|
人工智能 算法 前端开发
OmAgent:轻松构建在终端设备上运行的 AI 应用,赋能手机、穿戴设备、摄像头等多种设备
OmAgent 是 Om AI 与浙江大学联合开源的多模态语言代理框架,支持多设备连接、高效模型集成,助力开发者快速构建复杂的多模态代理应用。
163 72
OmAgent:轻松构建在终端设备上运行的 AI 应用,赋能手机、穿戴设备、摄像头等多种设备
|
6天前
|
人工智能 自然语言处理 搜索推荐
【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人
【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人
69 23
【上篇】-分两篇步骤介绍-如何用topview生成和自定义数字人-关于AI的使用和应用-如何生成数字人-优雅草卓伊凡-如何生成AI数字人
|
14天前
|
人工智能 自然语言处理 JavaScript
微软开源课程!21节课程教你开发生成式 AI 应用所需了解的一切
微软推出的生成式 AI 入门课程,涵盖 21 节课程,帮助开发者快速掌握生成式 AI 应用开发,支持 Python 和 TypeScript 代码示例。
200 14