Faça Física do Tornado no Unity

Neste tutorial, criaremos uma simulação de Tornado dentro de Unity.

Sharp Coder Reprodutor de vídeo

Unity versão usada neste tutorial: Unity 2018.3.0f2 (64 bits)

Etapa 1: crie todos os scripts necessários

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

Etapa 2: Criando um Tornado

1. Crie partículas de Tornado:

  • Crie um novo GameObject (GameObject -> Criar Vazio) e nomeie-o "Tornado"
  • Crie outro GameObject e nomeie-o "Particles", mova-o para dentro de "Tornado" e mude sua posição para (0, 0, 0)
  • Adicione um componente ParticleSystem ao "Particles" GameObject
  • No Particle System habilite estes módulos: Emission, Shape, Velocity over Lifetime, Color over Lifetime, Size over Lifetime , Rotação ao longo da vida útil, Forças externas, Renderizador.

2. Atribua os valores para cada módulo do Sistema de Partículas (confira as capturas de tela abaixo):

Módulo principal (partículas):

Módulo de emissão:

Módulo de forma:

Módulo de velocidade ao longo da vida:

Módulo de cor ao longo da vida:

(2 cores cinza em cada extremidade e 2 cores brancas na parte interna)

Módulo de tamanho ao longo da vida útil:

(O tamanho ao longo da vida usa uma curva semelhante a esta):

(O tamanho diminui ligeiramente e depois aumenta)

Rotação ao longo da vida:

Módulo de Forças Externas:

Este módulo não necessita de nenhuma alteração, basta deixar os valores padrão.

Módulo de renderização:

Para este módulo só precisamos atribuir o seguinte material:

  • Crie um novo material e chame-o "tornado_material"
  • Mude seu shader para "Legacy Shaders/Particles/Alpha Blended"
  • Atribua a ele a textura abaixo (ou clique aqui):

Textura de nuvem pequena transparente

  • Atribua o tornado_material a um módulo Renderer:

Agora as partículas do Tornado devem ser parecidas com isto:

Mas como você pode ver ele não se parece em nada com um Tornado, isso porque temos mais um componente para adicionar, que é o Particle System Force Field, este componente é necessário para simular o vento circular:

  • Crie um novo GameObject e nomeie-o "ForceField"
  • Mova "ForceField" dentro de "Tornado" GameObject e mude sua posição para (0, 0, 0)

  • Adicionar o componente Campo de Força do Sistema de Partículas ao "ForceField"
  • Altere os valores do componente Campo de Força para os mesmos da captura de tela abaixo:

Visualização do Inspetor de Campo de Força do Sistema de Partículas

Agora as partículas devem ficar parecidas com isto, o que é muito melhor:

Efeito Tornado no Unity 3D

3. Configurando a Física do Tornado

  • Adicione os componentes Rigidbody e SC_Tornado ao "Tornado" GameObject

  • Crie um novo GameObject e nomeie-o "Trigger"
  • Mova "Trigger" dentro de "Tornado" GameObject e mude sua posição para (0, 10, 0) e mude sua escala para (60, 10, 60)
  • Adicione o componente MeshCollider ao "Trigger" GameObject, marque as caixas de seleção Convex e IsTrigger e altere sua malha para o cilindro padrão

O tornado agora está pronto!

Para testá-lo basta criar um Cube e adicionar um componente Rigidbody, depois colocá-lo dentro da área Trigger.

Depois de pressionar Play, o Cube deve ser puxado pelo Tornado:

Cubo puxado pelo Tornado.

Fonte
📁TornadoSystem.unitypackage239.71 KB
Artigos sugeridos
Implementando Física em Jogos Feitos em Unity
Trabalhando com o componente Rigidbody do Unity
Adicionando física de bola quicando no Unity
Criando um jogo de corrida baseado em física no Unity
Implementando um gancho 2D no Unity
Criando uma simulação de bandeira no Unity
Implementando Mecânica de Mineração no Unity Game