# 2D Sprite Level Editor Konzeptfrage



## stKev (10. Aug 2012)

Hallo,

ich arbeite derzeit an einem 2D Level Editor und stehe nun leider vor einer leeren Klasse, welche mein WorkPanel darstellt. Das einladen von Sprites(Tile,Npc,WorldObject,Enemy) ist bereits möglich. Aus den geladen PNGs werden auch Objekte des jeweiligen Typs erstellt. Über JButton wähle ich ein Objekt aus diese wird als selected abgespeichert und dem JFrame(parent) übergeben, damit alle JPanel(OptionPanel, WorkPanel etc.) darauf zugreifen können.

Nun stellt sich für mich die Frage wie ich das WorkPanel aufbaue um das Level zu kreieren. Was ich auf keinen Fall möglich ist am Ende eine einfache Tile-Map als Image abzuspeichern. Da kann ich gleich Photoshop oder sonstiges nehmen und mir ein Bild malen. Die Logik der einzelnen Objekte sollen dem Editor bekannt sein und über das OptionPanel verändert werden. Dies würd ich realisieren indem ich mit Hilfe des OptionPanel die Attribute des selected Sprites verändere.

Aber bleiben ich lieber beim WorkPanel und schweife nicht allzu weit ab.

Meine Ideen für das WorkPanel:

Ich hatte mir gedacht, das WorkPanel hätte als Attribute einige 2 dimensionale ArrayLists. Die ich gerastert auf dem WorkPanel irgendwie darstelle ( Lücke Nr.1  ). Durch klicken(bevorzugt natürlich mouseDragged) der dargestellen Zellen soll der Index ermittelt werden und der Datenstruktur das selected Objekt in diesem "Fach" übergeben werden.

Den Layereffekt würde ich, so hatte ich es mir gedacht, erzeugen indem ich einfach die Datenstrukturen im späteren Verlauf im Spiel in der Reihenfolge Tile(Bodenbeläge),Npc,Enemy, WorldObject(Bäume, Häuser etc.) zeichen lasse.

Die Klasse Sprite "has a" AffineTransform Objekt, welches die rotation und translation später übernehmen soll. Die Ansicht des Spiel soll gerade von oben sein, keine Vogelperspektive, diese würde sich nicht mit meiner Player Klasse vertragen, welche ebenfalls die Bewegung über AffineTransform bearbeitet. (So spare ich mir einige Sprites, da sich der Player immer nach der Maus ausrichtet, wie z.b. bei Counter-Strike 2D).

Ich belasse es fürs erste einmal dabei, und hoffe das einige Leute die sich damit auskennen, mir interessante Denkanstöße geben können.

Zum Schluß noch schnell etwas zu meiner Person, da ich ja neu im Forum bin. Ich heiße Kevin, bin 24 Jahre alt, studiere Wirtschaftinformatik(nach den Semesterferien gehts ab ins 3.  ) und versuche in diesem Projekt einige Inhalte des SCJP einzubinden(da ich diesen Kurs letztes Sem. belegt hatte).

z.B. Serialisierung
Ich dachte mir das so, dass ich die Datenstrukturen auf dem WorkPanel befülle und später nur die Datenstrukturen abspeichere und meine spätere Level-Klasse diese lädt.

Insofern Bedarf an Quelltext besteht poste ich den natürlich gerne nach.

Danke für die Aufmerksamkeit und verbleibe erwartungsvoll. 
Bin für alle Ideen aufgeschlossen.


----------



## stKev (10. Aug 2012)

Hmmm... niemand von den 33 Leuten bisher eine Ideen, wie man es realisieren kann? Meine Vorstellungen bzw. Idee habe ich nur mal als Ansatz gepostet, wollte nicht ganz mir leeren Händen dastehen. Falls jemand eine Idee oder ein Ansatz hat wie man die Objekte auf dem WorkPanel(Vorlage für das spätere Level) anordnen kann immer her damit.


----------



## Fu3L (10. Aug 2012)

