# Frage zum Scrolling



## Paladin (7. Jan 2007)

Hi,

in dem Spiel welches ich gerade versuche umzusetzen steuert der Spieler ein Fahrzeug welches sich in alle Himmelsrichtungen bewegen kann. Man sieht das ganze aus der Vogelperspektive(2D). Die Welt in der sich
das Fahrzeug bewegen kann soll so groß sein, dass der Spieler auch mal eine oder zwei Minuten in eine bestimmte Richtung fahren kann. In der Welt soll es eine Menge von Objekten wie zum Beispiel Bäume, Häuser, Straßen
und ähnliches geben (die natürlich nicht nur Hintergrundbild sein sollen sondern relevant für die Kollisionsabfrage).
Idealerweise soll das Fahrzeug natürlich auch Reifen- bzw. Kettenspuren auf dem Boden hinterlassen.

Ich bin jetzt am überlegen wie man für dieses Szenario am geschicktesten ein Scrolling baut. Da dies allerdings
mein erstes Spieleprojekt ist bin ich auf diesem Gebiet noch extrem unerfahren. 

Mein erster Gedanke war, dass ich allen Objekten(außer dem Fahrzeug) eine feste X und Y Position zuweise. Aber
ich bin mir nicht so sicher ob das die beste Lösung für das Problem ist.

Wie macht ihr das in euren Spieleprojekten?

Vielen Dank im voraus

Gruß

Paladin


----------



## Revenant (7. Jan 2007)

Naja, die Map auf der sich die Objekte befinden ist eine Grafik die relativ zu den x und y Koordinaten des Bildschirms platziert und beim Scrollen bewegt wird. 

Das Fahrzeug des Spielers befindet sich somit in der Mitte des Bildes und das Scrolling setzt ein, sobald es sich bewegt (oder besser: es eine gewisse Toleranz-Zone verlässt). 

Die Koordinaten der restlichen ("festen") Objekte sind auch relativ zu dem 'Bildschirm' und müssen entsprechend dem Scrolling bewegt werden. Bewegen sich die Objekte zusätzlich, so muss man ihre Koordinaten eben noch zusätzlich erhöhen/erniedrigen.

Schöner Zusatz ist auch eine 2. Scroll - Ebene (Grafik mit Transparenz, durch die man die 1. Ebene sehen kann). Eine abgespeckte Version kann ich mir bei einer so großen Karte wie du sie haben willst als Wolken-Objekte vorstellen. Die eben über die Karte fliegen und die restlichen Objekte von Zeit zu Zeit verdecken.


----------



## LoN_Nemesis (7. Jan 2007)

Ich würde Scrolling so realisieren:

Erstmal hat jedes Objekt auf der Karte eine X und Y Position, das ist ja klar. Das sind einfach die ganz normalen Koordinaten, die die absolute Position auf der Weltkarte angeben. Falls die Objekte sich bewegen, verändern sich Koordinaten.
Zusätzlich hat jedes dieser Objekte einfach noch eine .getDrawCoords(float playerX, float playerY) Methode. Diese berechnet wo das Objekt auf dem Bildschirm gezeichnet wird, abhängig vom Spieler eben. Bzw statt den Koordinaten des Spielers übergibst du wohl besser (wie Revenant sagte) den aktuellen 'Bildschirm".


----------



## Paladin (8. Jan 2007)

Vielen Dank für eure Hinweise Revenant und LoN_Nemesis. Ich versuche das so umzusetzen.

Ich hätte jetzt nicht eine so große Grafik als Map verwendet weil ich gedacht hatte, dass dies ein Performance Problem darstellen könnte. Aber ich werde das jetzt mal ausprobieren und sehen wie's wird.

Gruß

Paladin


----------



## LoN_Nemesis (8. Jan 2007)

Ob deine Map ein einzelnes riesiges Bild ist oder viele kleine Tiles spielt dabei keine Rolle. Im ersten Fall musst du halt die gesamte Map einmal verschieben beim Scrollen, im zweiten Fall jedes einzelne Tile. Ich rate dir aber davon ab die ganze Map als ein Bild darzustellen.


----------



## Paladin (8. Jan 2007)

Macht es denn wirklich einen Unterschied ob ich ein großes Bild oder viele kleine Bilder verschiebe?
Oder warum rätst du mir davon ab alles auf einem großen Bild zu machen?


----------



## EgonOlsen (8. Jan 2007)

Einen Unterschied macht es im Speicherverbrauch, wenn dein großes Bild aus sich wiederholenden Teilen besteht. Und es macht u.U. einen Unterschied in Bezug auf Hardwarebeschleunigung der Bilder. Ein 256*256 Bild wird (wenn das Format korrekt gewählt wurde) von einer aktuellen VM hardwarebeschleunigt auf den Bildschirm gebracht. Bei einem 3000*3000er Bild bezweifel ich das mal...


----------



## Paladin (8. Jan 2007)

Vielen Dank für die Erklärung.

Ich denke mal ich habe jetzt eine ungefähre Vorstellung wie das Scrolling in dem Spiel funktionieren soll.

Gruß

Paladin


----------



## Paladin (9. Jan 2007)

Es will mir mit dem Scrolling noch nicht so gelingen wie ich mir das vorstelle. Ich habe jetzt folgendes gemacht.

Ich lade beim Start des Spiels das Spielfeld(jpg, 4096*4096). Diese Grafik teile ich mittels getSubImage in 16 256*256 Tiles.

Sobald die Spielfigur sich bewegt wird überprüft auf welchem Feld sie sich gerade befindet und dann das aktuelle Feld und die umliegenden Felder gezeichnet.

Aber irgendwie klappt das bei mir nicht. Teilweise werden die Tiles richtig dargestellt und teilweise habe ich weiße Flächen.

Was mache ich falsch?

Gruß

Paladin


----------



## Paladin (9. Jan 2007)

Ich habe mir mal angeschaut welche Tiles wann gezeichnet werden. Eigentlich sollten alle Tiles immer
nach dem folgenden Schema gezeichnet werden tile1, tile2,..., tile_25

Aber offensichtlich werden manchmal tiles mehrmals hintereinander gezeichnet. Ich nehme mal an, dass das daran liegt, dass ich einen Thread zum Timen benutze. Kann das sein?

Gruß

Paladin


----------



## EgonOlsen (9. Jan 2007)

Sein kann alles. Was macht der Thread denn genau? Wenn du beim Zeichnen auf das Timing reagierst oder der Thread selber den Zustand von irgendwas zu Zeichnendem ändert, dann kann es natürlich passieren, dass das Zeichnen mit dem einen Wert beginnt und mittendrin einen anderen vorgesetzt bekommt. Die Ergebnisse können dann von peinlich bis lustig werden. Threads in Spielen sind schwierig zu handhaben...wenn es geht, verzichte besser darauf.


----------



## LoN_Nemesis (9. Jan 2007)

EgonOlsen hat gesagt.:
			
		

> Threads in Spielen sind schwierig zu handhaben...wenn es geht, verzichte besser darauf.



Leider gibt es gerade bei Spielen viele Sachen, bei denen man einfach Threads braucht. Das Zeichnen sollte schon von einem Thread gemacht werden, dann braucht man noch einen für die Spielelogik, Sounds müssen in einem eigenen Thread laufen, Inputerfassung sowieso, falls man Netzwerkcode schreibt dann dieser auch noch, etc.

Vielleicht kannst du mal den Teil des Codes posten wo du die Tiles zeichnest, dann kann man dir da eventuell besser helfen.


----------



## EgonOlsen (9. Jan 2007)

LoN_Nemesis hat gesagt.:
			
		

> Leider gibt es gerade bei Spielen viele Sachen, bei denen man einfach Threads braucht. Das Zeichnen sollte schon von einem Thread gemacht werden, dann braucht man noch einen für die Spielelogik, Sounds müssen in einem eigenen Thread laufen, Inputerfassung sowieso, falls man Netzwerkcode schreibt dann dieser auch noch, etc.


Nein, eben genau nicht. Wenn du das machst, hast du einen enormen Aufwand mit der Synchronization oder stattdessen skurile Fehler, weil du z.B. in der Logik nichts drehen darfst während gezeichnet wird u.ä. 
Sounds brauchen ebenfalls definitiv keinen eigenen Thread (wozu?), Tasten- und Mausabfrage über Listener laufen sowieso im AWT-Eventthread ab, da braucht es keinen eigenen usw. Von deinen Beispielen gibt es eine Sache, was ganz sicher in einen eigenen Thread gehört, und das ist die Netzwerkgeschichte. Dafür sind Threads perfekt und nötig. Ansonsten: Finger weg, außer man braucht die Performance dank Multicore für irgendwelche Sachen oder will z.B. ein Schachbrett drehen, während die K.I. noch rechnet.
Du wirst z.B. in Paradroidz genau einen zusätzlichen Thread finden und der ist optional und beim Laden der Level. Danach ist alles nur noch einer. Nur damit bekommst du gesichertes Timing und einen gesicherten Ablauf des Ganzen.


----------



## Wildcard (9. Jan 2007)

EgonOlsen hat gesagt.:
			
		

> Du wirst z.B. in Paradroidz genau einen zusätzlichen Thread finden und der ist optional und beim Laden der Level. Danach ist alles nur noch einer. Nur damit bekommst du gesichertes Timing und einen gesicherten Ablauf des Ganzen.


Dazu sollte man aber erwähnen das es so nur bei Active-Rendering (in welcher Form auch immer) funktioniert. Bei kleinen Spielen die sich auf die Mechanismen des Swing Toolkits stützen brauchen Spiele in aller Regel 2 Threads.


----------



## EgonOlsen (9. Jan 2007)

Wildcard hat gesagt.:
			
		

> Dazu sollte man aber erwähnen das es so nur bei Active-Rendering (in welcher Form auch immer) funktioniert. Bei kleinen Spielen die sich auf die Mechanismen des Swing Toolkits stützen brauchen Spiele in aller Regel 2 Threads.


Ja, implizit. Einer zeichnet (der besagte AWT-Eventthread) und einer macht andere schlaue Sachen, wie die Spiellogik usw. Das stimmt wohl. Aber dieser Thread ist ja quasi immer da, auch wenn er vielleicht dank Active-Rendering und/oder OpenGL-Nutzung nichts oder nicht mehr viel zu tun hat.


----------



## Wildcard (9. Jan 2007)

EgonOlsen hat gesagt.:
			
		

> Aber dieser Thread ist ja quasi immer da, auch wenn er vielleicht dank Active-Rendering und/oder OpenGL-Nutzung nichts oder nicht mehr viel zu tun hat.


