# Gelände gestalten



## manuche (4. Mrz 2008)

Hallo,
ich plane eine Art TankWar selbst zu programmieren... Allerdings weiss ich noch nicht so ganz, wie ich das lösen soll! Das Problem besteht darin, dass ein Bombeneinschlag Gelände wegsprengt... Klingt erstmal nich wirklich kompliziert aber wie kann man das ganze umsetzen? Eine Idee wäre, für jeden Pixel der Spielfeldbreite einen senkrechten Strich zu zeichnen, was allerdings mehr als viel Arbeit wäre... Eine andere Idee wäre das ganze mit drawPoligon (int xPoints[], int yPoints[], int nPoints) zu lösen. Da stellt sich mir allerdings die Frage, wie ein Krater dargestellt wird... So würde es im Prinzip auch auf ein Array hinauslaufen, dass so groß ist wie das Spielfeld in Pixel hat!
Hat evtl jemand eine bessere/elegantere Lösung? Wenn ja dann gerne her damit...
Gruß manuche


----------



## Campino (4. Mrz 2008)

Ich würde das lösen, indem ich mir einzelne Objekte merke: 

Nach dem Motto: 
Objekt A ist ein Polygon. Alles, was innerhalb dieses Polygons liegt, ist Gelände. 
Objekt B ist ein Kreis. Alles, was innerhalb dieses Kreises liegt, ist kein Gelände (ein Sprengkrater). 
Objekt C ist ebenfalls ein Kries, alles innerhalb ist kein Gelände (noch ein Sprengkrater). 

Für die Kollisionserkennung prüfst du zunächst auf Kollision mit dem zugrundeliegenden Polynom und dann, ob der Punkt in einem der Kreise liegt. Wenn ersteres der Fall ist, wird als Krater ein neuer Kreis mit dem Einschlagspunkt als Mittelpunkt festgelegt. Wenn letzteres ebenfalls der Fall ist, ist der Schnittpunkt der Flugbahn mit dem Kreis, der innerhalb des Polynoms liegt der Einschlagpunkt. 
Eine Optimierungsfunktion müsste Kreise, die komplett in anderen enthalten sind, löschen (ein Krater ist so groß, das ein zweiter Komplett darin verschwindet). 

Zum Darstellen: 

Variante 1: Du prüfst für jedes Pixel, ob es im Polynom und innerhalb eines der Kreise liegt. Ersteres wid gemalt, keins von beidem oder beides nicht. Das könnte allerdings Performanceprobleme geben. 

Variante 2: Du malst erst das Polynom und übermalst dann die Stellen, wo schon weggesprengt ist. Dabei solltest du in jedem Fall einen Buffer verwenden, sonst gibt das Geflacker. 

campino

PS: Ich hab jetzt immer Kreise geschrieben. Für eckige Krater, die Gänge von Waffen die sich in den Boden fressen usw. zählt natürlich dasselbe, sind dann halt nur keine Kreise sondern Rechtecke, Polynome oder was auch immer.


----------



## Wildcard (4. Mrz 2008)

Polynom!=Polygon


----------



## 0x7F800000 (4. Mrz 2008)

Könntest du vielleicht näher erläutern, wie dein gelände überhaupt aussieht?

?1):
 Ist es ein 2D oder 3D Spiel? (also, wenn du es mit den drawPolygon() zeichnen willst, wirds wohl ein eckiges 2D-spielchen sein)

?2):
gibt es eine injektive Projektion der Geländeoberfläche auf eine ebene (bzw gerade in 2D) ?

_Wenn ja:_ dann wirst du das gelände wohl einfach durch eine Funktion R->R darstellen können (Polygonzug, Polynom bzw Spline, wäre eigentlich auch was feines, auch wenn das im vorletzten post als tippfehler gemeint war)
Krater erzeugst du einfach dadurch, dass du einige Gitterpunkte in der Nähe des einschlags absenkst.

_Wenn nein:_ (wenn die karte irgendwelche höhlen enthalten soll, wie bei Worms Armageddon o.ä.) da brauchst du schon eine wesentlich komplexere Datenstruktur um die karte überhaupt vernünftig zu speichern zu können, da wirds schon problematisch... Vor allem weil sich die Topologie des Geländes verändern kann (in Worms wars afaik so: stell dir vor eine Kugel die auf einer Säule ruht. Wenn man die Säule wegsprengt, bleibt die Kugel in der Luft schweben. Das gelände, was aus einem Stück bestand, besteht dann aus zwei nicht zusammenhängenden Stücken)

das alles ist imho nicht so sonderlich einfach... da könnte man lange dran rumtüfteln.


----------



## Campino (4. Mrz 2008)

Wildcard hat gesagt.:
			
		

> Polynom!=Polygon


*schäm* Lag wohl daran, dass ich für eine 2D- Landschaft, die sich als zusammenhängendes Stück am unteren Bildschirmrand entlangzieht tatsächlich über ein PolyNOM nachgedacht hatte. Schreiben wollte ich aber PolyGON. 

Ich war von der TankWars- Variante, die ich kenne ausgegangen, die ist 2D und zusammenhängend. Durch Explosionen abgetrennte Geländeteile fallen nicht runter. 



> Wenn ja: dann wirst du das gelände wohl einfach durch eine Funktion R->R darstellen können (Polygonzug, Polynom bzw Spline, wäre eigentlich auch was feines, auch wenn das im vorletzten post als tippfehler gemeint war)
> Krater erzeugst du einfach dadurch, dass du einige Gitterpunkte in der Nähe des einschlags absenkst.



In der Variante des Spiels, die ich kenne, war es tatsächlich so, dass die Krater rund waren, also scharfe Kanten hatten, exakte Kreise mit der Einschlagstelle als Mittelpunkt und dem Explosionesradius als Radius. Das wird damit schwierig, da das einfache Absenken der nächsten Gitterpunkte ja auch das Gelände absenkt statt wirklich scharf zu schneiden. 
(Hängt natürlich von der Gitterpunktdichte ab, aber damit die Krater wirklich rund werden, braucht man ziemlich viele...)


----------



## 0x7F800000 (4. Mrz 2008)

öhm... also okay, ich weiss jetzt in etwa worum es geht... Also, ich bin durch googln auf eine variante gestoßen, die sehr unförmige krater unterstützt, die müssen nicht rund sein, die können schräg im boden verlaufen usw. Bei mir ist der eindruck entstanden, dass dort alles auf pixel genau ist.


Also, deswegen lautet mein vorschlag:
einfach alles auf pixel genau machen. Das gelände an sich speicherst du in einer separaten textur, die dann beim zeichenvorgang in eine andere rüberkopiert, und mit den hintergrund, ganzen panzern und granaten zusammengefügt wird. Das gelände machst du sichtbar, die fläche, wo später der hintergrund hinkommt machst du transparent.

Kollisionsabfragen sind bis zum lächerlichen einfach: Du nimmst einfach immer die position der granate, und schaust, ob der pixel an dieser position transparent ist oder nicht. falls nicht: zeichnest du einfach einen transparenten kreis um diesen punkt in die geländetextur (der sprengkraft entsprechend groß).

mehr ist da nicht zu tun, aber das wird schon ziemlich gut aussehen schätz ich mal...


----------



## Quaxli (5. Mrz 2008)

Ein ähnliche Lösung:

Eigentlich mußt Du gar nicht viel speichern. Alles was Du brauchst ist ein BufferedImage 

- Beim Initiieren Deines Spiels erstsellst Du Dir ein leeres BufferedImage in Spielfeldgröße und füllst es z. B. schwarz aus
- In dieses BufferedImage zeichnest Du Deine Landschaft, z. B. mit einer Schleife immer einen Strich neben den anderen. Wenn man noch ein paar Regeln aufstellt, damit die Unterschiede nicht so groß sind, kriegt man durchaus gute Ergebnisse.
- Diese Image enthält jetzt Deine Landschaft und wird im GameLoop immer als erstes gezeichnet, damit es im Hintergrund steht.
- Wenn Du jetzt schießt, läßt Du Deinen Schuß seine Position gegen das Image prüfen und greifst die Farbwerte ab (BufferedImage.getRGB(int x, inty).
- Sobald die zurückgegebenen Farbwerte Deinem Untergrund entsprechen, zeichnest Du an der Stelle erst eine Explosion
 und dann einen schwarzen Kreis. Schon hast Du den schösten Krater in Deiner Landschaft.
- Abhängig davon, an welcher Stelle Deiner Granate Du den Farbwert des BufferedImage abgreifst hast Du schon gewisse unterschiedliche Schuß-Eigenschaften. Prüfst Du z. B. für einen Punkt an der Oberseite, dringt die Granate ein Stück in die Landschaft ein, ehe die Bedinung greift. Prüfst Du die Unterseite, hast Du eine Explosion, die an der Landschaft kaum Schaden anrichtet.


----------



## manuche (5. Mrz 2008)

Uff... mit soviel Rückmeldung hätte ich jetzt nicht gerechnet!
Vielen dank an euch... jetzt liegt es wohl erstmal an mir alles aufzuarbeiten und zu gucken was am Besten zu meinen Vorstellungen passt! Zur Zeit sieht mir die Methode von quaxli am trivialsten aus... Dazu allerdings noch eine Frage:
Wird das "leere" BufferedImage schwarz gezeichnet um später die RGB-Abfrage möglich zu machen? Nen netter Hintergrund wäre evtl nich schlecht, könnte man das dann nicht über Layer oder ähnliches regeln?
Danke nochmal für das Brainstorming! Scheint ja ein heisses Thema zu sein ^^
Gruß

manuche


----------



## Quaxli (6. Mrz 2008)

Trivial? Meine Lösung?  :bahnhof: 

Die Lösung beschreibt die Basics. Was Du daraus machst, hängt von Deiner Kreativität ab. :bae:


----------



## manuche (6. Mrz 2008)

erweitern kann man alles... nur so wie du es beschreiben hast klingt es auf jeden fall trivial!

-RGB Abfragen
-wenn RGB gleich dem Boden
-schwarzen kreis zeichnen

also wenn das nicht trivial ist... über alles weiter möchte ich mir (noch) keine gedanken machen 
Problem könnte nur werden wenn die panzer bewegt werden sollen!


----------



## Gast (6. Mrz 2008)

manuche: Das mit den Layern ist eigentlich Andreys Idee: 

Ein BufferedImage mit Hintergrund, das malst du. 
Darauf malst du ein weiteres BufferedImage, dass die Landschaft enthält und an den Stellen, wo keine Landschaft ist, transparent ist. Bei Explosionen musst du nur das oberste (das mit der Landschaft) ändern.


----------



## Quaxli (6. Mrz 2008)

Wenn Dich meine einfache Lösung mit getRGB und BufferedImage schon vor Probleme stellt, bzw. Du bei der Methoden-Angabe dier BufferedImage nicht in die API schaust und dir die Aussage "scharzen Kreis zeichnen" Probleme bereitet, DANN frage ich mich, ob Du mit Layern überhaupt anfangen solltest.


----------



## manuche (6. Mrz 2008)

Es stellt mich ja nicht wirklich vor ein Problem... Ich kanns halt noch nich genau sagen weil ich noch nichts testen konnte aber es klingt erstmal sehr einleuchtend!
Ich kann mir halt nur vorstellen, dass es alles andere als trivial wird wenn die panzer fahren sollen... Darüber möchte ich allerdings noch nich urteilen da ich noch ein wenig vor mir hab bevor ich dieses Problem (oder auch Nichtproblem) angehen kann! Sobald ich Zeit hab werde ich das mal testen! Dann seh ich wie leicht oder schwer es mir fällt!

mfG


----------



## Quaxli (6. Mrz 2008)

Hier mal ein ganz und gar untriviales Beispiel: Ohne Animationen, ohne GameLoop, ohne Kommentare. Nur die reine Funktion als ganz simples Beispiel, das man sicherlich farbenprächtiger gestalten könnte, daß aber mal die Basics aufzeigt:


```
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.*;


public class Test2 extends JPanel implements MouseListener{

	private static final long	serialVersionUID	= 1L;
	BufferedImage back;
	JFrame frame;

	public static void main(String[] args){
		new Test2();
	}
	
	public Test2(){
		initBackground();
		frame = new JFrame("Test");
		frame.setSize(800,600);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(this);
		addMouseListener(this);
		frame.setVisible(true);
	}

	private void initBackground(){
		back = new BufferedImage(800,600,BufferedImage.OPAQUE);
		Graphics g = back.getGraphics();
		g.setColor(Color.black);
		g.clearRect(0, 0, 800, 600);
		g.setColor(Color.green);
		
		int height = 400;
		for (int i=0;i<back.getWidth();i++){
			int rnd = (int)(Math.random()*2);
			if(rnd==0){
				height++;
			}else{
				height--;
			}
			g.drawLine(i, height, i, back.getHeight());
		}
		
	}
	
	
	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		g.drawImage(back,0,0,this);
		
	}

	public void mouseClicked(MouseEvent e) {
		
		Color c = new Color(back.getRGB(e.getX(), e.getY()));
		if(c.equals(Color.green)){
			Graphics g = back.getGraphics();
			g.setColor(Color.black);
			g.fillOval(e.getX()-20,e.getY()-20,40,40);
		}
		frame.repaint();
		
	}

	public void mouseEntered(MouseEvent e) {
		
	}

	public void mouseExited(MouseEvent e) {
		
	}

	public void mousePressed(MouseEvent e) {
		
	}

	public void mouseReleased(MouseEvent e) {
		
	}

	
}
```


----------



## manuche (6. Mrz 2008)

Ich hab da ma was vorbereitet...
Die Klasse ist von JPanel abgeleitet... Im Prinzip beinhaltet sie jetzt die ganze Zeichenlogik für sich selbst! Die x bzw y Werte werden in einer GUI-Klasse anhand  einer Fromel berechnet. Soll hier allerdings grad keine Rolle spielen (es sei denn der Wunsch wird geäußert)! Ein kleines Problem hab ich allerdings noch, und zwar den Rand. In der GUI Klasse wird mit Hilfe der BorderFactory ein Rand um das JPanel gezeichnet... wenn allerdings ein Ein Krater zu nah am Rand gezeichnet wird, so verschwindet der Rand unter ihm... Gibt es eine Möglichkeit, den Rand immer im Vordergrund zu zeichnen?


```
import java.awt.Color;
import java.awt.Graphics;
import java.awt.LayoutManager;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.JPanel;


public class DrawPanel extends JPanel {
	private static final long serialVersionUID = 4377090214358288973L;
	
	private GUI parent;
	private BufferedImage terrain;
	private ArrayList<Caldara> calderas = new ArrayList<Caldara>();

	public DrawPanel (LayoutManager lm){
		super (lm);
	}
	
	public void initTerrain(){
		terrain = new BufferedImage (this.getWidth(), this.getHeight(), BufferedImage.OPAQUE);
		Graphics g = terrain.getGraphics();
		g.setColor (Color.WHITE);
		g.fillRect (1, 1, this.getWidth() - 2, this.getHeight() - 2);
		g.setColor (Color.GREEN);
		int height = 150; 
	    for (int i = 1; i < this.getWidth() -1; i++){ 
	    	int rnd = (int)(Math.random()*2); 
	    	if(rnd==0){ 
	    		height++; 
	    	}else{ 
	    		height--; 
	    	}
	    	g.drawLine (i, height, i, this.getHeight() - 2);
	    }
	}
	
	public void setParent (GUI parent){
		this.parent = parent;
	}
	
	@Override
	public void paint (Graphics g){
		super.paint (g);
		g.drawImage (terrain ,0 ,0, this);
		g.drawOval (parent.xCoOrd(), parent.yCoOrd(), 2, 2);
		if (parent.xCoOrd() != 0 && parent.yCoOrd() != 0){
			if (Color.GREEN.equals (new Color (terrain.getRGB (parent.xCoOrd(), parent.yCoOrd())))){
				calderas.add (new Caldara (parent.xCoOrd() - 20, parent.yCoOrd() - 20, 40, 40));
			}
		}
		for (Caldara caldara : calderas){
			g.setColor (Color.WHITE);
			g.fillOval(caldara.getX(), caldara.getY(), caldara.getWidth(), caldara.getHeight());
		}
	}
}
```

Die Klasse Caldera speichert eigentlich nur die Koordinaten und den Umfang des Kraters um diese Werte später wieder aufrufen zu können (die Krater sollen ja schließlich weiter gezeichnet werden)... Dies bietet mehr Möglichkeiten, falls später Krater mit vreschiedenen Radien gezeichnet werden sollen...


----------



## 0x7F800000 (6. Mrz 2008)

sag mal manuche, wo soll der witz sein?
von zwei leuten kam schon praktisch synchron der vorschlag, wie man es einfach pixelweise machen könnte, quaxli war sogar so fleißig, und hat das in super effizienten funktionierenden code umgesetzt, und jetzt präsentierst du hier wieder irgendeine unnnötig komplizierte version, in der du die krater einzeln abspeicherst...

naja, warum einfach, wenn es kompliziert geht  :roll:


----------



## manuche (6. Mrz 2008)

Hui... Hab garnicht gecheckt, dass die Krater bei quaxlis Code auch bleiben... Hab mir seinen Code mal angeschaut und teilweise in das implementiert was ich eh schon hatte... Nun gut, jetzt haben wir zwei funktionierende Bespiele! 
Wer lesen kann is klar im Vorteil... -.-


----------



## 0x7F800000 (6. Mrz 2008)

manuche hat gesagt.:
			
		

> Nun gut, jetzt haben wir zwei funktionierende Bespiele!


naja, was deinen code angeht, da wär ich nicht allzu optimistisch  wenn es nämlich später an kollisionserkennung und bewegung der panzer geht, drehst du durch, wenn du da jeden krater fragen musst ob der panzer da reinfallen soll oder nicht...
bei quaxli's methode bleibt es immer alles nur bei pixeln. Alles bleibt einheitlich, ein krater unterscheidet sich absolut durch gar nix vom sonstigen gelände


----------



## manuche (6. Mrz 2008)

Das ist auch der Grund warum ich meine allzu schöne ArrayList auch wieder rausgenommen hab... 
Hast da ja Recht... Is halt nen bissl zuviel des guten, wenn man den Hintergrund direkt "statisch" verändern kann und nicht jedes mal auf vorhandene Krater prüfen muss!
Aus viel Code mach schnell wenig 
Danke an euch! Habt mir super geholfen!!! Nur mit dem Border muss ich nochmal schauen ob ich was finde...
Gruß


----------

