Unity Velocity Field Tool
Developed a tool to create a curve that generates a velocity field which will drive/attract game objects around it.
C#, HLSL, Unity
1. Unity does not have curve creator so I create bezier/hermite curve creator.
2. Create velocity field and have a mesh follow along with the curve.
3. Translating control point of curve will update the movement along with the curve in real-time.
4. Not just general game objects but particle can use it as well.
using UnityEngine;
[ExecuteInEditMode]
public class VelocityField : BezierCurveBase
{
[Header("Velocity Field")]
[Tooltip("Create Velocity Field")]
public bool CreateVelocityField = true;
[Tooltip("Update Velocity Field every frame")]
public bool UpdateEveryFrame = false;
[Tooltip("Visualize the radius that the velocity field will affect")]
public bool ShowSearchRadius = true;
[Tooltip("Raidus - the size of the radius that the velocity field will affect")]
[Range(0.1f, 100)]
public float SearchRadius = 5f;
[Tooltip("Map the magnitude of curve velocity")]
public AnimationCurve VelocityMagCurve = AnimationCurve.EaseInOut(0f, 0.8f, 1f, 1.0f);
public bool Visualize = true;
public float DisplayVelocityLength = 12f;
protected override void Start()
{
base.Start();
if (CreateVelocityField)
{
CalculateVelocityField();
}
}
protected override void Update()
{
base.Update();
#if UNITY_EDITOR
CalculateVelocityField(); // just run calculating the velocity field everytime we iterate on editor..
#endif
if (UpdateEveryFrame)
{
CalculateVelocityField();
}
}
public void CalculateVelocityField()
{
VelocityField.Clear();
Vector3 prevPos = P0.transform.position;
for (int c = 1; c <= Resolution; c++)
{
float t = (float)c / Resolution;
Vector3 currPos = CurveMath.CalculateBezierPoint(t, P0.transform.position, P0_Tangent.transform.position, P1_Tangent.transform.position, P1.transform.position);
Vector3 currTan = (currPos - prevPos).normalized;
float mag = VelocityMagCurve.Evaluate(t);
VelocityFieldNode ti = new VelocityFieldNode();
ti.TargetPosition = prevPos;
ti.TargetVelocity = currTan;
ti.Mag = mag;
VelocityField.Add(ti);
prevPos = currPos;
}
}
override public void OnDrawGizmos()
{
base.OnDrawGizmos();
if (ShowSearchRadius)
{
Gizmos.color = GizmoColor;
Gizmos.DrawWireSphere(P0.transform.position, SearchRadius);
Gizmos.DrawWireSphere(P1.transform.position, SearchRadius);
}
if (Visualize)
{
#if UNITY_EDITOR
CalculateVelocityField();
#endif
float MaxMag = float.MinValue;
float MinMag = float.MaxValue;
for (int i = 0; i < VelocityField.Count; i++)
{
if (VelocityField[i].Mag > MaxMag)
{
MaxMag = VelocityField[i].Mag;
}
if (VelocityField[i].Mag < MinMag)
{
MinMag = VelocityField[i].Mag;
}
}
for (int i = 1; i < VelocityField.Count; i++)
{
float color = Remap(VelocityField[i - 1].Mag, MinMag, MaxMag, 0.05f, 1f);
Color colorShift = new Color(GizmoColor.r * color, GizmoColor.g * color, GizmoColor.b * color);
Gizmos.color = colorShift;
Vector3 direction = transform.TransformDirection((VelocityField[i].TargetPosition - VelocityField[i - 1].TargetPosition).normalized) * DisplayVelocityLength;
Gizmos.DrawRay(VelocityField[i - 1].TargetPosition, direction);
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class ParticleCurveFollow : MonoBehaviour {
[Header("VelocitySource")]
[Tooltip("Use Bezier Curve to drive particle's position and velocity")]
public VelocityField Curve;
[Header("Particle Control")]
[Tooltip("How fast the particle will move along the curve")]
public float SpeedOnCurve = 1f;
[Tooltip("How fast the particle will move to the curve")]
public float ForceToNearestCurve = 0f;
private ParticleSystem ParticleSys;
private float SearchRadius = 5f;
// Use this for initialization
void Start ()
{
ParticleSys = GetComponent<ParticleSystem>();
if (!ParticleSys)
{
Debug.LogError("There is no ParticleSystem component in this gameObject.");
return;
}
// set the particle systems that particle surf is using set to simulation space to be world
ParticleSystem.MainModule main = ParticleSys.main;
if (main.simulationSpace != ParticleSystemSimulationSpace.World)
main.simulationSpace = ParticleSystemSimulationSpace.World;
SetupCurveData();
}
void Update() {
if (!ParticleSys)
{
return;
}
#if UNITY_EDITOR
if (Curve)
{
Curve.CalculateVelocityField();
}
#endif
UpdateParticles();
}
public void SetupCurveData()
{
if (!Curve)
return;
SearchRadius = Curve.SearchRadius;
}
void UpdateParticles()
{
if (!ParticleSys)
return;
ParticleSystem.Particle[] ParticleList = new ParticleSystem.Particle[ParticleSys.particleCount];
int NumParticleAlive = ParticleSys.GetParticles(ParticleList);
for (int i = 0; i < NumParticleAlive; ++i)
{
ParticleSystem.Particle Particle = ParticleList[i];
VelocityFieldNode velocityField = new VelocityFieldNode();
bool IsImported = false;
if (Curve)
{
IsImported = GetTargetNode(Particle, ref Curve.VelocityField, ref velocityField);
}
if (!IsImported)
continue;
Vector3 targetVelocity = velocityField.TargetVelocity * SpeedOnCurve * velocityField.Mag;
// get vector from particle position to curve's iteration pos
Vector3 toCurveVelocity = (velocityField.TargetPosition - Particle.position).normalized;
targetVelocity += toCurveVelocity * ForceToNearestCurve;
// apply hierarchy scale for velocity as well
targetVelocity.x *= transform.lossyScale.x;
targetVelocity.y *= transform.lossyScale.y;
targetVelocity.z *= transform.lossyScale.z;
ParticleList[i].position = Particle.position + (targetVelocity * Time.deltaTime);
ParticleList[i].velocity = Particle.velocity;
}
ParticleSys.SetParticles(ParticleList, ParticleSys.particleCount);
}
private bool GetTargetNode(ParticleSystem.Particle Particle, ref List<VelocityFieldNode> velocityField, ref VelocityFieldNode targetInfo)
{
float minDist = float.MaxValue;
VelocityFieldNode node = new VelocityFieldNode();
for (int i = 0; i < velocityField.Count; i++)
{
float dist = Vector3.Distance(velocityField[i].TargetPosition, Particle.position);
if (dist > SearchRadius)
continue;
if (dist < minDist)
{
minDist = dist;
node = velocityField[i];
}
}
targetInfo = node;
return true;
}
}