Maak Tornado-fysica in eenheid

In deze tutorial maken we een Tornado-simulatie in Unity.

Sharp Coder Video speler

Unity versie gebruikt in deze tutorial: Unity 2018.3.0f2 (64-bit)

Stap 1: Maak alle benodigde scripts

Voor deze tutorial zijn 2 scripts vereist:

SC_Caught.cs

//This script is attached automatically to each Object caught in Tornado

using UnityEngine;

public class SC_Caught : MonoBehaviour
{
    private SC_Tornado tornadoReference;
    private SpringJoint spring;
    [HideInInspector]
    public Rigidbody rigid;

    // Use this for initialization
    void Start()
    {
        rigid = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        //Lift spring so objects are pulled upwards
        Vector3 newPosition = spring.connectedAnchor;
        newPosition.y = transform.position.y;
        spring.connectedAnchor = newPosition;
    }

    void FixedUpdate()
    {
        //Rotate object around tornado center
        Vector3 direction = transform.position - tornadoReference.transform.position;
        //Project
        Vector3 projection = Vector3.ProjectOnPlane(direction, tornadoReference.GetRotationAxis());
        projection.Normalize();
        Vector3 normal = Quaternion.AngleAxis(130, tornadoReference.GetRotationAxis()) * projection;
        normal = Quaternion.AngleAxis(tornadoReference.lift, projection) * normal;
        rigid.AddForce(normal * tornadoReference.GetStrength(), ForceMode.Force);

        Debug.DrawRay(transform.position, normal * 10, Color.red);
    }

    //Call this when tornadoReference already exists
    public void Init(SC_Tornado tornadoRef, Rigidbody tornadoRigidbody, float springForce)
    {
        //Make sure this is enabled (for reentrance)
        enabled = true;

        //Save tornado reference
        tornadoReference = tornadoRef;

        //Initialize the spring
        spring = gameObject.AddComponent<SpringJoint>();
        spring.spring = springForce;
        spring.connectedBody = tornadoRigidbody;

        spring.autoConfigureConnectedAnchor = false;

        //Set initial position of the caught object relative to its position and the tornado
        Vector3 initialPosition = Vector3.zero;
        initialPosition.y = transform.position.y;
        spring.connectedAnchor = initialPosition;
    }

    public void Release()
    {
        enabled = false;
        Destroy(spring);
    }
}

SC_Tornado.cs

//Tornado script controls tornado physics

using System.Collections.Generic;
using UnityEngine;

public class SC_Tornado : MonoBehaviour
{
    [Tooltip("Distance after which the rotation physics starts")]
    public float maxDistance = 20;

    [Tooltip("The axis that the caught objects will rotate around")]
    public Vector3 rotationAxis = new Vector3(0, 1, 0);

    [Tooltip("Angle that is added to the object's velocity (higher lift -> quicker on top)")]
    [Range(0, 90)]
    public float lift = 45;

    [Tooltip("The force that will drive the caught objects around the tornado's center")]
    public float rotationStrength = 50;

    [Tooltip("Tornado pull force")]
    public float tornadoStrength = 2;

    Rigidbody r;

    List<SC_Caught> caughtObject = new List<SC_Caught>();

    // Start is called before the first frame update
    void Start()
    {
        //Normalize the rotation axis given by the user
        rotationAxis.Normalize();

        r = GetComponent<Rigidbody>();
        r.isKinematic = true;
    }

