# Scrolling



## Moonlight1234 (21. Jul 2005)

Ich bin gerade dabei ein 2D-Spiel zu programmieren um mir die Grundlagen der Spieleprogrammierung beizubringen.
Das Spiel ist soweit "fertig" (wenn man davon absieht das es bislang nur 5 Level hat)  .
Allerdings findet der Spieleablauf bislang nur in einem fest stehenden Bildschirm statt.
Als letztes Projekt will ich versuchen das Spiel um Scrolling zu erweitern. Ich glaube zwar nicht das sich das aus Performance-Gründen realisieren läßt, aber ich will es zumindest versuchen.
Ich verwende Page-Flipping und habe den kompletten Hintergrund als Image vorliegen. Mit getSubimage "schneide" ich mir den Bereich der gerade auf dem Bildschirm sichtbar sein soll aus und zeichne den dann mit drawImage.
Allerdings ist das ganze nicht sehr performant.
Gibt es eine schnellere Methode für Scrolling?
Mir geht es um eine grundsätzliche Strategie.
Ich kann leider keinen Code posten, da das Programm zu umfangreich ist.

Gruß, Moonlight


----------



## Nick H. (21. Jul 2005)

ich hab jetzt leider nicht ganz verstanden was genau du meinst
aber ich mach das so:

bei jedem Objekt das vorkommt
(also alle Charas Item, usw nur nicht der eigene Chara)
geb ich einen zusätzlichen Integer bei der X-Koordinate mit an
(also bei Quer-Scrolling)
und wenn ich nun weiter nach rechts will ehöhe ich diesen Integer
wodurch alles ausser der eigene Charakter verschoben wird
geht natürlich mit Höhen Scrolling auch mit der Y-Koordinate
oder sogar beiden gleichzeitig


----------



## Moonlight1234 (21. Jul 2005)

Ist schon klar.
Hast du kein Hintergrundbild?
Bei mir ist das ganze eine Diashow.
Meine Bildschirmauflösung ist 1024x768.
Ich habe ein Bild welches den ganzen Level darstellt. Der Level hat eine Größe von zwei Bildschirmbreiten und zwei Bildschirmhöhen, also hat das Bild eine Größe von 2*1024 x 2*768.
Aus diesem Bild kopiere ich mir den gerade sichbaren Bereich mit getSubimage und stelle den dann mit drawImage dar.
Das ganze ist bloß nicht besonders schnell.

Ich wollte nach Möglichkeit nur die "offiziellen" Klassen von Sun benutzen, also nicht LWJGL.

Oder ist Scrolling letzten Endes mit Java aus Performance-Gründen nicht machbar?


----------



## thomas.g (21. Jul 2005)

naja, es ist klar, das das scrollen nicht sehr performant ist wenn man ein Bild als Hintergrund hat. Vor längerer Zeit habe ich mal ein Spiel programmiert das sich scrollen ließ. Allerdings war der Hintergrund nicht ein ganzes Bild sondern bestand aus mehreren Bildern. zb die Bäume waren extra, die Sonne, der Boden ....... .

Ich habe die Welt in eine .txt datei als Vector gespeichert.
Das sieht dann ungefähr so aus:


    g-g-g-g                 g-g-g-g
                        b
g-g-g-g-g-g-g-g-g-g-g-g-g-g        usw.

das g in der Datei stand für das Boden-Bild, das b für einen Baum.

auf diese Art und weiß habe ich mir einen Vector gebastelt der dann immer durch abfragen des Indexes (x, y) Koordinaten, die Bilder gezeichnet hat. Da sie sehr klein waren (png) ging das zeichnen der Welt sehr schnell.

Naja, ist nicht jedermanns sache aber auf jeden Fall schneller als ein ganzes Bild.


----------



## Moonlight1234 (21. Jul 2005)

Auf diese Weise erstelle ich mein Bild vom ganzen Level. Ich dachte es wäre schneller erstmal ein komplettes Bild zu erstellen und dann mit getSubimage das Bild vom sichtbaren Bereich zu erstellen.

Aber letzten Endes muß man wohl sagen Scrolling ist mit Java nicht realisierbar. Jedefalls nicht wenn man noch sechs Feinde darstellen muß :cry: 

Schade, muß ich mich wohl oder übel in Richtung C++ bewegen.
Gefällt mir gar nicht.

Weiß jemand ob Java3D irgendwelche Klassen/Methoden für Scrolling zur Verfügung stellt welche performanter sind?
Das Spiel selber soll allerdings 2D bleiben.


----------



## Roar (21. Jul 2005)

lies mal hier http://javacooperation.gmxhome.de/ScrollingDeu.html


----------



## 0xdeadbeef (21. Jul 2005)

Prinzipiell sollte ein scrollendes Spielchen kein Problem sein, wenn man sich sowas hier ansieht:
http://realmdesigns.russki.net/?command=syncscroll

Ich vermute, daß Deine Performanceprobleme aus der Größe Deines Spielfeldes, des Images und eventuell auch aus der Subimage-Methodik resultieren. Theoretisch sollte subImage performant sein und keine Kopie der Daten anfertigen, aber offensichtlich frißt es doch eine Menge Laufzeit.
Eventuell solltest DU auch über eine niedrigere Auflösung (800x600 oder so) nachdenken.

Ich würde Dir in jedem Fall dazu raten, daß BufferedImage nur in der Größe des angezeigten Spielfeldes zu definieren und das Spielfeld aus "Tiles" zusammenzubauen. Sprich: Du definierst Dir quadratische "Kacheln", aus denen das Spielfeld zusammengebaut ist. Die Maps kannst Du dann z.B. ganz billig mit einem Texteditor machen.

```
7 10 10 10 10 10 10  9 33 33 33 33 33 33 33 33 33 33 
11  0  0  0  0  0 24  4 10 10 10 10 10  9 33 33 33 33 
11 32  0  0  0  0  7  3 34  0  0  0 34 11 33 33 33 33 
11  0  0  0  0  0 27 32  0  0  0  0  0  4 10 10 10  9 
 4 18 10 10 10 10  2  9  0  0  0  0  0 11  0  0 34 11 
11  0  0  0  0  0  0 11  0  0  0  0  0 27  0  0  0 11 
11  0  0  0  0  0  0 11  0  0  0  0  0 11 17  0  0 11 
11  0  0  0  0  0  0 11  0  0  0  0  0  4 10 10 10  3 
11  0  0  0  0  0  0 11 34  0  0  0 34 11 33 33 33 33 
11 13  0  0 26  0  0  4 10 10 10 10 10  3 33 33 33 33 
 1 10 10 10 10 10 10  3 33 33 33 33 33 33 33 33 33 33
```
(Beispiel: Level aus Freedroid)

Die "Kacheln" kopierst folgendermaßen in Dein BufferedImage-Objekt: entweder Du holst Dir das Raster des Images mit "getRaster()" und kopierst die Kacheln per "setRect()" rein oder Du holst Dir ein Graphics2D-Objekt des Images mit "createGraphics" und kopierst die Kacheln per drawImage() rein. Hat beides Vor- und Nachteile. Bei einem Raster hast Du mehr Freiheiten, bei einem Graphics2D-Objekt mehr vordefinierte Funktionen.


----------



## Soulfly (21. Jul 2005)

Ähm was hat denn C++ damit jetzt zu tun. Beleidige Java nicht!:noe:

Du gehst das Problem halt nur falsch an.
Die funktion subimage ist dermaßen langsam das ich dir zustimmen muss, dass es damit nicht geht
Vor allem bei der größe des gesamten Images, dauert das ein bißchen.

Ich selber habe viel mit Scrolling gemacht. Und muss leider sagen: "Es funktioniert wunderbar, wenn man weiß, wie es gemacht wird." :meld:

hier ein paar sachen die du machen kannst:

1. Versuche es mit kleineren Images die du aneinanderhängst und dann anzeigst
2. Überprüfe deinen Sourcecode auf Herz und Nieren, ob da nicht nochwas anderes ist, was total stupid ist

Try it!


----------



## Soulfly (21. Jul 2005)

Hab wohl ein bißchen zu lange zum schreiben gebraucht.

Meinst du wirklich, dass subimage performant ist?
Erklär mir:?:


----------



## Moonlight1234 (21. Jul 2005)

@all
Danke für die Tips.




			
				0xdeadbeef hat gesagt.:
			
		

> Ich vermute, daß Deine Performanceprobleme aus der Größe Deines Spielfeldes, des Images und eventuell auch aus der Subimage-Methodik resultieren. Theoretisch sollte subImage performant sein und keine Kopie der Daten anfertigen, aber offensichtlich frißt es doch eine Menge Laufzeit.
> Eventuell solltest DU auch über eine niedrigere Auflösung (800x600 oder so) nachdenken.
> 
> Ich würde Dir in jedem Fall dazu raten, daß BufferedImage nur in der Größe des angezeigten Spielfeldes zu definieren und das Spielfeld aus "Tiles" zusammenzubauen. Sprich: Du definierst Dir quadratische "Kacheln", aus denen das Spielfeld zusammengebaut ist. Die Maps kannst Du dann z.B. ganz billig mit einem Texteditor machen.
> ...



Letzten Endes mache ich es so, dh. das Spielfeld besteht aus Tiles. Die Position der Tiles sind in einer Textfile definiert. Mit getGraphics erstelle ich ein Graphics2D Object und mit drawImage zeichne ich die Tiles und erhalte zu Schluß eine BufferedImage vom *ganzen* Level.

Bei deiner Methode zeichne ich immer nur den sichtbaren Bereich.
Wenn ich jedes mal den gerade sichtbaren Bildschirmbereich neu zeichne, warum ist das schneller als wenn ich das komplette Bild vorzeichne und nur den sichtbaren Bereich in den Backbuffer kopiere?
Bei deiner Methode werden doch auch jedesmal die images in den Backbuffer kopiert?
Bei meiner Methode wird mehr Speicherplatz verbraucht, weil das Image vom gesamten Bild auch im Speicher ist, aber Speicherprobleme habe ich im Moment nicht.

Die Frage ist gibt es eine Methode den gerade benötigten Ausschnitt, schnell in den Bildschirm (Backbuffer) zu kopieren?

Ich habe den Eindruck das getSubimage relativ langsam ist. Mit setRect() habe ich es noch nicht ausprobiert, ist ein Versuch wert sich mit Raster auseinderzusetzen.

Performace kosten sicher die animierten Enemy´s, aber bis zu 10 Enemys im gerade sichtbaren Bereich sollten schon drin sein. Zumindest benötige ich das für dieses Spiel.

Die Auflösung kann ich senken, wäre zwar schade, aber wenn es dann läuft.



			
				Soulfly hat gesagt.:
			
		

> Ähm was hat denn C++ damit jetzt zu tun. Beleidige Java nicht!:noe:
> 
> Du gehst das Problem halt nur falsch an.
> Die funktion subimage ist dermaßen langsam das ich dir zustimmen muss, dass es damit nicht geht
> ...



Ich will Java nicht beleidigen. Ganz im Gegenteil ich bin absoluter Fan dieser Programmiersprache, sonst hätte ich das Spiel nicht in Java programmiert.
Ich habe nur C++ erwähnt weil ich weiß das es in C++ funktioniert.
Ich hoffe in Java auch, besonders da ich bei dem Spiel schon relativ weit bin.[/b]


----------



## 0xdeadbeef (21. Jul 2005)

Soulfly hat gesagt.:
			
		

> Hab wohl ein bißchen zu lange zum schreiben gebraucht.
> 
> Meinst du wirklich, dass subimage performant ist?
> Erklär mir:?:



Naja, laut API-Beschreibung werden die Daten nicht kopiert, sondern das Subimage verweist auf die gleichen Daten wie das ursprüngliche Image. Klingt nach einer reinen Pointeroperation ohne Kopieren. Man weiß aber halt nicht, was hinter den Kulissen noch so abgeht. Anscheinend 'ne Menge.



			
				Moonlight1234 hat gesagt.:
			
		

> Wenn ich jedes mal den gerade sichtbaren Bildschirmbereich neu zeichne, warum ist das schneller als wenn ich das komplette Bild vorzeichne und nur den sichtbaren Bereich in den Backbuffer kopiere?
> Bei deiner Methode werden doch auch jedesmal die images in den Backbuffer kopiert?


Ich stimme Dir ja zu, daß subImage prinzipiell vernünftig klingt, wenn man der API-Doku glaubt. Aber wenn es von der Performance nicht hinhaut, ist es vermutlich nicht so toll implementiert. Die Tiles auf den Bildschirm ausgeben sollte aber auf jeden Fall schnell gehen und müßte sogar hardwareunterstützt sein, insbesondere wenn Du ein VolatileImage benutzt. Damit geht's also auf jeden Fall.

Wie sieht's eigentlich mit DrawImage aus? Da kann man doch auch ein Rechteck aus dem Quellimage vorgeben. Hast Du das mal anstelle von subImage ausprobiert?


----------



## Moonlight1234 (22. Jul 2005)

0xdeadbeef hat gesagt.:
			
		

> [Ich stimme Dir ja zu, daß subImage prinzipiell vernünftig klingt, wenn man der API-Doku glaubt. Aber wenn es von der Performance nicht hinhaut, ist es vermutlich nicht so toll implementiert. Die Tiles auf den Bildschirm ausgeben sollte aber auf jeden Fall schnell gehen und müßte sogar hardwareunterstützt sein, insbesondere wenn Du ein VolatileImage benutzt. Damit geht's also auf jeden Fall.
> 
> Wie sieht's eigentlich mit DrawImage aus? Da kann man doch auch ein Rechteck aus dem Quellimage vorgeben. Hast Du das mal anstelle von subImage ausprobiert?



VoltileImage schaue ich mir an.
Mit drawImage habe ich es noch nicht versucht.
Soweit ich das sehe muß ich dazu eine AffineTransform benutzen.

Dazu muß ich mir ehrlich gesagt  erstmal Matrizenrechnung anschauen.
Bin bislang ohne ausgekommen.


----------



## Guest (22. Jul 2005)

Moonlight1234 hat gesagt.:
			
		

> Mit drawImage habe ich es noch nicht versucht.
> Soweit ich das sehe muß ich dazu eine AffineTransform benutzen.
> 
> Dazu muß ich mir ehrlich gesagt  erstmal Matrizenrechnung anschauen.
> Bin bislang ohne ausgekommen.


Nö, die affinen Transformation sind in Graphics2D dazugekommen, aber Graphics2D erbt ja auch die "normalen" drawImage-Methoden aus Graphics.
Versuch das mal und melde Dich wieder. Würde mich nämlich selber interessieren.


----------



## Moonlight1234 (23. Jul 2005)

Am meisten Performance fürs Scrolling bringt es das Bild aus Tiles zusammenzusetzen.
So ist die Performance auch ausreichend für das Spiel.
Zwischen getSubimage und drawImage konnte ich keinen Unterschied feststellen.
VolatileImage habe ich noch nicht ausprobiert, werde ich evtl. noch.
Allerdings verwende ich BufferStrategy bzw. PageFlipping das soll schon sehr schnell sein.

Am meisten Peformance kostet es die Sprites auf dem Bildschirm auszugeben.


----------



## 0xdeadbeef (24. Jul 2005)

Einige Vorschläge:

1) "Dirty retcangle clipping": wenn Du Sprites o.ä. auf den Hintergund im Offscreen-Buffer malst und gerade nicht gescrollt wird, kannst Du Dir bei jedem Sprite etc. merken, welcher Teil des Bildschirm verändert ("dirty") wurde. Vor dem Zeichnen des nächsten Frames muß dann nicht der gesamte Offscreen-Buffer restauriert werden, sondern nur die veränderten Teile.


2) "Managed Images": 
Das sind BufferedImages, die hardwarebeschleunigt sind. Im Gegensatz zu VolatileImage muß man sich aber nicht damit herumschlagen, daß der Speicher (in der Grafikkarte) überschrieben wird. Zum Erzeugen eines "Managed Images" benutzt man die Methode CreateCompatibleImage (z.B.) des GC:

```
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gs = ge.getDefaultScreenDevice();
    GraphicsConfiguration gc = gs.getDefaultConfiguration();
    
    // Create an image that does not support transparency
    bimage = gc.createCompatibleImage(width, height, Transparency.OPAQUE);
    
    // Create an image that supports transparent pixels
    bimage = gc.createCompatibleImage(width, height, Transparency.BITMASK);
    
    // Create an image that supports arbitrary levels of transparency
    bimage = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
```
Siehe auch hier:
http://weblogs.java.net/blog/chet/archive/2003/08/bufferedimage_a_1.html
http://javaalmanac.com/egs/java.awt.image/CreateBuf.html
http://javaalmanac.com/egs/java.awt.image/Image2Buf.html


