Skip to content

Instantly share code, notes, and snippets.

@Andicraft
Created August 8, 2025 08:42
Show Gist options
  • Select an option

  • Save Andicraft/a80796f8542979e88b98bed352e883ec to your computer and use it in GitHub Desktop.

Select an option

Save Andicraft/a80796f8542979e88b98bed352e883ec to your computer and use it in GitHub Desktop.
Particle Wrapping for Unity Shuriken
// Copyright 2025 Iron Gate Studio AB
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
// and associated documentation files (the “Software”), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIESOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using Unity.Collections;
using UnityEngine;
using UnityEngine.ParticleSystemJobs;
[ExecuteAlways]
public class WrapParticles : MonoBehaviour
{
ParticleSystem m_ps;
[SerializeField] bool m_effectOn = true;
[SerializeField] WrapMode m_wrapMode;
[SerializeField] bool m_ignoreY;
[SerializeField] Vector3 m_wrapCenterOffset = Vector3.zero;
[SerializeField] Vector3 m_wrapBoxSize = Vector3.one * 5;
[SerializeField] float m_wrapSphereRadius = 5f;
WrapParticlesJob job = new();
void Start()
{
m_ps = GetComponent<ParticleSystem>();
if (m_ps == null)
{
ZLog.LogWarning($"{nameof(WrapParticles)} object '{gameObject.name}' is missing a particle system and disabled!");
m_effectOn = false;
}
}
// Update is called once per frame
void Update()
{
job.isGlobal = m_ps.main.simulationSpace == ParticleSystemSimulationSpace.World;
job.globalCenter = transform.position;
job.wrapMode = m_wrapMode;
job.sphereRadius = m_wrapSphereRadius;
job.sphereRadiusSqr = m_wrapSphereRadius * m_wrapSphereRadius;
job.boxSize = m_wrapBoxSize;
job.wrapCenterOffset = m_wrapCenterOffset;
job.ignoreY = m_ignoreY;
}
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
switch (m_wrapMode)
{
case WrapMode.Box:
Gizmos.DrawWireCube(transform.position + m_wrapCenterOffset, m_wrapBoxSize);
break;
case WrapMode.Sphere:
Gizmos.DrawWireSphere(transform.position + m_wrapCenterOffset, m_wrapSphereRadius);
break;
}
}
private void OnParticleUpdateJobScheduled()
{
if(m_ps == null) // can sometimes be called before start for some obscure reason
{
m_ps = GetComponent<ParticleSystem>();
if(m_ps == null)
{
ZLog.LogWarning($"{nameof(WrapParticles)} object '{gameObject.name}' is missing a particle system and disabled!");
m_effectOn = false;
}
}
if (m_effectOn)
job.Schedule(m_ps, 256);
}
// If you use Unity.Mathematics you can switch out the Vector3s for float3 and mark this [BurstCompile]
// for increased performance
struct WrapParticlesJob : IJobParticleSystemParallelFor
{
[ReadOnly] public bool isGlobal;
[ReadOnly] public Vector3 globalCenter;
[ReadOnly] public WrapMode wrapMode;
[ReadOnly] public Vector3 boxSize;
[ReadOnly] public float sphereRadiusSqr;
[ReadOnly] public float sphereRadius;
[ReadOnly] public Vector3 wrapCenterOffset;
[ReadOnly] public bool ignoreY;
public void Execute(ParticleSystemJobData particles, int i)
{
Vector3 pos = new(particles.positions.x[i], particles.positions.y[i], particles.positions.z[i]);
Vector3 center = (isGlobal ? globalCenter : Vector3.zero) + wrapCenterOffset;
if (ignoreY)
{
pos.y = 0;
center.y = 0;
}
switch (wrapMode)
{
case WrapMode.Box:
var dist = pos - center;
if (Mathf.Abs(dist.x) > boxSize.x/2)
pos.x -= Mathf.Sign(dist.x) * boxSize.x;
if (!ignoreY && Mathf.Abs(dist.y) > boxSize.y/2)
pos.y -= Mathf.Sign(dist.y) * boxSize.y;
if (Mathf.Abs(dist.z) > boxSize.z/2)
pos.z -= Mathf.Sign(dist.z) * boxSize.z;
break;
case WrapMode.Sphere:
if (pos.SquaredDistanceTo(center) < sphereRadiusSqr)
return;
var dir = (pos - center).normalized;
pos -= dir * sphereRadius*2;
break;
}
var posX = particles.positions.x;
var posY = particles.positions.y;
var posZ = particles.positions.z;
posX[i] = pos.x;
if (!ignoreY)
posY[i] = pos.y;
posZ[i] = pos.z;
}
}
[Serializable]
public enum WrapMode
{
Sphere,
Box
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment