【制作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

源码

源码在最后一期

目录
相关文章
|
18天前
|
存储 人工智能 关系型数据库
拥抱Data+AI|玩家去哪儿了?解码Data+AI如何助力游戏日志智能分析
本文为阿里云瑶池数据库「拥抱Data+AI」系列连载第2篇,基于真实客户案例和最佳实践,探讨如何利用阿里云Data+AI解决方案应对游戏行业挑战,通过AI为游戏行业注入新的活力。文章详细介绍了日志数据的实时接入、高效查询、开源开放及AI场景落地,展示了完整的Data+AI解决方案及其实际应用效果。
|
19天前
|
存储 人工智能 关系型数据库
拥抱Data+AI|解码Data+AI助力游戏日志智能分析
「拥抱Data+AI」系列第2篇:阿里云DMS+AnalyticDB助力游戏日志数据分析与预测
拥抱Data+AI|解码Data+AI助力游戏日志智能分析
|
3月前
|
机器学习/深度学习 人工智能 开发者
谷歌推世界首个AI游戏引擎,2000亿游戏产业恐颠覆!0代码生成游戏,老黄预言成真
【9月更文挑战第22天】谷歌近日推出的AI游戏引擎GameNGen,作为全球首款神经模型驱动的游戏引擎,引发了广泛关注。该引擎使用户无需编写代码即可生成游戏,并实现了与复杂环境的实时交互,显著提升了模拟质量。在单TPU上,GameNGen能以超20帧/秒的速度流畅模拟经典游戏《DOOM》。这项技术不仅简化了游戏开发流程,降低了成本,还为游戏设计带来了更多可能性。然而,它也可能改变游戏产业的商业模式和创意多样性。无论如何,GameNGen标志着游戏开发领域的一次重大革新。
57 2
|
3月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
122 4
|
3月前
|
前端开发 图形学 开发者
【独家揭秘】那些让你的游戏瞬间鲜活起来的Unity UI动画技巧:从零开始打造动态按钮,提升玩家交互体验的绝招大公开!
【9月更文挑战第1天】在游戏开发领域,Unity 是最受欢迎的游戏引擎之一,其强大的跨平台发布能力和丰富的功能集让开发者能够迅速打造出高质量的游戏。优秀的 UI 设计对于游戏至关重要,尤其是在手游市场,出色的 UI 能给玩家留下深刻的第一印象。Unity 的 UGUI 系统提供了一整套解决方案,包括 Canvas、Image 和 Button 等组件,支持添加各种动画效果。
143 3
|
3月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
192 3
|
8天前
|
机器学习/深度学习 人工智能 算法
AI技术在医疗诊断中的应用及前景展望
本文旨在探讨人工智能(AI)技术在医疗诊断领域的应用现状、挑战与未来发展趋势。通过分析AI技术如何助力提高诊断准确率、缩短诊断时间以及降低医疗成本,揭示了其在现代医疗体系中的重要价值。同时,文章也指出了当前AI医疗面临的数据隐私、算法透明度等挑战,并对未来的发展方向进行了展望。
|
16天前
|
机器学习/深度学习 人工智能 自然语言处理
当前AI大模型在软件开发中的创新应用与挑战
2024年,AI大模型在软件开发领域的应用正重塑传统流程,从自动化编码、智能协作到代码审查和测试,显著提升了开发效率和代码质量。然而,技术挑战、伦理安全及模型可解释性等问题仍需解决。未来,AI将继续推动软件开发向更高效、智能化方向发展。
|
20天前
|
机器学习/深度学习 人工智能 自然语言处理
AI在医疗领域的应用及其挑战
【10月更文挑战第34天】本文将探讨人工智能(AI)在医疗领域的应用及其面临的挑战。我们将从AI技术的基本概念入手,然后详细介绍其在医疗领域的各种应用,如疾病诊断、药物研发、患者护理等。最后,我们将讨论AI在医疗领域面临的主要挑战,包括数据隐私、算法偏见、法规合规等问题。
40 1