    void FixedUpdate()
    {
        //Apply force to caught objects
        for (int i = 0; i < caughtObject.Count; i++)
        {
            if(caughtObject[i] != null)
            {
                Vector3 pull = transform.position - caughtObject[i].transform.position;
                if (pull.magnitude > maxDistance)
                {
                    caughtObject[i].rigid.AddForce(pull.normalized * pull.magnitude, ForceMode.Force);
                    caughtObject[i].enabled = false;
                }
                else
                {
                    caughtObject[i].enabled = true;
                }
            }
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if (!other.attachedRigidbody) return;
        if (other.attachedRigidbody.isKinematic) return;

        //Add caught object to the list
        SC_Caught caught = other.GetComponent<SC_Caught>();
        if (!caught)
        {
            caught = other.gameObject.AddComponent<SC_Caught>();
        }

        caught.Init(this, r, tornadoStrength);

        if (!caughtObject.Contains(caught))
        {
            caughtObject.Add(caught);
        }
    }

    void OnTriggerExit(Collider other)
    {
        //Release caught object
        SC_Caught caught = other.GetComponent<SC_Caught>();
        if (caught)
        {
            caught.Release();

            if (caughtObject.Contains(caught))
            {
                caughtObject.Remove(caught);
            }
        }
    }

    public float GetStrength()
    {
        return rotationStrength;
    }

    //The axis the caught objects rotate around
    public Vector3 GetRotationAxis()
    {
        return rotationAxis;
    }

    //Draw tornado radius circle in Editor
    void OnDrawGizmosSelected()
    {
        Vector3[] positions = new Vector3[30];
        Vector3 centrePos = transform.position;
        for (int pointNum = 0; pointNum < positions.Length; pointNum++)
        {
            // "i" now represents the progress around the circle from 0-1
            // we multiply by 1.0 to ensure we get a fraction as a result.
            float i = (float)(pointNum * 2) / positions.Length;

            // get the angle for this step (in radians, not degrees)
            float angle = i * Mathf.PI * 2;

            // the X & Y position for this angle are calculated using Sin & Cos
            float x = Mathf.Sin(angle) * maxDistance;
            float z = Mathf.Cos(angle) * maxDistance;

            Vector3 pos = new Vector3(x, 0, z) + centrePos;
            positions[pointNum] = pos;
        }

        Gizmos.color = Color.cyan;
        for (int i = 0; i < positions.Length; i++)
        {
            if (i == positions.Length - 1)
            {
                Gizmos.DrawLine(positions[0], positions[positions.Length - 1]);
            }
            else
            {
                Gizmos.DrawLine(positions[i], positions[i + 1]);
            }
        }
    }
}

Stap 2: Een tornado maken

1. Tornado-deeltjes maken:

  • Maak een nieuw GameObject (GameObject -> Leeg maken) en geef het een naam "Tornado"
  • Maak nog een GameObject en noem het "Particles", verplaats het naar "Tornado" en verander de positie in (0, 0, 0)
  • Voeg een ParticleSystem-component toe aan het "Particles" GameObject
  • Schakel in het deeltjessysteem deze modules in: Emissie, Vorm, Snelheid gedurende levensduur, Kleur gedurende levensduur, Grootte gedurende levensduur , Rotatie gedurende levensduur, Externe krachten, Renderer.

2. Wijs de waarden toe voor elke Particle System-module (bekijk de onderstaande schermafbeeldingen):

Hoofdmodule (deeltjes):

Emissiemodule:

Vormmodule:

Snelheid over levensduur module:

Kleur over Lifetime-module:

(2 grijze kleuren aan elk uiteinde en 2 witte kleuren aan de binnenkant)

Grootte gedurende levensduur module:

(Size over Lifetime gebruikt een curve die er als volgt uitziet):

(Maat gaat iets naar beneden en gaat dan omhoog)

Rotatie gedurende de levensduur:

Module Externe krachten:

Deze module heeft geen wijzigingen nodig, laat gewoon de standaardwaarden staan.

Renderermodule:

Voor deze module hoeven we alleen het volgende materiaal toe te wijzen:

  • Maak een nieuw materiaal en noem het "tornado_material"
  • Verander de Shader naar "Legacy Shaders/Particles/Alpha Blended"
  • Wijs er onderstaande textuur aan toe (of klik hier):

Kleine wolk textuur transparant

  • Wijs het tornado_materiaal toe aan een Renderer-module:

Nu zouden Tornado-deeltjes er ongeveer zo uit moeten zien:

Maar zoals je kunt zien lijkt het helemaal niet op een Tornado, dat komt omdat we nog een component moeten toevoegen, namelijk het Particle System Force Field, dit component is nodig om de cirkelvormige wind te simuleren:

  • Maak een nieuw GameObject en geef het een naam "ForceField"
  • Verplaats "ForceField" binnen "Tornado" GameObject en verander de positie in (0, 0, 0)

  • Voeg de Particle System Force Field-component toe aan "ForceField"
  • Wijzig de waarden van de Force Field-component in dezelfde als in de onderstaande schermafbeelding:

Deeltjessysteem Force Field Inspector View

Nu zouden de deeltjes er ongeveer zo uit moeten zien, wat veel beter is:

Tornado-effect in Unity 3D

3. Tornado-fysica instellen

  • Voeg Rigidbody- en SC_Tornado-componenten toe aan "Tornado" GameObject

  • Maak een nieuw GameObject en geef het een naam "Trigger"
  • Verplaats "Trigger" binnen "Tornado" GameObject en verander de positie in (0, 10, 0) en verander de schaal in (60, 10, 60)
  • Voeg de MeshCollider-component toe aan "Trigger" GameObject, schakel de selectievakjes Convex en IsTrigger in en wijzig de Mesh ervan in de standaardcilinder

De tornado is nu klaar!

Om het te testen, maakt u eenvoudigweg een kubus, voegt u een Rigidbody-component toe en plaatst u deze vervolgens in het Trigger-gebied.

Zodra je op Play drukt, moet de Kubus door de Tornado naar binnen worden getrokken:

Kubus aangetrokken door de tornado.