Particle system - controlling/locking direction?

Sorry if this is the wrong subforum, didn’t see anything closer for particle system questions.

Had an idea yesterday and was looking at the particle system and don’t see an obvious way to accomplish it.

I purchased the Sci-Fi Shift UI asset, and the default background for the UI includes a particle system where motes appear, drift about randomly, then fade out. I’d like something similar, but instead of random directions, the motes are locked to moving in cardinal directions. In addition, I’d like some of them to randomly/abruptly change directions. I’m assuming the second part will involve scripting, but the first seems like it should be a standard option, yet I can’t seem to find anything like that as I poke through the options.

Sorry if this is easily explained in the docs and I missed it, but after a bit of experimenting and looking online, I can’t seem to figure this one out. Thanks.

You might be able to accomplish this with multiple particle systems set up to emit in a linear fashion in the axis you want.

Subtle effects like this can be tricky to get right, but they can be essential to setting a game’s mood and context.

I have floating dust motes in 3D space around my Jetpack Kurt vehicle as it goes through the air, to give an impression of relative wind speed. I started out with particles and ended up just scripting my own. Here’s the module if you want some ideas… it ended up quite hairy but it works nicely.

using UnityEngine;
using System.Collections;

// part of Jetpack Kurt by Kurt Dekker (@kurtdekker)

public class MoteSystem : MonoBehaviour
{
    public Transform target;
    System.Func<Vector3> GetPlayerVelocity;

    public class MoteObject
    {
        public Vector3 worldPosition;
        public float angle;
    }

    const float limitDistance = 15.0f;
    const int BASE_NUM_MOTES = 300;
    MoteObject[] motes;
    int numMotes;

    GameObject CommonMoteGameObject;

    float MasterScale;

    public static MoteSystem Create(
        Transform target,
        System.Func<Vector3> GetPlayerVelocity,
        float MasterSizeScale = 1.0f,
        float MasterCountScale = 1.0f)
    {
        MoteSystem ms = new GameObject ("MoteSystem.Create();").AddComponent<MoteSystem> ();

        ms.target = target;
        ms.GetPlayerVelocity = GetPlayerVelocity;
        ms.MasterScale = MasterSizeScale;

        ms.mote_mtl = new Material( Resources.Load<Material> ( "Materials/mote1"));

        ms.mote_mtl = new Material( Resources.Load<Material>(
            "Textures/particles/dust_mote_sheet_mtl"));

        GameObject go = new GameObject ("MoteSystem.CommonMoteGameObject");
        go.transform.SetParent (ms.transform);

        MeshFilter mf = go.AddComponent<MeshFilter> ();
        ms.mesh = mf.mesh;

        ms.numMotes = (int)(BASE_NUM_MOTES * MasterCountScale);

        ms.verts = new Vector3[ ms.numMotes * 4];
        ms.tris = new int[ ms.numMotes * 6];
        ms.uvs = new Vector2[ ms.verts.Length];

        MeshRenderer mr = go.AddComponent<MeshRenderer>();
        mr.material = ms.mote_mtl;

        ms.CommonMoteGameObject = go;

        ms.motes = new MoteObject[ms.numMotes];
        for (int i = 0; i < ms.numMotes; i++)
        {
            MoteObject mo = new MoteObject ();
            mo.worldPosition = new Vector3 (
                Random.Range (-limitDistance, limitDistance),
                Random.Range (-limitDistance, limitDistance),
                Random.Range (-limitDistance, limitDistance)) + target.position;
            mo.angle = Random.Range ( 0, Mathf.PI * 2);
            ms.motes [i] = mo;
        }

        ms.RenderMotesToMesh ( true);

        return ms;
    }

    Material mote_mtl;

    Mesh mesh;
    Vector3[] verts;
    int[] tris;
    Vector2[] uvs;

