Unity 实验功能实现:天敌捕食猎物(含对象池 + 点击交互)

        在虚拟仿真项目开发中,经常需要实现类似 天敌捕食猎物 的交互实验功能。本文将介绍如何用 Unity 实现一个完整的 猎物生成 + 捕食控制 + 点击反馈 的系统,并结合对象池、事件回调等常见开发技巧,最终实现高效可复用的实验功能。

🎯 功能需求

  • 猎物批量生成:按照配置数量生成猎物,避免重复 Instantiate 带来的性能消耗。

  • 点击反馈:用户点击猎物时,触发缩放动画等视觉反馈。

  • 猎物回收:猎物被捕食后自动回收,复用到对象池中。

  • 天敌控制:不同的天敌(Predator)可通过按钮触发捕食行为。

  • 实验流程演示:支持点击交互、提示信息清除与重新开始。


🧩 核心脚本实现

下面展示几个主要的脚本(均已加上详细备注,便于理解和学习)。

1. IHuntable 接口(猎物通用规范)

/// <summary>
/// 可捕食对象接口(猎物基类接口)
/// 用于解耦 PredatorController 与具体的猎物实现。
/// 所有猎物(如 PreyController)必须实现该接口,才能被天敌识别和捕食。
/// </summary>
public interface IHuntable
{
    /// <summary>
    /// 当猎物被点击时调用
    /// - 负责触发点击反馈(例如高亮、闪烁、缩放等视觉效果)。
    /// - 由猎物自身实现,保证不同猎物可以有不同反馈。
    /// </summary>
    void OnClicked();

    /// <summary>
    /// 重置猎物状态(对象池复用时调用)
    /// - 用于恢复初始状态,例如位置、颜色、动画状态等。
    /// - 避免频繁销毁/实例化造成性能浪费。
    /// </summary>
    void ResetPrey();

    /// <summary>
    /// 第一次被点击的时间(秒)
    /// - 用于统计和记录捕食数据。
    /// - 由实现类维护(通常在第一次 OnClicked 时赋值)。
    /// - PredatorController 在 ConsumePrey 时可以读取该值,用于分析。
    /// </summary>
    float FirstClickTime { get; }
}

👉 这样可以保证所有“猎物”类都有统一的点击与回收逻辑。


2. Prey.cs(猎物类,负责点击与反馈)

using UnityEngine;
using UnityEngine.EventSystems;
using System;
using System.Collections;

/// <summary>
/// Prey 脚本:
/// - 实现了 IHuntable(可捕猎接口)和 IPointerClickHandler(UI点击接口)
/// - 提供点击反馈(缩放动画)
/// - 支持对象池回收(ResetPrey)
/// - 提供点击时间记录(FirstClickTime)
/// </summary>
[RequireComponent(typeof(Collider))] // 确保物体身上有 Collider,用于鼠标/点击检测
public class Prey : MonoBehaviour, IHuntable, IPointerClickHandler
{
    /// <summary>
    /// 静态事件:当任意 Prey 被点击时触发
    /// 外部可以通过订阅 OnPreyClicked 来获取点击的 Prey 对象
    /// </summary>
    public static event Action<Prey> OnPreyClicked;

    /// <summary>
    /// 第一次被点击的时间(记录点击时间点)
    /// -1 表示未点击过
    /// </summary>
    public float FirstClickTime { get; private set; } = -1f;

    [Header("点击视觉反馈")]
    [Tooltip("点击时缩放比例")]
    public float clickScale = 1.3f; 

    [Tooltip("反馈持续时间(秒)")]
    public float feedbackDuration = 0.15f;

    /// <summary>
    /// 原始缩放,用于恢复大小
    /// </summary>
    private Vector3 _originalScale;

    /// <summary>
    /// 是否已经被点击过
    /// </summary>
    private bool _isClicked = false;

    private void Awake()
    {
        // 确保物体带有 Collider(否则点击检测无效)
        if (GetComponent<Collider>() == null)
            gameObject.AddComponent<BoxCollider>();

        // 记录初始缩放
        _originalScale = transform.localScale;
    }

    #region IHuntable 实现
    /// <summary>
    /// 当猎物被点击时调用
    /// - 记录点击时间
    /// - 标记为已点击
    /// - 播放点击反馈动画
    /// </summary>
    public void OnClicked()
    {
        if (!_isClicked)
        {
            FirstClickTime = Time.time; // 记录点击时间
            _isClicked = true;          // 防止重复记录
        }

        // 停止所有可能的点击反馈协程(避免叠加)
        StopAllCoroutines();
        StartCoroutine(ClickFeedback());
    }

    /// <summary>
    /// 点击视觉反馈协程
    /// - 等待一段时间后恢复原始缩放
    /// </summary>
    private IEnumerator ClickFeedback()
    {
        // 点击瞬间放大
        transform.localScale = _originalScale * clickScale;

        // 等待指定时间
        yield return new WaitForSeconds(feedbackDuration);

        // 恢复原始缩放
        transform.localScale = _originalScale;
    }

    /// <summary>
    /// 对象池回收时重置猎物状态
    /// - 恢复缩放
    /// - 重置点击状态
    /// - 重新激活对象
    /// </summary>
    public void ResetPrey()
    {
        _isClicked = false;
        FirstClickTime = -1f;
        transform.localScale = _originalScale;
        gameObject.SetActive(true);
    }
    #endregion

    #region 点击事件
    /// <summary>
    /// UI 点击接口实现(支持 EventSystem 点击)
    /// </summary>
    public void OnPointerClick(PointerEventData eventData) => TriggerClick();

    /// <summary>
    /// 鼠标点击事件(非 UI 点击,也能触发)
    /// </summary>
    private void OnMouseDown() => TriggerClick();

    /// <summary>
    /// 触发点击逻辑:
    /// - 先广播事件(通知外部)
    /// - 再执行自身的点击反馈
    /// </summary>
    private void TriggerClick()
    {
        OnPreyClicked?.Invoke(this); // 通知外部有 Prey 被点击
        OnClicked();                 // 播放反馈效果
    }
    #endregion
}

👉 该脚本负责猎物点击后的反馈(缩放效果)和回收逻辑。


3. InsertAnimation.cs(捕食动画配置)

using UnityEngine;
using System;

/// <summary>
/// InsertAnimation
/// —— 用于配置“天敌”与其相关的动画、猎物生成信息
/// —— 作为数据容器供 PredatorController 使用
/// </summary>
[Serializable] // 可序列化,能在 Inspector 中显示
public class InsertAnimation
{
    [Header("配置名称(唯一标识)")]
    [Tooltip("该配置的唯一名称标识,例如:'七星瓢虫配置',用于区分不同天敌的设置")]
    public string name;

    [Header("天敌对象")]
    [Tooltip("天敌在场景中的 GameObject,例如:七星瓢虫的模型对象")]
    public GameObject mainHunter;

    [Header("猎物生成的父物体")]
    [Tooltip("猎物生成时的父级 Transform,方便统一管理猎物位置与层级")]
    public Transform root;

    [Header("猎物预制体")]
    [Tooltip("猎物的预制体(Prefab),用于实例化新的猎物对象")]
    public Prey preyPrefab;

    [Header("天敌爬行动画")]
    [Tooltip("天敌的 Animation 组件,用于播放移动、捕食等动画")]
    public Animation predatorAni;
}

👉 通过 ScriptableObject 或 Inspector 配置不同天敌及其动画效果。


4.PredatorController.cs(天敌控制器)

using UnityEngine;
using System.Collections.Generic;
using TMPro;
using System.Globalization;
using Random = UnityEngine.Random;

/// <summary>
/// 天敌控制器(PredatorController)
/// 功能:
/// 1. 控制天敌捕食猎物的过程(包含点击触发、移动路径、捕食逻辑)
/// 2. 支持对象池,减少频繁创建/销毁 Prey 对象
/// 3. 提供捕食关系表(可扩展,不同天敌对应不同猎物)
/// 4. 显示提示信息(如耗时)
/// </summary>
public class PredatorController : MonoBehaviour
{
    [Header("天敌配置")]
    [Tooltip("插入的动画和猎物配置(Prefab、动画、根节点等)")]
    public InsertAnimation config;

    [Header("移动参数")]
    [Tooltip("天敌最小移动速度")]
    public float minSpeed = 0.1f;
    [Tooltip("天敌最大移动速度")]
    public float maxSpeed = 0.2f;
    private float _speed; // 当前速度

    public bool isMove;        // 是否正在移动
    private int _moveStep;     // 当前移动阶段
    private Vector3 _targetPos; // 当前目标位置
    private Vector3[] _targetPath; // 天敌移动的路径点数组
    private int _maxPathPoints;    // 路径点数量
    private int _nowOpt;           // 当前路径点索引
    private Prey _clickPrey;       // 被点击的猎物(天敌追逐目标)

    private float _timeUsed; // 捕食过程耗时

    // 管理 Prey 的对象池
    private readonly List<Prey> _preyList = new(); // 当前活跃的猎物列表
    private readonly Queue<Prey> _preyPool = new(); // 可复用的猎物对象池

    /// <summary>
    /// 捕食关系表(可扩展:天敌种类 -> 可捕食的猎物列表)
    /// 示例: predatorPreyTable["七星瓢虫"] = new List<string> { "蚜虫", "白粉虱" };
    /// </summary>
    public Dictionary<string, List<string>> predatorPreyTable = new();

    private void OnEnable()
    {
        // 订阅 Prey 点击事件
        Prey.OnPreyClicked += HandlePreyClicked;
    }

    private void OnDisable()
    {
        // 取消订阅,避免内存泄漏
        Prey.OnPreyClicked -= HandlePreyClicked;
    }

    /// <summary>
    /// 开始狩猎,生成指定数量的猎物
    /// </summary>
    public void BeginHunt(int count)
    {
        _preyList.Clear();
        _speed = Random.Range(minSpeed, maxSpeed); // 随机速度
        _maxPathPoints = Random.Range(2, 6);       // 随机路径点数量

        // 对象池生成猎物
        for (var i = 0; i < count; i++)
        {
            Prey prey;
            if (_preyPool.Count > 0)
            {
                // 从池中取出已有的猎物
                prey = _preyPool.Dequeue();
                prey.ResetPrey();
            }
            else
            {
                // 池中没有,则实例化新的猎物
                prey = Instantiate(config.preyPrefab, config.root);
            }

            // 随机放置猎物位置
            prey.transform.localPosition = new Vector3(
                Random.Range(-0.2f, 0.2f),
                0,
                Random.Range(-0.2f, 0.2f)
            );

            _preyList.Add(prey);
            prey.gameObject.SetActive(true);
        }

        // 重置状态
        _timeUsed = 0;
        _moveStep = 0;
        isMove = false;
        TipManager.instance.ClearTip();
    }

    /// <summary>
    /// 处理猎物点击事件
    /// </summary>
    private void HandlePreyClicked(Prey prey)
    {
        if (isMove) return;              // 如果天敌正在移动,不响应
        if (!_preyList.Contains(prey)) return; // 必须是当前活跃猎物

        _clickPrey = prey;
        _targetPos = prey.transform.localPosition;

        // 生成移动路径
        _targetPath = GetPath(_maxPathPoints, config.mainHunter.transform.localPosition, _targetPos);

        // 进入移动状态
        isMove = true;
        _nowOpt = 0;
        _moveStep = 0;

        // 播放天敌移动动画
        config.predatorAni["七星瓢虫-爬"].speed = 5;
        config.predatorAni.Play("七星瓢虫-爬");
    }

    private void Update()
    {
        if (config != null)
            MainFlow();
    }

    /// <summary>
    /// 天敌移动与捕食主流程
    /// </summary>
    private void MainFlow()
    {
        if (!isMove) return;

        var frameMove = Time.deltaTime * _speed; // 当前帧的移动步长
        Vector3 nextPos;

        switch (_moveStep)
        {
            // 阶段 0:天敌沿路径移动,追逐点击的猎物
            case 0:
            {
                _timeUsed += Time.deltaTime;
                nextPos = _targetPath[_nowOpt];

                // 移动天敌
                MoveHunter(nextPos, frameMove);

                // 如果到达路径点
                if (config.mainHunter.transform.localPosition == nextPos)
                {
                    _nowOpt++;
                    // 到达终点,进入捕食阶段
                    if (_nowOpt >= _targetPath.Length)
                    {
                        _moveStep = 1;
                        ConsumePrey(_clickPrey);
                        return;
                    }
                }

                // 显示用时(取两位小数)
                TipManager.instance.ShowTip((Mathf.Round(_timeUsed * 100f) / 100f).ToString());
                break;
            }
            // 阶段 1:没有猎物了,停止
            case 1 when _preyList.Count == 0:
                isMove = false;
                config.predatorAni.Stop();
                return;

            // 阶段 1:继续追逐下一个猎物
            case 1:
            {
                nextPos = _preyList[0].transform.localPosition;
                MoveHunter(nextPos, frameMove);

                if (config.mainHunter.transform.localPosition == nextPos)
                {
                    ConsumePrey(_preyList[0]);
                }

                break;
            }
        }
    }

    /// <summary>
    /// 控制天敌移动(位置 + 朝向)
    /// </summary>
    private void MoveHunter(Vector3 target, float moveStep)
    {
        var pos = config.mainHunter.transform.localPosition;

        // 移动到目标位置
        config.mainHunter.transform.localPosition = Vector3.MoveTowards(pos, target, moveStep);

        // 平滑旋转,朝向目标
        config.mainHunter.transform.localRotation = Quaternion.Lerp(
            config.mainHunter.transform.localRotation,
            Quaternion.LookRotation(target - pos),
            5f * Time.deltaTime
        );
    }

    /// <summary>
    /// 捕食猎物:从列表移除并回收到对象池
    /// </summary>
    private void ConsumePrey(Prey prey)
    {
        if (_preyList.Contains(prey))
            _preyList.Remove(prey);

        prey.gameObject.SetActive(false); // 隐藏
        _preyPool.Enqueue(prey);          // 回收进对象池

        // 输出调试信息
        Debug.Log($"捕食 {prey.name},首次捕食时间 {prey.FirstClickTime}");
    }

    /// <summary>
    /// 生成路径点(起点 → 终点,中间有随机扰动)
    /// </summary>
    private static Vector3[] GetPath(int count, Vector3 start, Vector3 end)
    {
        var path = new Vector3[count];
        var dx = Mathf.Abs(start.x - end.x) / (count + 1);
        var dy = Mathf.Abs(start.y - end.y) / (count + 1);
        var dz = Mathf.Abs(start.z - end.z) / (count + 1);

        // 逐点生成路径
        for (var i = 0; i < path.Length - 1; i++)
        {
            path[i].x = start.x >= end.x ? start.x - (i + 1) * dx : start.x + (i + 1) * dx;
            path[i].y = start.y >= end.y ? start.y - (i + 1) * dy : start.y + (i + 1) * dy;
            path[i].z = start.z >= end.z ? start.z - (i + 1) * dz : start.z + (i + 1) * dz;
        }

        // 最后一个点是终点
        path[^1] = end;

        // 给中间点加一些随机偏移(避免路径完全直线)
        for (var i = 0; i < path.Length - 1; i++)
            path[i].x += Random.Range(-0.3f, 0.3f);

        return path;
    }
}

👉 PredatorController天敌的行为控制器,负责处理猎物点击后的捕食流程,包括移动路径规划、动画驱动、猎物消耗与回收。


5. HuntManager.cs(整体捕食控制)

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 天敌信息数据类
/// —— 绑定 UI 启动按钮 和 对应的天敌控制器
/// —— 用于在 HuntManager 中统一管理多个天敌
/// </summary>
[Serializable] // 可序列化,能在 Inspector 中显示
public class PredatorInfo
{
    [Header("开始按钮")]
    [Tooltip("点击该按钮后,启动对应的天敌捕食流程")]
    public Button startButton;

    [Header("天敌控制器")]
    [Tooltip("该按钮绑定的天敌控制器对象(PredatorController 脚本)")]
    public PredatorController predatorController;
}

/// <summary>
/// HuntManager
/// —— 捕食管理器
/// —— 主要负责:
/// 1. 绑定按钮点击事件与天敌的捕食逻辑
/// 2. 控制猎物数量
/// 3. 调用 PredatorController 来启动捕食流程
/// </summary>
public class HuntManager : MonoBehaviour
{
    [Header("猎物数量")]
    [Tooltip("每次点击按钮后,生成的猎物数量")]
    public int preyCount = 10;

    [Header("天敌信息列表")]
    [Tooltip("所有需要控制的天敌信息(按钮 + 天敌控制器的绑定关系)")]
    public List<PredatorInfo> predators;

    /// <summary>
    /// 初始化时执行
    /// —— 遍历所有天敌信息,将按钮点击事件与对应的 StartHunt() 绑定
    /// </summary>
    private void Start()
    {
        foreach (var info in predators.Where(info => info.startButton != null && info.predatorController != null))
        {
            // 给每个按钮添加点击事件,启动对应天敌的捕食逻辑
            info.startButton.onClick.AddListener(() =>
            {
                StartHunt(info.predatorController);
            });
        }
    }
    
    /// <summary>
    /// 启动某个天敌的捕食流程
    /// —— 会重置其状态,并生成猎物
    /// </summary>
    /// <param name="predator">指定的天敌控制器</param>
    private void StartHunt(PredatorController predator)
    {
        // 1. 停止天敌的移动与动画
        predator.isMove = false;
        if (predator.config != null && predator.config.predatorAni != null)
        {
            predator.config.predatorAni.Stop();
        }

        // 2. 清空提示信息
        TipManager.instance.ClearTip();

        // 3. 启动捕食逻辑(生成猎物并进入捕食状态)
        predator.BeginHunt(preyCount);
    }
}

