571 lines
17 KiB
C#
571 lines
17 KiB
C#
|
|
// =================================
|
|
// Namespaces.
|
|
// =================================
|
|
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
// =================================
|
|
// Define namespace.
|
|
// =================================
|
|
|
|
namespace MirzaBeig
|
|
{
|
|
|
|
namespace Scripting
|
|
{
|
|
|
|
namespace Effects
|
|
{
|
|
|
|
// =================================
|
|
// Classes.
|
|
// =================================
|
|
|
|
public abstract class ParticleForceField : MonoBehaviour
|
|
{
|
|
// =================================
|
|
// Nested classes and structures.
|
|
// =================================
|
|
|
|
// ...
|
|
|
|
protected struct GetForceParameters
|
|
{
|
|
public float distanceToForceFieldCenterSqr;
|
|
|
|
public Vector3 scaledDirectionToForceFieldCenter;
|
|
public Vector3 particlePosition;
|
|
}
|
|
|
|
// =================================
|
|
// Variables.
|
|
// =================================
|
|
|
|
// ...
|
|
|
|
[Header("Common Controls")]
|
|
|
|
[Tooltip("Force field spherical range.")]
|
|
public float radius = Mathf.Infinity;
|
|
|
|
[Tooltip("Maximum baseline force.")]
|
|
public float force = 5.0f;
|
|
|
|
[Tooltip("Internal force field position offset.")]
|
|
public Vector3 center = Vector3.zero;
|
|
|
|
public float scaledRadius
|
|
{
|
|
get
|
|
{
|
|
return radius * transform.lossyScale.x;
|
|
}
|
|
}
|
|
|
|
float _radius;
|
|
float radiusSqr;
|
|
|
|
Vector3 transformPosition;
|
|
|
|
float[] particleSystemExternalForcesMultipliers;
|
|
|
|
[Tooltip("Force scale as determined by distance to individual particles.")]
|
|
public AnimationCurve forceOverDistance = new AnimationCurve(
|
|
|
|
new Keyframe(0.0f, 1.0f),
|
|
new Keyframe(1.0f, 1.0f)
|
|
|
|
);
|
|
|
|
// If (attached to a particle system): forces will be LOCAL.
|
|
// Else if (attached to a particle system): forces will be SELECTIVE.
|
|
// Else: forces will be GLOBAL.
|
|
|
|
new ParticleSystem particleSystem;
|
|
|
|
[Tooltip(
|
|
"If nothing no particle systems are assigned, this force field will operate globally on ALL particle systems in the scene (NOT recommended).\n\n" +
|
|
"If attached to a particle system, the force field will operate only on that system.\n\n" +
|
|
"If specific particle systems are assigned, then the force field will operate on those systems only, even if attached to a particle system.")]
|
|
|
|
public List<ParticleSystem> _particleSystems;
|
|
|
|
int particleSystemsCount;
|
|
|
|
// All required particle systems that will actually be used.
|
|
|
|
List<ParticleSystem> particleSystems = new List<ParticleSystem>();
|
|
|
|
// Particles in each system.
|
|
|
|
// Second dimension initialized to max particle count
|
|
// and not modified unless max particle count for that system changes.
|
|
|
|
// Prevents allocations each frame.
|
|
|
|
ParticleSystem.Particle[][] particleSystemParticles;
|
|
ParticleSystem.MainModule[] particleSystemMainModules;
|
|
|
|
Renderer[] particleSystemRenderers;
|
|
|
|
// ^I could also just put the system and the module in a struct and make a 2D array
|
|
// instead of having a seperate array for the modules.
|
|
|
|
// Current iteration of the particle systems when looping through all of them.
|
|
// Useful to derived classes (like for the vortex particle affector).
|
|
|
|
protected ParticleSystem currentParticleSystem;
|
|
|
|
// Parameters used by derived force classes.
|
|
|
|
protected GetForceParameters parameters;
|
|
|
|
// Update even when entire particle system is invisible?
|
|
|
|
// Default: FALSE -> if all particles are invisible/offscreen, update will not execute.
|
|
|
|
[Tooltip(
|
|
"If TRUE, update even if target particle system(s) are invisible/offscreen.\n\n" +
|
|
"If FALSE, update only if particles of the target system(s) are visible/onscreen.")]
|
|
|
|
public bool alwaysUpdate = false;
|
|
|
|
// =================================
|
|
// Functions.
|
|
// =================================
|
|
|
|
// ...
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
|
|
}
|
|
|
|
// ...
|
|
|
|
protected virtual void Start()
|
|
{
|
|
particleSystem = GetComponent<ParticleSystem>();
|
|
}
|
|
|
|
// Called once per particle system, before entering second loop for its particles.
|
|
// Used for setting up based on particle system-specific data.
|
|
|
|
protected virtual void PerParticleSystemSetup()
|
|
{
|
|
|
|
}
|
|
|
|
// Direction is NOT normalized.
|
|
|
|
protected virtual Vector3 GetForce()
|
|
{
|
|
return Vector3.zero;
|
|
}
|
|
|
|
// ...
|
|
|
|
protected virtual void Update()
|
|
{
|
|
|
|
}
|
|
|
|
// Add/remove from public list of particles being managed.
|
|
// Remember that adding specific systems will override all
|
|
// other contexts (global and pure local).
|
|
|
|
// Duplicates are not checked (intentionally).
|
|
|
|
public void AddParticleSystem(ParticleSystem particleSystem)
|
|
{
|
|
_particleSystems.Add(particleSystem);
|
|
}
|
|
public void RemoveParticleSystem(ParticleSystem particleSystem)
|
|
{
|
|
_particleSystems.Remove(particleSystem);
|
|
}
|
|
|
|
// ...
|
|
|
|
protected virtual void LateUpdate()
|
|
{
|
|
_radius = scaledRadius;
|
|
radiusSqr = _radius * _radius;
|
|
|
|
transformPosition = transform.position + center;
|
|
|
|
// SELECTIVE.
|
|
// If manually assigned a set of systems, use those no matter what.
|
|
|
|
if (_particleSystems.Count != 0)
|
|
{
|
|
// If editor array size changed, clear and add again.
|
|
|
|
if (particleSystems.Count != _particleSystems.Count)
|
|
{
|
|
particleSystems.Clear();
|
|
particleSystems.AddRange(_particleSystems);
|
|
}
|
|
|
|
// Else if array size is the same, then re-assign from
|
|
// the editor array. I do this in case the elements are different
|
|
// even though the size is the same.
|
|
|
|
else
|
|
{
|
|
for (int i = 0; i < _particleSystems.Count; i++)
|
|
{
|
|
particleSystems[i] = _particleSystems[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// LOCAL.
|
|
// Else if attached to particle system, use only that.
|
|
// Obviously, this will only happen if there are no systems specified in the array.
|
|
|
|
else if (particleSystem)
|
|
{
|
|
// If just one element, assign as local PS component.
|
|
|
|
if (particleSystems.Count == 1)
|
|
{
|
|
particleSystems[0] = particleSystem;
|
|
}
|
|
|
|
// Else, clear entire array and add only the one.
|
|
|
|
else
|
|
{
|
|
particleSystems.Clear();
|
|
particleSystems.Add(particleSystem);
|
|
}
|
|
}
|
|
|
|
// GLOBAL.
|
|
// Else, take all the ones from the entire scene.
|
|
|
|
// This is the most expensive since it searches the entire scene
|
|
// and also requires an allocation for every frame due to not knowing
|
|
// if the particle systems are all the same from the last frame unless
|
|
// I had a list to compare to from last frame. In that case, I'm not sure
|
|
// if the performance would be better or worse. Do a test later?
|
|
|
|
else
|
|
{
|
|
particleSystems.Clear();
|
|
particleSystems.AddRange(FindObjectsOfType<ParticleSystem>());
|
|
}
|
|
|
|
parameters = new GetForceParameters();
|
|
|
|
particleSystemsCount = particleSystems.Count;
|
|
|
|
// If first frame (array is null) or length is less than the number of systems, initialize size of array.
|
|
// I never shrink the array. Not sure if that's potentially super bad? I could always throw in a public
|
|
// bool as an option to allow shrinking since there's a performance benefit for each, but depends on the
|
|
// implementation case.
|
|
|
|
if (particleSystemParticles == null || particleSystemParticles.Length < particleSystemsCount)
|
|
{
|
|
particleSystemParticles = new ParticleSystem.Particle[particleSystemsCount][];
|
|
particleSystemMainModules = new ParticleSystem.MainModule[particleSystemsCount];
|
|
|
|
particleSystemRenderers = new Renderer[particleSystemsCount];
|
|
particleSystemExternalForcesMultipliers = new float[particleSystemsCount];
|
|
|
|
for (int i = 0; i < particleSystemsCount; i++)
|
|
{
|
|
particleSystemMainModules[i] = particleSystems[i].main;
|
|
particleSystemRenderers[i] = particleSystems[i].GetComponent<Renderer>();
|
|
|
|
particleSystemExternalForcesMultipliers[i] = particleSystems[i].externalForces.multiplier;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < particleSystemsCount; i++)
|
|
{
|
|
if (!particleSystemRenderers[i].isVisible && !alwaysUpdate)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
int maxParticles = particleSystemMainModules[i].maxParticles;
|
|
|
|
// Force delta time should reflect particle system delta time mode.
|
|
|
|
float forceDeltaTime = force * (particleSystemMainModules[i].useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
|
|
|
|
if (particleSystemParticles[i] == null || particleSystemParticles[i].Length < maxParticles)
|
|
{
|
|
particleSystemParticles[i] = new ParticleSystem.Particle[maxParticles];
|
|
}
|
|
|
|
currentParticleSystem = particleSystems[i];
|
|
|
|
PerParticleSystemSetup();
|
|
|
|
int particleCount = currentParticleSystem.GetParticles(particleSystemParticles[i]);
|
|
|
|
ParticleSystemSimulationSpace simulationSpace = particleSystemMainModules[i].simulationSpace;
|
|
ParticleSystemScalingMode scalingMode = particleSystemMainModules[i].scalingMode;
|
|
|
|
// I could also store the transforms in an array similar to what I do with modules.
|
|
// Or, put all of those together into a struct and make an array out of that since
|
|
// they'll always be assigned/updated at the same time.
|
|
|
|
Transform currentParticleSystemTransform = currentParticleSystem.transform;
|
|
Transform customSimulationSpaceTransform = particleSystemMainModules[i].customSimulationSpace;
|
|
|
|
// If in world space, there's no need to do any of the extra calculations... simplify the loop!
|
|
|
|
if (simulationSpace == ParticleSystemSimulationSpace.World)
|
|
{
|
|
for (int j = 0; j < particleCount; j++)
|
|
{
|
|
parameters.particlePosition = particleSystemParticles[i][j].position;
|
|
|
|
parameters.scaledDirectionToForceFieldCenter.x = transformPosition.x - parameters.particlePosition.x;
|
|
parameters.scaledDirectionToForceFieldCenter.y = transformPosition.y - parameters.particlePosition.y;
|
|
parameters.scaledDirectionToForceFieldCenter.z = transformPosition.z - parameters.particlePosition.z;
|
|
|
|
parameters.distanceToForceFieldCenterSqr = parameters.scaledDirectionToForceFieldCenter.sqrMagnitude;
|
|
|
|
if (parameters.distanceToForceFieldCenterSqr < radiusSqr)
|
|
{
|
|
float distanceToCenterNormalized = parameters.distanceToForceFieldCenterSqr / radiusSqr;
|
|
float distanceScale = forceOverDistance.Evaluate(distanceToCenterNormalized);
|
|
|
|
Vector3 force = GetForce();
|
|
float forceScale = (forceDeltaTime * distanceScale) * particleSystemExternalForcesMultipliers[i];
|
|
|
|
force.x *= forceScale;
|
|
force.y *= forceScale;
|
|
force.z *= forceScale;
|
|
|
|
Vector3 particleVelocity = particleSystemParticles[i][j].velocity;
|
|
|
|
particleVelocity.x += force.x;
|
|
particleVelocity.y += force.y;
|
|
particleVelocity.z += force.z;
|
|
|
|
particleSystemParticles[i][j].velocity = particleVelocity;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector3 particleSystemPosition = Vector3.zero;
|
|
Quaternion particleSystemRotation = Quaternion.identity;
|
|
Vector3 particleSystemLocalScale = Vector3.one;
|
|
|
|
Transform simulationSpaceTransform = currentParticleSystemTransform;
|
|
|
|
switch (simulationSpace)
|
|
{
|
|
case ParticleSystemSimulationSpace.Local:
|
|
{
|
|
particleSystemPosition = simulationSpaceTransform.position;
|
|
particleSystemRotation = simulationSpaceTransform.rotation;
|
|
particleSystemLocalScale = simulationSpaceTransform.localScale;
|
|
|
|
break;
|
|
}
|
|
case ParticleSystemSimulationSpace.Custom:
|
|
{
|
|
simulationSpaceTransform = customSimulationSpaceTransform;
|
|
|
|
particleSystemPosition = simulationSpaceTransform.position;
|
|
particleSystemRotation = simulationSpaceTransform.rotation;
|
|
particleSystemLocalScale = simulationSpaceTransform.localScale;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
throw new System.NotSupportedException(
|
|
|
|
string.Format("Unsupported scaling mode '{0}'.", simulationSpace));
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < particleCount; j++)
|
|
{
|
|
parameters.particlePosition = particleSystemParticles[i][j].position;
|
|
|
|
switch (simulationSpace)
|
|
{
|
|
case ParticleSystemSimulationSpace.Local:
|
|
case ParticleSystemSimulationSpace.Custom:
|
|
{
|
|
switch (scalingMode)
|
|
{
|
|
case ParticleSystemScalingMode.Hierarchy:
|
|
{
|
|
parameters.particlePosition = simulationSpaceTransform.TransformPoint(particleSystemParticles[i][j].position);
|
|
|
|
break;
|
|
}
|
|
case ParticleSystemScalingMode.Local:
|
|
{
|
|
// Order is important.
|
|
|
|
parameters.particlePosition = Vector3.Scale(parameters.particlePosition, particleSystemLocalScale);
|
|
parameters.particlePosition = particleSystemRotation * parameters.particlePosition;
|
|
parameters.particlePosition = parameters.particlePosition + particleSystemPosition;
|
|
|
|
break;
|
|
}
|
|
case ParticleSystemScalingMode.Shape:
|
|
{
|
|
parameters.particlePosition = particleSystemRotation * parameters.particlePosition;
|
|
parameters.particlePosition = parameters.particlePosition + particleSystemPosition;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
throw new System.NotSupportedException(
|
|
|
|
string.Format("Unsupported scaling mode '{0}'.", scalingMode));
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
parameters.scaledDirectionToForceFieldCenter.x = transformPosition.x - parameters.particlePosition.x;
|
|
parameters.scaledDirectionToForceFieldCenter.y = transformPosition.y - parameters.particlePosition.y;
|
|
parameters.scaledDirectionToForceFieldCenter.z = transformPosition.z - parameters.particlePosition.z;
|
|
|
|
parameters.distanceToForceFieldCenterSqr = parameters.scaledDirectionToForceFieldCenter.sqrMagnitude;
|
|
|
|
//particleSystemParticles[i][j].velocity += forceDeltaTime * Vector3.Normalize(parameters.scaledDirectionToForceFieldCenter);
|
|
|
|
if (parameters.distanceToForceFieldCenterSqr < radiusSqr)
|
|
{
|
|
// 0.0f -> 0.99...f;
|
|
|
|
float distanceToCenterNormalized = parameters.distanceToForceFieldCenterSqr / radiusSqr;
|
|
|
|
// Evaluating a curve within a loop which is very likely to exceed a few thousand
|
|
// iterations produces a noticeable FPS drop (around minus 2 - 5). Might be a worthwhile
|
|
// optimization to check outside all loops if the curve is constant (all keyframes same value),
|
|
// and then run a different block of code if true that uses that value as a stored float without
|
|
// having to call Evaluate(t).
|
|
|
|
float distanceScale = forceOverDistance.Evaluate(distanceToCenterNormalized);
|
|
|
|
// Expanded vector operations for optimization. I think this is already done by
|
|
// the compiler, but it's nice to have for the editor anyway.
|
|
|
|
Vector3 force = GetForce();
|
|
float forceScale = (forceDeltaTime * distanceScale) * particleSystemExternalForcesMultipliers[i];
|
|
|
|
force.x *= forceScale;
|
|
force.y *= forceScale;
|
|
force.z *= forceScale;
|
|
|
|
switch (simulationSpace)
|
|
{
|
|
case ParticleSystemSimulationSpace.Local:
|
|
case ParticleSystemSimulationSpace.Custom:
|
|
{
|
|
switch (scalingMode)
|
|
{
|
|
case ParticleSystemScalingMode.Hierarchy:
|
|
{
|
|
force = simulationSpaceTransform.InverseTransformVector(force);
|
|
|
|
break;
|
|
}
|
|
case ParticleSystemScalingMode.Local:
|
|
{
|
|
// Order is important.
|
|
// Notice how rotation and scale orders are reversed.
|
|
|
|
force = Quaternion.Inverse(particleSystemRotation) * force;
|
|
force = Vector3.Scale(force, new Vector3(
|
|
|
|
1.0f / particleSystemLocalScale.x,
|
|
1.0f / particleSystemLocalScale.y,
|
|
1.0f / particleSystemLocalScale.z));
|
|
|
|
break;
|
|
}
|
|
case ParticleSystemScalingMode.Shape:
|
|
{
|
|
force = Quaternion.Inverse(particleSystemRotation) * force;
|
|
|
|
break;
|
|
}
|
|
|
|
// This would technically never execute since it's checked earlier (above).
|
|
|
|
default:
|
|
{
|
|
throw new System.NotSupportedException(
|
|
|
|
string.Format("Unsupported scaling mode '{0}'.", scalingMode));
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
Vector3 particleVelocity = particleSystemParticles[i][j].velocity;
|
|
|
|
particleVelocity.x += force.x;
|
|
particleVelocity.y += force.y;
|
|
particleVelocity.z += force.z;
|
|
|
|
particleSystemParticles[i][j].velocity = particleVelocity;
|
|
}
|
|
}
|
|
}
|
|
|
|
currentParticleSystem.SetParticles(particleSystemParticles[i], particleCount);
|
|
}
|
|
}
|
|
|
|
// ...
|
|
|
|
void OnApplicationQuit()
|
|
{
|
|
|
|
}
|
|
|
|
// ...
|
|
|
|
protected virtual void OnDrawGizmosSelected()
|
|
{
|
|
Gizmos.color = Color.green;
|
|
Gizmos.DrawWireSphere(transform.position + center, scaledRadius);
|
|
}
|
|
|
|
// =================================
|
|
// End functions.
|
|
// =================================
|
|
|
|
}
|
|
|
|
// =================================
|
|
// End namespace.
|
|
// =================================
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// =================================
|
|
// --END-- //
|
|
// =================================
|