Quick answer: Enable the Particle System’s Collision module, set Type to World, check Send Collision Messages, and make sure the script with OnParticleCollision is on the GameObject the particles hit (not the emitter). Layer mask must include the target’s layer.
Here is how to fix Unity particles that visually collide with surfaces but never fire OnParticleCollision. You hook up a damage script to a target so bullets-as-particles trigger health loss, but the callback never runs. The Send Collision Messages flag is per-system and off by default; without it, the particle system bounces off colliders silently.
The Symptom
Particles visibly collide with the world (you see them stop or bounce), but the OnParticleCollision(GameObject other) message never reaches your script. No errors. No warnings. The particles know about collisions; your code does not.
What Causes This
Send Collision Messages disabled. Without this checkbox in the Collision module, the system performs collisions but does not dispatch messages.
Script on the wrong GameObject. OnParticleCollision fires on the GameObject that owns the collider being hit, not on the particle system. A script on the emitter never receives messages.
Collision layer mismatch. The Collision module has a Collides With layer mask. If the target is on Layer 8 but the mask only includes Layer 0 (Default), no collision is detected.
2D vs 3D mismatch. Particle System collisions are 3D by default. 2D colliders need Type = World and a 2D physics layer setup, plus the right collision mode.
The Fix
Step 1: Enable the Collision module correctly. On the Particle System, expand Collision:
Type: World
Mode: 3D (or 2D for sprite-based games)
Send Collision Messages: true
Visualize Bounds: on (helpful while debugging)
Collides With: Default + your custom layer
Collision Quality: High
Voxel Size: 0.5
Step 2: Place OnParticleCollision on the target.
using UnityEngine;
public class EnemyHealth : MonoBehaviour
{
[SerializeField] private float hp = 100f;
void OnParticleCollision(GameObject other)
{
// other = the particle system GameObject
hp -= 10f;
if (hp <= 0) Destroy(gameObject);
}
}
Attach this to the enemy, not the bullet’s ParticleSystem.
Step 3: Read collision details if needed.
using System.Collections.Generic;
ParticleCollisionEvent[] events = new ParticleCollisionEvent[16];
void OnParticleCollision(GameObject other)
{
ParticleSystem ps = other.GetComponent<ParticleSystem>();
int count = ps.GetCollisionEvents(gameObject, events);
for (int i = 0; i < count; i++)
{
Vector3 hitPoint = events[i].intersection;
Vector3 hitNormal = events[i].normal;
// spawn impact effect at hitPoint
}
}
Step 4: Match physics layers and tags. Ensure the target’s layer is included in the Particle System Collision module’s Collides With mask. Open Edit → Project Settings → Physics and verify the Particle Collision Layer Matrix permits the interaction.
Step 5: Test with a known-good setup. Drop a default cube on the floor with the EnemyHealth script attached. Aim a default Particle System with Collision enabled at it. If the message fires, your config is correct; iterate from there.
Performance Considerations
World collision with high quality is expensive at high particle counts. For magic-bullet effects with hundreds of particles, consider Planes mode (cheap), or use raycasts for a single bullet and ParticleSystem only as visual.
“Send Collision Messages flag, script on the target, layer mask matches. Three checks for working callbacks.”
Related Issues
For particles failing to play, see Particle System Not Playing. For trail rendering issues, see Particle Trail Through Walls.
Module on. Flag on. Script on the target. Layers match. Messages flow.