👉 HuntManager 是核心调度器,负责启动猎物生成和捕食逻辑。


6.TipManager.cs(提示管理器)

using TMPro;
using UnityEngine;

/// <summary>
/// TipManager —— 全局提示文本管理器
/// 用于在实验过程中显示或清空提示信息(例如捕食计时、提示文字)。
/// 采用单例模式,方便在其他脚本中通过 TipManager.instance 调用。
/// </summary>
public class TipManager : MonoBehaviour
{
    /// <summary>
    /// 静态单例引用,保证全局只有一个 TipManager。
    /// 其他脚本可通过 TipManager.instance 快速访问。
    /// </summary>
    public static TipManager instance;

    [Header("共用提示文本")]
    /// <summary>
    /// UI 上用于显示提示信息的 TMP_Text 组件。
    /// 可以在 Inspector 中绑定,例如场景中的 TextMeshProUGUI。
    /// </summary>
    public TMP_Text tipText;

    /// <summary>
    /// Unity 生命周期方法 —— 脚本实例化时调用。
    /// 设置单例引用,并初始化提示文本为空。
    /// </summary>
    private void Awake()
    {
        instance = this;
        if (tipText != null)
            tipText.text = "";
    }

    /// <summary>
    /// 显示提示信息。
    /// 在运行时调用该方法可更新提示文本内容。
    /// </summary>
    /// <param name="msg">需要显示的提示内容</param>
    public void ShowTip(string msg)
    {
        if (tipText != null)
            tipText.text = msg;
    }

    /// <summary>
    /// 清空提示文本。
    /// 例如在重新开始实验或捕食流程结束时调用。
    /// </summary>
    public void ClearTip()
    {
        if (tipText != null)
            tipText.text = "";
    }
}

👉 TipManager负责界面提示(第一次捕食时间)。


⚙️脚本挂载:

1.创建一个名为【天敌捕食管理器】的空物体,挂载【HuntManager.cs】和【TipManager.cs

        在天敌信息列表中配置,创建一个按钮启动捕食功能,将创建好的【Predator】拖给按钮对应的天敌控制器,实现按钮与天敌的一对一控制。确保天敌上有爬行动画,否则注释对应的爬行动画功能。

2.创建一个名为【Predator】的空物体,挂载【PredatorController.cs】

3.猎物预制体上需要挂载【Prey.cs】,确保预制体上有【BoxCollider】组件用于交互。


🐞天敌捕食流程图:展示了 天敌启动 → 猎物生成 → 点击反馈 → 动画捕食 → 回收复用 的完整流程

HuntManager(按钮启动)
       │
       ▼
PredatorController.BeginHunt()
       │
       ▼
生成猎物(Prey对象池)
       │
点击猎物 → Prey.OnClicked()
       │                 ▲
       ▼                 │
PredatorController.HandlePreyClicked()
       │
动画捕食(config.predatorAni)
       │
       ▼
ConsumePrey() → 回收到对象池
       │
       ▼
TipManager(提示更新)

🖼️ 效果演示

  1. 点击“开始捕食”按钮,指定天敌出动。

  2. 在场景中生成多个猎物(对象池管理)。

  3. 玩家可点击生成的猎物,触发缩放反馈及捕食行为。

  4. 天敌执行捕食动画,会记录第一次捕食所用的时间,待全部捕食完成。


💡 技术要点总结

  • 对象池复用:避免频繁销毁/实例化,提高性能。

  • 接口抽象:通过 IHuntable 保证所有猎物都有统一的交互逻辑。

  • 事件驱动:使用 OnPreyClicked,避免直接耦合逻辑,方便扩展。

  • UI 控制 + 场景交互结合:按钮启动天敌 → 猎物生成 → 点击反馈 → 动画播放,完整闭环。


🚀 应用场景

该功能可以应用于:

  • 虚拟仿真教学(如食物链、生态实验)。

  • 游戏开发(捕猎玩法、反应力测试)。

  • 交互式科普展示。


📌 总结

通过本文,我们实现了一个完整的 天敌捕食猎物实验功能,涉及 UI 控制、对象池、点击反馈、动画控制 等多个常见开发要素。这不仅能直接应用到虚拟仿真实验中,也能为其他类似交互玩法提供参考。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值