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; }}