    void RenderMotesToMesh ( bool firstUpdate)
    {
        CommonMoteGameObject.transform.position = TargetPosition;

        Quaternion Q = target.rotation;

        Vector3 WindStretchStreaking = Vector3.zero;
        if (DSM.Settings.MoteSystemEnableStreaking.bValue)
        {
            WindStretchStreaking = wind;

            WindStretchStreaking -= GetPlayerVelocity();

            WindStretchStreaking *= Time.deltaTime;
        }

        int nv = 0;
        int nt = 0;
        for (int i = 0; i < numMotes; i++)
        {
            MoteObject mo = motes [i];

            Vector3 pos = mo.worldPosition - TargetPosition;

            float sz = 0.05f + (0.10f * i) / numMotes;

            sz *= MasterScale;

            float S = Mathf.Sin (mo.angle) * sz;
            float C = Mathf.Cos (mo.angle) * sz;

            verts [nv + 0] = pos + Q * new Vector3 (-S, -C) + WindStretchStreaking;
            verts [nv + 1] = pos + Q * new Vector3 (-C,  S);
            verts [nv + 2] = pos + Q * new Vector3 ( S,  C) - WindStretchStreaking;
            verts [nv + 3] = pos + Q * new Vector3 ( C, -S);

            if (firstUpdate)
            {
// this was for the single-particle material
//                uvs [nv + 0] = new Vector2 (0, 0);
//                uvs [nv + 1] = new Vector2 (1, 0);
//                uvs [nv + 2] = new Vector2 (1, 1);
//                uvs [nv + 3] = new Vector2 (0, 1);

                // this is for the tilesheet of particles
                int n = i % 12;
                float f = 0.25f;
                float x = (n & 3) * f;
                float y = (n / 4) * f;
                y = 0.75f - y;
                uvs [nv + 0] = new Vector2 (x, y);
                uvs [nv + 1] = new Vector2 (x + f, y);
                uvs [nv + 2] = new Vector2 (x + f, y + f);
                uvs [nv + 3] = new Vector2 (x, y + f);
            }

            tris [nt + 0] = nv + 0;
            tris [nt + 1] = nv + 1;
            tris [nt + 2] = nv + 2;

            tris [nt + 3] = nv + 0;
            tris [nt + 4] = nv + 2;
            tris [nt + 5] = nv + 3;

            nv += 4;
            nt += 6;
        }

        mesh.vertices = verts;
        mesh.uv = uvs;
        mesh.triangles = tris;

        if (firstUpdate)
        {
            mesh.RecalculateBounds();
        }

        // <WIP> do we have to reassign the mesh to the MeshFilter?

    }

    Vector3 wind { get { return WeatherController.CurrentWind/
            WeatherController.WindToPlayerVelocityAdjustmentFactor; } }

    void UpdateMoteParticle( MoteObject mo)
    {
        mo.worldPosition += wind * Time.deltaTime;

        if (mo.worldPosition.x < TargetPosition.x - limitDistance)
        {
            mo.worldPosition += Vector3.right * limitDistance * 2;
        }
        if (mo.worldPosition.x > TargetPosition.x + limitDistance)
        {
            mo.worldPosition += Vector3.left * limitDistance * 2;
        }
        if (mo.worldPosition.y < TargetPosition.y - limitDistance)
        {
            mo.worldPosition += Vector3.up * limitDistance * 2;
        }
        if (mo.worldPosition.y > TargetPosition.y + limitDistance)
        {
            mo.worldPosition += Vector3.down * limitDistance * 2;
        }
        if (mo.worldPosition.z < TargetPosition.z - limitDistance)
        {
            mo.worldPosition += Vector3.forward * limitDistance * 2;
        }
        if (mo.worldPosition.z > TargetPosition.z + limitDistance)
        {
            mo.worldPosition += Vector3.back * limitDistance * 2;
        }
    }

    Vector3 TargetPosition;        // snapshot

    void FixedUpdate ()
    {
        TargetPosition = target.position;
        foreach( MoteObject mo in motes)
        {
            UpdateMoteParticle( mo);
        }
    }

    void Update()
    {
        TargetPosition = target.position;

        RenderMotesToMesh (false);
    }
}

There’s a lot of external dependencies here but you should be able to mock and stub them out… they are mostly self-explanatory, I think but let me know if you have questions.

Oof - thats a bit beyond my ability at the moment, but I’ll keep this bookmarked for when I’ve dove that deep into Unity! Thanks for the code, I’ll see what I can do with it, ya know, once I can try reading it without getting dizzy! :slight_smile: