[MonoGame]Minecraft-Klon

    • C#
    • .NET (FX) 4.5–4.8

    Es gibt 161 Antworten in diesem Thema. Der letzte Beitrag () ist von seh.

      [MonoGame]Minecraft-Klon

      Hallo,

      ich präsentiere euch mein Minecraft-Klon.
      Es ist in C# und unter der Framework Monogame entwickelt wurden.

      Der Klon offeriert :
      - (temporär) eine 20x20 Chunks große Map. (Ein Chunk ist 16x16x256 Einheiten groß).
      - Geringe Ladezeiten (20x20 Chunks werden in unter 3 Sekunden generiert)
      - Kollisionserkennung(stark Verbesserungswürdig)
      - Picking( ohne einen Würfel zu entfernen oder zu platzieren )
      - die Seite eines Würfels zu determinieren(Picking)

      Die Motivation, dieses Projekt in dieser Periode zu publizieren ist die, dass ich konstruktive Kritik erhoffe, um eventualiter den Source dementsprechend zu optimieren.

      Ein besonderer Dank gilt @jvbsl , der mich auf das Projekt seines guten Freundes verwies(wodurch ich den Levelgenerator implementieren konnte).

      Den Source findet ihr auf Github:
      github.com/NET-D3v3l0p3r/Minec…er/MinecraftCloneMonoGame (obsolet!)


      Besonders interessant ist die Namespace "CoreOptimized".

      Es empfiehlt sich "CoreObsolete" mit "CoreOptimized" zu parallelisieren( um daraus die Kenntnis zu schöpfen, wie es nicht aussehen zu dürfen hat).


      Liebe Grüße.

      Hinweis
      Die Repository github.com/NET-D3v3l0p3r/Minec…er/MinecraftCloneMonoGame ist inzwischen obsolet.
      https://github.com/NET-D3v3l0p3r/MCMG ist die aktuelle Version!

      Bilder
      • Screenshot (461).png

        1,03 MB, 1.072×720, 856 mal angesehen
      • Screenshot (457).png

        1,38 MB, 1.081×721, 801 mal angesehen
      • Screenshot (460).png

        1,27 MB, 1.076×720, 858 mal angesehen
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „φConst“ ()

      Kleines Update

      Kürzere Ladezeiten, größere Maps!

      Ich habe die Struktur DefaultCubeStructure als Klasse markiert.
      Dadurch werden nur jene Würfel in den Chunk-Array geladen, die auch wirklich gebraucht werden.

      Demnach sind nicht 20x20 große Chunk Maps sondern 32x32 möglich!(Etwa wie bei Minecraft) (Grad getestet, auch 64x64 große Maps sind diskutabel [45~60 FPS im DEBUG])
      Die Ladezeit beträgt um die 0.7 Sekunden[DEBUG] in [RELEASE] (circa) 0.387s !!!

      Viel Vergnügen!
      Bilder
      • Screenshot (472).png

        1,36 MB, 1.091×752, 820 mal angesehen
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „φConst“ ()

      Nur um das Klarzustellen: Der Map Generator aus dem Projekt von Tom Wendel ist sogar von PatteKi und mir.
      Insgesamt ist es ein Community Projekt, das zwar Hauptsächlich von Tom programmiert wurde, aber viele Dinge aus der Community enthält.

      Trotzdem danke fürs erwähnen ;)

      Edit:
      Ich sehe aktuell noch nicht den Sinn, warum du die 3D Noise verwendest aber doch nur 2D Maps generierst. Mit der 2D Noise bekommst sicher nochmal etwas geschwindigkeit und ähnliche Ergebnisse

      Durch die Verwendung einer Primitiven Datenstruktur(short z.B.) im Gegensatz zu einer komplizierten Klasse(DefaultCubeClass) bekommst du sicher auch noch Geschwindigkeit.
      Den Chunk Renderer interessiert nur die Textur und evtl. Orientierung dieser, sowie die Position eines Blockes.
      Durch eine Verwendung eines Arrays mit indiziertem Zugriff int index = ((z*width)+y)*depth + x; reicht ein Eindimensionales Array und die Position ist automatisch daraus bekannt(BlockPosition muss nicht mehr gespeichert werden)

      Habs mir jetzt nicht im Detail angeguckt, aber ich denke dass das Instancing dir eher Probleme bereiten wird, das Instancing ergibt zwar Sinn, aber eher in einer Kombination mit einem bereits genannten System zur Chunk Generierung.
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „jvbsl“ ()

      Hallo,
      danke für die Rückmeldung!

      jvbsl schrieb:

      Ich sehe aktuell noch nicht den Sinn, warum du die 3D Noise verwendest aber doch nur 2D Maps generierst. Mit der 2D Noise bekommst sicher nochmal etwas geschwindigkeit und ähnliche Ergebnisse


      Einfach nur zum Testen.
      Wollte Höhlengenerierung implementieren, kommt aber später..
      nach "InfDev".

      jvbsl schrieb:

      Durch die Verwendung einer Primitiven Datenstruktur(short z.B.) im Gegensatz zu einer komplizierten Klasse(DefaultCubeClass) bekommst du sicher auch noch Geschwindigkeit.


      Das ist wohl war,
      doch es wird nicht klar, was gemeint ist.

      Die DefaultCubeClass ist obligatorisch, weil nun mal einige Werte darin enthalten sind.
      Meinst du etwa, dass ich die Datentypen der Eigenschaften dieser Klasse (exemplarisch Vektor) ersetzen solle durch drei Komponenten
      short X, Y, Z?

      jvbsl schrieb:

      Den Chunk Renderer interessiert nur die Textur und evtl. Orientierung dieser, sowie die Position eines Blockes.


      Tut er ja auch.
      Bloß die Transformations-Matrix und die Texture-Id.

      Quellcode

      1. ​int index = ((z*width)+y)*depth + x
      gab's mal Probleme damit im Namespace "ChunkObsolete", gebe dir aber vollkommen recht.

      ​(BlockPosition muss nicht mehr gespeichert werden)

      Die Idee hatte ich auch, kommt voraussichtlich im nächsten Update.


      jvbsl schrieb:

      aber ich denke dass das Instancing dir eher Probleme bereiten wird


      Inwiefern denn ^^ ?


      Liebe Grüße.
      Und Gott alleine weiß alles am allerbesten und besser.

      φConst schrieb:

      Die DefaultCubeClass ist obligatorisch, weil nun mal einige Werte darin enthalten sind.
      Meinst du etwa, dass ich die Datentypen der Eigenschaften dieser Klasse (exemplarisch Vektor) ersetzen solle durch drei Komponenten
      short X, Y, Z?

      Ich meinte eher soetwas
      0 Air
      1 Gras
      2 Dirt

      mehr muss der Renderer auch erstmal gar nicht Wissen Zusatzinformationen wie MetaData können gerne noch mit einem ZweitArray/tatsächlichem Element in einem primitiven Typen realisiert werden.
      Falls mit MetaDaten tatsächlich für das Rendern entscheidende Daten gemeint sind, ansonsten komplett getrennt von deinem einfachen Short Array halten.

      φConst schrieb:

      Bloß die Transformations-Matrix und die Texture-Id.

      Eine Komplette Transformationsmatrix für einen einzelnen Block, der Relativ zu den restlichen Blöcken nie skaliert oder rotiert wird und auch in seiner Position(relativ) immer gleich bleibt?
      4x4 float ist ein bisschen viel für eigt. nur eine Position 3 float oder gar per Indizierung 1 int

      φConst schrieb:


      Inwiefern denn ^^ ?


      Irgendwann kommst du vmtl. an den Punkt, andem du so viele Würfel zeichnest, dass die Optimierung durch das Instancing nicht mehr ausreicht, da immer noch unnötige Flächen der Würfel gezeichnet werden, die wegoptimiert werden können, jenach HW trifft dies früher oder später ein.
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Ich werde dann im HardwareInstancedRenderer nur die Position übergeben(und die Matrix im Shader erzeugen).


      ​Irgendwann kommst du vmtl. an den Punkt, andem du so viele Würfel zeichnest, dass die Optimierung durch das Instancing nicht mehr ausreicht, da immer noch unnötige Flächen der Würfel gezeichnet werden, die wegoptimiert werden können, jenach HW trifft dies früher oder später ein.


      Werde ich nicht.
      Wenn exemplarisch die Map 32x32 Chunks groß ist, dann bleibt sie immer genau so groß.
      Was höchstens eine Komplikation generieren kann ist das Platzieren von Cubes.

      Kennst du denn eine Alternative zu Hardware-Instancing?

      Dieses Video ist mir ein Mysterium :


      In seinem Deskription gibt er an, dass er;
      ​No occlusion culling, hardware instancing or other tricks.

      verwende...

      und

      ​Ich meinte eher soetwas0 Air1 Gras2 Dirt


      Stimmt, muss ich mir überlegen, wie ich die Klasse neu definiere.

      Liebe Grüße.
      Und Gott alleine weiß alles am allerbesten und besser.
      Ist das, was in OpenGL VBO's sind?

      EDIT: @jvbsl

      Ich sehe um ehrlich zu sein keinen Unterschied zu dem wie ich es mache.(Ich habe es gewiß missverstanden).

      Meine Hardware-Instanced-Renderer Klasse(etwas modifizierte Version von Microsoft) sieht folgendergestalt aus,
      Die Render-Methode:

      C#-Quellcode

      1. public bool Render()
      2. {
      3. if (MatrixBufferArray.Length == 0)
      4. return false;
      5. if ((InstancedVertexBuffer == null) || (MatrixBufferArray.Length > InstancedVertexBuffer.VertexCount))
      6. {
      7. if (InstancedVertexBuffer != null)
      8. {
      9. InstancedVertexBuffer.Dispose();
      10. InstancedTextureBuffer.Dispose();
      11. }
      12. InstancedVertexBuffer = new DynamicVertexBuffer(GlobalShares.GlobalDevice, InstancedVertexDeclaration, MatrixBufferArray.Length, BufferUsage.WriteOnly);
      13. InstancedTextureBuffer = new DynamicVertexBuffer(GlobalShares.GlobalDevice, InstancedTextureIDs, TextureBufferArray.Length, BufferUsage.WriteOnly);
      14. InstancedVertexBuffer.SetData<Matrix>(MatrixBufferArray, 0, MatrixBufferArray.Length, SetDataOptions.Discard);
      15. InstancedTextureBuffer.SetData<Vector2>(TextureBufferArray, 0, TextureBufferArray.Length, SetDataOptions.Discard);
      16. }
      17. for (int i = 0; i < Texture2Ds.Length; i++)
      18. {
      19. if (Texture2Ds[i] != null)
      20. InstancingShader.Parameters["Texture" + i].SetValue(Texture2Ds[i]);
      21. }
      22. foreach (var mesh in Model.Meshes)
      23. {
      24. foreach (var meshPart in mesh.MeshParts)
      25. {
      26. GlobalShares.GlobalDevice.SetVertexBuffers(
      27. new VertexBufferBinding(meshPart.VertexBuffer, 0, 0),
      28. new VertexBufferBinding(InstancedVertexBuffer, 0, 1),
      29. new VertexBufferBinding(InstancedTextureBuffer, 0, 1));
      30. GlobalShares.GlobalDevice.Indices = meshPart.IndexBuffer;
      31. InstancingShader.CurrentTechnique = InstancingShader.Techniques["HardwareInstancing"];
      32. InstancingShader.Parameters["World"].SetValue(WorldMatrix);
      33. InstancingShader.Parameters["View"].SetValue(Camera3D.ViewMatrix);
      34. InstancingShader.Parameters["Projection"].SetValue(Camera3D.ProjectionMatrix);
      35. InstancingShader.Parameters["EyePosition"].SetValue(Camera3D.CameraPosition);
      36. InstancingShader.Parameters["FogEnabled"].SetValue(1.0f);
      37. InstancingShader.Parameters["FogColor"].SetValue(Color.CornflowerBlue.ToVector3());
      38. InstancingShader.Parameters["FogStart"].SetValue(0.0f);
      39. InstancingShader.Parameters["FogEnd"].SetValue((float)Camera3D.RenderDistance);
      40. foreach (EffectPass pass in InstancingShader.CurrentTechnique.Passes)
      41. {
      42. pass.Apply();
      43. GlobalShares.GlobalDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 1, 0, 12, MatrixBufferArray.Length);
      44. }
      45. }
      46. }
      47. return true;
      48. }


      Interessant ist diese Zeile,

      C#-Quellcode

      1. if ((InstancedVertexBuffer == null) || (MatrixBufferArray.Length > InstancedVertexBuffer.VertexCount))
      2. {
      3. if (InstancedVertexBuffer != null)
      4. {
      5. InstancedVertexBuffer.Dispose();
      6. InstancedTextureBuffer.Dispose();
      7. }
      8. InstancedVertexBuffer = new DynamicVertexBuffer(GlobalShares.GlobalDevice, InstancedVertexDeclaration, MatrixBufferArray.Length, BufferUsage.WriteOnly);
      9. InstancedTextureBuffer = new DynamicVertexBuffer(GlobalShares.GlobalDevice, InstancedTextureIDs, TextureBufferArray.Length, BufferUsage.WriteOnly);
      10. InstancedVertexBuffer.SetData<Matrix>(MatrixBufferArray, 0, MatrixBufferArray.Length, SetDataOptions.Discard);
      11. InstancedTextureBuffer.SetData<Vector2>(TextureBufferArray, 0, TextureBufferArray.Length, SetDataOptions.Discard);
      12. }


      Ich erzeuge nur dann ein neues VertexBufferObjekt wenn etwas geändert wurde.

      Die Methodenaufrufe:

      C#-Quellcode

      1. GlobalShares.GlobalDevice.SetVertexBuffers(
      2. new VertexBufferBinding(meshPart.VertexBuffer, 0, 0),
      3. new VertexBufferBinding(InstancedVertexBuffer, 0, 1),
      4. new VertexBufferBinding(InstancedTextureBuffer, 0, 1));
      5. GlobalShares.GlobalDevice.Indices = meshPart.IndexBuffer;

      sind obligatorisch, weil nun mal der BackBuffer geleert wird; (und wohl auch der Vertex -und Indexbuffer des GraphicDevices)

      Und dann findet ein Render()-Call für einen Chunk statt..

      Wie sähe es denn aus, wie du es beschreibst?

      Ich danke dir herzlichst!
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „φConst“ ()

      Mir ist etwas eingefallen.

      Ich glaube ich weiß was du meinst.
      Ein Chunk soll ich betrachten wie ein Model.

      Bei der Generierung eines Chunks, generiere ich einen VertexBuffer mit einer gewissen Anzahl an Vertices.
      (Bei 16x16 müssten es also nur die sein, die die Oberfläche des Chunks repräsentieren).

      Also generiere ich mehrere Würfel, und lade sie in EIN VertexBuffer.

      EDIT:
      Ist auf der TODO-Liste.


      Das wäre genial; ist es das was du meinst?
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      Ich habe es _ geschafft in die Z -Achse unendlich weites Terrain zu generieren(wird generiert wenn die hinteren Chunks nicht mehr in der Map-BoundingBox liegen).
      Das Problem ist nun der GarbageCollector.

      Er tut nicht, was sein Name verspricht.
      Wie kann ich ein Array disposen?

      Ich weiß, ich weiß, nicht null setzen , sondern seine Elemente diposen.
      Mein ChunkOptimized erbt von IDisposable, :

      C#-Quellcode

      1. public void Dispose()
      2. {
      3. for (int i = 0; i < ChunkData.Length; i++)
      4. {
      5. if (ChunkData[i] != null)
      6. ChunkData[i].Dispose();
      7. }
      8. HeightMap = null;
      9. GC.SuppressFinalize(this);
      10. }


      bringt gar nichts.
      Der Ram Speicher wird exponentiell stupriert.
      Wie dispose ich eine benutzerdefinierte Klasse nach WindowsForms-Manier?

      Lieben Dank.
      Und Gott alleine weiß alles am allerbesten und besser.
      Du kannst ein Array oä in .NET nicht einfach in C-Manier "free'en", der GC macht das automatisch, sobald das Array ausm Scope fällt, allerdings arbeitet er nicht immer so schnell, wie man es gerne hätte.
      Ich glaube du hast verstanden was ich meine du erstellst einen VertexBuffer+IndexBuffer IndexBuffer genügt einer für alle Chunks, da die indices immer gleich aufgebaut sind.
      VertexBuffer ist einfach nur die Oberfläche deiner Welt/Würfel, somit braucht man die Hinterseiten einiger Würfel z.B. nicht mehr zu zeichnen(oder cullen).
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      @jvbsl Jop, das ist gut.
      Muss mir nur überlegen, wie ich den IndexBuffer erzeuge.(respektive diesen mit Indices fülle).

      Wenn ich das hab, implementiere ich das.

      @GarbageCollector ist ja mal richtig Müll.
      Der GC hat gar nichts gesammelt.
      Ich muss die Arrays neu initialisieren(mit der Anzahl 0), wenn ich das nicht mache, steigt der RAM Verbrauch auf 5 Gigabyte...

      EDIT: Perfekt!
      Der GC macht nun was er will.

      Ein Beispielprojekt.. nur in -Z und +Z Richtung möglich.
      Generiert äonenweites Terrain!
      Dateien
      • Debug.zip

        (2,87 MB, 344 mal heruntergeladen, zuletzt: )
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      Der GC räumt managed Dinge schon auf, aber VertexBuffer und ähnliches müssen disposed werden.
      Außerdem ist dann später die Idee den GC gar nicht erst arbeiten zu lassen, sondern den allozierten Speicher wiederzuverwenden.
      Für die ChunkDaten ist das z.B. ganz einfach wenn es einfach nur primitive values sind.
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Infinitve-Maps ist implementiert!

      Die Repository wird in Kürze synchronisiert.

      Nächstes TODO:
      Interaktion und Menü.

      EDIT: github.com/NET-D3v3l0p3r/Minec…zed/Chunk/ChunkManager.cs
      Und Gott alleine weiß alles am allerbesten und besser.

      Update, neuer Rausch-Algorithmus

      Ich wollte neben dem SimplexNoise einen weiteren Generator implementieren: DiamondSquare!

      Dadurch waren Inseln -Inselngruppen ; und sogar approximiert Europa möglich!
      (Letzteres scheint mir so).

      Im Anhang einige Bilder.

      Repository wird in Ferne aktualisiert.
      Bilder
      • Screenshot (487).png

        713,66 kB, 1.082×719, 568 mal angesehen
      • Screenshot (488).png

        744,31 kB, 1.081×725, 431 mal angesehen
      • Screenshot (489).png

        848,17 kB, 1.082×722, 595 mal angesehen
      Und Gott alleine weiß alles am allerbesten und besser.
      Hi,

      das sieht sehr gut aus. Finde es auch sehr toll, dass Du aktiv am Ball bist und so schnell dazulernst. :thumbup:
      Ich wollte mir gerade den Source kompilieren, allerdings konnte er Earlz.BareMetal, ​VertexBufferBinding und ​GraphicsDevice.SetVertexBuffers nicht auflösen, sodass das nicht klappt. Woher kriege ich das Zeugs? Über NuGet ist nichts verfügbar und Release gibt es auf GitHub keinen.

      Grüße
      #define for for(int z=0;z<2;++z)for // Have fun!
      Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

      Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
      Danke Dir!

      Earlz: bitbucket.org/earlz/datastruct…63e80cffda1e3d/BareMetal/

      Ist eine Klasse um die Größen benutzerdefinierter Klassen auszulesen.

      Im Übrigen brauchst du Monogame 3.5 ..

      nochmals vielen Dank für das Kompliment.. motiviert arg!
      Und Gott alleine weiß alles am allerbesten und besser.
      Ah okay, Bitbucket.^^
      Hmm, ich habe glaube wirklich nur die 3.4.x, weil das Ganze beim Herunterladen und Installieren von 3.5 rumgemeckert hat. Kann ich das einfach so samt Templates in VS etc. updaten?

      Grüße
      #define for for(int z=0;z<2;++z)for // Have fun!
      Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

      Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!: