Faire de la physique des tornades dans Unity

Dans ce didacticiel, nous allons créer une simulation Tornado dans Unity.

Sharp Coder Lecteur vidéo

Unity version utilisée dans ce tutoriel: Unity 2018.3.0f2 (64 bits)

Étape 1: Créez tous les scripts nécessaires

Ce tutoriel nécessite 2 scripts:

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

Étape 2: Créer une tornade

1. Créez des particules Tornado:

  • Créez un nouveau GameObject (GameObject -> Create Empty) et nommez-le "Tornado"
  • Créez un autre GameObject et nommez-le "Particles", déplacez-le à l'intérieur de "Tornado" et changez sa position en (0, 0, 0)
  • Ajoutez un composant ParticleSystem au "Particles" GameObject
  • Dans Particle System, activez ces modules: Émission, Forme, Vitesse sur la durée de vie, Couleur sur la durée de vie, Taille sur la durée de vie, Rotation sur la durée de vie, Forces externes, Rendu.

2. Attribuez les valeurs pour chaque module du système de particules (vérifiez les captures d'écran ci-dessous):

Module principal (Particules):

Module d'émission:

Module Forme:

Module Vitesse sur durée de vie:

Module Couleur sur durée de vie:

(2 couleurs Gris à chaque extrémité et 2 couleurs Blanc dans la partie intérieure)

Module Taille sur durée de vie:

(La taille sur la durée de vie utilise une courbe qui ressemble à ceci):

(La taille diminue légèrement puis augmente)

Rotation sur la durée de vie:

Module Forces externes:

Ce module ne nécessite aucune modification, laissez simplement les valeurs par défaut.

Module de rendu:

Pour ce module, nous devons uniquement attribuer le matériel suivant:

  • Créez un nouveau matériau et appelez-le "tornado_material"
  • Changez son Shader en "Legacy Shaders/Particles/Alpha Blended"
  • Attribuez-lui la texture ci-dessous (ou cliquez ici):

Texture De Petit Nuage Transparente

  • Attribuez le tornado_material à un module Renderer:

Les particules Tornado devraient maintenant ressembler à ceci:

Mais comme vous pouvez le voir, cela ne ressemble pas du tout à une tornade, c'est parce que nous avons un composant supplémentaire à ajouter, qui est le Champ de force du système de particules, ce composant est nécessaire pour simuler le vent circulaire:

  • Créez un nouveau GameObject et nommez-le "ForceField"
  • Déplacez "ForceField" à l'intérieur de "Tornado" GameObject et changez sa position en (0, 0, 0)

  • Ajouter le composant Champ de force du système de particules à "ForceField"
  • Modifiez les valeurs du composant Force Field comme dans la capture d'écran ci-dessous:

Vue de l'inspecteur du champ de force du système de particules

Maintenant, les particules devraient ressembler à ceci, ce qui est bien mieux:

Effet tornade dans Unity 3D

3. Configuration de la physique des tornades

  • Ajoutez des composants Rigidbody et SC_Tornado à "Tornado" GameObject

  • Créez un nouveau GameObject et nommez-le "Trigger"
  • Déplacez "Trigger" à l'intérieur de "Tornado" GameObject et changez sa position en (0, 10, 0) et changez son échelle en (60, 10, 60)
  • Ajoutez le composant MeshCollider à "Trigger" GameObject, cochez les cases Convex et IsTrigger et modifiez son maillage en cylindre par défaut.

La tornade est maintenant prête !

Pour le tester, créez simplement un Cube et ajoutez un composant Rigidbody, puis placez-le dans la zone Trigger.

Une fois que vous appuyez sur Play, le cube devrait être attiré par la tornade:

Cube attiré par la tornade.

Source
📁TornadoSystem.unitypackage239.71 KB
Articles suggérés
Mise en œuvre de la physique dans les jeux créés dans Unity
Travailler avec le composant Rigidbody de Unity
Ajout de la physique des balles rebondissantes dans Unity
Comment détecter les collisions à l'aide du code dans Unity
Création d'un jeu de course basé sur la physique dans Unity
Implémentation d'un grappin 2D dans Unity
Création d'une simulation de drapeau dans Unity