Creare forme organiche come gli alberi può essere un interessante progetto collaterale per potenziali sviluppatori di giochi. È possibile utilizzare la stessa logica per creare livelli o altre strutture logiche complicate. In questo tutorial, creeremo forme di alberi 2D in Unity utilizzando due diversi approcci: Fractal e L-System.
Sebbene chiamiamo queste forme di albero 2D, sono essenzialmente oggetti mesh 3D in Unity. L'oggetto del gioco che ha lo script dell'albero avrà bisogno di avere questi componenti mesh 3D collegati per creare la nostra forma ad albero. Quei componenti sono i MeshRenderer
e il MeshFilter
, come mostrato di seguito.
Con questi componenti collegati, creeremo una nuova mesh usando i diversi algoritmi per i frattali e L-Systems.
Una mesh 3D viene creata utilizzando più vertici che si combinano per formare le facce. Per creare una singola faccia, avremo bisogno di almeno tre vertici. Quando colleghiamo tre vertici in una sequenza in senso orario, otterremo una faccia che ha una normale punta verso l'esterno. La visibilità di una faccia dipende dalla direzione della sua normale, e quindi la sequenza in cui i vertici vengono passati per creare una faccia conta. Si prega di leggere la documentazione ufficiale di Unity per ulteriori dettagli riguardanti la creazione di una mesh.
Con la potenza della creazione di mesh sotto la nostra cintura, passiamo al nostro primo metodo per la creazione di un albero 2D: il frattale.
Un frattale è una forma creata ripetendo un modello con scale diverse. Teoricamente un frattale può essere un modello senza fine, in cui il modello di base viene ripetuto indefinitamente mentre le sue dimensioni vengono progressivamente ridotte. Quando si tratta di un albero, il modello frattale di base può essere un ramo che si divide in due rami. Questo motivo base può essere ripetuto per creare la forma simmetrica mostrata di seguito.
Dovremo interrompere la ripetizione dopo un certo numero di iterazioni e il risultato ovviamente non è una forma di albero molto realistica. Tuttavia, la bellezza di questo approccio - e dei frattali in generale - è che possono essere facilmente creati usando semplici funzioni ricorsive. Il metodo di disegno del modello di base può chiamare se stesso in modo ricorsivo riducendo la scala fino al completamento di un certo numero di iterazioni.
Il componente principale in una forma ad albero è un ramo. Nel nostro approccio, abbiamo un Ramo
classe, che ha un CreateBranch
metodo come mostrato di seguito.
vuoto privato CreateBranch (origine Vector3, float branchLength, float branchWidth, float branchAngle, Vector3 offset, float widthDecreaseFactor) Vector3 bottomLeft = new Vector3 (origine.x, origine.y, origine.z), bottomRight = new Vector3 (origine.x , origin.y, origin.z), topLeft = new Vector3 (origin.x, origin.y, origin.z), topRight = new 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);
Un ramo è essenzialmente una forma (o a quadrilatero
) con quattro vertici angolari: in basso a sinistra
, in alto a sinistra
, in alto a destra
, e in basso a destra
. Il CreateBranch
metodo esegue il posizionamento corretto del ramo traducendo, ruotando e ridimensionando questi quattro vertici in base alla forma, alla posizione e alla rotazione del ramo. La punta del ramo è rastremata usando il widthDecreaseFactor
valore. Il metodo dell'albero principale può chiamare questo metodo mentre passa i valori di posizione e rotazione per quel ramo.
Il FractalTreeProper
la classe ha un ricorsivo CreateBranch
metodo, che a sua volta creerà il Ramo
La classe di CreateBranch
metodo costruttore.
private void CreateBranch (int currentLayer, Vector3 branchOffset, float angle, int baseVertexPointer) if (currentLayer> = numLayers) return; float length = trunkLength; float width = trunkBaseWidth; per (int i = 0; iOgni chiamata a
CreateBranch
avvia due nuove chiamate a se stesso per i suoi due rami figlio. Per il nostro esempio, stiamo usando un angolo di ramificazione di 30 gradi e un valore di 8 come numero di iterazioni di ramificazione.Usiamo i punti di questi rami per creare i vertici necessari, che vengono poi utilizzati per creare facce per la nostra struttura ad albero.
faces = new List(); vertici = nuova lista (); ftree = GetComponent ().maglia; fTree.name = "albero frattale"; // ... (in 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 (); Il
baseVertexPointer
valore è usato per riutilizzare i vertici esistenti in modo da evitare di creare vertici duplicati, poiché ogni ramo può avere quattro vertici ma solo due di questi sono nuovi.Aggiungendo un po 'di casualità all'angolo di ramificazione, possiamo creare anche varianti asimmetriche del nostro albero frattale, che possono sembrare più realistiche.
3. Creazione di un albero del sistema L
Il secondo metodo, il sistema L, è una bestia completamente diversa. È un sistema molto complicato che può essere utilizzato per creare forme organiche complesse complesse o per creare serie di regole complesse o sequenze di stringhe. È l'acronimo di Lindenmayer System, i cui dettagli possono essere trovati su Wikipedia.
Le applicazioni dei sistemi L includono la robotica e l'intelligenza artificiale e toccheremo solo la punta dell'iceberg mentre la usiamo per i nostri scopi. Con un sistema L, è possibile creare manualmente forme di albero o arbusti molto realistiche con controllo preciso o utilizzando l'automazione.
Il
Ramo
il componente rimane lo stesso dell'esempio frattale, ma il modo in cui creiamo i rami cambierà.Dissecting the L-System
I sistemi L sono usati per creare frattali complicati in cui i modelli non sono facilmente evidenti. Diventa umanamente impossibile trovare questi schemi ripetitivi visivamente, ma i sistemi L rendono più facile crearli a livello di codice. I sistemi L consistono in un insieme di alfabeti che si combinano per formare una stringa, insieme a un insieme di regole che mutano queste stringhe in un'unica iterazione. L'applicazione di queste regole su più iterazioni crea una stringa lunga e complicata che può fungere da base per la creazione del nostro albero.
Gli alfabeti
Per il nostro esempio, useremo questo alfabeto per creare la nostra stringa di albero:
F
,+
,-
,[
, e]
.Le regole
Per il nostro esempio, avremo bisogno solo di una regola in cui l'alfabeto
F
cambia in una sequenza di alfabeti, per esempioF + [+ FF-F-FF] - [- FF + F + F]
. Ad ogni iterazione, faremo lo scambio mantenendo inalterati tutti gli altri alfabeti.L'assioma
L'assioma, o la stringa iniziale, sarà
F
. Ciò significa essenzialmente che dopo la prima iterazione, la stringa diventeràF + [+ FF-F-FF] - [- FF + F + F]
.Verificheremo tre volte per creare una stringa di albero utilizzabile come mostrato di seguito.
lString = "F"; rules = nuovo dizionario(); regole [ "F"] = "F + [+ FF-F-FF] - [- FF + F + F]"; per (int i = 0; i Parsing the Tree String
Ora che abbiamo la stringa dell'albero usando il sistema L, dobbiamo analizzarla per creare il nostro albero. Osserveremo ciascun carattere nella stringa dell'albero e faremo azioni specifiche basate su di essi come elencato di seguito.
- Alla ricerca
F
, creeremo un ramo con parametri correnti di lunghezza e rotazione.- Alla ricerca
+
, aggiungeremo al valore corrente di rotazione.- Alla ricerca
-
, sottraeremo dal valore di rotazione corrente.- Alla ricerca
[
, memorizzeremo la posizione corrente, la lunghezza e il valore di rotazione.- Alla ricerca
]
, ripristineremo i valori sopra dallo stato memorizzato.Usiamo un valore di angolo di
25
gradi per la rotazione delle branche per il nostro esempio. IlCreateTree
metodo nelLSystemTree
la classe fa l'analisi. Per la memorizzazione e il ripristino degli stati, useremo aLevelState
classe che memorizza i valori necessari insieme aBranchState
struct.levelStates = new List(); 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 = new List (); levelStates.Add (levelState); Vector3 tipPosition = new Vector3 (); Coda savedStates = new Queue (); per (int i = 0; i (); levelStates.Add (levelState); currentLength * = lengthDecreaseFactor; rompere; caso '+': currentRotation + = angle; rompere; case '-': currentRotation- = angle; rompere; case '[': savedStates.Enqueue (levelState); rompere; caso ']': levelState = savedStates.Dequeue (); CurrentPosition = levelState.position; currentRotation = levelState.rotation; currentLength = levelState.length; currentWidth = levelState.width; levelIndex = levelState.levelIndex; rompere; La variabile
levelStates
memorizza un elenco diLevelState
istanze, in cui un livello può essere considerato come un punto di diramazione. Poiché ciascun punto di ramificazione può avere più rami o un solo ramo, noi archiviamo tali rami in un elencologicBranches
tenendo multipliBranchState
le istanze. IlsavedStates
Coda
tiene traccia della memorizzazione e del ripristino di diversiLevelState
S. Una volta che avremo la struttura logica per il nostro albero, possiamo usare illevelStates
lista per creare rami visivi e creare la maglia dell'albero.per (int i = 0; i
GetClosestVextexIndices
il metodo viene utilizzato per trovare i vertici comuni per evitare duplicati durante la creazione della mesh.Variando leggermente le regole, possiamo ottenere strutture dell'albero drasticamente diverse, come mostrato nell'immagine qui sotto.
È possibile creare manualmente la stringa dell'albero per progettare un tipo specifico di albero, sebbene questo possa essere un compito molto noioso.
Conclusione
Giocare con i sistemi L può essere divertente, e potrebbe portare a risultati molto imprevedibili. Prova a cambiare le regole o ad aggiungere più regole per creare altre forme complicate. La prossima cosa logica da fare sarebbe cercare di estenderlo allo spazio 3D sostituendo questi quad 2D con cilindri 3D per i rami e aggiungendo la dimensione Z per la ramificazione.
Se si è seriamente intenzionati a creare alberi 2D, suggerirei di esaminare gli algoritmi di colonizzazione dello spazio come passo successivo.