3) "BufferStrategy"
Statt Double-Buffering zu simulieren, indem man per graphics.drawImage den Offscreen-Buffer in einem Rutsch malt, kann man sich auch der dafür gedachten "BufferStrategy" bedienen:

```
frame.createBufferStrategy(2);
    BufferStrategy strategy = frame.getBufferStrategy();
    ...
    // Render loop
    while (!done) {
        Graphics g = strategy.getDrawGraphics();
        // Draw to graphics
        ...
        strategy.show();
    }
    ...
```
Im Idealfall entspricht das Umschalten des Puffers dann einer Pointeroperation bzw. einer BlitCopy-Aktion im internen Grafikspeicher.
Aber Achtung: der per getDrawGraphics erhaltene Offscreen-Puffer kann theoretisch von außen überschrieben werden. Ist aber schon etwas entschärft gegenüber VolatileImage.


4) "IndexColorModel"
Zumindest die letzte genannte Methode funktioniert nur in Applikationen, nicht in Applets, aber auch die anderen Ansätze werden auf einigen Plattformen keinerlei Beschleunigung bewirken. In diesen Fällen hilft der gute alte 256-Farben-Modus. Dann ist es zwar Essig mit Blending usw, aber dafür braucht ein Pixel nur ein Byte statt 4 Bytes im DirectColorModel. Will heißen: mit einem 32bit-Schreibzugriff lassen sich 4 Pixel auf einmal schreiben statt einem einzigen.


----------



## Moonlight1234 (28. Jul 2005)

Danke für die Tips.
Ich hatte mich für BufferStartegy entschieden, da hier "automatisch" die schnellste Methode ausgewählt wird.
Mit PageFlipping ist es sehr schnell. Ich weiß nicht ob es noch schneller ist direkt VolatileImages zu programmieren.
Laut Sun-Dokumentation handelt es sich bei BufferStrategy um eine Art "Hüllklasse" für VolatileImages, daher denke ich wird es da keine großen Geschwindigkeitssteigerungen mehr geben. 

"Dirty Rectangel Clipping" ist ein interessanter Ansatz. Ich weiß bloß wie ich ihn umsetzen sollte.
Allerdings wird in meinem Spiel meistens gescrollt, daher fällt das ohnehin flach und ohne Scrolling ist das spiel schnell genug.
Mit Scrolling jetzt wohl auch, allerdings habe ich bislang nicht die Grenzen getestet, dh. ab welcher Anzahl von Sprites das Spiel nicht mehr läuft.


----------



## 0xdeadbeef (28. Jul 2005)

Moonlight1234 hat gesagt.:
			
		

> Danke für die Tips.
> Ich hatte mich für BufferStartegy entschieden, da hier "automatisch" die schnellste Methode ausgewählt wird.


Ist wohl eine sinnvolle Entscheidung bei einem scrollendem Spiel.



> Mit PageFlipping ist es sehr schnell. Ich weiß nicht ob es noch schneller ist direkt VolatileImages zu programmieren.
> Laut Sun-Dokumentation handelt es sich bei BufferStrategy um eine Art "Hüllklasse" für VolatileImages, daher denke ich wird es da keine großen Geschwindigkeitssteigerungen mehr geben.


Falls möglich, ist der Offscreen-Buffer, den BufferStrategy meldet eh ein VolatileImage. Man könnte natürlich noch die Tiles, Sprites usw, die in den Offscreen-Buffer kopiert werden, ebenfalls als VolatileImages deklarieren. Das kann aber etwas kompliziert werden, weil VolatileImages ja "flüchtig" sind, also jederzeit überschrieben werden können und dann vom eigenen Programm restauriert werden müssen. Insofern denke ich mal, reicht es völlig aus, wenn Du BufferedImages reinkopierst, bzw. optimalerweise mit CreateCompatibleImage erzeugte BufferedImages (Tiles als Transparency.OPAQUE).



> "Dirty Rectangel Clipping" ist ein interessanter Ansatz. Ich weiß bloß wie ich ihn umsetzen sollte.
> Allerdings wird in meinem Spiel meistens gescrollt, daher fällt das ohnehin flach und ohne Scrolling ist das spiel schnell genug.


Wenn sich der Bildschirmhintergrund in jedem Frame bewegt, bringt es gar nichts. Ansonsten kann es was bringen, ist aber u.U. recht aufwendig und sorgt für ungleichmäßige Belastung von Frame zu Frame.



> Mit Scrolling jetzt wohl auch, allerdings habe ich bislang nicht die Grenzen getestet, dh. ab welcher Anzahl von Sprites das Spiel nicht mehr läuft.


Die Sprites solltest Du per CompatibleImage (Transparency.BITMASK) erzeugen, zumindest, wenn Du kein Alpha-Blending brauchst. Dann sollten sie eigentlich kein großes Performance-Problem darstellen.


----------



## Moonlight1234 (30. Jul 2005)

Der Tip mit createCompatibleImage hat einen Menge gebracht. Tatsächlich kann ich jetzt keinen messbaren Performance-Verlust feststellen. Jetzt kann ich die Ressourcen für andere Dinge verwenden.
Ich bin begeistert. 
Vielen Dank an dieser Stelle.
 :applaus:

Rein Interesse halber:
Benötigt man für die Standard-Java-Klassen DirectX? Java3D gibt es für DirectX und OGL. Für die Standart-Runtime habe ich nichts dergleichen gelesen.
Methoden wie createCompatibleImage() greifen doch auch auf die Grafikhardware zu, eigentlich müßten diese auch eine Grafikschnittstelle benötigen oder ist das unabhängig davon?


----------



## 0xdeadbeef (31. Jul 2005)

Offensichtlich benutzt die Sun-JVM unter Windows DirectX, falls es verfügbar ist. Zumindest haben z.B. Anti-Aliasing-Einstellungen des ATI-Treibers unter DirectX teils einen unguten Effekt auf Java-Anwendungen, die Swing oder Graphics2D benutzen. 
Falls kein DirectX vorhanden ist, was ja aber nur auf Windows-Versionen vor XP der Fall sein kann, wird es vermutlich einen Fallback auf die ältere Schnittstelle (deren Namen mir gerade nicht einfällt) oder Software-Modus geben. In dem Fall ist's natürlich Essig mit Hardwarebeschleunigung.

Ansonsten wr es mir eine Freude, geholfen zu haben


----------



## Moonlight1234 (31. Jul 2005)

Also wird DirectX nicht unbedingt benötigt, ohne DirectX wären mässive Performanceeinbrüche die Folge.
Wobei es ja durchaus sein kann das Java OpenGL benutzt.


----------



## 0xdeadbeef (31. Jul 2005)

Moonlight1234 hat gesagt.:
			
		

> Also wird DirectX nicht unbedingt benötigt, ohne DirectX wären mässive Performanceeinbrüche die Folge.


Prinzipiell ja. Allerdings gab es wie gesagt vor DirectX (bzw. DirectDraw) schon eine API, um auf den Speicher der Grafikkarte zuzugreifen. Habe allerdings den Namen vergessen. Ich weiß, daß es Anwendungen/Spiele gab/gibt, die diese alte API als Fallback benutzen, wenn kein DirectX verfügbar ist. Könnte theoretisch bei der JVM auch so sein. Aber nichts genaues weiß man nicht.



> Wobei es ja durchaus sein kann das Java OpenGL benutzt.


Java ist eine Sprache, hier geht es aber um die JVM. 
Eventuell benutzen die JVM-Implementierungen auf Nicht-Windows-Systemen (Linux usw.) eine OpenGL-Unterstützung.
Was Windows angeht, glaube ich nicht so recht daran. Die Anzahl der Windows-Rechner, auf denen zwar OpenGL hardwarebeschleunigt läuft, aber kein DirectX-Treiber installiert ist, dürfte ziemlich gering sein. Insofern taugt OGL nicht so recht als Fallback. 
Auf Linux-System usw. ist das was anderes. Dort wäre OGL-Unterstützung sinnvoll. Ob sie aber implementiert ist, vermag ich nicht zu sagen...


----------

