Het maken van organische vormen zoals bomen kan een interessant nevenproject zijn voor potentiële game-ontwikkelaars. U kunt dezelfde logica gebruiken om niveaus of andere gecompliceerde logische structuren te maken. In deze zelfstudie maken we 2D-boomvormen in Unity met behulp van twee verschillende benaderingen: Fractal en L-System.
Hoewel we deze 2D-boomvormen noemen, zijn het in wezen 3D-netobjecten in Unity. Het spelobject dat het tree-script heeft, moet deze 3D-mesh-componenten hebben om onze boomvorm te creëren. Die componenten zijn de MeshRenderer
en de MeshFilter
, zoals hieronder getoond.
Met deze componenten gekoppeld, zullen we een nieuwe mesh maken met behulp van de verschillende algoritmen voor fractals en L-Systems.
Er wordt een 3D-net gemaakt met behulp van meerdere hoekpunten die samen gezichten vormen. Om een enkel gezicht te maken, hebben we minimaal drie hoekpunten nodig. Wanneer we drie hoekpunten met de klok mee verbinden, krijgen we een gezicht dat naar buiten wijst. De zichtbaarheid van een gezicht is afhankelijk van de richting van zijn normaal, en daarom is de volgorde waarin de hoekpunten worden gepasseerd om een gezicht te maken van belang. Lees de officiële Unity-documentatie voor meer informatie over het maken van een mesh.
Laten we met de kracht van meshcreatie onder onze riem doorgaan naar onze eerste methode voor het maken van een 2D-tree: fractal.
Een fractal is een vorm die is gecreëerd door een patroon met verschillende schalen te herhalen. Theoretisch kan een fractal een oneindig patroon zijn, waarbij het basispatroon voor onbepaalde tijd wordt herhaald, terwijl de grootte ervan geleidelijk afneemt. Als het om een boom gaat, kan het fractale basispatroon een tak zijn die zich splitst in twee takken. Dit basispatroon kan worden herhaald om de hieronder getoonde symmetrische boomvorm te creëren.
We moeten de herhaling na een bepaald aantal iteraties stoppen en het resultaat is duidelijk niet een erg realistische boomvorm. Maar de schoonheid van deze benadering - en fractals in het algemeen - is dat ze gemakkelijk kunnen worden gemaakt met behulp van eenvoudige recursieve functies. De basispatroon-tekenmethode kan zichzelf recursief noemen terwijl de schaal wordt verkleind totdat een bepaald aantal iteraties is voltooid.
Het primaire onderdeel in een boomvorm is een tak. In onze benadering hebben we een Tak
klasse, die een heeft CreateBranch
methode zoals hieronder getoond.
private void CreateBranch (Vector3 origin, 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 = 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-as = Vector3.back; Quaternion rotationValue = Quaternion.AngleAxis (branch Angle, axis); vertices.Add ((rotationValue * (bottomleft)) + offset); vertices.Add ((rotationValue * (topLeft)) + offset); vertices.Add ((rotationValue * (topright)) + offset); vertices.Add ((rotationValue * (bottomRight)) + offset);
Een tak is in wezen een vorm (of een Quad
) met vier hoekhoekpunten: linksonder
, linksboven
, rechtsboven
, en rechts onder
. De CreateBranch
methode doet de juiste positionering van de tak door deze vier hoekpunten te vertalen, roteren en schalen op basis van de vorm, positie en rotatie van de tak. De punt van de tak loopt taps toe met behulp van de widthDecreaseFactor
waarde. De hoofdboommethode kan deze methode aanroepen bij het doorgeven van de positie- en rotatiewaarden voor die tak.
De FractalTreeProper
klas heeft een recursieve CreateBranch
methode, die op zijn beurt de Tak
klasse CreateBranch
constructormethode.
private void CreateBranch (int currentLayer, Vector3 branchOffset, float angle, int baseVertexPointer) if (currentLayer> = numLayers) return; vlotterlengte = stamlengte; zwevende breedte = trunkBaseWidth; voor (int i = 0; iElke oproep aan
CreateBranch
initieert twee nieuwe oproepen naar zichzelf voor de twee onderliggende takken. Voor ons voorbeeld gebruiken we een vertakkingshoek van 30 graden en een waarde van 8 als het aantal vertakkingsiteraties.We gebruiken de punten van deze takken om de benodigde hoekpunten te maken, die vervolgens worden gebruikt om vlakken te maken voor onze boomgaas.
gezichten = nieuwe lijst(); hoekpunten = nieuwe lijst (); fTree = GetComponent () .Mesh; fTree.name = "fractal tree"; // ... (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-index Delta); 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 (); De
baseVertexPointer
waarde wordt gebruikt om bestaande hoekpunten opnieuw te gebruiken, zodat we geen dubbele hoekpunten maken, omdat elke tak vier hoekpunten kan hebben, maar slechts twee daarvan zijn nieuwe hoekpunten.Door wat willekeur aan de vertakkingshoek toe te voegen, kunnen we ook asymmetrische varianten van onze fractale boom maken, die er realistischer uit kunnen zien.
3. Een L-systeemstructuur maken
De tweede methode, het L-systeem, is een heel ander beest. Het is een zeer ingewikkeld systeem dat kan worden gebruikt om ingewikkeld complexe organische vormen te maken of om complexe regelsets of reeksreeksen te maken. Het staat voor Lindenmayer System, waarvan de details te vinden zijn op Wikipedia.
De toepassingen van L-systemen omvatten robotica en AI, en we zullen alleen het topje van de ijsberg raken terwijl we het voor onze doeleinden gebruiken. Met een L-systeem is het mogelijk om zeer realistisch ogende boom- of heestervormen handmatig te creëren met precieze besturing of automatisering.
De
Tak
component blijft hetzelfde als in het fractal-voorbeeld, maar de manier waarop we takken maken, zal veranderen.Het L-systeem ontleden
L-systemen worden gebruikt om gecompliceerde fractals te maken waar de patronen niet gemakkelijk zichtbaar zijn. Het wordt menselijk onmogelijk om deze herhalingspatronen visueel te vinden, maar L-systemen maken het eenvoudiger om ze programmatisch te maken. L-systemen bestaan uit een reeks alfabetten die samen een reeks vormen, samen met een reeks regels die deze reeksen muteren in een enkele iteratie. Als u deze regels toepast op meerdere iteraties, wordt een lange, gecompliceerde reeks gemaakt die als basis kan dienen voor het maken van onze structuur.
De alfabetten
Voor ons voorbeeld gebruiken we dit alfabet om onze tree string te maken:
F
,+
,-
,[
, en]
.De regels
Voor ons voorbeeld hebben we maar één regel nodig waar het alfabet staat
F
verandert bijvoorbeeld in een reeks alfabettenF + [+ FF-F-FF] - [- FF + F + F]
. Op elke iteratie maken we deze ruil terwijl alle andere alfabetten ongewijzigd blijven.Het Axioma
Het axioma, of de startreeks, zal zijn
F
. Dit betekent in essentie dat na de eerste iteratie de string wordtF + [+ FF-F-FF] - [- FF + F + F]
.We herhalen drie keer om een bruikbare boomstructuur te maken, zoals hieronder weergegeven.
lString = "F"; rules = new Dictionary(); voorschriften [ "F"] = "F + [+ FF-F-FF] - [- FF + F + F]"; voor (int i = 0; i De Tree String parseren
Nu we de boomstring met het L-systeem hebben, moeten we deze analyseren om onze boom te maken. We zullen elk karakter in de boomstring bekijken en specifieke acties uitvoeren op basis van hen zoals hieronder vermeld.
- Over het vinden
F
, we zullen een tak creëren met huidige parameters van lengte en rotatie.- Over het vinden
+
, we zullen toevoegen aan de huidige rotatiewaarde.- Over het vinden
-
, we zullen aftrekken van de huidige rotatiewaarde.- Over het vinden
[
, we slaan de huidige positie, lengte en rotatiewaarde op.- Over het vinden
]
, we zullen de bovenstaande waarden herstellen vanuit de opgeslagen status.We gebruiken een hoekwaarde van
25
graden voor takrotatie voor ons voorbeeld. DeCreateTree
methode in deLSystemTree
klasse doet de ontleding. Voor het opslaan en herstellen van staten gebruiken we eenLevelState
klasse die de noodzakelijke waarden samen met een opslaatBranchState
struct.levelStates = nieuwe lijst(); char [] chars = lString.ToCharArray (); float currentRotation = 0; float currentLength = startLength; float currentWidth = startWidth; Vector3 currentPosition = treeOrigin; int levelIndex = 0; LevelState levelState = nieuwe LevelState (); levelState.position = currentPosition; levelState.levelIndex = levelIndex; levelState.width = currentWidth; levelState.length = currentLength; levelState.rotation = currentRotation; levelState.logicBranches = nieuwe lijst (); levelStates.Add (levelState); Vector3 tipPosition = new Vector3 (); Wachtrij savedStates = nieuwe wachtrij (); voor (int i = 0; i (); levelStates.Add (levelState); currentLength * = lengthDecreaseFactor; breken; case '+': currentRotation + = angle; breken; case '-': currentRotation- = hoek; breken; case '[': savedStates.Enqueue (levelState); breken; case ']': levelState = savedStates.Dequeue (); currentPosition = levelState.position; currentRotation = levelState.rotation; currentLength = levelState.length; currentWidth = levelState.width; levelIndex = levelState.levelIndex; breken; De variabele
levelStates
slaat een lijst op vanLevelState
gevallen waarin een niveau als een vertakkingspunt kan worden beschouwd. Omdat elk vertakkingspunt meerdere takken of slechts één tak kan hebben, slaan we die takken op in een lijstlogicBranches
meerdere vasthoudenBranchState
instances. DesavedStates
Wachtrij
volgt het opslaan en herstellen van verschillendeLevelState
s. Zodra we de logische structuur voor onze boom op zijn plaats hebben, kunnen we delevelStates
lijst om visuele vertakkingen te maken en de maas te maken.voor (int i = 0; i
De GetClosestVextexIndices
methode wordt gebruikt om gemeenschappelijke hoekpunten te vinden om duplicaten te voorkomen tijdens het maken van de mesh.Door de regels enigszins te variëren, kunnen we drastisch verschillende boomstructuren krijgen, zoals in de onderstaande afbeelding.
Het is mogelijk om de boomstring handmatig te maken voor het ontwerpen van een specifieke soort boom, hoewel dit een zeer vervelende taak kan zijn.
Conclusie
Spelen met L-systemen kan leuk zijn en het kan ook tot zeer onvoorspelbare resultaten leiden. Probeer de regels te veranderen of meer regels toe te voegen om andere gecompliceerde vormen te maken. Het volgende logische ding om te doen zou zijn om dit uit te breiden naar 3D ruimte door deze 2D Quads te vervangen door 3D cilinders voor takken en de Z-dimensie toe te voegen voor vertakking.
Als je serieus bent over het maken van 2D-bomen, zou ik willen voorstellen dat je de volgende stap bekijkt in algoritmen voor ruimtekolonisatie.