2D Simulation möglichst effizient (ggf. Transparenz)

conehead

Mitglied
Hallo.
So ich wollte jetzt mal ein größeres Projekt in Angriff nehmen und nicht einfach mal drauf los programmieren, sondern mir erst mal genauer notieren, wie ich vorgehen soll.

Ich habe hier schon etwas die Suche benutzt, aber bisher nichts Passendes gefunden.

Also:
Ich möchte einen Simulator programmieren.
Das ganze soll auf einem 2D-Grid sein und möglichst effizient.
Also brauche ich ein möglichst großes Grid und möglichst viele Agenten, die sich frei bewegen können.
Ich möchte in diesem Grid auch gerne Zoomen, falls das "Spielfeld" zu groß für das Anzeigefenster wird.
Schön wäre es natürlich auch noch, wenn die Objekte aus einem Bild geladen werden und ggf. transparent sind.

Zusätzlich soll man noch die Roboter anklicken können und sich Informationen anzeigen lassen können.

Mir würden spontan 3 Möglichkeiten einfallen:

-ein Panel, auf dem das Grid zeichnen lasse, dann alle Roboter und es anschließend anzeigen lasse
-ein Panel auf dem sehr viele kleine Panels sind, welche das Grid ergeben
-ein Table
-evtl. sogar OpenGL?


Also alles auf ein Panel zeichnen lassen und anschließend ausgeben habe ich bereits versucht, aber schon bei einem 10x10 Grid mit 20 Agenten und insgesamt 5 Texturen, schien es mir schnell langsam zu werden.
Optimal wäre, wenn ich in Richtung 1000x1000 Grid kommen würde und es noch flüssig läuft.

Hoffe ihr habt ein paar Tips für mich, womit man das ganze am besten realisieren könnte.

MfG

conehead
 

Marco13

Top Contributor
Erstmal solltest du die Simulation (das Modell) von der Visualisierung trennen. Dann kannst du im Idealfall relativ leicht und schnell die 4 genannten Möglichkeiten ausprobieren, und sehen, welche am besten ist. 1 und 4 klingen am passendsten, 2 und 3 dürften bei >100x100 Feldern unerträglich langsam werden.

1 Sollte eigentlich kein Geschwindigkeitsproblem bringen. Poste vielleicht mal ein bißchen Code.
 

conehead

Mitglied
Natürlich soll das ganze getrennt werden. Es soll erst einmal nur um die Visualisierung gehen.
Es soll später für eine Simulation genutzt werden.

Und die 4 genannten Dinge auszuprobieren wollte ich mir eigentlich sparen ;)
Der Vorteil wäre bei 2 halt, dass ich bei einem OnClick-Ereignis nicht erst schauen müsste, an welcher Position geklickt wurde, sondern direkt das gedrückte Feld bestimmen kann.

Und OpenGL würde ich eigentlich eher ungerne drauf zugreifen, damit ich mit Java noch keine Erfahrungen damit gemacht habe.
 

conehead

Mitglied
Es ging mir ja generell darum, was am schnellsten ist.
Ich dachte da gäbe es irgendwelche vorgaben.

Deswegen wollte ich erst einmal fragen.
Der Code den ich habe, ist kein Stück optimiert und war nur mal testweise ob es überhaupt so gehen würde.

Wie bereits gesagt wollt ich ERST fragen und DANN umsetzen.
 

Michael...

Top Contributor
Ich würde auch Variante 1 nehmen. Wenn da etwas langsam läuft, könnte es am Code bzw. Vorgehen liegen.
Mit einem MouseListener lässt sich recht leicht das angeklickte Feld berechnen.
 

Marco13

Top Contributor
Ist ja OK. Es ist oft so, dass man "Zeitaufwand" verschieben kann. Man kann in einer Woche etwas programmieren, was dann mit 10FPS läuft, oder in einem Monat etwas programmieren, was mit 80 FPS läuft (ist aber eine Sättigungsfunktion, nicht linear ;) ).

Als kleines Beispiel, worauf sich das bezieht:

Der Vorteil wäre bei 2 halt, dass ich bei einem OnClick-Ereignis nicht erst schauen müsste, an welcher Position geklickt wurde, sondern direkt das gedrückte Feld bestimmen kann.