Nicht so ungeduldig in 3 Stunden 

Zur Serialisierung: Ich hoffe, dass du in diesem Kurs auch gelernt hast, dass man für sowas nicht die Standard-Java-Serialisierung ohne Anpassung nehmen kann? Gibt Probleme sobald du auch nur irgendwas an der Klasse änderst.

Zum Aufbau:


> Die ich gerastert auf dem WorkPanel irgendwie darstelle



2 for Schleifen und fertig  Man muss natürlich sehen was man anzeigt, also ein Rechteck definieren und dann gucken, ob das Sprite Rechteck in diesem liegt und nur diese dann anzeigen. (Selbstverständlich entgegen der Verschiebung des Viewports verschoben. (Alternativ: Das ganze per Graphics Object regeln, da kann man glaub ich auch den Ursprung verschieben))
Für den Level Editor mag das reichen, aber für das Spiel an sich, müsste es dann vermutlich noch intelligenter gelöst werden. Der Darstellungscode könnte auch gut für beides verwendet werden. 



> Den Layereffekt würde ich, so hatte ich es mir gedacht, erzeugen indem ich einfach die Datenstrukturen im späteren Verlauf im Spiel in der Reihenfolge Tile(Bodenbeläge),Npc,Enemy, WorldObject(Bäume, Häuser etc.) zeichen lasse.



Das geht. Eventuell ist JLayeredPane effizienter, da du ja alles von hinten nach vorne malen musst und JLayeredPane da intern vllt (ich weiß es nicht) optimiert. Einfach mal in die JavaAPI gucken, ist gut erklärt. Bietet außerdem gute Unterstützung für Drag&Drop.



> Die Klasse Sprite "has a" AffineTransform Objekt



Habe ehrlich gesagt die AffineTransormation Objekte noch nicht genutzt, wäre aber sparsam mit Rotationen, die können schonmal rechenintensiv sein. Beim Spieler natürlich, aber zB bei einfachen Bäumen oder so eher geizen und den Boden Tiles sowieso.

Das ist so das, was mir etwas müde dazu einfällt^^


----------



## stKev (11. Aug 2012)

> Nicht so ungeduldig in 3 Stunden


Ich war mir nicht ganz sicher ob ich alles gut rüber gebracht habe bzw. meine Intention ankam beim Leser. Wobei ein wenig ungeduldig war ich wohl wirklich, mag daran liegen, dass es doch recht selten bei mir bisher vorkam, dass google mal keine gute Antwort parat hatte.




> Zur Serialisierung: Ich hoffe, dass du in diesem Kurs auch gelernt hast, dass man für sowas nicht die Standard-Java-Serialisierung ohne Anpassung nehmen kann? Gibt Probleme sobald du auch nur irgendwas an der Klasse änderst.


Du meinst die Methoden readObject(); und writeObject(); und transient Attribute?
Falls ja, dann ja. 
Dazu gleich ne Frage: Ich hatte in der API geschaut - ArrayList implements Serializable - wobei sich mir da die Frage stellte, ob auch alle Objekte in der Datenstruktur dieses Interface implementieren müssen.
Müssen auch die Klassen diese Methoden haben bzw. muss ich dort etwas reinschreiben oder wir das beim deserializieren alles reibungslos ablaufen? Mein Plan war es beim Speichern der Karte/Levels eine neue Klasse z.b. LevelContainer zu erstellen als Parameter die 4 Datenstrukturen und nur diese Klasse zu serialisieren. Die ist dann so schön rein.  hehe^^




> 2 for Schleifen und fertig


Das hättest du mir ruhig zutrauen können.  Ich bin eher auf der Suche nach einer guten Idee, wie ich diesem Raster ein Logik verpasse. Das Raster ist ja zu Beginn, bei Kartenerstellung, erstmal völlig leer. Somit auch alle Datenstrukturen. Hier stellt sich genau meine Frage aus welcher Komponente oder mit welchem "Werkzeug" erstelle ich das Raste. Mir schwirrte ja ein Rectangle[x][y] vor. Der User gibt via JMenuItem "new" die Größe der Karte ein (x,y). Beim Klick wird die Referenz abgeben und schon weiß das Objekte, wo es in die ArrayList seines Typs einsortiert werden soll. Mausklick kriegt eine Rectangle Hitbox
hmmm.... Glaub seine Gedanken mal aufschreiben brings. Klingt nach einem Plan oder?  Danke^^

Wobei sich dort die Frage ergibt, wie ich das mit der Größe des Rasters machen? Ich habe einige Sprites, die sind 32x32 andere variieren. (Dort könnten die Rectangle ja 32x32 sein) Nur was passiert dann, wenn ich ein Baum plazieren will.


(Problem)
Der Baum hat nun also 6 x 32 x 32 wie regel ich das? Oder ist es völlig egal? Der Baum kriegt den Platz des angeklickten Indizes und wird dennoch später wenn alle Objekte gezeichnet werden richtig dargestellt? Sollt ich mal testen.
(/Problem)




> Eventuell ist JLayeredPane effizienter


Danke, werde ich mir mal anschauen.




> wäre aber sparsam mit Rotationen, die können schonmal rechenintensiv sein. Beim Spieler natürlich, aber zB bei einfachen Bäumen oder so eher geizen und den Boden Tiles sowieso.



Hat ich noch nicht wirklich drüber nachgedacht. Predigen sie uns bei Algorithmen und Optimieren doch ewig "Laufzeit" ein.... und ich denk nicht dran.  böser Student...^^
Da die Tiles und WorldObjects zwar von Sprite erben, haben sie natürlich auch eine AffineTransform, diese wollt ich jedoch nur bei dem Editor verwenden. Um die Sprite vor dem hinzufügen in die Datenstruktur noch zu skalieren oder sontiges. Wobei ich über eine Monstersteuerung mit AffinTransform-Sequenzen nachgedacht hatte, damit sie nicht nur so langweilige autohitter sind. Sondern vllt auch ein paar Moves.

Danke für den Tipp! Ich werd die Rechenintensität im Auge behalten. Erstmal erkundigen was man dazu findet(bezüglich AffineTransform). :rtfm:


Denkt ihr das klappt? Keine Lust einfach wieder drauf loszutippen und dann wieder wie bei der nachträglichen JScrollPane Implementierung realisieren zu müssen, dass meine erste Herangehensweise nicht ganz zielführend war.


----------



## stKev (11. Aug 2012)

Da oben sind keine Rechtschreibfehler nein... :noe:


----------



## Fu3L (11. Aug 2012)

> Wobei sich dort die Frage ergibt, wie ich das mit der Größe des Rasters machen? Ich habe einige Sprites, die sind 32x32 andere variieren. (Dort könnten die Rectangle ja 32x32 sein) Nur was passiert dann, wenn ich ein Baum plazieren will.



Das scheint wirklich etwas mehr zum Nachdenken zu sein... Hier kommts stark auf deine Anforderungen an:
1) Sollen die Bodentiles relativ groß oder sehr fein sein? Bräuchte man also zwei verschiedene Grids für Boden und Strukturen oder reicht eines?
2)Wie stark variieren die anderen Elemente? Nehmen wir einen Baum und eine Mauer... Wenn jedes Mauerstück so breit sein kann, wie ein Baum, könnte man sagen: Ein Sprite im Feld x/y wird immer in die obere, linke Ecke platziert, auch wenn es nicht den ganzen Raum ausfüllt. Für größere Sachen müsste man sehen, ob man sie über mehrere Zellen ausdehnen könnte. (Mir kommen hierbei grad die Pokemon Editionen Silber bis Rubin in den Sinn^^)
3)Wie siehts mit Items und Gegnern aus? Je nach dem wie klein die Gridzellen für, ich sage mal, statische Strukturen ist, müsste man darüber nachdenken, ob man dann die "Entities" auch aufs Grid platziert oder, wenn die Zellen zu groß sind eher frei.



> Hat ich noch nicht wirklich drüber nachgedacht. Predigen sie uns bei Algorithmen und Optimieren doch ewig "Laufzeit" ein.... und ich denk nicht dran.



Wenn du erstmal am Spiel bauste, wirste das nie wieder vergessen  60 FPS entspricht 16 Millisekunde pro Logic/Zeichendurchlauf... Das brennt sich ein^^
Solltest du PNGs verwenden wollen (was eine gute Wahl wäre), such hier im Forum mal nach "Performance von BufferedImage" von Marco13, sonst fliegt dir eh gleich alles um die Ohren 



> Dazu gleich ne Frage: Ich hatte in der API geschaut - ArrayList implements Serializable



Mein Wissen über Serialisierung reicht leider nur so weit, dass ich weiß, dass man damit aufpassen muss und "Effective Java" hab ich grad nicht zur Hand^^ Ich würde ein einfaches Textfile verwenden, aber das musst du wissen^^


----------



## stKev (11. Aug 2012)

> 1) Sollen die Bodentiles relativ groß oder sehr fein sein? Bräuchte man also zwei verschiedene Grids für Boden und Strukturen oder reicht eines?
> 2)Wie stark variieren die anderen Elemente? Nehmen wir einen Baum und eine Mauer... Wenn jedes Mauerstück so breit sein kann, wie ein Baum, könnte man sagen: Ein Sprite im Feld x/y wird immer in die obere, linke Ecke platziert, auch wenn es nicht den ganzen Raum ausfüllt. Für größere Sachen müsste man sehen, ob man sie über mehrere Zellen ausdehnen könnte. (Mir kommen hierbei grad die Pokemon Editionen Silber bis Rubin in den Sinn^^)
> 3)Wie siehts mit Items und Gegnern aus? Je nach dem wie klein die Gridzellen für, ich sage mal, statische Strukturen ist, müsste man darüber nachdenken, ob man dann die "Entities" auch aufs Grid platziert oder, wenn die Zellen zu groß sind eher frei.


1 + 2) Die Bodenbeläge sind alle 32x32 habe sie bei einem fremden LevelEditor eingeladen, um zu testen, ob mehrere nebeneinanderzu einem Rastereffekt führen. (Das würd man ja gerne vermeiden IG, war aber nicht der Fall.
Es reicht eins für die Bodenbeläge. Dachte vllt daran, dass die Bilder die zu groß sind einfach über andere umliegen gezeichnet werden. Ein Baum also einfach die Bodenbeläge überlagert. Von temporärer Transparenz sobald der Spiel unter einem Baum steht fang ich an der Stelle mal gar nicht an. Das wird sich zeigen, ob ich das implementieren kann.

3) Wäre eine gute Idee, dachtest du da daran, dass jeder Bodenbelag ein Item "halten" kann? Denke das wäre mit wenig Aufwand realisiert die Klasse Tile erhält ein Attribute für je 1 Item max. Thx



> 60 FPS entspricht 16 Millisekunde pro Logic/Zeichendurchlauf


Mein Gameloop läuft immo noch auf 25 ms. Meinst du 16 ms wäre besser? Hast du ne Formel zur Berechung der FPS? Sonst schau ich google wenn ich es brauche.


----------



## Fu3L (11. Aug 2012)

FPS ist mit standard Java eh schwer umzusetzen, weil Swing zeichnet, wenn es Lust hat.
Musst dann eventuell auf andere Techniken umsteigen als Swing (da gibts eineigen Threads hier).



> Ein Baum also einfach die Bodenbeläge überlagert.



Ja, die Bodenbeläge, aber was ist mit anderen Objekten?

Ja, das mit den Items könnte man mit 1 pro Gridzelle machen.


----------



## stKev (12. Aug 2012)

> Ja, die Bodenbeläge, aber was ist mit anderen Objekten?


Gute Frage, werde ich mal morgen beim Bötchen fahren, ein wenig perfundieren müssen.


----------



## Spacerat (12. Aug 2012)

Von Serialisierung der Gamemaps kann man nur abraten. Ich habe mich zwar mit dem Thema mal (zwangsläufig, im Zuge standardisierter Datentyperkennung) befasst, aber ich weiss beim besten Willen nicht sehr viel damit anzufangen.
Die Serialize-Problematik: Serializeable bietet einen Mechanismus, Objektinstanzen in einen OutputStream zu serialisieren bzw. serialisierte Objekte aus einem InputStream zu instanzieren. Bei der Serialisierung werden ausschliesslich Membervariablen des Objektes betrachtet, die nicht besonders markiert (static, transient usw.) sind. Serialisiert werden Klassenname, eine "serialVersionUID" und der Inhalt nicht markierter Member. Ist einer dieser Member nicht Serializable, gilt das letztendlich für das gesamte Objekt und es fliegt eine entsprechende Exception. Diese "serialVersionUID" kann fest im Quelltext (und damit auch im ByteCode) fest verankert werden oder aber man lässt sie durch das API automatich berechnen. Bei letzterem werden alle bisher serialisierten Objekte ungültig, wenn man auch nur den kleinsten Hauch am Quelltext bzw. am Bytecode (das Kompilieren in einer anderen Javaversion reicht völlig aus) ändert. Definiert man dagegen eine UID, beschränken sich die "tödlichen" Änderungen nur auf den serialisierbaren Teil der Klasse (nicht markierte Member), da darf weder was hinzugefügt, noch geändert oder entfernt werden. Ferner gibt es noch einige Pitfalls, die beim ständigen Datenaustausch per Serialisierung über's Netzwerk auftauchen, aber ich denke, das führt hier zu weit.
Nun ja, wer sich ein bis n Mal mit Datentyperkennung befasst hat, weis was Dateien (bzw. Bytestreams aller Art) sind und würde sagen, das, was Serializable kann, kann - wenn auch nicht so vereinfacht, dafür aber performanter - jeder. Deswegen würde ich dafür plädieren: Entwickel dir 'nen eigenen Datentyp oder verwende XML (oder besser ODF).


----------



## stKev (12. Aug 2012)

von mir:


> wobei sich mir da die Frage stellte, ob auch alle Objekte in der Datenstruktur dieses Interface implementieren müssen.





> Bei der Serialisierung werden ausschliesslich Membervariablen des Objektes betrachtet, die nicht besonders markiert (static, transient usw.) sind.



Hmm..also ist es anscheinend so. 



> Nun ja, wer sich ein bis n Mal mit Datentyperkennung befasst hat, weis was Dateien (bzw. Bytestreams aller Art) sind und würde sagen, das, was Serializable kann, kann - wenn auch nicht so vereinfacht, dafür aber performanter - jeder. Deswegen würde ich dafür plädieren: Entwickel dir 'nen eigenen Datentyp oder verwende XML (oder besser ODF).



Kennst du da ne gute Seite auf der ich mich einlesen könnte? Davon habe ich nämlich bisher keine Ahnung.
Dachte nicht, dass es so "schwer" wird, wenn ich eine neue Klasse erstelle, der die Datenstrukturen übergebe wird und nur diese serialisiere.


----------



## Fu3L (12. Aug 2012)

Im einfachsten Fall machstes so in der Art in einem einfachen Textfile:


```
0;0;Water;0;
0;1;Grass;1;
```

Heißt: An Stelle 0/0 Wasser, nicht betretbar, an Stelle 0/1 Gras und betretbar.
Wenn du nun noch feste Nummern zuweist, kannste sogar das FIle noch Datengrößenmäßig etwas verkleinern, was aber kaum Bedeutung haben dürfte.


----------



## Spacerat (12. Aug 2012)

Fu3L hat gesagt.:


> Im einfachsten Fall machstes so in der Art in einem einfachen Textfile:


Ja, nee, is klar... auf das Einfachste komm' ich natürlich nicht... :autsch:

@TO: Zum Einlesen in Dateistrukturen gibt es leider nicht sehr viel, das einzige was man wissen muss, ist die Tatsache, dass jede Art von Datei schlicht eine Bytesequenz ist. Serialisierte Objekte gehören natürlich auch dazu. Erst das Interpretieren von Bytefolgen macht ein Dateiformat draus. Viele Formate haben eine sog. Magic Number (die ersten 4 Bytes) mit welcher der Typ erkannt werden kann. Das hat aber alles nichts damit zu tun, wie man eigene Dateistrukturen entwickelt, da sind einem keine Grenzen gesetzt. Wie Fu3L schon sagt: Im Zweifel nimm 'ne Textdatei. Oder erstelle ein Enum von Landschaftsformen (Wasser, Grass, Fels usw) und speichere davon die Ordinale oder besser selbst definierte IDs. Wenn das Spielfeld nicht mehr als 256 * 256 Felder gross und es nicht mehr als 256 Landschaftsformen gibt, bekommst du ein Feld in ganze 3 Bytes (x, y, id), das gesamte Spielfeld in 3 * 256 = 768 Bytes. Ob ein Feld begehbar ist oder nicht, bestimmst du durch's Enum. Ganz geschickt wäre es, eines dieser Enum-Elemente als NULL zu markieren und das Spielfeld immer komplett als "Quadrat" zu speichern, so entfallen pro Feld noch mal die 2 Koordinatenbytes und ein Spielfeld würde dann nur noch 256 Bytes belegen.


----------



## Fu3L (12. Aug 2012)

> Ja, nee, is klar... auf das Einfachste komm' ich natürlich nicht...



Es war nicht auf dich bezogen  Es sollte vor allem dem TO zeigen, dass es wirklich einfache Alternativen zur Serialisierung gibt.
Außerdem hat ein Menschenlesbares Format auch den Vorteil, dass man Fehler eventuell leichter identifizieren kann. 
Dein Mini-Format hat aber auch was in Zeiten, in denen jeder unnötig Speicher verschwendet


----------



## stKev (13. Aug 2012)

Ok, das mit dem Textfile scheint wohl eine gute Alternative zu sein. Habe aber dennoch eine Frage. Was wäre denn, wenn alle Objekte in den Datenstrukturen Serializable implementieren. Attribute so wie isSolid die ich auch im Editor einstellen können möchte. Würd ich dann über die read und writeObject() regeln. Die anderen Werte sowie z.B. die Position des Objektes, würde ja über den Platz in der Datenstruktur geregelt. Dachte mir das ich später für jedes Objekt in den Datenstruktur je das Image(bzw. BufferdImage) zeichen lasse. Hier würd ich die AffineTransformation gerne einsetzen. Standardmäßig würde alle Tiles durch deserialisieren bei int x,y halt 0,0 haben, aber das wäre ja egal. Denn vor jedem neuem Zeichnen tx.translate(dx,dy); und dann über g2d.drawImage(img,tx,null); dx dy würde dann nach gewisser Logik inkrementiert.

Oder übersehe ich hier die Komplexität???

Insofern ich das richtig verstehe, wäre es doch über das Textfile im Prinzip das selbe oder etwa nicht?
Die Objekte haben ihre Position dies wird in diesem Textfile gespeichert und später eingelesen und die Attribute bekommen ihre "alten" Werte. Oder?




> Oder erstelle ein Enum von Landschaftsformen (Wasser, Grass, Fels usw) und speichere davon die Ordinale oder besser selbst definierte IDs.



Wäre ja quasi so wie du es schreibst. Die Reihefolge definieren die Datenstrukturen, da es ja eben gerastert ist.

Nebenbei danke ihr 2 für die hilfreichen Hinweise, an die ich gar nicht gedacht habe.


----------



## Spacerat (13. Aug 2012)

Also mit Enums würde alles nur halb so schwer. Wieso denn immer alles verkomplizieren?

```
package tilemap;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;

public enum Tile {
	NULL(0, false, "null"),
	GRASS(1, true, "grass"),
	ROCKS(2, true, "rocks"),
	WATER(3, false, "water"),
	//... aso
	;

	public final BufferedImage image;
	public final boolean walkable;
	public final int id;

	private Tile(int id, boolean walkable, String imgageName) {
		this.image = getImage(imgageName);
		this.walkable = walkable;
		this.id = id;
	}

	public static Tile getByID(int id) {
		for(Tile t : values()) {
			if(id == t.id) {
				return t;
			}
		}
		return NULL;
	}

	private static BufferedImage getImage(String name) {
		try {
			InputStream in = ClassLoader.getSystemResourceAsStream("res/" + name + ".png");
			ImageReader ir = ImageIO.getImageReadersBySuffix("png").next();
			ir.setInput(ImageIO.createImageInputStream(in));
			return ir.read(0);
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
	}
}
```


```
package tilemap;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;

public class TilesTest {
	public static void main(String[] args) {
		File mapFile = new File("test.map").getAbsoluteFile();
		List<Tile> gameMap = new ArrayList<Tile>(256);
		int x = 0; int y = 0;
		BufferedImage visual = new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_ARGB);
		Graphics g = visual.getGraphics();

		// randomisierte Karte erstellen
		for(int n = 0; n < 256; n++) {
			Tile t = Tile.getByID((int) (Math.random() * 4.0));
			gameMap.add(t);
			g.drawImage(t.image, x, y, t.image.getWidth(), t.image.getHeight(), null); // vereinfachte Festwerte in der Groesse der Tiles
			x += 64;
			if(x == 1024) {
				y += 64;
				x = 0;
			}
		}

		// Karte speichern
		try {
			DataOutputStream out = new DataOutputStream(new FileOutputStream(mapFile));
			out.writeInt(gameMap.size());
			for(Tile t : gameMap) {
				out.writeInt(t.id);
			}
			out.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		// Karte loeschen
		gameMap.clear();

		// Visualisierung speichern
		try {
			ImageOutputStream out = ImageIO.createImageOutputStream(new FileOutputStream(new File("./visual_a.jpg")));
			ImageWriter iw = ImageIO.getImageWritersBySuffix("jpg").next();
			iw.setOutput(out);
			iw.write(visual);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		// Visualisierung loeschen
		g.fillRect(0, 0, 1024, 1024);

		// Karte laden
		try {
			DataInputStream in = new DataInputStream(new FileInputStream(mapFile));
			int size = in.readInt();
			for(; size > 0; size--) {
				gameMap.add(Tile.getByID(in.readInt()));
			}
			in.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		// Vergleichsvisualisierung erstellen
		x = 0;
		y = 0;
		for(Tile t : gameMap) {
			g.drawImage(t.image, x, y, t.image.getWidth(), t.image.getHeight(), null); // vereinfachte Festwerte in der Groesse der Tiles
			x += 64;
			if(x == 1024) {
				y += 64;
				x = 0;
			}
		}

		// Vergleichsvisualisierung speichern
		try {
			ImageOutputStream out = ImageIO.createImageOutputStream(new FileOutputStream(new File("./visual_b.jpg")));
			ImageWriter iw = ImageIO.getImageWritersBySuffix("jpg").next();
			iw.setOutput(out);
			iw.write(visual);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
}
```
 (Im Anhang als ausführbares Jar, mit einfarbigen "Beispieltexturen")

Ich denke mal, dass sich die Eigenschaften der Tiles nicht von Map zu Map ändern werden, deshalb kann man sie als Konstanten implementieren, sogar als öffentliche. Das einzige was hier nun gespeichert werden muss, sind die IDs in den Feldern der ArrayList. Die ArrayList lässt sich auch durch ein zweidimensionales Array ersetzen (Tile[][] gameMap = new Tile[16][16]). Ein NULL-Tile ist im übrigen dann sinvoll, wenn mindestens ein drittel der Map mit anderen Tiles belegt ist, ansonsten bräuchte man pro Tile noch mal 2 ints mehr für die Koordinaten.
In dem Beispiel speichere ich vor den ganzen Tile-IDs die Anzahl der Felder ab, da könnte man evtl. auch die Dimensionen des MapArrays speichern.


----------



## stKev (13. Aug 2012)

Danke euch beide. Denke ich bin derzeit auf einem guten Weg, habt einige Sachen erwähnt an die ich gar nicht gedacht habe. War sehr sinnvoll für einige Implementierung. Werd mich morgen nochmal mit dem lieben Martin, sehr hilfbereiter Lehrbeauftrager bei uns an der FH, treffen. Zwecks kleiner Fragen zur Serialisierung, ja ich werde es Versuche , es sieht gut aus als könne ich es damit schaffen.

Die Werte die beim deserialisieren verloren gehen können sind maximal das AffineTransform Objekt, und eine Hand voll int Werte, die meist aus x,y und den Image Werten wie width/2 und height/2(vllt sind die auch double  ka). Aber selbst wenn. Sollte mit writeInt / readInt nicht so das Problem darstellen. Hoffe ich!!  AffineTransform benötige ich erst IG kann also ruhig null sein.

BB
Man liest sich ja vllt nochmal. :rtfm:


----------



## stKev (14. Aug 2012)

Ich poste noch zum Abschluß mein Objekt, welches die Serialisierung der "Map" des Editor übernimmt. Bin ein wenig Stolz, hat gut geklappt dafür das es mein erstes größeres Projekt ist. Ich mag Java nun noch ein wenig mehr.  Nun folgen noch die Feinheiten bin mal auf die undo()-Methode gespannt. Hatte sie vorhin mal ein wenig getestet. Klappte auch, nur sehr müßig nicht pro Button-Klick eins weg.
Das leider nicht  Dachte da vllt an einen Stack. Nun der Quelltext aber!



```
package editor;

import java.awt.Rectangle;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;

public class LevelContainer implements Serializable {
	
	private static final long serialVersionUID = -8149528364382979358L;
	
	
	// Datenstrukturen
	private Tile[][] tilesL1;
	private Tile[][] tilesL2;
	private WorldObject[][] objects;
	private Npc[][] npcs;
	private Enemy[][] enemies;
	private Rectangle[][] raster;
	
	// von Datei eingelesener LevelContainer
	private LevelContainer lcTemp;
	
	private String path;
	
	// Konstruktor für Level laden
	public LevelContainer(String path) {
		this.path = path;
		load();
	}
	
	// Konstruktor für Level speichern... Erhält die Arrays mit den Level-Objekten
	public LevelContainer(String path, Tile[][] tilesL1, Tile[][] tilesL2, WorldObject[][] objects, Npc[][] npcs, Enemy[][] enemies, Rectangle[][] raster) {
		this.path = path;
		this.tilesL1 = tilesL1;
		this.tilesL2 = tilesL2;
		this.objects = objects;
		this.npcs = npcs;
		this.enemies = enemies;
		this.raster = raster;
		save();
	}

	// speichert den LevelContainer anhand des Path
	private void save() {
		try {
			
			ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(path));
			os.writeObject(this);
			os.flush();
			os.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	// lädt ein LevelContainer aus einem Dateiverzeichnis
	private void load() {
		try {
			
			ObjectInputStream is = new ObjectInputStream(new FileInputStream(path));
			this.lcTemp = (LevelContainer) is.readObject();
			is.close();
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	// verpackt die eingelesen Level-Objekte-Arrays in einer ArrayList und stellt sie über die Methode getData zur Verfügung
	public ArrayList<Sprite[][]> getData() {
		ArrayList<Sprite[][]> data = new ArrayList<Sprite[][]>();
		
		data.add(lcTemp.tilesL1);
		data.add(lcTemp.tilesL2);
		data.add(lcTemp.objects);
		data.add(lcTemp.npcs);
		data.add(lcTemp.enemies);
		
		return data;
	}
	
	// gibt das benötigte Raster zurück
	public Rectangle[][] getGrid() {
		return lcTemp.raster;
	}
}
```


----------

