Crea Tornado Physics in Unity

In questo tutorial creeremo una simulazione di Tornado all'interno di Unity.

Sharp Coder Lettore video

Unity versione utilizzata in questo tutorial: Unity 2018.3.0f2 (64 bit)

Passaggio 1: crea tutti gli script necessari

Questo tutorial richiede 2 script:

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

Passaggio 2: creazione di un tornado

1. Crea particelle Tornado:

  • Crea un nuovo GameObject (GameObject -> Crea vuoto) e assegnagli un nome "Tornado"
  • Crea un altro GameObject e chiamalo "Particles", spostalo all'interno di "Tornado" e cambia la sua posizione in (0, 0, 0)
  • Aggiungi un componente ParticleSystem al "Particles" GameObject
  • In Particle System abilita questi moduli: Emission, Shape, Velocity over Lifetime, Color over Lifetime, Size over Lifetime , Rotazione nel corso della vita, Forze esterne, Renderer.

2. Assegnare i valori per ciascun modulo Particle System (controllare le schermate seguenti):

Modulo principale (Particelle):

Modulo di emissione:

Modulo forma:

Modulo Velocity over Lifetime:

Modulo Color over Lifetime:

(2 colori grigi su ciascuna estremità e 2 colori bianchi nella parte interna)

Dimensioni del modulo a vita:

(Size over Lifetime utilizza una curva simile a questa):

(La dimensione diminuisce leggermente e poi aumenta)

Rotazione nel corso della vita:

Modulo Forze Esterne:

Questo modulo non necessita di modifiche, lascia semplicemente i valori predefiniti.

Modulo di rendering:

Per questo modulo dobbiamo solo assegnare il seguente materiale:

  • Crea un nuovo materiale e chiamalo "tornado_material"
  • Cambia il suo Shader in "Legacy Shaders/Particles/Alpha Blended"
  • Assegnagli la texture sottostante (o clicca qui):

Piccola nuvola Texture trasparente

  • Assegna il tornado_material a un modulo Renderer:

Ora le particelle Tornado dovrebbero assomigliare a questo:

Ma come puoi vedere non assomiglia affatto ad un Tornado, è perché abbiamo un altro componente da aggiungere, che è il Campo di forza del sistema particellare, questo componente è necessario per simulare il vento circolare:

  • Crea un nuovo GameObject e dagli un nome "ForceField"
  • Muovi "ForceField" all'interno di "Tornado" GameObject e cambia la sua posizione in (0, 0, 0)

  • Aggiungi il componente Campo di forza del sistema particellare a "ForceField"
  • Cambia i valori del componente Campo di forza come nello screenshot qui sotto:

Vista dell'ispettore del campo di forza del sistema di particelle

Ora le particelle dovrebbero assomigliare a queste, il che è molto meglio:

Effetto Tornado in Unity 3D

3. Impostazione della fisica dei tornado

  • Aggiungi i componenti Rigidbody e SC_Tornado a "Tornado" GameObject

  • Crea un nuovo GameObject e dagli un nome "Trigger"
  • Muovi "Trigger" all'interno di "Tornado" GameObject e cambia la sua posizione in (0, 10, 0) e cambia la sua scala in (60, 10, 60)
  • Aggiungi il componente MeshCollider a "Trigger" GameObject, seleziona le caselle di controllo Convex e IsTrigger e modifica la sua Mesh in Cilindro predefinito

Il tornado è ora pronto!

Per testarlo è sufficiente creare un Cubo e aggiungere un componente Rigidbody, quindi posizionarlo all'interno dell'area Trigger.

Dopo aver premuto Gioca, il Cubo dovrebbe essere trascinato dal Tornado:

Cubo trascinato dal tornado.

Fonte
📁TornadoSystem.unitypackage239.71 KB
Articoli suggeriti
Lavorare con il componente Rigidbody di Unity
Implementazione della fisica nei giochi Made in Unity
Aggiunta della fisica della palla rimbalzante in Unity
Implementazione di un rampino 2D in Unity
Creazione di una simulazione di bandiera in Unity
Implementazione della meccanica mineraria in Unity Game
Come rilevare le collisioni utilizzando il codice in Unity