Crear formas de árbol 2D con código

Lo que vas a crear

Crear formas orgánicas como árboles puede ser un proyecto paralelo interesante para los desarrolladores de juegos potenciales. Puede utilizar la misma lógica para crear niveles u otras estructuras lógicas complicadas. En este tutorial, crearemos formas de árbol 2D en Unity utilizando dos enfoques diferentes: Fractal y L-System.

1. Creando 2D Con 3D

Aunque llamamos a estas formas de árbol 2D, son esencialmente objetos de malla 3D en Unity. El objeto del juego que tiene el script de árbol deberá tener estos componentes de malla 3D adjuntos para poder crear la forma de nuestro árbol. Esos componentes son los MeshRenderer y el Filtro de malla, Como se muestra abajo.

Con estos componentes conectados, crearemos una nueva malla utilizando los diferentes algoritmos para fractales y L-Systems.

Creando una malla

Se crea una malla 3D utilizando múltiples vértices que se combinan para formar caras. Para hacer una sola cara, necesitaremos un mínimo de tres vértices. Cuando conectamos tres vértices en una secuencia en el sentido de las agujas del reloj, obtendremos una cara que tiene una orientación normal hacia afuera. La visibilidad de una cara depende de la dirección de su normalidad, y por lo tanto, la secuencia en la que se pasan los vértices para crear una cara importa. Lea la documentación oficial de Unity para obtener más información sobre la creación de una malla..

Con el poder de la creación de mallas bajo nuestro cinturón, pasemos a nuestro primer método para crear un árbol 2D: fractal.

2. Creando un árbol fractal

Un fractal es una forma creada al repetir un patrón con diferentes escalas. En teoría, un fractal puede ser un patrón interminable, donde el patrón base se repite indefinidamente mientras su tamaño se reduce progresivamente. Cuando se trata de un árbol, el patrón fractal base puede ser una rama que se divide en dos ramas. Este patrón base se puede repetir para crear la forma de árbol simétrica que se muestra a continuación.

Tendremos que detener la repetición después de un cierto número de iteraciones, y el resultado obviamente no es una forma de árbol muy realista. Sin embargo, la belleza de este enfoque, y de los fractales en general, es que pueden crearse fácilmente utilizando funciones recursivas simples. El método de dibujo del patrón base puede llamarse de forma recursiva al mismo tiempo que reduce la escala hasta que se completa un cierto número de iteraciones.

La rama

El componente principal en la forma de un árbol es una rama. En nuestro enfoque, tenemos un Rama clase, que tiene una CreateBranch método como se muestra a continuación.

private void CreateBranch (Vector3 origen, float branchLength, float branchWidth, float branchAngle, Vector3 offset, float widthDecreaseFactor) Vector3 bottomLeft = new Vector3 (origin.x, origin.y, origin.z), bottomRight = new Vector3 (origin.x , origin.y, origin.z), topLeft = nuevo Vector3 (origin.x, origin.y, origin.z), topRight = nuevo Vector3 (origin.x, origin.y, origin.z); bottomLeft.x- = branchWidth * 0.5f; bottomRight.x + = branchWidth * 0.5f; topLeft.y = topRight.y = origin.y + branchLength; float newWidth = branchWidth * widthDecreaseFactor; topLeft.x- = newWidth * 0.5f; topRight.x + = newWidth * 0.5f; Vector3 axis = Vector3.back; Quaternion rotationValue = Quaternion.AngleAxis (branchAngle, axis); vertices.Add ((rotationValue * (bottomLeft)) + offset); vertices.Add ((rotationValue * (topLeft)) + offset); vertices.Add ((rotationValue * (topRight)) + offset); vertices.Add ((rotationValue * (bottomRight)) + offset); 

Una rama es esencialmente una forma (o una Patio) con cuatro vértices de esquina: abajo izquierda, arriba a la izquierda, parte superior derecha, y abajo a la derecha. los CreateBranch El método realiza el posicionamiento correcto de la rama al traducir, rotar y escalar estos cuatro vértices en función de la forma, la posición y la rotación de la rama. La punta de la rama se estrecha utilizando el widthDecreaseFactor valor. El método del árbol principal puede llamar a este método mientras pasa los valores de posición y rotación para esa rama.

Árbol fractal

los FractalTreeProper la clase tiene un recursivo CreateBranch método, que a su vez creará el Rama clase CreateBranch método constructor.

private void CreateBranch (int currentLayer, Vector3 branchOffset, float angle, int baseVertexPointer) if (currentLayer> = numLayers) return; longitud de flotación = longitud del tronco; ancho de flotación = trunkBaseWidth; para (int i = 0; i

Cada llamada a CreateBranch Inicia dos nuevas llamadas a sí mismo por sus dos ramas secundarias. Para nuestro ejemplo, estamos utilizando un ángulo de ramificación de 30 grados y un valor de 8 como el número de iteraciones de ramificación.

Usamos los puntos de estas ramas para crear los vértices necesarios, que luego se usan para crear caras para la malla de nuestro árbol..

caras = nueva lista(); vértices = nueva lista(); fTree = GetComponent().malla; fTree.name = "árbol fractal"; //… (en CreateBranch) if (currentLayer == 0) vertices.AddRange (branch.vertices); faces.Add (baseVertexPointer); faces.Add (baseVertexPointer + 1); faces.Add (baseVertexPointer + 3); faces.Add (baseVertexPointer + 3); faces.Add (baseVertexPointer + 1); faces.Add (baseVertexPointer + 2);  else int vertexPointer = vertices.Count; vertices.Add (branch.vertices [1]); vertices.Add (branch.vertices [2]); int indexDelta = 3; if (currentLayer! = 1) indexDelta = 2;  faces.Add (baseVertexPointer-indexDelta); faces.Add (vertexPointer); faces.Add (baseVertexPointer- (indexDelta-1)); faces.Add (baseVertexPointer- (indexDelta-1)); faces.Add (vertexPointer); faces.Add (vertexPointer + 1);  baseVertexPointer = vertices.Count; //… fTree.vertices = vertices.ToArray (); fTree.triangles = faces.ToArray (); fTree.RecalculateNormals ();

los baseVertexPointer el valor se utiliza para reutilizar los vértices existentes para evitar la creación de vértices duplicados, ya que cada rama puede tener cuatro vértices, pero solo dos de ellos son nuevos.

Al agregar algo de aleatoriedad al ángulo de bifurcación, también podemos crear variantes asimétricas de nuestro árbol fractal, que pueden parecer más realistas..

3. Creando un árbol del sistema L

El segundo método, el sistema L, es una bestia completamente diferente. Es un sistema muy complicado que se puede usar para crear formas orgánicas intrincadamente complejas o para crear conjuntos de reglas complejas o secuencias de cadenas. Es el sistema Lindenmayer, cuyos detalles se pueden encontrar en Wikipedia..

Las aplicaciones de los sistemas L incluyen la robótica y la inteligencia artificial, y solo tocaremos la punta del iceberg mientras lo usamos para nuestros propósitos. Con un sistema L, es posible crear formas de árboles o arbustos de aspecto muy realista de forma manual con un control preciso o utilizando la automatización.

los Rama el componente sigue siendo el mismo que en el ejemplo fractal, pero la forma en que creamos las ramas cambiará.

Disección del sistema L

Los sistemas L se utilizan para crear fractales complicados donde los patrones no son fácilmente evidentes. Se vuelve humanamente imposible encontrar estos patrones repetitivos visualmente, pero los sistemas L hacen que sea más fácil crearlos mediante programación. Los sistemas L consisten en un conjunto de alfabetos que se combinan para formar una cadena, junto con un conjunto de reglas que mutan estas cadenas en una sola iteración. La aplicación de estas reglas en múltiples iteraciones crea una cadena larga y complicada que puede servir de base para crear nuestro árbol..

Los alfabetos

Para nuestro ejemplo, usaremos este alfabeto para crear nuestra cadena de árbol: F+-El, y ].

Las normas

Para nuestro ejemplo, solo necesitaremos una regla donde el alfabeto F cambia en una secuencia de alfabetos, digamos F + [+ FF-F-FF] - [- FF + F + F]. En cada iteración, haremos este intercambio manteniendo todos los demás alfabetos sin cambios.

El axioma

El axioma, o la cadena de inicio, será F. Esto esencialmente significa que después de la primera iteración, la cadena se convertirá en F + [+ FF-F-FF] - [- FF + F + F].

Vamos a iterar tres veces para crear una cadena de árbol utilizable como se muestra a continuación.

lString = "F"; reglas = nuevo diccionario(); reglas ["F"] = "F + [+ FF-F-FF] - [- FF + F + F]"; para (int i = 0; i

Analizando la cadena del árbol

Ahora que tenemos la cadena de árbol que usa el sistema L, debemos analizarla para crear nuestro árbol. Veremos cada carácter en la cadena del árbol y realizaremos acciones específicas basadas en ellos como se indica a continuación..

  • En encontrar F, Crearemos una rama con los parámetros actuales de longitud y rotación..
  • En encontrar +, Añadiremos al valor de rotación actual..
  • En encontrar -, restaremos del valor de rotación actual.
  • En encontrar El, almacenaremos la posición actual, la longitud y el valor de rotación.
  • En encontrar ], Restauraremos los valores anteriores del estado almacenado..

Utilizamos un valor de ángulo de 25 Grados de rotación de ramas para nuestro ejemplo. los CreateTree método en el LSystemTree La clase hace el análisis. Para almacenar y restaurar estados, utilizaremos un Estado del nivel clase que almacena los valores necesarios junto con una Sucursal estructura.

levelStates = nueva lista(); char [] chars = lString.ToCharArray (); float currentRotation = 0; float currentLength = startLength; float currentWidth = startWidth; Vector3 currentPosition = treeOrigin; int levelIndex = 0; LevelState levelState = new LevelState (); levelState.position = currentPosition; levelState.levelIndex = levelIndex; levelState.width = currentWidth; levelState.length = currentLength; levelState.rotation = currentRotation; levelState.logicBranches = nueva lista(); levelStates.Add (levelState); Vector3 tipPosition = new Vector3 (); Cola savedStates = nueva cola(); para (int i = 0; i(); levelStates.Add (levelState); currentLength * = lengthDecreaseFactor; descanso; caso '+': currentRotation + = angle; descanso; caso '-': currentRotation- = angle; descanso; caso '[': savedStates.Enqueue (levelState); descanso; caso ']': levelState = savedStates.Dequeue (); currentPosition = levelState.position; currentRotation = levelState.rotation; currentLength = levelState.length; currentWidth = levelState.width; levelIndex = levelState.levelIndex; descanso; 

La variable estados del nivel almacena una lista de Estado del nivel instancias, donde un nivel puede ser considerado como un punto de ramificación. Como cada punto de ramificación puede tener múltiples ramificaciones o solo una, almacenamos esas ramificaciones en una lista lógicaFrancos sosteniendo múltiples Sucursal instancias. los estados guardados Cola rastrea el almacenamiento y restauración de diferentes Estado del nivels. Una vez que tengamos la estructura lógica de nuestro árbol en su lugar, podemos usar el estados del nivel Lista para crear ramas visuales y crear la malla del árbol..

para (int i = 0; i

Los GetClosestVextexIndices El método se usa para encontrar vértices comunes para evitar duplicados al crear la malla.

Al variar levemente las reglas, podemos obtener estructuras de árboles drásticamente diferentes, como se muestra en la imagen a continuación..

Es posible crear manualmente la cadena de árbol para diseñar un tipo específico de árbol, aunque esto puede ser una tarea muy tediosa.

Conclusión

Jugar con los sistemas L puede ser divertido, y también puede resultar en resultados muy impredecibles. Intente cambiar las reglas o agregar más reglas para crear otras formas complicadas. La siguiente cosa lógica sería intentar extender esto al espacio 3D reemplazando estos Quads 2D con cilindros 3D para las ramas y agregando la dimensión Z para las ramas..

Si se toma en serio la creación de árboles 2D, le sugiero que considere los algoritmos de colonización del espacio como el siguiente paso..