Heißt das jetzt das du in Paradroidz die Arbeit nicht im EDT sondern einem seperaten erledigst?
Weil dann ist's prinzipiell ja schon Multithreading  ???:L 
Ich frag da aus Interesse nach, da ich nicht wirklich tief in der Materie bin (eben Anwendungsentwickler :wink und ich mir nicht sicher bin in wie weit du mit Fullscreen-Modus, Page-Flipping usw. auch aus einem seperaten Thread zeichnen kannst.
Also:
Verwendest du den EDT nur für Input-Events, machst du (fast) alles darin, oder ist er für InputEvents und Rendern auf den Screen verantwortlich?


----------



## EgonOlsen (9. Jan 2007)

Wildcard hat gesagt.:
			
		

> Ich frag da aus Interesse nach, da ich nicht wirklich tief in der Materie bin (eben Anwendungsentwickler :wink und ich mir nicht sicher bin in wie weit du mit Fullscreen-Modus, Page-Flipping usw. auch aus einem seperaten Thread zeichnen kannst.
> Also:
> Verwendest du den EDT nur für Input-Events, machst du (fast) alles darin, oder ist er für InputEvents und Rendern auf den Screen verantwortlich?


Paradroidz hat zwei Modi, OpenGL- und Softwarerendering. Bei ersterem liegt der Eventthread brach, da Rendering, Tasten- und Mausabfrage komplett im nativen OpenGL-Fenster stattfinden, welches von Java2D losgelöst ist.
Im Softwaremodus nutze ich einen Frame und darauf auch die entsprechenden Listener für Tastatur und Maus, die aber nur die Eingaben entgegennehmen und sonst nichts tun. Im eigentlichen "Spielthread" werden die Eingaben dann ausgewertet, das aktuelle Bild berechnet und der Frame einfach mittels <Graphics>.drawImage(...) aus einem BufferedImage heraus befüllt.
Theoretisch kann ich die Engine auch mit OpenGL in Kombination mit AWT betreiben. Dann wird die Grafik im Eventthread auf eine spezielle Canvas gezeichnet, während der Rest im "normalen" Spielthread abläuft. Die Synchronization dazwischen läuft über zahlreiche Puffer und sonstiges Gedönse, ist also recht aufwendig. Dafür bekommt man quasi Multithreading "for free"...wenn man den Overhead ignoriert.
Du kannst, wenn du das Zeichnen komplett selber übernimmst, aus jedem Thread heraus in einem Graphics-Object herum malen...das ist kein Problem. Das Teil hier macht das z.B. mittels einer BufferStrategy, allerdings nicht ganz sauber, weil es in 4K passen musste:  www.jpct.net/4k/buggaroo


----------



## Wildcard (9. Jan 2007)

Danke für die Erklärung. War mir neu das man in der OpenGL Komponente das Eventhandling komplett ohne den AWT Dispatcher abwickeln kann  :shock: 
Muss ich mich irgendwann mal einlesen.
Wie auch immer: weiter so! Paradroidz ist richtig gut geworden  :toll:


----------



## Paladin (10. Jan 2007)

Ich habe meinen Thread durch ein Stück Quellcode von EgonOlsen ersetzt. Das doppelt zeichnen hat sich damit
erledigt aber jetzt habe ich ein ganz anderes Problem und zwar zeichnet das Programm oft leere Tiles auf den
Bildschirm. Zum Anfang sieht alles super aus. Sobald ich die Spielfigur jetzt aber in irgendeine richtung bewege fehlen fehlen plötzlich tiles(der platz wo das tile stehen sollte ist weiß). 

Ich berechne immer das Tile auf dem der Spieler sich befindet und zeichne dieses und die umliegende Tiles neu. Dann verschiebe ich das ganze um die BewegungX und Y des Spielers.

Fie folgende Funktion lädt beim Spielstart die BufferedImages

```
public void loadImages() {
	
    try {
        battlefield = ImageIO.read(new File("grafix//battlefield.jpg"));
        battleTiles = new BufferedImage[17][17];
        int xpos = 0;
        int ypos = 0;
			
        //Das BufferedImage battlefield wird in 256 Teile a 256*256 zerlegt.
        //Die startx und starty Koordinaten jedes Teils werden in tileCoord abgelegt.    
        for(int y=0;y<16;y++) {
            ypos = 0;
            for(int x=0;x<16;x++) {
                battleTiles[(x+1)][(y+1)] = battlefield.getSubimage(xpos, ypos, 256, 256);
	tileCoord.put("tile" + (x+1) + "_" + (y+1) + "_x", xpos);
	tileCoord.put("tile" + (x+1) + "_" + (y+1) + "_y", ypos);
	ypos+=256;
            }		
            xpos+=256;
        }
    ...
    //hier werden jetzt noch andere Grafiken geladen die aber mit den Tiles nichts zu tun haben...
}
```

Die folgende Funktion ist meine paint-Funktion. paint() und update() habe ich so überschrieben, dass sie gar nichts mehr tun.


```
public void myUpdate() {
    g2 = (Graphics2D) bufstrat.getDrawGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.clearRect(0, 0, field_width, field_height);		
    AffineTransform trans_org = new AffineTransform();
    AffineTransform trans = new AffineTransform();
    AffineTransform trans_top = new AffineTransform();
    drawTiles((int)td.getTankPosx(),(int)td.getTankPosy(),g2);
    drawTank(g2, trans, trans_org, trans_top);
    //Spiel GUI zeichnen
    drawGUI(g2);
    int[] tile = getTileID((int)td.getTankPosx(),(int)td.getTankPosy());
    bufstrat.show();		
}
```

Bei der folgenden Funktion handelt es sich um den Timer


```
public void runMe() { 
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY); 
        try { 
            long s=System.currentTimeMillis(); 
            int fps=0; 
            Ticker ticker=new Ticker(20); 
            while (true) { 
                int ticks=ticker.getTicks(); 
                if (ticks>0) { 
    	    if(td.getSpeed()!=0) {
    	        tank_x_delta = (Math.cos(td.getRotation()/rotation_teiler)*td.getSpeed());
    	        tank_y_delta = (Math.sin(td.getRotation()/rotation_teiler)*td.getSpeed());
    	        td.setTankPosx(tank_x_delta);
    	        td.setTankPosy(tank_y_delta);
                         movex-=tank_x_delta;
                         movey-=tank_y_delta;
    	         if(((td.getXpos()+tank_x_delta) > 500)||((td.getXpos()+tank_x_delta) < 200)||((td.getYpos()
                                +tank_y_delta) > 500)||((td.getYpos()+tank_y_delta) < 200)) {
    	            acd.setXpos(-tank_x_delta);
    	            acd.setYpos(-tank_y_delta);
    	            bf_x-=tank_x_delta;
    	            bf_y-=tank_y_delta;
    	        }    									
    	    }
                    
                    if(td.isBrake()) {
                        if(td.getSpeed()>0) td.setSpeed(-0.01);
    	            else td.setBrake(false);
    	        }                	
                    } 
                
                    myUpdate(); 
                    fps++; 
                    if (System.currentTimeMillis()-s>1000) { 
                    //tfps=fps; 
                    fps=0; 
                    s=System.currentTimeMillis(); 
                } 
                Thread.yield(); 
        } 
    } catch (Exception e) {e.printStackTrace();} 
} 

class Ticker { 
        
    private int rate; 
    private long s; 
        
    Ticker(int tickrateMS) { 
        rate=tickrateMS; 
        s=System.nanoTime(); 
    } 
        
    int getTicks() { 
        long i=System.nanoTime(); 
        if ((i-s)/1000000L>rate) { 
            int ticks=(int)(((i-s)/1000000L)/(long)rate); 
            s+=rate*ticks*1000000L; 
            return ticks; 
        } 
        return 0; 
    } 
}
```

Die folgende Funktion malt die einzelnen Tiles auf den Bildschirm


```
public void drawTiles(int x, int y, Graphics2D g2) {
    int pos_x = 0;
    int pos_y = 0;
    int temp_x = 1;
    int temp_y = 1;
    //Auf welchem Tile befindet sich der Spieler?
    int tileid[] = getTileID(x,y);	
				
    pos_x = tileid[0];
    pos_y = tileid[1];
		
    if(pos_x>2 && pos_y>2) {
        temp_x = pos_x-2;
        temp_y = pos_y-2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y 
            + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
            +movey, this);
    }
    		
    if(pos_x>1 && pos_y>2) {
        temp_x = pos_x-1;
        temp_y = pos_y-2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y 
            + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
            +movey, this);
    }
   	
    if(pos_y>2) {
        temp_y = pos_y-2;
        temp_x = pos_x;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
            + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
            +movey, this);
    }
    		
    if(pos_x<16 && pos_y>2) {
        temp_x = pos_x+1;
        temp_y = pos_y-2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
            + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
            +movey, this);
       }     
		
    if(pos_x<15 && pos_y>2) {
        temp_x = pos_x+2;
        temp_y = pos_y-2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
             + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString
             ())+movey, this);
    }		

    if(pos_x>2 && pos_y>1) {
        temp_x = pos_x-2;
        temp_y = pos_y-1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }
		
    if(pos_x>1 && pos_y>1) {
        temp_x = pos_x-1;
        temp_y = pos_y-1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }
		
    if(pos_y>1) {
        temp_y = pos_y-1;
        temp_x = pos_x;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }
		
    if(pos_x<16 && pos_y>1) {
        temp_x = pos_x+1;
        temp_y = pos_y-1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }        

    if(pos_x<15 && pos_y>1) {
        temp_x = pos_x+2;
        temp_y = pos_y-1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }
		
    if(pos_x>2) {
        temp_x = pos_x-2;
        temp_y = pos_y;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
		
    if(pos_x>1) {
        temp_x = pos_x-1;
        temp_y = pos_y;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }    				

    //Dies ist das Tile auf dem der Spieler sich befindet. Daher wird es immer gezeichnet.
    g2.drawImage(battleTiles[pos_x][pos_y],Integer.parseInt(tileCoord.get("tile" + pos_x + "_" + pos_y
    + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + pos_x + "_" + pos_y + "_y").toString())
    +movey, this);
				
    if(pos_x<16) {
        temp_x = pos_x+1;
        temp_y = pos_y;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }

    if(pos_x<15) {
        temp_x = pos_x+2;
        temp_y = pos_y;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }
		
    if(pos_x>2 && pos_y<16) {
        temp_x = pos_x-2;
        temp_y = pos_y+1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }

    if(pos_x>1 && pos_y<16) {
        temp_x = pos_x-1;
        temp_y = pos_y+1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }    
			
    if(pos_y<16) {
        temp_y = pos_y+1;
        temp_x = pos_x;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }

    if(pos_x<16 && pos_y<16) {
        temp_x = pos_x+1;
        temp_y = pos_y+1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }
		
    if(pos_x<15 && pos_y<16) {
        temp_x = pos_x+2;
        temp_y = pos_y+1;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }   

    if(pos_x>2 && pos_y<15) {
        temp_x = pos_x-2;
        temp_y = pos_y+2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }

    if(pos_x>1 && pos_y<15) {
        temp_x = pos_x-1;
        temp_y = pos_y+2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }

    if(pos_y<15) {
        temp_y = pos_y+2;
        temp_x = pos_x;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }    
		
    if(pos_x<16 && pos_y<15) {
        temp_x = pos_x+1;
        temp_y = pos_y+2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);
    }				

    if(pos_x<15 && pos_y<15) {
        temp_x = pos_x+2;
        temp_y = pos_y+2;
        g2.drawImage(battleTiles[temp_x][temp_y],Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y
        + "_x").toString())+movex, Integer.parseInt(tileCoord.get("tile" + temp_x + "_" + temp_y + "_y").toString())
        +movey, this);				
    }
}
```

Diese Methode berechnet auf welchem Tile sich der Spieler befindet.
Ihr braucht mir übrigens nicht zu sagen, dass die Methode scheiße ist. Das weiß ich selber. Ich freue mich über Verbesserungsvorschläge...



```
public int[] getTileID(int x, int y) {
        int tileid[] = new int[2];
        int startTile_x=0;
        int startTile_y=0;
		
        if(x<256) {
            startTile_x=0;
        }
        else if(x<512 && x>=256) {
            startTile_x=256;
        }
        else if(x<768 && x>=512) {
            startTile_x=512;
        }
        else if(x<1024 && x>=768) {
            startTile_x=768;
        }
        else if(x<1280 && x>=1024) {
            startTile_x=1024;
        }
        else if(x<1536 && x>=1280) {
            startTile_x=1280;
        }
        else if(x<1792 && x>=1536) {
            startTile_x=1536;
        }
        else if(x<2048 && x>=1792) {
            startTile_x=1792;
        }
        else if(x<2304 && x>=2048) {
            startTile_x=2048;
        }
        else if(x<2560 && x>=2304) {
            startTile_x=2304;
        }
        else if(x<2816 && x>=2560) {
            startTile_x=2560;
        }
        else if(x<3072 && x>=2816) {
            startTile_x=2816;
        }
        else if(x<3328 && x>=3072) {
            startTile_x=3072;
        }
        else if(x<3584 && x>=3328) {
            startTile_x=3328;
        }
        else if(x<3840 && x>=3584) {
            startTile_x=3584;
        }
        else if(x>=3840) {
            startTile_x=3840;
        }
		
        if(y<256) {
            startTile_y=0;
        }
        else if(y<512 && y>=256) {
            startTile_y=256;
        }
        else if(y<768 && y>=512) {
            startTile_y=512;
        }
        else if(y<1024 && y>=768) {
            startTile_y=768;
        }
        else if(y<1280 && y>=1024) {
            startTile_y=1024;
        }
        else if(y<1536 && y>=1280) {
            startTile_y=1280;
        }
        else if(y<1792 && y>=1536) {
            startTile_y=1536;
        }
        else if(y<2048 && y>=1792) {
            startTile_y=1792;
        }
        else if(y<2304 && y>=2048) {
            startTile_y=2048;
        }
        else if(y<2560 && y>=2304) {
            startTile_y=2304;
        }
        else if(y<2816 && y>=2560) {
            startTile_y=2560;
        }
        else if(y<3072 && y>=2816) {
            startTile_y=2816;
        }
        else if(y<3328 && y>=3072) {
            startTile_y=3072;
        }
        else if(y<3584 && y>=3328) {
            startTile_y=3328;
        }
        else if(y<3840 && y>=3584) {
            startTile_y=3584;
        }
        else if(y>=3840) {
            startTile_y=3840;
        }
		
        tileid[0] = (startTile_x / 256)+1;
        tileid[1] = (startTile_y / 256)+1;
				
        return tileid;
}
```

So dass sollte der relevante Teil des codes gewesen sein.


----------



## EgonOlsen (10. Jan 2007)

Puh, das sieht...komisch aus...also zuerst würde ich die getTileID-Methode mal irgendwie zusammenstreichen auf sowas hier:


```
public int[] getTileID2(int x, int y) {
    int tileid[]=new int[2];
    tileid[0]=Math.min((x>>8)+1, 16);
    tileid[1]=Math.min((y>>8)+1, 16);
    return tileid;
}
```

Die Methode zum Setzen der Tiles sieht auch monstermäßig aus. Ich schlage vor, die Tiles nicht über einen Schlüssel in einer Hashtabelle zu referenzieren, sondern einfach über ein zweidimensionales Array oder ein extra Object dafür. Wenn du ein Array nimmst, kannst doch einfach anhand der aktuellen Spielerposition durchiterieren und malen. In Pseudocode etwa so:


```
int ys=ypos-3;
    int yScreen=0;
    for (int y=ys; y<ys+6; y++) {
        int xs=xpos-3;
        int xScreen=0;
        for (int x=xs; x<xs+6; x++) {
            Tile tile=tiles[x][y];
            tile.draw(xScreen, yScreen);
            xScreen+=256;
        }
        yScreen+=256;
    }
```


----------



## Paladin (10. Jan 2007)

Vielen Dank, dass du dir die Mühe gemacht hast das alles zu lesen.
Ich setze mich jetzt mal an den Code und werde deine Hinweise einarbeiten.


----------



## Paladin (10. Jan 2007)

So, habe die Methode drawTiles jetzt wie folgt umgeschrieben:


```
public void drawTiles(int x, int y, Graphics2D g2) {
    int pos_x = 0;
    int pos_y = 0;
    int temp_x = 1;
    int temp_y = 1;
    //Auf welchem Tile befindet sich der Spieler?
    int tileid[] = getTileID(x,y);	
		
    for(int tiley=tileid[0]-2;tiley<tileid[0]+3;tiley++) {
        for(int tilex=tileid[1]-2;tilex<tileid[1]+3;tilex++) {
            if(tilex>0 && tiley>0 && tilex<17 && tiley<17) {
                g2.drawImage(battleTiles[tilex][tiley], ((Integer.parseInt(tileCoord.get("tile" + tilex + "_" + tiley
                + "_x").toString()))+(movex)),((Integer.parseInt(tileCoord.get("tile" + tilex + "_" + tiley + "_y").toString
                ()))+(2*movey)),this);
            }				
        }			
    }
}
```

Die Methode getTileID habe ich 1:1 von dir übernommen. Ich
könnte mich echt schlagen dass ich die drawTiles so kompliziert geschrieben habe obwohl es doch
in wenigen Zeilen geht. Beim nächsten mal denke ich dran.

Die Random weissen Flächen sind jetzt weg. Hab zwar noch das Problem, dass der Spieler nachdem er eine weile gefahren ist nicht mehr im Zentrum der gezeichneten Tiles ist und somit die Bilder "abhauen" aber das sollte ich
eigentlich selber rausbekommen.

Vielen Dank für den Denkanstoss EgonOlsen.

Gruß

Paladin


----------



## LoN_Nemesis (11. Jan 2007)

EgonOlsen hat gesagt.:
			
		

> Nein, eben genau nicht. Wenn du das machst, hast du einen enormen Aufwand mit der Synchronization oder stattdessen skurile Fehler, weil du z.B. in der Logik nichts drehen darfst während gezeichnet wird u.ä.
> Sounds brauchen ebenfalls definitiv keinen eigenen Thread (wozu?), Tasten- und Mausabfrage über Listener laufen sowieso im AWT-Eventthread ab, da braucht es keinen eigenen usw. Von deinen Beispielen gibt es eine Sache, was ganz sicher in einen eigenen Thread gehört, und das ist die Netzwerkgeschichte. Dafür sind Threads perfekt und nötig. Ansonsten: Finger weg, außer man braucht die Performance dank Multicore für irgendwelche Sachen oder will z.B. ein Schachbrett drehen, während die K.I. noch rechnet.
> Du wirst z.B. in Paradroidz genau einen zusätzlichen Thread finden und der ist optional und beim Laden der Level. Danach ist alles nur noch einer. Nur damit bekommst du gesichertes Timing und einen gesicherten Ablauf des Ganzen.



OK da hab ich mich wohl falsch ausgedrückt, man muss als Programmierer nicht explizit für alles einen eigenen Thread schreiben. Aber wie du bereits gesagt hast läuft der Input im AWT Eventthread. Und wie Wildcard bereits gesagt hat ist es bei AWT und Swing durchaus üblich und sinnvoll z.B. einen Thread zu erstellen welcher regelmässig .repaint aufruft. Und warum auch nicht? Also in meinen draw Methoden ist immer nur "lesender" Code enthalten, solange da nichts modifiziert wird gibt es keine Probleme. Man kann sich nun streiten ob Sounds in einen eigenen Thread gehören, aber warum nicht? Da musst du rein gar nix synchronisieren, weil kein anderer Thread etwas mit den Sounddaten zu tun hat. Sicher WENN man synchronisieren muss, dann ist das wirklich kein Spaß. Aber grundsätzlich zu sagen: "1 Thread und nicht mehr!" halte ich für falsch.


----------



## EgonOlsen (11. Jan 2007)

LoN_Nemesis hat gesagt.:
			
		

> OK da hab ich mich wohl falsch ausgedrückt, man muss als Programmierer nicht explizit für alles einen eigenen Thread schreiben. Aber wie du bereits gesagt hast läuft der Input im AWT Eventthread. Und wie Wildcard bereits gesagt hat ist es bei AWT und Swing durchaus üblich und sinnvoll z.B. einen Thread zu erstellen welcher regelmässig .repaint aufruft. Und warum auch nicht? Also in meinen draw Methoden ist immer nur "lesender" Code enthalten, solange da nichts modifiziert wird gibt es keine Probleme. Man kann sich nun streiten ob Sounds in einen eigenen Thread gehören, aber warum nicht? Da musst du rein gar nix synchronisieren, weil kein anderer Thread etwas mit den Sounddaten zu tun hat. Sicher WENN man synchronisieren muss, dann ist das wirklich kein Spaß. Aber grundsätzlich zu sagen: "1 Thread und nicht mehr!" halte ich für falsch.


Das habe ich ja auch nicht gesagt. Ich habe gesagt: Möglichst vermeiden. Wozu z.B. brauchst du einen repaint()-Thread? Du kannst das repaint() auch genauso in deiner normalen "Spielschleife" aufrufen, d.h. die gesamte Spiellogik ausführen, dann repaint(). Wenn du das in einem Thread machst, kann z.B. folgendes passieren: Spielfigur soll sich um 5 Pixel nach links und nach unten bewegen. Dies ist aber nicht atomar, d.h. der repaint() könnte kommen, wenn links schon passiert ist, aber unten noch aussteht. In diesem Fall bekommst du einen Zustand zu sehen, den es gar nicht geben dürfte. Ok, das ist in diesem Fall nicht so dramatisch, aber das kann es werden. Nimm 3D: Du manipulierst gerade lustig die Rotationsmatrix von irgendwas und da kommt dein repaint()...da malst du dann irgendwas...an falscher Stelle oder gleich völlig deformiert..."been there, done that"! Ich fand das früher auch mal toll mit den Threads, aber ich habe mein Lehrgeld bezahlt. Wobei...da repaint() ja nur ein Vorschlag zum Neuzeichnen ist, wirst du das Problem, wenn auch vielleicht abgemildert, an dieser Stelle ebenfalls bekommen, es sei denn, du wartest den tatsächlichen repaint ab oder übernimmst das Malen gleich selber.
Sounds in einem Thread...dazu gibt es hier irgendwo einen Beitrag, wo jemand das gemacht hat mit dem Ergebnis, dass alle Sounds am Ende des Programms abgespielt wurden, weil der Thread vorher nicht dran kam.
Es ist nicht garantiert, wann dein Thread dran kommt...gerade bei Sounds ist es doch daneben, wenn der Schuss eine halbe Sekunde nach der Aktion kommt. Es gibt 0 Gründe, normale Sounds in einen Thread zu packen.


----------

