Unity 粒子系统 Triggers 使用解析:监听粒子进入与离开区域并触发事件

【Unity笔记】Unity 粒子系统 Triggers 使用解析:监听粒子进入与离开区域并触发事件

引言

Unity 粒子系统(Particle System)的 Triggers 模块 是一个强大的交互功能,用于检测粒子进入/离开指定碰撞器区域并触发自定义回调。它支持 粒子生命周期事件(Enter/Exit/Inside)、碰撞类型过滤自定义脚本响应,适用于特效交互(如火花触碰敌人)、物理模拟(如水滴落地)和游戏逻辑(如魔法粒子击中目标)。

核心原理

  • Triggers 模块通过物理引擎(Physics System)检测粒子与 Collider 的碰撞
  • 支持多种触发模式:Inside(粒子在区域内)、Outside(区域外)、Enter(进入)、Exit(离开)
  • 每帧检查粒子的位置/速度,与 Collider 边界进行空间查询
  • 可触发 OnParticleTrigger() 回调,访问粒子数据(位置、速度、寿命等)

性能特点

  • 时间复杂度 O(n),n 为活动粒子数
  • 推荐 Collider 为简单几何体(Box/Sphere)避免复杂 Mesh 计算
  • 大量粒子场景需优化触发频率和区域大小

适用版本:Unity 2018.4+(推荐 2022.3 LTS),兼容 URP/HDRP。

Triggers 模块核心设置

1. 基本配置步骤

  1. 创建粒子系统:GameObject > Effects > Particle System
  2. 添加 Collider:在场景中创建触发区域(Box Collider、Sphere Collider 等),取消 “Is Trigger”(Triggers 模块需要物理碰撞检测)
  3. 启用 Triggers 模块
  • 选中 Particle System > Modules > Triggers > 勾选 “Enabled”
  • Colliders:拖拽触发区域的 Collider 到列表
  • Inside:粒子进入区域后的行为(Callback/Callback Separation/Kill)
  • Outside:粒子离开区域的行为
  • Enter:进入瞬间触发
  • Exit:离开瞬间触发

2. 触发模式详解

模式描述典型应用
Inside粒子持续在 Collider 内持续伤害区域、风力场
Outside粒子持续在 Collider 外安全区检测
Enter粒子首次进入(单次触发)粒子击中、爆炸触发
Exit粒子离开(单次触发)脱离效果、状态恢复

Callback Separation:每个 Collider 可独立配置触发模式(例如 Collider1=Enter,Collider2=Inside)

核心脚本实现

3. OnParticleTrigger 回调基础

using UnityEngine;

public class ParticleTriggerHandler : MonoBehaviour
{
    private ParticleSystem particleSystem;
    private ParticleSystem.TriggerModule triggerModule;

    void Start()
    {
        particleSystem = GetComponent<ParticleSystem>();
        triggerModule = particleSystem.trigger;

        // 配置触发器
        SetupTriggers();

        // 注册回调
        var main = particleSystem.main;
        main.stopAction = ParticleSystemStopBehavior.Callback;  // 粒子停止时回调
    }

    private void SetupTriggers()
    {
        // 添加 Collider 引用(运行时动态添加)
        triggerModule.AddCollider(gameObject.AddComponent<SphereCollider>());

        // 配置触发模式
        triggerModule.inside = ParticleSystemTriggerAction.Callback;
        triggerModule.outside = ParticleSystemTriggerAction.None;
        triggerModule.enter = ParticleSystemTriggerAction.Callback;
        triggerModule.exit = ParticleSystemTriggerAction.Callback;

        // 启用碰撞形状(优化性能)
        triggerModule.colliderInteractionType = ParticleSystemColliderInteractionType.ForceBoth;
    }

    // 核心回调:每帧检查触发粒子
    void OnParticleTrigger()
    {
        int triggerCount = particleSystem.GetTriggerParticles(ParticleSystemTriggerEventType.Inside);
        for (int i = 0; i < triggerCount; i++)
        {
            ParticleSystem.TriggerEvent triggerEvent = particleSystem.GetTriggerEvent(i, ParticleSystemTriggerEventType.Inside);
            HandleInsideTrigger(triggerEvent);
        }

        // 处理其他触发类型
        ProcessTriggerEvents(ParticleSystemTriggerEventType.Enter);
        ProcessTriggerEvents(ParticleSystemTriggerEventType.Exit);
    }
}

4. 详细触发事件处理

private void ProcessTriggerEvents(ParticleSystemTriggerEventType eventType)
{
    int count = particleSystem.GetTriggerParticles(eventType);
    for (int i = 0; i < count; i++)
    {
        ParticleSystem.TriggerEvent trigger = particleSystem.GetTriggerEvent(i, eventType);

        switch (eventType)
        {
            case ParticleSystemTriggerEventType.Enter:
                OnParticleEnter(trigger);
                break;
            case ParticleSystemTriggerEventType.Exit:
                OnParticleExit(trigger);
                break;
            case ParticleSystemTriggerEventType.Inside:
                OnParticleInside(trigger);
                break;
            case ParticleSystemTriggerEventType.Outside:
                OnParticleOutside(trigger);
                break;
        }
    }
}

private void OnParticleEnter(ParticleSystem.TriggerEvent trigger)
{
    int particleIndex = trigger.index;
    ParticleSystem.Particle particle = particleSystem.GetParticleByIndex(particleIndex);

    // 示例:粒子击中目标,改变颜色并减少生命
    particle.startColor = Color.red;
    particle.remainingLifetime *= 0.5f;  // 加速消亡

    // 触发游戏逻辑
    DealDamageAtPosition(particle.position);

    particleSystem.SetParticles(new ParticleSystem.Particle[] { particle }, 1);
}

private void OnParticleInside(ParticleSystem.TriggerEvent trigger)
{
    ParticleSystem.Particle particle = particleSystem.GetParticleByIndex(trigger.index);

    // 持续效果:减速、旋转等
    particle.velocity *= 0.95f;  // 阻力
    particle.angularVelocity = Vector3.up * 90f;  // 旋转

    particleSystem.SetParticles(new ParticleSystem.Particle[] { particle }, 1);
}

private void OnParticleExit(ParticleSystem.TriggerEvent trigger)
{
    // 粒子离开:恢复属性或销毁
    ParticleSystem.Particle particle = particleSystem.GetParticleByIndex(trigger.index);
    particle.startSize *= 2f;  // 放大离开效果
    particleSystem.SetParticles(new ParticleSystem.Particle[] { particle }, 1);
}

private void DealDamageAtPosition(Vector3 position)
{
    // 射线检测击中物体
    RaycastHit hit;
    if (Physics.Raycast(position, Vector3.down, out hit, 1f))
    {
        IDamageable damageable = hit.collider.GetComponent<IDamageable>();
        damageable?.TakeDamage(10f);
    }
}

5. 性能优化版本(批量处理)

public class OptimizedParticleTrigger : MonoBehaviour
{
    private ParticleSystem particleSystem;
    private NativeArray<ParticleSystem.Particle> particles;

    void OnEnable()
    {
        particleSystem = GetComponent<ParticleSystem>();
        var main = particleSystem.main;
        main.maxParticles = 1000;  // 设置上限

        // 预分配 NativeArray(Burst 兼容)
        particles = new NativeArray<ParticleSystem.Particle>(main.maxParticles, Allocator.Persistent);
    }

    void OnParticleTrigger()
    {
        // 批量获取触发粒子(避免逐个 GetParticle)
        int enterCount = particleSystem.GetTriggerParticles(ParticleSystemTriggerEventType.Enter);
        int insideCount = particleSystem.GetTriggerParticles(ParticleSystemTriggerEventType.Inside);

        // 批量处理 Enter 事件
        ProcessBatchEvents(ParticleSystemTriggerEventType.Enter, enterCount);

        // 批量处理 Inside 事件
        ProcessBatchEvents(ParticleSystemTriggerEventType.Inside, insideCount);
    }

    private void ProcessBatchEvents(ParticleSystemTriggerEventType eventType, int count)
    {
        if (count == 0) return;

        // 获取所有触发粒子的索引
        NativeArray<int> triggerIndices = new NativeArray<int>(count, Allocator.Temp);
        particleSystem.GetTriggerIndices(triggerIndices, eventType);

        // 批量获取粒子数据
        particleSystem.GetParticles(particles);

        for (int i = 0; i < count; i++)
        {
            int index = triggerIndices[i];
            if (index < particles.Length)
            {
                ProcessSingleParticle(particles, index, eventType);
            }
        }

        // 批量设置修改后的粒子
        particleSystem.SetParticles(particles);
        triggerIndices.Dispose();
    }

    private void ProcessSingleParticle(NativeArray<ParticleSystem.Particle> particles, int index, ParticleSystemTriggerEventType eventType)
    {
        ref var particle = ref particles[index];

        switch (eventType)
        {
            case ParticleSystemTriggerEventType.Enter:
                particle.startColor = Color.yellow;
                // 记录击中位置用于后续逻辑
                RecordHit(particle.position);
                break;
            case ParticleSystemTriggerEventType.Inside:
                // 持续效果
                particle.velocity *= 0.98f;
                break;
        }
    }

    void OnDisable()
    {
        particles.Dispose();
    }
}

高级应用场景

6. 魔法粒子击中系统

public class MagicParticleSystem : MonoBehaviour
{
    [Header("魔法设置")]
    public float damagePerParticle = 5f;
    public ParticleSystem magicParticles;
    public LayerMask targetLayers;

    private HashSet<Collider> affectedTargets = new HashSet<Collider>();

    void OnParticleTrigger()
    {
        // Enter 事件:首次击中
        int enterCount = magicParticles.GetTriggerParticles(ParticleSystemTriggerEventType.Enter);
        for (int i = 0; i < enterCount; i++)
        {
            ParticleSystem.TriggerEvent trigger = magicParticles.GetTriggerEvent(i, ParticleSystemTriggerEventType.Enter);
            Collider targetCollider = trigger.colliderComponent;

            if (targetCollider != null && (targetLayers & (1 << targetCollider.gameObject.layer)) != 0)
            {
                affectedTargets.Add(targetCollider);
                ApplyMagicEffect(targetCollider);
            }
        }

        // Inside 事件:持续伤害
        ProcessContinuousDamage();
    }

    private void ApplyMagicEffect(Collider target)
    {
        // 视觉效果:目标发光
        var renderer = target.GetComponent<Renderer>();
        if (renderer != null)
        {
            renderer.material.SetFloat("_Emission", 2f);
        }

        // 触发魔法逻辑
        IMagicTarget magicTarget = target.GetComponent<IMagicTarget>();
        magicTarget?.OnMagicHit();
    }

    private void ProcessContinuousDamage()
    {
        foreach (var target in affectedTargets)
        {
            Health health = target.GetComponent<Health>();
            if (health != null)
            {
                health.TakeDamage(damagePerParticle * Time.deltaTime);
            }
        }
    }
}

7. 环境交互粒子系统

public class EnvironmentParticleTrigger : MonoBehaviour
{
    [Header("环境效果")]
    public ParticleSystem fireParticles;
    public ParticleSystem waterParticles;
    public ParticleSystem windParticles;

    private Dictionary<Collider, ParticleEffectType> activeEffects = new Dictionary<Collider, ParticleEffectType>();

    public enum ParticleEffectType { Fire, Water, Wind, None }

    void OnParticleTrigger()
    {
        ProcessEnvironmentalInteractions(ParticleSystemTriggerEventType.Enter);
        ProcessEnvironmentalInteractions(ParticleSystemTriggerEventType.Inside);
    }

    private void ProcessEnvironmentalInteractions(ParticleSystemTriggerEventType eventType)
    {
        int count = fireParticles.GetTriggerParticles(eventType);
        for (int i = 0; i < count; i++)
        {
            var trigger = fireParticles.GetTriggerEvent(i, eventType);
            var targetCollider = trigger.colliderComponent;

            if (targetCollider != null)
            {
                HandleEnvironmentalInteraction(targetCollider, ParticleEffectType.Fire, eventType);
            }
        }
    }

    private void HandleEnvironmentalInteraction(Collider target, ParticleEffectType effectType, ParticleSystemTriggerEventType eventType)
    {
        IEnvironmentalInteractable interactable = target.GetComponent<IEnvironmentalInteractable>();

        if (eventType == ParticleSystemTriggerEventType.Enter)
        {
            interactable?.OnParticleEnter(effectType);
            activeEffects[target] = effectType;
        }
        else if (eventType == ParticleSystemTriggerEventType.Inside)
        {
            interactable?.OnParticleStay(effectType);
        }
        else if (eventType == ParticleSystemTriggerEventType.Exit)
        {
            interactable?.OnParticleExit(effectType);
            activeEffects.Remove(target);
        }
    }
}

性能优化与最佳实践

8. 性能优化技巧

  1. 减少检查频率
   [Header("性能设置")]
   public float triggerCheckInterval = 0.1f;  // 每 0.1s 检查一次
   private float lastTriggerCheck;

   void Update()
   {
       if (Time.time - lastTriggerCheck > triggerCheckInterval)
       {
           lastTriggerCheck = Time.time;
           // 执行触发检查
       }
   }
  1. 空间分区优化
  • 使用多个小 Collider 替代一个大 Collider
  • 启用 Layer-based 过滤:LayerMask 只检测特定层
  1. 粒子池管理
   private void OptimizeParticleCount()
   {
       var main = particleSystem.main;
       main.maxParticles = 500;  // 限制最大粒子数
       main.startLifetime = 2f;  // 缩短寿命,减少持续计算
   }
  1. LOD 系统
   public void SetTriggerLOD(float distance)
   {
       bool enableTriggers = distance < 50f;  // 远距离禁用 Triggers
       triggerModule.enabled = enableTriggers;
   }

9. 调试与可视化

void OnDrawGizmos()
{
    if (triggerModule.enabled)
    {
        // 可视化触发区域
        foreach (var collider in triggerModule.colliders)
        {
            if (collider != null)
            {
                Gizmos.color = Color.yellow;
                Gizmos.DrawWireCube(collider.bounds.center, collider.bounds.size);
            }
        }

        // 显示触发统计
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireSphere(transform.position, 1f);
        UnityEditor.Handles.Label(transform.position + Vector3.up * 2f, 
            $"Active Triggers: {particleSystem.GetTriggerParticles(ParticleSystemTriggerEventType.Inside)}");
    }
}

常见问题与解决方案

问题原因解决方案
触发不准确Collider 太小/粒子速度快增大 Collider,启用 Continuous Collision
性能瓶颈粒子数量过多限制 maxParticles,使用 LOD,批量处理
误触发Layer 冲突设置正确的 LayerMask,排除不相关物体
回调不执行模块未启用检查 Triggers 模块和 Collider 引用
URP 兼容渲染管线问题确保粒子材质兼容 URP Shader

完整使用示例

创建一个火球粒子系统,击中敌人造成伤害:

  1. 创建 Particle System(Fireball)
  2. 添加 Sphere Collider 到敌人
  3. 启用 Triggers 模块,设置 Enter=Callback
  4. 附加脚本处理 OnParticleTrigger
  5. 测试:火球击中敌人触发伤害和特效

Triggers 模块为粒子系统提供了强大的交互能力,通过合理配置和优化,可实现复杂的特效逻辑。如果需要特定场景的完整实现或性能分析,请提供更多细节!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注