Jump to content
Unity Insider Forum

Marrrk's Blog

  • Einträge
    8
  • Kommentare
    5
  • Aufrufe
    31.072

UltraTerrain Dev Diary #6


Mark

908 Aufrufe

Hab die Arbeit wieder an UltraTerrain aufgenommen und schon einige Tests der bisherigen Strukturen machen können.

Die ganzen bisherigen Bugs konnte ich beheben (meist Deadlocks oder Raceconditions) und habe dabei viele Subsysteme umgeschrieben:

Fehler beim Speichern der Daten in das VFS: War die Menge die geschrieben wurde zu gering konnte es passieren das ein neuer Speicherblock nicht angefordert wurde.

Threading System: Habe den UTH verwendet und stark aufgemöbelt, der UTH bietet nun eine bessere Grundstruktur (Nur noch Task und Task<T> als Basiselemente. Elemente der TPL wurden übernommen und ein brauchbares Eventsystem wurde implementiert, wodurch ich nun mehrere asynchrone Aufgaben elegant aneinander Ketten kann und auch in alle relevanten Threads kommunizieren kann.

Page und Voxel System wurde stark überarbeitet:
In einem vorherigen Blogpost wurde ja bereits beschrieben wie das Material und Datensystem aussieht, dies hat sich nun aber weiter verändert und nun bin ich einen Schritt zufriedener. Da nun einige Subsysteme des Voxelsystems weiter getrennt voneinander wurden.

Ein VoxelObject (das Objekt was alles verwaltet) wird nun wie folgt erzeugt:
[CODE]
var voxelDataDescriptor = VoxelDataDescriptor.Create().With<int>("TileId");

var voxelObjectDescriptorData = new VoxelObjectDescriptorData()
{
WorldPageBounds = new Box(new Integer3(int.MinValue, int.MinValue, 0), new Integer3(int.MaxValue, int.MaxValue, 1)),
ChunksPerPage = new Integer3(2, 2, 2),
VoxelsPerChunk = new Integer3(5, 5, 10),
ChunkSizeInWorldUnits = new Integer3(10, 10, 10),
PageDataGenerator = new PointPageGenerator(),
ChunkMeshProvider = new TileProvider(),
DataStorage = new DataStorage("VoxelObject.voxel"),
UnusedPageUnloadTimeOutInMilliseconds = 20000,
VoxelDataDescriptor = voxelDataDescriptor
};

voxelObject = new VoxelObject(voxelObjectDescriptorData);
[/CODE]

Dieser Code ist direkt aus meinem Beispielprojekt entnommen, welches nur dazu dient eine 2D Welt (Top Down) darzustellen, die in der Höhe dennoch mehrere Voxelebenen enthalten kann.

Die WorldPageBounds geben an welche Reichweite die erzeugten Pages haben dürfen, hier zb ist die Welt auf eine ziemlich Große 2D Ebene beschränkt ohne Platz für Pages über und unter dem Nullpunkt.

Pro Page können 2³ Chunks erzeugt werden, Chunks enthalten zB Meshdaten, etc.
Jeder Chunk enthält 5x5X10 Voxel (250 Voxel pro Chunk).
Und jeder Chunk hat eine Größe von 10x10x10 Welteinheiten.

Zum generieren der Voxeldaten für jede angeforderte Page wird eine PointPageGenerator Klasse verwendet, in meinem Fall erzeugt diese Klasse einfach nur kleine Hügel, das ist quasi der Weltgenerator.

Der TileProvider wandelt hier wiederum die Daten der Page für einen einzelnen Chunk in die Daten für den Chunk um. Hier würden zB für jeden Voxel ein Tile generiert, zb ein Grasteil für einen Grasvoxel.

Der DataStorage gibt an in welche Datei die persistenten Daten gespeichert werden sollen. Dies passiert aber nur wenn Chunks dies wollen (Kann je nach Chunkmesh Klasse spezifiziert werden) oder wenn eine manuelle Anderung der Voxel eines Pages vorgenommen wurde (irgendetwas wurde abgebaut oder eben verändert).

Nicht benötigte Pages und Chunks werden nach 2 Sekunden entladen. Unbenötigt sind sie dann wenn niemand für den Bereich in dem die Page liegt eine Anforderung gestellt hat. Meist wäre dies der Fall wenn der Bereich zu weit vom Spieler entfernt liegt.

Als letztes gibt es den VoxelDataDescriptor, der angibt was ein Voxel an Daten enthalten kann. In diesem Beispiel möchte ich zB nur die TileIds in den Voxeln speichern und zwar als int (byte oder ushort würde hier komplett ausreichen um Speicher zu sparen.)

Ohne weitere Parameter hat diese TileID die Wirkung dass es das ganze ChunkMesh zur kompletten Regenerierung zwingt. Es gibt hier als Parameter 3 Optionen:
1. Gar nichts am ChunkMesh ändern
2. ChunkMesh soll nur geupdated werden
3. ChunkMesh muss komplett neu aufgebaut werden

Das VoxelObjekt liefert nun auch eine Reihe von Events, zB wenn eine page geladen/entladen wird, ein Mesh geupdated oder erstellt oder gelöscht werden muss. Solche Dinge.

Das VoxelObjekt arbeitet im Hintergrund immer, intern gibt es einen TickThread der das VoxelObjekt alle 1000 Millisekunden aktualisiert, verwaiste Pages entfernt (gelöschte Pages werden eine Zeit lang mithilfe von WeakReferences gecached) und auch Pages und Chunks bereinigt die nicht mehr gebraucht werden.

Man benachrichtigt das VoxelObjekt über die benötigten Daten so:

[CODE]
var instantArea = new Box(playerPosition, voxelObject.Descriptor.PageVoxelSize);
var delayedBorder = voxelObject.Descriptor.PageVoxelSize * 1
var type = RequirementType.ChunkMesh | RequirementType.VoxelData;
var requirement = new Requirement(instantArea, delayedBorder, type);

voxelObject.TrackRequirement(requirement);
[/CODE]

Die instantArea gibt den Bereich an der sofort geladen werden muss, dabei wird das VoxelObjekt blockieren bis alle nötigen Daten in diesem angegebenen Bereich (Bereich ist angegeben als Voxel) geladen wurden.

Der delayedBorder gibt an um wieviele Voxel in jede Achse der Box/Area der Lade Bereich ausgehend der instantArea erweitert werden soll. Dieser Bereich wird dann im Hintergrund geladen und kann irgendwann fertig werden. Das anfordern dieses extra Bereiches wird dabei nichts blockieren.

Der type gibt wiederum an was genau wir eigentlich in diesen Bereichen haben wollen. In diesem Beispiel sind es sowohl Voxeldaten als auch ChunkMesh Daten. Wenn wir zB ein Spiel basteln bei dem wir nichts abbauen/verändern könne, ist es zb nicht notwendig die Voxel Daten permanent im Speicher zu behalten. Das laden der Chunk Daten sorgt passiv dafür dass diese VoxelDaten geladen werden sollte es noch kein ChunkMesh geben.

Zu guter letzt noch ein wenig Code um zu zeigen wie man jetzt die VoxelDaten verändern kann:

[CODE]
var box = new Box(playerPosition, new Integer3(10, 10. 10));
var mapRequest = MapRequest.Create()
.ForMaterials(0)
.ForBox(box)
.ForBuffers("TileId")
.ReadAndWriteAccess();
using (var mapHandle = voxelObject.Map(mapRequest))
{
var tileIds = mapHandle.GetBuffer<int>("TileId", 0);
for (int i = 0; i < tileIds.Length; ++i)
{
if (tileIds[i] != 0)
tileIds[i] = 1; // Grass
}
}
[/CODE]

In diesem Beispiel sagen wir dem VoxelObject dass wir gerne einen 10x10x10 Bereich um die Position des Spielers verändern wollen. Wir fordern das Material mit dem Index 0 an und interessieren uns nur für die TileIds und wollen dabei auch die aktuellen Daten haben.

Die aktuelln Daten benutzen wir um in diesem Beispiel nur Tiles zu verändern die eine TileID ungleich 0 haben, 0 steht dabei für "hier ist nichts". Sprich wir machen alles was existiert in einem 10x10x10 Bereich um den Spieler zu Gras.

Das war es soweit, letztendlich noch ein Screenshot wie es aktuell aussieht, nicht spektakulär aber zum testen und debuggen reicht es mir bisher aus:

[attachment=1808:Untitled2.png]

[attachment=1809:Untitled3.png]

0 Kommentare


Recommended Comments

Keine Kommentare vorhanden

Gast
Kommentar schreiben...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...