Beim Laden besonders großer Karten wird bei der Lademethode eine Speicherausnahme ausgelöst, bei der eine neue Instanz der Kartenkachel erstellt wird. Ich möchte, dass die gesamte Karte zumindest auf der Server-App (und wenn möglich auf dem Client) verarbeitet wird. Wie soll ich dieses Problem lösen?
UPD: Die Frage hier ist, wie das Spiel nicht mehr abstürzt, wenn noch freier Speicherplatz zur Verfügung steht. Das Aufteilen der Karte in Blöcke ist ein guter Ansatz, aber nicht das, was ich in meinem Fall möchte.
UPD2: Zuerst habe ich jeder neuen Instanz der Kachelklasse eine Textur zugewiesen, und das hat so viel Speicherplatz in Anspruch genommen (auch Ladezeit). Jetzt braucht es ungefähr viermal weniger Platz. Vielen Dank an alle, jetzt kann ich riesige Karten erstellen, ohne daran zu denken, sie noch in Stücke zu zerlegen.
UPD3: Nachdem ich den Code neu geschrieben hatte, um festzustellen , ob Arrays von Kacheleigenschaften schneller funktionieren oder weniger Speicher verbrauchen (als die Eigenschaften in ihren jeweiligen Kacheln als Objekteigenschaften), stellte ich fest, dass ich nicht nur viel Zeit gebraucht habe, um dies zu versuchen brachte keine Leistungsverbesserung und machte das Debuggen des Spiels furchtbar schwierig.
quelle
Antworten:
Dies deutet stark darauf hin, dass Sie Ihre Kacheln nicht richtig darstellen, da dies bedeuten würde, dass jede Kachel eine Größe von ~ 80 Byte hat.
Was Sie verstehen müssen, ist, dass es eine Trennung zwischen dem Gameplay- Konzept einer Kachel und der visuellen Kachel geben muss , die der Benutzer sieht. Diese beiden Konzepte sind nicht dasselbe .
Nehmen wir zum Beispiel Terraria. Die kleinste Terraria-Welt nimmt 4200 x 1200 Kacheln auf, was 5 Millionen Kacheln entspricht. Wie viel Gedächtnis braucht es, um diese Welt darzustellen ?
Nun, jede Fliese hat eine Vordergrundebene, eine Hintergrundebene (die Hintergrundwände), eine "Drahtschicht", in die Drähte gehen, und eine "Möbelebene", in die Möbelstücke gehen. Wie viel Speicher nimmt jede Kachel ein? Auch hier sprechen wir nur konzeptionell, nicht visuell.
Eine Vordergrundkachel könnte leicht in einem nicht signierten Kurzschluss gespeichert werden. Es gibt nicht mehr als 65536 Vordergrundkacheltypen, daher macht es keinen Sinn, mehr Speicher als diesen zu verwenden. Die Hintergrundkacheln können sich leicht in einem vorzeichenlosen Byte befinden, da es weniger als 256 verschiedene Arten von Hintergrundkacheln gibt. Die Drahtschicht ist rein binär: Entweder enthält eine Kachel einen Draht oder nicht. Das ist also ein Bit pro Kachel. Und die Möbelschicht könnte wieder ein vorzeichenloses Byte sein, abhängig davon, wie viele verschiedene Möbelstücke es gibt.
Gesamtspeichergröße pro Kachel: 2 Bytes + 1 Byte + 1 Bit + 1 Byte: 4 Bytes + 1 Bit. Somit beträgt die Gesamtgröße für eine kleine Terraria-Karte 20790000 Byte oder ~ 20 MB. (Hinweis: Diese Berechnungen basieren auf Terraria 1.1. Das Spiel hat sich seitdem stark erweitert, aber selbst moderne Terraria könnten innerhalb von 8 Bytes pro Kachelort oder ~ 40 MB passen. Immer noch ziemlich erträglich).
Sie sollten diese Darstellung niemals als Arrays von C # -Klassen speichern. Sie sollten Arrays von ganzen Zahlen oder ähnlichem sein. AC # struct würde auch funktionieren.
Wenn es nun an der Zeit ist, einen Teil einer Karte zu zeichnen (beachten Sie die Betonung), muss Terraria diese konzeptionellen Kacheln in tatsächliche Kacheln umwandeln . Jede Kachel muss tatsächlich ein Vordergrundbild, ein Hintergrundbild, ein optionales Möbelbild und ein Drahtbild haben. Hier kommt XNA mit seinen verschiedenen Sprite-Blättern und dergleichen ins Spiel.
Sie müssen lediglich den sichtbaren Teil Ihrer konzeptionellen Karte in tatsächliche XNA-Sprite-Blattkacheln konvertieren. Sie sollten nicht versuchen, das Ganze auf einmal zu konvertieren . Jede Kachel, die Sie speichern, sollte nur ein Index sein, der besagt, dass "Ich bin Kacheltyp X", wobei X eine Ganzzahl ist. Sie verwenden diesen ganzzahligen Index, um das Sprite abzurufen, mit dem Sie es anzeigen. Und Sie verwenden die Sprite-Blätter von XNA, um dies schneller zu machen als nur einzelne Quads zu zeichnen.
Jetzt muss der sichtbare Bereich der Kacheln in verschiedene Teile aufgeteilt werden, damit Sie nicht ständig Sprite-Blätter erstellen, wenn sich die Kamera bewegt. Sie könnten also 64x64 Teile der Welt als Sprite-Blätter haben. Welche 64x64-Chunks der Welt von der aktuellen Kameraposition des Players aus sichtbar sind, sind die Chunks, die Sie zeichnen. Alle anderen Brocken haben nicht einmal Sprite-Blätter. Wenn ein Block vom Bildschirm fällt, werfen Sie das Blatt weg (Hinweis: Sie löschen es nicht wirklich; Sie behalten es bei sich und spezifizieren es erneut für einen neuen Block, der später sichtbar werden kann).
Ihr Server muss die visuelle Darstellung von Kacheln nicht kennen oder sich nicht darum kümmern. Es muss sich nur um die konzeptionelle Darstellung kümmern. Der Benutzer fügt hier eine Kachel hinzu, sodass dieser Kachelindex geändert wird.
quelle
Teilen Sie das Gelände in Regionen oder Abschnitte auf. Laden Sie dann nur die Blöcke, die für die Spieler sichtbar sind, und entladen Sie diejenigen, die nicht sichtbar sind. Sie können sich das wie ein Förderband vorstellen, bei dem Sie an einem Ende Brocken laden und am anderen Ende entladen, während sich der Spieler weiterbewegt. Immer vor dem Spieler bleiben.
Sie können auch Tricks wie Instanzen verwenden. Wenn alle Kacheln eines Typs nur eine Instanz im Speicher haben und an allen Stellen, an denen sie benötigt werden, mehrmals gezeichnet werden.
Weitere Ideen wie diese finden Sie hier:
Wie haben sie das gemacht: Millionen Fliesen in Terraria
Zonierung von Bereichen auf einer großen Kachelkarte sowie von Dungeons
quelle
Die Antwort von Byte56 ist gut, und ich habe angefangen, dies als Kommentar zu schreiben, aber es wurde zu lang und enthält einige Tipps, die bei einer Antwort hilfreicher sein könnten.
Der Ansatz ist für Server genauso gut wie für einen Client. In der Tat ist es wahrscheinlich mehr gegeben falls , dass der Server über weit mehr Bereiche wird gebeten , als ein Kunde kümmern wird. Der Server hat eine andere Vorstellung davon, was geladen werden muss als ein Client (er kümmert sich um alle Regionen, die allen verbundenen Clients wichtig sind), ist jedoch gleichermaßen in der Lage, eine funktionierende Gruppe von Regionen zu verwalten.
Selbst wenn Sie eine solch gigantische Karte in den Speicher einfügen könnten (und dies höchstwahrscheinlich nicht können), möchten Sie dies nicht . Das Laden von Daten, die Sie nicht sofort verwenden müssen, ist absolut null. Es ist ineffizient und langsam. Selbst wenn Sie es in den Speicher einfügen könnten, wären Sie höchstwahrscheinlich nicht in der Lage, alles in einer vernünftigen Zeitspanne zu verarbeiten.
Ich vermute, Ihr Einwand gegen das Entladen bestimmter Regionen liegt darin, dass Ihr Welt-Update alle Kacheln durchläuft und verarbeitet. Das ist sicherlich gültig, schließt aber nicht aus, dass nur bestimmte Teile geladen werden. Wenden Sie stattdessen beim Laden einer Region alle fehlenden Aktualisierungen an, um sie auf den neuesten Stand zu bringen. Dies ist auch in Bezug auf die Verarbeitung weitaus effizienter (Konzentration eines großen Aufwands auf einen kleinen Speicherbereich). Wenn es Verarbeitungseffekte gibt, die Regionsgrenzen überschreiten könnten (z. B. ein Lavastrom oder ein Wasserfluss, der in eine andere Region übertragen wird), binden Sie diese beiden Regionen so zusammen, dass beim Laden auch die andere Region geladen wird. Im Idealfall werden diese Situationen minimiert, andernfalls befinden Sie sich schnell wieder im Fall "Alle Regionen müssen jederzeit geladen sein".
quelle
Ein effizienter Weg, den ich in einem XNA-Spiel verwendet habe, war:
Sie können auf diese Weise auch eine einzelne große Karte verwenden, wenn diese nicht so groß ist, und die dargestellte Logik verwenden.
Natürlich muss die Größe Ihrer Texturen ausgewogen sein und die Auflösung zahlt sich hierfür aus. Versuchen Sie, mit einer niedrigeren Auflösung zu arbeiten, und erstellen Sie bei Bedarf ein hochauflösendes Paket zum Laden, wenn eine Konfigurationseinstellung festgelegt ist.
Sie müssen jedes Mal nur die relevanten Teile dieser Karte schleifen und nur das rendern, was benötigt wird. Auf diese Weise verbessern Sie die Leistung erheblich.
Ein Server kann eine riesige Karte schneller verarbeiten, da er das Spiel nicht rendern muss (eine der langsamsten Operationen). Die Logik dafür kann also die Verwendung größerer Teile oder sogar der gesamten Karte sein, sondern nur die Logik um Spieler herum, wie in einem 2-fachen des Spieler-Ansichtsfensters des Bereichs um den Spieler herum.
Ein Ratschlag hier ist, dass ich keineswegs ein Experte für Spieleentwickler bin und mein Spiel für das Abschlussprojekt meines Abschlusses gemacht wurde (das ich mit der besten Punktzahl hatte), also bin ich vielleicht nicht in jedem Punkt so richtig, aber ich habe recherchierte im Internet und auf Websites wie http://www.gamasutra.com und auf der XNA-Erstellerseite creators.xna.com (zu diesem Zeitpunkt http://create.msdn.com ), um Wissen und Fähigkeiten zu sammeln, und es funktionierte gut für mich .
quelle
Entweder
quelle
texture2d
? Hat jede Kachel eine eigene oder teilen sie Texturen? Und wo hast du das gesagt? Sie haben es sicherlich nicht in Ihre Frage aufgenommen, wo die Informationen sein sollten. Erklären Sie bitte Ihre besonderen Umstände besser.Als Antwort auf Ihr Update können Sie keine Arrays mit einer größeren
Int32.MaxValue
Größe zuweisen . Sie müssen es in Stücke teilen. Sie können es sogar in eine Wrapper-Klasse einkapseln, die eine Array-ähnliche Fassade freigibt:quelle