Wenn man mit der Maus über ein JPanel fährt, in dem 10000 Components in einem Gitter liegen, dann wird (um das nochmal zu betonen: Bei jeder Mausbewegung - nicht nur bei einem Klick!) nachgeschaut, in welcher dieser 10000 Components diese Maus gerade ist - und das auf die denkbar ineffizienteste Weise, weil einfach alle 10000 Components durchgegangen werden, und überall sowas wie
Code:
if (component.getBounds().contains(point)) dispatchEventTo(component);
gemacht wird. Das arme, kleine JPanel weiß ja nicht, dass die Components in einem Gitter liegen, und die Gitterzelle einfach mit (point.x/100, point.y/100) ausgerechnet werden könnte - es müssen alle durchprobiert werden. Von der absurden Speicherverschwendung (siehe http://www.java-forum.org/java-basi...694-pingpong-selber-schreiben.html#post394005 ) mal ganz abgesehen.

Und eigentlich sollte man ohne Probleme ein Gitter mit so vielen Sprites zeichnen können. Lädst du die Sprites vielleicht bei jedem Neuzeichnen neu? ???:L (Soll's ja geben ... :bahnhof: )
 

conehead

Mitglied
Nein hatte sie bei Initialisierung des Feldes geladen.
Aber dann werde ich es wohl über Variante eins mal ordentlich versuchen und meine Ergebnisse hier dann posten.
Bin ich mal gespannt ob das alles so gut geht ;)

//edit: mal davon abgesehen ist mir gerade schon eine Optimierung eingefallen.
Ich lasse nicht immer das komplette Grid zeichnen, sondern nur die Felder die neu belegt werden müssen.
Sollte bei 1000x1000 Feldern schon einen riesen Unterschied machen
 
Zuletzt bearbeitet:

conehead

Mitglied
Hmm irgendwie ist es schon langsam wo es noch nicht langsam sein dürfte :(
Hab das hier grad mal schnell zusammengeschrieben:

Java:
public class Grid extends JPanel {

	private BufferedImage[] tilePics;
	private int tileSize = 16;
	private int mapWidth = 60;
	private int mapHeight = 60;
	
	//init
	public Grid(){
		try {
			tilePics = new BufferedImage[2]; 
			tilePics[0] = ImageIO.read(new File(".\\tiles\\road.jpg"));
			tilePics[1] = ImageIO.read(new File(".\\tiles\\goal.jpg"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//draw grid
	@Override public void paint(Graphics g) {     	 
   	 for (int i=0; i<mapHeight;i++){
		for (int j=0; j<mapWidth;j++){
			this.getComponentGraphics(getGraphics()).drawImage(tilePics[0],j*tileSize, i*tileSize, this);
		}
   	 }
	}
	
	//double buffer
    public void update(){
    	Graphics g = this.getGraphics();
    	Image dbImage = null;
    	Graphics dbGraphics = null;
    	if (dbImage == null) {
    		dbImage=createImage(this.getSize().width, this.getSize().height);
    		dbGraphics = dbImage.getGraphics();
    	}
    	
    	dbGraphics.setColor(getBackground());
    	dbGraphics.fillRect(0,0,this.getSize().width, this.getSize().height);
    	dbGraphics.setColor(getForeground());
    	paint(dbGraphics);
    	g.drawImage(dbImage,0,0,this);
    }
}

Aber bereits beim ersten Zeichnen kann ich dem Grid schon mehr oder weniger zusehen.
Und darauf sind noch nicht einmal die Roboter eingetragen.
Bereits bei einem Grid von 60x60
 

Marco13

Top Contributor
Erstaunlich, dass man ca. 15 Jahre lang mit Java und Swing arbeiten kann, ohne die Methode "getComponentGraphics" zu kennen. Aber getGraphics, und wenn man DIE verwendet, liegt meistens was im Argen. Kurz:
- nicht getGraphics aufrufen
- paintComponent statt paint überschreiben, und NUR in das übergebene Graphics reinzeichnen
- double buffering ist bei Swing schon eingebaut.

Falls nötig, morgen mehr...
 

conehead

Mitglied
Danke erst mal!

Ha da sagst du auch was ;).
Habe mir heute morgen erst einmal selbst an den Kopf gepackt, warum ich nicht g direkt verwendet habe.
So paintComponent habe ich überschrieben und es läuft jetzt auch deutlich besser.

Und mit "double buffering ist bei Swing schon eingebaut", meinst du, dass ich die komplette update-Methode weglassen kann? Also ich habe sie gerade mal rausgelassen und es scheint noch gut zu funktionieren.

Ich lasse ein 100x100 Felder-Grid mit 30ms pause über den Bildschirm wandern und es sieht noch ziemlich flüssig aus.
Bei 300x300 Feldern wird es allerdings schon etwas ruckelig. Ist es in diesem Bereich denn schon normal?
Auch wenn sich kaum etwas am Code geändert hat, hier nochmal der aktuelle:

Java:
public class Grid extends JPanel {

	private BufferedImage[] tilePics;
	private int tileSize = 8;
	private int mapWidth = 300;
	private int mapHeight = 100;
	
	//init
	public Grid(){
		try {
			tilePics = new BufferedImage[2]; 
			tilePics[0] = ImageIO.read(new File(".\\tiles\\road.jpg"));
			tilePics[1] = ImageIO.read(new File(".\\tiles\\goal.jpg"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	//draw grid
	@Override public void paintComponent(Graphics g) {     	 
   	 for (int i=0; i<mapHeight;i++){
		for (int j=0; j<mapWidth;j++){
			g.drawImage(tilePics[0],j*tileSize, i*tileSize,j*tileSize+tileSize, i*tileSize+tileSize,0,0,tileSize,tileSize,this);
		}
   	 }
	}
}

Als nächstes werde ich mal einbauen, dass sich ein paar Roboter, so etwa 100 Stück, sich über das Grid bewegen sollen und schauen, ob es immernoch in annehmbarer Zeit geht.
 

Marco13

Top Contributor
300x8 passt wohl nur auf die wenigsten Bildschirme, also gehe ich mal davon aus, dass das Grid in einem ScrollPane liegt? Du könntest ausprobieren, inwieweit es etwas bringt, nur den tatsächlich angezeigten Bereich zu zeichnen - also quasi das Scrollen selbst "per Hand" nachzubauen, falls das praktikabel ist, GANZ grob im Sinne von
Java:
     int minX = ...
     int minY = ...
     int maxX = ...
     int maxY = ...

     for (int i=minY; i<maxY;i++){
        for (int j=minX; j<maxY;j++){
            ...
        }
     }
Es könnte aber sein, dass das nicht sooo viel bringt. Eigentlich sollte das durch das Clipping beim Graphics-Objekt schon intern so geregelt werden, dass keine Zeit mit "dem Zeichnen unsichtbarer Dinge" verschwendet wird.

Was aber evtl. deutlich mehr bringen könnte, wäre, die einfachste mögliche drawImage-Funktion zu verwenden. Im Moment verwendest du die, mit der man irgendein Rechteck des Bildes in irgendein Rechteck des Graphics-Objektes reinzeichnen kann. Ggf. mit Verschiebung und Skalierung und Abschneiden von Rändern usw. Besonders eine skalierung kann extrem teuer sein. Du solltest die Tiles, falls nötig, im Konstruktor auf die Größe bringen, mit der sie auch gezeichnet werden sollen (also sie auf tileSize*tileSize skalieren), und dann in der Schleife nur noch
g.drawImage(properlyScaledTilePics[0],j*tileSize, i*tileSize, this);
aufrufen.
 

conehead

Mitglied
Ich möchte ja später noch Zoomen können.
Wäre das mit deiner Methode noch möglich?

So wie es jetzt gerade ist, könnte ich die TileSize zur Laufzeit verändern und es passt noch alles.
Zum Thema Grid verschieben hatte ich das ganze so vor:

Ich habe ja ein JPanel und dieses kommt auf meine Form.
Der Form hätte ich einfach ein Null-Layout gegeben und nur stumpf die Location des JPanels verändert.

Dann könnte ich ja per Hand noch ausrechnen welche Tiles gezeichnet werden sollen usw.
 

Marco13

Top Contributor
Ah, OK, zoomen ist so eine Sache, das kann (!) frickelig werden, je nachdem, wie genau man es macht. Ehrlich gesagt habe ich da jetzt keine konkreten Erfahrungen, wie man das "am schnellsten" (besten, einfachsten, flexibelsten...) hinbekommt. In der aktuellen Form könnte ja nur gezoomt werden, indem die tileSize geändert wird - und bei 500 Tiles könnte man quasi nur zwischen tileSize=1,2 oder 3 "umschalten". Für ein kontinuierliches Zoomen könnte man Graphics2D#scale(...) verwenden, aber nochmal: Ich weiß nicht, wie es dann mit der Effizienz aussieht. Es KÖNNTE(!!!) in bezug auf die reine "Zoom-und-Scroll-Performance" dann sinnvoll sein, die ganzen Hintergrund-Tiles EINmal in ein großes BufferedImage zu malen, und nurnoch dieses Bild anzuzeigen, statt 10000 einzelne mini-Bildchen zu malen. Die (wenigen) beweglichen Roboter-Bildchen könnten dann einfach passend drübergemalt werden.
 

conehead

Mitglied
Ja das hatte ich mir auch überlegt, einfach einmal alles zu Zeichnen und dann dieses Bild zu speichern und immer anzuzeigen.
Schneller wäre es wohl noch, bei Initalisierung alle Bilder für alle Zoomstufen einmal vorzubereiten, so dass diese zur Laufzeit gar nicht mehr neu berechnet werden müssen.

So viele Zoomstufen werden es auch nicht werden:
8,16,32,64 Pixel als TileSize...also gerade mal 4 Stufen.
Ich denke das beste ist gerade...ich probiere es einfach erst mal aus, werde etwas Code posten und dann sehen wir weiter ;).
 

conehead

Mitglied
Kurzer Zwischenstand:
Auf einem 50x50 Feld tummeln sich nun bereits bis zu 500 Agenten und das ganze noch ziemlich schnell.

Eine Schöne Scrollfunktion habe ich bisher noch nicht geschafft zu implementieren, da muss ich mir noch etwas cleveres einfallen lassen.

Das Zoomen an sich funktioniert nun auch schon in den 4 Stufen ( hmm...wenn das Grid einmal das gesamte Frame ausgefüllt hat und anschließend wieder kleiner wird, werden die Reste nicht gelöscht? :bahnhof: ?
moepp.jpg

Er sollte das Größere nicht mehr zeichnen...

Wo ich mir auch noch nicht so sicher bin:
Soll ich überhaupt für den Hintergrund Bilder laden?
Ich glaube fast, normale Linien sollten es auch tun.
Das würde mir wahrscheinlich viel Arbeit ersparen.

Genauso die Roboter.
Evtl. sollte ich da auch von Bilderchen auf Kreise zurückgehen.
Besonders was das Zoomen angeht, scheint es hier deutlich einfacher zu sein.

Denn beim eigentlichen Zoomen sind die Bilder ja noch nicht skaliert, sondern es wird nur ein Ausschnitt genommen. Also müsste ich jedes Bild noch extra skalieren um es dann neu Zeichnen zu können.

Hier noch mal der aktuelle Code:

Java:
public class Grid extends JPanel implements MouseMotionListener, MouseWheelListener{

	private BufferedImage[] tilePics;
	private int tileSize = 8;
	private int mapWidth = 50;
	private int mapHeight = 50;
	//for scrolling
	private int lastDragPosX;
	private int lastDragPosY;
	private int scrollSpeed = 1;
	
	private ArrayList<Robot> robots;
	
	//init
	public Grid(){
		robots = new ArrayList<Robot>();
		try {
			addMouseMotionListener(this);
			addMouseWheelListener(this);
			tilePics = new BufferedImage[2]; 
			tilePics[0] = ImageIO.read(new File(".\\tiles\\road.jpg"));
			tilePics[1] = ImageIO.read(new File(".\\tiles\\goal.jpg"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		//add new robots
		for (int i=0; i<200;i++){
			robots.add(new Robot());
		}
		
	}
	
	//draw grid
	@Override public void paintComponent(Graphics g) {     	 
   	 for (int i=0; i<mapHeight;i++){
		for (int j=0; j<mapWidth;j++){
			g.drawImage(tilePics[0],j*tileSize, i*tileSize,j*tileSize+tileSize, i*tileSize+tileSize,0,0,tileSize,tileSize,this);
		}
   	 }
   	 //draw robots
   	 for (int k=0; k<robots.size();k++){
   		g.drawImage(tilePics[1],robots.get(k).getPosX()*tileSize, robots.get(k).getPosY()*tileSize,robots.get(k).getPosX()*tileSize+tileSize, robots.get(k).getPosY()*tileSize+tileSize,0,0,tileSize,tileSize,this);
   	 }
	}
	
	//move robots
	public void updateRobots(){
		for (int i=0;i<robots.size();i++){
			robots.get(i).moveRandom();
		}
	}

	//zoom in/out
	public void mouseWheelMoved(MouseWheelEvent e) {
		if (e.getWheelRotation()==-1) {
			if(tileSize!=8) tileSize=tileSize/2;
		}
		if (e.getWheelRotation()==1) {
			if(tileSize!=64) tileSize=tileSize*2;
		}
	}
	
	//scrolling
	public void mouseDragged(MouseEvent e){
		lastDragPosX = e.getLocationOnScreen().x;
		lastDragPosY = e.getLocationOnScreen().y;
	}
	
	public void mouseMoved(MouseEvent e) {	
		//do nothing
	}
}
 

Marco13

Top Contributor
Zum Hintergrund/Löschen und so: In den allermeisten Fällen sollte, die erste Zeile von paintComponent
Code:
@Override 
public void paintComponent(Graphics g) {
    [b]super.paintComponent(g);[/b]
sein.

Das Zoomen: Im Moment wird wohl nur ein Ausschnitt der Bilder genommen, aber man kann durch geeignete Wahl der drawImage-Parameter auch erreichen, dass das gesamte Bild einfach nur kleiner (d.h. mit der aktuellen tileSize) gezeichnet wird. Aber wie gesagt: Das kann dann langsam werden. Man sollte dann dafür sorgen, dass man nur das schon kleiner skalierte Bild zeichnen muss, und es nicht beim drawImage jedes mal (10000 mal) "on the fly" skaliert wird.
In welchen Fällen ein Zeichnen "per Hand" schneller ist, als ein Image, ist schwer zu sagen. Ich habe auch schonmal was gemacht, wo etwas relativ "komplexes" gezeichnet werden musste, und ich das dann in Images "gecacht" habe. Images sind schon recht schnell, sofern die Größe stimmt (und keine Transparenzen drin sind) wird dort einfach nur ein bißchen Speicher kopiert, das ist kaum aufwändiger als ein leeres Rechteck zu zeichnen. Was für dein Ziel am günstigsten ist... hängt in erster Linie von deinem Ziel ab ;)
 

conehead

Mitglied
Erst einmal noch vielen Dank für deine Hilfe. :toll:
Habe jetzt in sehr kurzer Zeit schon für MICH gesehen schon beachtliche Fortschritte gemacht ;)

Problem mit dem falsch neuzeichnen ist gelöst.
Und zum Skalieren an sich:

Ich werde es wohl wirklich so machen:
Das eigentliche Grid wird zur Initialisierung in allen skalierbaren Größen "vorgerendert" und gecached wie du es gesagt hast. Ich versuche es erst einmal dass die Roboterbilder bei jedem draw neu skaliert werden. Sollten eigentlich nicht mehr als 1000 Stück werden. Falls das ganze langsam wird, kann ich auch dort immernoch die anderen Größen vorberechnen lassen.

Achja und falls es noch jemanden interessiert, was der Fehler zuvor war beim Skalieren:
Ich hatte zuerst
Java:
 g.drawImage(tilePics[1],robots.get(k).getPosX()*tileSize, robots.get(k).getPosY()*tileSize,robots.get(k).getPosX()*tileSize+tileSize, robots.get(k).getPosY()*tileSize+tileSize,0,0,tileSize,tileSize,this);
...und es musste heißen (siehe ganz am Ende vom Befehl):
Java:
 g.drawImage(tilePics[1],robots.get(k).getPosX()*tileSize, robots.get(k).getPosY()*tileSize,robots.get(k).getPosX()*tileSize+tileSize, robots.get(k).getPosY()*tileSize+tileSize,0,0,64,64,this);
Da meine Texturen alle die Größe 64x64 haben werden.

Das Caching und das Scrollen werde ich dann evtl. heute noch implementieren und mich dann wieder melden ;)
 

muckelzwerg

Bekanntes Mitglied
Ist "super.paintComponent" nicht ziemliche Verschwendung, wenn man eh wieder drüberzeichnet?
"setBackgGround" und "clearRect" reichen doch aus?
 

Marco13

Top Contributor
Beim JPanel macht paintComponent (also das, was auf 'super' ausgeführt wird) nicht viel anderes, als den Hintergrund zu füllen.
 

muckelzwerg

Bekanntes Mitglied
Joah. Geht das nicht über JComponent, ComponentUI, update(), paint() usw. ? Da hängt ja schon ein bisschen was dran.
Aber ich hatte auch irgendwie im Kopf, dass die ganzen Felder einzelne JPanels sind.


conehead, ich würde die die Images so gut wie möglich rauswerfen. Das Grid sind doch bloß ein paar Linien und eine Hintergrundfarbe.
Solange in in den Feldern wirklich nur dunkle Flecken sind, könntest Du ja sogar die Tabelle direkt auf ein BufferedImage in der passenden Auflösung übertragen, da alles reinzeichnen und dann dieses Bild an der gewünschten Stelle im JPanel anzeigen.
Scrollen wäre dann nur die Verschiebung des Bildes.
 

André Uhres

Top Contributor
Hallo,

was die MouseListener betrifft, hatte ich mal eine kleine Demo gebaut um ihre Leistung auf 300000 JLabels zu testen. Bei einem Heapsize von 600m reagieren die Listener erstaunlich effizient:
Java:
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
import javax.swing.event.*;
public class LettersMagnifiedDemo implements Runnable {
    private static final int WIDTH = 1250;
    private static final int HEIGHT = 800;
    private Random random = new Random();
    private JFrame frame = new JFrame("Letters Magnified Demo");
    private JPanel lettersPanel;
    public void run() {
        lettersPanel = new JPanel(null);
        for (int i = 300000; --i >= 0;) {
            Letter letter = new Letter(Character.toString((char) ('a' + random.nextInt(26))));
            letter.setBounds(random.nextInt(WIDTH), random.nextInt(HEIGHT), 16, 16);
            lettersPanel.add(letter);
        }
        frame.add(lettersPanel, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(WIDTH, HEIGHT);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        lettersPanel.requestFocusInWindow();
    }
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new LettersMagnifiedDemo());
    }
}
class Letter extends JLabel {
    Font font1;
    Font font2;
    private final FontRenderContext fontRenderContext1;
    private final FontRenderContext fontRenderContext2;
    public Letter(final String letter) {
        super(letter);
        setFocusable(true);
        setBackground(Color.RED);
        font1 = getFont();
        font2 = font1.deriveFont(48f);
        fontRenderContext1 = getFontMetrics(font1).getFontRenderContext();
        fontRenderContext2 = getFontMetrics(font2).getFontRenderContext();
        MouseInputAdapter mouseHandler = new MouseInputAdapter() {
            @Override
            public void mouseEntered(final MouseEvent e) {
                Letter.this.setOpaque(true);
                setFont(font2);
                Rectangle bounds = getBounds();
                Rectangle2D stringBounds = font2.getStringBounds(getText(), fontRenderContext2);
                bounds.width = (int) stringBounds.getWidth();
                bounds.height = (int) stringBounds.getHeight();
                setBounds(bounds);
            }
            @Override
            public void mouseExited(final MouseEvent e) {
                Letter.this.setOpaque(false);
                setFont(font1);
                Rectangle bounds = getBounds();
                Rectangle2D stringBounds = font1.getStringBounds(getText(), fontRenderContext1);
                bounds.width = (int) stringBounds.getWidth();
                bounds.height = (int) stringBounds.getHeight();
                setBounds(bounds);
            }
        };
        addMouseListener(mouseHandler);
    }
}

Gruß,
André
 

conehead

Mitglied
@muckelzwerg:
ja das war ja schon meine überlegung, die images rauszunehmen. im moment sind es nur graue kästen, aber evtl. soll es irgendwann ja auch mal etwas schöner werden :) deswegen erst einmal bilder.

mit den mouselistenern werd ich mir später dann noch mal anschauen. erst werd ich nun den hintergrund cachen und dann weitersehen
 

conehead

Mitglied
Den alten Post lösche ich mal komplett ;).
Erst einmal bin ich gerade selbst erstaunt, wie gut es eigentlich schon funktioniert.

Ich habe jetzt ein 100x100 Felder-Grid und es düsen bis zu 1000 Roboter durch die Gegend...
Und das ganze auch noch schneller als ich erwartet habe.
Es wird der Background gecached und es werden wirklich nur die Agenten gezeichnet, welche sichtbar sind.

Nur einen Fehler hat das Programm bisher:
Ich muss es erst maximieren bzw. die Größe verändern, damit zum ersten mal etwas gezeichnet wird.
Also beim Programmstart wird noch nichts gezeichnet.

Die andere Sache wäre:
Hat jemand spontan eine Idee, wie man ein "schönes" Scrollen einbinden könnte?
Also ich lasse irgendwo die Maustaste gedrückt und kann das Grid hin und herschieben.
Im Moment ist das Grid einfach da, wo ich hingeklickt habe.

So jetzt werde ich schnell noch implementieren, dass man angezeigt bekommt, wo man hingeklickt hat und ich denke dann können den Quelltext bestimmt auch noch ein paar andere Leute gebrauchen.

Der ist dann ja schon fast für Spiele tauglich ;)

//edit:
so anzeige wo man hingeklickt hat ist nun auch drin!
Hier mal der komplette Quelltext.
Aber danke noch einmal an alle die mir geholfen haben:toll:

Java:
public class Grid extends JPanel implements MouseMotionListener,
		MouseWheelListener, MouseListener  {

	private ArrayList<BufferedImage> tilePics;
	private ArrayList<Robot> robots;
	private BufferedImage cachedBackground;
	private int tileSize = 8;
	private int mapWidth = 50;
	private int mapHeight = 50;
	private int drawPosX = 0;
	private int drawPosY = 0;

	// init
	public Grid() throws IOException {
		robots = new ArrayList<Robot>();
		addMouseMotionListener(this);
		addMouseWheelListener(this);
		addMouseListener(this);
		tilePics = new ArrayList<BufferedImage>();
		tilePics.add(ImageIO.read(new File(".\\tiles\\road.jpg")));
		tilePics.add(ImageIO.read(new File(".\\tiles\\goal.jpg")));
		// create cached background
		cachedBackground = new BufferedImage(mapWidth * 64, mapHeight * 64,
				BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < mapHeight; i++) {
			for (int j = 0; j < mapWidth; j++) {
				cachedBackground.getGraphics().drawImage(tilePics.get(0),
						j * 64, i * 64, j * 64 + 64, i * 64 + 64, 0, 0, 64, 64,
						this);
			}
		}
		// caching background finished
		// add new robots
		for (int i = 0; i < 100; i++) {
			robots.add(new Robot());
		}

	}

	// draw grid
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		// draw cached background
		g.drawImage(cachedBackground, drawPosX, drawPosY, (mapWidth * tileSize)
				+ drawPosX, (mapHeight * tileSize) + drawPosY, 0, 0,
				mapWidth * 64, mapHeight * 64, this);
		// draw robots
		int count = 0;
		for (int k = 0; k < robots.size(); k++) {
			if (robots.get(k).getPosX() * tileSize + drawPosX > 0 - tileSize
					&& robots.get(k).getPosX() * tileSize + drawPosX < this
							.getWidth()
					&& robots.get(k).getPosY() * tileSize + drawPosY > 0 - tileSize
					&& robots.get(k).getPosY() * tileSize + drawPosY < this
							.getHeight()) {
				g.drawImage(
						// picture
						tilePics.get(1),
						// position start drawing
						(robots.get(k).getPosX() * tileSize) + drawPosX,
						(robots.get(k).getPosY() * tileSize) + drawPosY,
						// position end drawing
						(robots.get(k).getPosX() * tileSize + tileSize)
								+ drawPosX,
						(robots.get(k).getPosY() * tileSize + tileSize)
								+ drawPosY,
						// source size and observer
						0, 0, 64, 64, this);
				count++;
			}
		}
		//System.out.println(new Integer(count).toString()+ " roboter gezeichnet");
	}

	// move robots
	public void updateRobots() {
		for (int i = 0; i < robots.size(); i++) {
			robots.get(i).moveRandom();
		}
	}

	// zoom in/out
	public void mouseWheelMoved(MouseWheelEvent e) {
		if (e.getWheelRotation() == -1) {
			if (tileSize != 8)
				tileSize = tileSize / 2;
		}
		if (e.getWheelRotation() == 1) {
			if (tileSize != 64)
				tileSize = tileSize * 2;
		}
	}

	// scrolling
	public void mouseDragged(MouseEvent e) {
		// smoother scrolling
		if (e.getPoint().x % 2 == 0)
			drawPosX = e.getPoint().x;
		if (e.getPoint().y % 2 == 0)
			drawPosY = e.getPoint().y;

	}

	public void mouseMoved(MouseEvent e) {
		// do nothing
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		int posX = (e.getX()-drawPosX)/tileSize;
		int posY = (e.getY()-drawPosY)/tileSize;
		if (posX>=0 && posX<mapWidth && posY>=0 && posY<mapHeight)
		System.out.println("click bei:"+new Integer(posX).toString()+"/"+new Integer(posY).toString());	
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}
}
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben