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.
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.
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.
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.
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.
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; iCada 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, digamosF + [+ 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á enF + [+ 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. losCreateTree
método en elLSystemTree
La clase hace el análisis. Para almacenar y restaurar estados, utilizaremos unEstado del nivel
clase que almacena los valores necesarios junto con unaSucursal
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 deEstado 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 listalógicaFrancos
sosteniendo múltiplesSucursal
instancias. losestados guardados
Cola
rastrea el almacenamiento y restauración de diferentesEstado del nivel
s. Una vez que tengamos la estructura lógica de nuestro árbol en su lugar, podemos usar elestados 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..