# Regen Effekt in 2D Spiel



## LoN_Nemesis (28. Sep 2006)

Hallo,

ich hab relativ lang bei Google gesucht aber nichts gefunden ausser einige Hinweise auf Bücher. Hat jemand einen Link zu einem Tutorial, in dem beschrieben wird wie man einigermassen realistisch aussehenden Regen in einen 2D Spiel hinbekommt? Oder vielleicht kann es sogar selbst jemand beschreiben.

Ich hab es mal selbst intuitiv versucht und zufallsmässig graue Streifen auf den Bildschirm gemalt, sieht leider null aus wie Regen  Wäre für jede Hilfe dankbar.


----------



## Redfrettchen (28. Sep 2006)

Hi,
ach, das erinnert mich an die guten alten, prozeduralen TCL-Zeiten, damit konnte man Schnee richtig einfach malen...
Oh well, im Grunde ganz viele ähnlich gerichtete (von mir aus auch parallele) Linien zufällig malen und sich die Koordinaten jedes Tropfens in einer ArrayList von unten nach oben geordnet merken (also die schon ganz unten im Bild sind sind ganz vorne). In jedem Tick der Hauptschleife wird die Position jedes Tropfens in die Fallrichtung verändert und der weiter gefallene Tropfen gemalt. Tropfen, die nicht mehr zu sehen sind, werden neu ausgewürfelt (am besten nimmt man sich als Zielfeld einen Bereich außerhalb des Bildes, so dass die Tropfen natürlich aus diesem nachfallen) und in die Liste einsortiert.

So würd ich das machen.


----------



## LoN_Nemesis (28. Sep 2006)

Werde ich mal ausprobieren, danke! Aber warum muss man die ArrayList nach den Koordinaten sortiert halten? Das ist doch unnötiger Rechenaufwand, oder? Es soll ja sowieso jeder Tropfen runterfallen, und wenn ich erst auf ein Offscreen Image male (Double Buffering) dann sieht man quasi alle auf einmal runterfallen.


----------



## Redfrettchen (28. Sep 2006)

Aber die Tropfen fallen ja nicht ewig und es müssen ja auch immer wieder neue erzeugt werden. Du musst also wissen, welche Tropfen schon verschwunden sind, also ist es am effizientesten, sie geordnet zu speichern. Dann prüfst du am Anfang jeder Bewegung, wo der erste Tropfen ist. Wenn er aus dem Bild ist, entfernst du ihn, erzeugst eine neue Position und sortierst sie mit binärer Suche in die ArrayList ein. Nicht soo der Aufwand.


----------



## Campino (28. Sep 2006)

Du musst die ArrayList garnicht sortieren. Die Tropfen fallen ja alle mit der selben Geschwindigkeit, was als Erstes erschaffen wurde, verschwindet also auch als Erstes wieder, die Liste ist automatisch sortiert (Zumindest wenn du immer hinten anhängst).

Ansonsten ist das ja auch egal, du musst so oder so für jeden Tropfen anhand der y-Koordinate prüfen, ob er schon raus ist... 

Redfrettchen will diese Überprüfung jeweils nur für den ersten Tropfen machen, was aber wenn mehrere auf einer Höhe sind Probleme macht...wenn sie sortiert sind kann man solange prüfen, bis man einen findet, der nicht raus ist...


----------



## Redfrettchen (28. Sep 2006)

Campino hat gesagt.:
			
		

> Du musst die ArrayList garnicht sortieren. Die Tropfen fallen ja alle mit der selben Geschwindigkeit, was als Erstes erschaffen wurde, verschwindet also auch als Erstes wieder, die Liste ist automatisch sortiert (Zumindest wenn du immer hinten anhängst).


Stichwort Erweiterbarkeit: Was ist wenn die unteren Tropfen schneller fallen sollen? ^^
Des weiteren muss man doch bloß am Anfang sortieren, weil man am Anfang viele Tropfen zufällig auf dem Bildschirm erstellt. Wenn ein Tropfen dazukommt, muss er bloß über dem höchsten Tropfen erstellt werden, also kann man sich sogar das binäre Einsortieren sparen.



			
				Campino hat gesagt.:
			
		

> Ansonsten ist das ja auch egal, du musst so oder so für jeden Tropfen anhand der y-Koordinate prüfen, ob er schon raus ist...
> 
> Redfrettchen will diese Überprüfung jeweils nur für den ersten Tropfen machen, was aber wenn mehrere auf einer Höhe sind Probleme macht...wenn sie sortiert sind kann man solange prüfen, bis man einen findet, der nicht raus ist...


Ganz genau: In meiner Liste würde ich so lange durchgehen, bis ein Tropfen noch sichtbar ist und die ersten, nicht-sichtbaren löschen und dafür neue Tropfen erzeugen.


----------



## Beni (28. Sep 2006)

Wieso Tropfen löschen? Benötigt doch nur unnötige Rechenzeit (Garbage Collector, Liste rumkopieren :!:, neuen Tropfen erstellen...). Schieb doch einen Tropfen der unten ist einfach wieder hoch :wink:


----------



## LoN_Nemesis (28. Sep 2006)

Das ist ein sehr berechtigter Einwand, hätte man auch selbst drauf kommen können  Danke


----------



## Redfrettchen (29. Sep 2006)

lol, wer hat den gesagt, dass man einem Tropfen eine Klasse spendieren muss (ok, blabla Erweiterbarkeit)?
Aber wie willst du das Umkopieren der Liste verhindern? Entweder wir prüfen für jeden Punkt, ob er außerhalb liegt, oder wir sortieren und müssen umstrukturieren (also vllt geschickte Wahl der Laufliste: LinkedList, bringt das was?). Alternative wäre natürlich auch HashSet (so könnte man natürlich doppelte Punkte aussortieren, Nachteil: die Anzahl muss hin und wieder korrigiert werden), über die man dann iteriert und gegebenfalls den ungültigen Punkt aus dem Set löscht und mit neuen Koordinaten wieder einfügt.


----------



## EgonOlsen (29. Sep 2006)

Sortieren? ArrayList? Wieviele Tropfen willst du denn bewegen? 10? Du musst sowieso die Tropfen durchgehen, um sie zu zeichnen. In dem Moment weißt schon, wo der Tropfen sitzt und ob er noch sichtbar ist. Dort kannst du dann entsprechend den Tropfen nach oben setzen. Das in zwei Durchläufe (Zeichnen und Sichtbarkeitsprüfung) inkl. Sortieren zu splitten ist meiner Ansicht nach völlig überflüssig. Ferner würde ich nicht jeden Tropfen als eigene Klasse ausführen sondern einfach eine "Regen"-Klasse bauen, die Tropfenkoordinaten und Geschwindigkeit in zwei/drei Arrays hält.


----------



## Redfrettchen (29. Sep 2006)

Nenene, so gehts ja mal gar nicht. Wir arbeiten doch nicht etwa im paint-Aufruf, oder? *shakes head in shame* :noe:
Des weiteren wäre jeder Tropfen ein eigenes Objekt, und keine Klasse - wenn überhaupt ^^


----------



## Düark (29. Sep 2006)

Ich hab mal die Sterne in einem Spiel mit Objekten gemacht.
Ein Stern-Objekt hatte eine Move-Methode, jeweils die Koordinaten um den Faktor Geschwindigkeit veränderte. Hatte den Vorteil, daß man bei unterschiedlichen Geschwindigkeiten einen räumlichen Eindruck hatte. Wenn der Stern den Bildschirmrand erreicht hatte, hat die move-Methode einfach false zurückgegeben und die aufrufende Methode hat dann das Stern-Objekt anfangs aus der Liste gelöscht, später dann neu initialisiert wie Beni schon meinte.
Die draw-Methode des Objekts hat die Farbe des Sterns je nach Geschwindigkeit gewählt (dunkel für langsam=entfernt, hell für schnelle= nah). 
Muss mal sehn ob ich den code finde...


----------



## EgonOlsen (29. Sep 2006)

Redfrettchen hat gesagt.:
			
		

> Nenene, so gehts ja mal gar nicht. Wir arbeiten doch nicht etwa im paint-Aufruf, oder? *shakes head in shame* :noe:
> Des weiteren wäre jeder Tropfen ein eigenes Objekt, und keine Klasse - wenn überhaupt ^^


Ja, natürlich Objekt und nicht Klasse. Ich brauche Urlaub. Ach, ich habe ab morgen ja Urlaub...egal. Es sagt ja niemand, dass er im AWT-Event-Thread zeichnen soll, aber wenn es ein "richtiges" Spiel ist, liegt die Vermutung nahe, dass er in irgendeine Art Puffer malt. Und damit könnte man das ganz wunderbar so machen. Wenn dem natürlich nicht so ist, dann will ich nichts gesagt haben. Aber dann wird das auch kein besonders ansprechendes und flüssig laufendes Spiel werden...


----------



## Redfrettchen (29. Sep 2006)

Auch mit Puffer trennt man rechnen und malen.
Wenns wirklich an der Performance mangeln sollte (was ich stark bezweifle), dann kann man immer noch tricksen, in dem man einen Tropfen immer mehrfach parallel verschiebt und so weniger Speicher braucht (die y-Koordinate weniger) und auch weniger Überprüfungen machen muss. ka, ob das gut aussieht, wär jetzt nur noch spontan ne Idee.


----------



## EgonOlsen (29. Sep 2006)

Redfrettchen hat gesagt.:
			
		

> Auch mit Puffer trennt man rechnen und malen.


Mal abgesehen davon, dass "man" das tut, was "man" eben tut und nichts davon per Gesetz vorgeschrieben ist: Bei komplexeren Dinge, wo eine höhere Abstraktion wünschenswert ist, stimme ich dir voll und ganz zu. Aber nicht unbedingt bei dem hier angestrebten Effekt. Der Rechenanteil fällt hier doch gar nicht ins Gewicht. Ganz einfach wäre z.B. if (y>height) {y=0;} und schon ist der Tropfen wieder oben und kann erneut runterplatschen. Dafür soll ich einzelne Instanzen von Tropfen sortieren und einen extra Rechendurchlauf starten? Also wenn er mit Swing-Komponenten arbeitet und jeder Tropfen eine ist, dann ok. Aber wenn er sowieso direkt auf einer BufferStrategy rumreitet oder in ein BufferedImage malt, dann ist das meiner Ansicht nach absolut überflüssig. Zumal rechnen und malen teilweise nicht zu trennen sind (wenn z.B. die Tropfen nicht einfach dicke blaue Klumpen sind, die über den Hintergrund gepinselt werden, sondern den Hintergrund verwischen oder verzwirbeln sollen).


----------



## Redfrettchen (29. Sep 2006)

Ok ok, ich mache sowas eben nicht, "man" kann tun und lassen, was "man" will. 

Und mit Rechnen (oops, muss man natürlich, genauso wie Malen, in diesem Fall groß schreiben, sry  ) meinte ich: die strukturellen Eigenschaften von (realen oder programmiertechnischen) Objekten verändern. Wie sie schließlich dargestellt werden, ist Aufgabe des Malens.
Das Sortieren soll übrigens nur am Anfang jedes "Regens" geschehen. Und den "extra" Rechenlauf wird er sowieso haben, bei einem Spiel wird er ja auch irgendetwas anderes bewegen wollen, also warum nicht dort auch die Regentropfen. Hat den Vorteil, dass man Rechen- und Malphase unterschiedlich viel Rechenzeit geben kann und man z.B. auch mal ein Neumalen überspringen und trotzdem ein Update vornehmen kann, was beim Mischen der beiden Phasen recht kompliziert ist (denke ich).


----------



## EgonOlsen (29. Sep 2006)

Naja, ich denke, wir sehen den Regen einfach gedanklich als was anderes. Bei dir ist es eine Ansammlung von Tropfen, die über einen Hintergrund bewegt werden. In meiner Denke ist es eine Art Pro-Pixel-Effekt, der eben z.B. auch den Hintergrund verändern könnte. Er unterscheidet sich in sofern auch von den anderen Dingen, die du angesprochen hast, d.h. von der restlichen Spiellogik. Denn dazu gehört er nicht. Die eigentlichen Spielentitäten sollten ihre Berechnungen und die Darstellung natürlich trennen, zumal letztere ja auch mal komplett entfallen kann, wenn die Dinger z.B. in einer Client-Server-Architektur verwendet werden sollen. Beim Regen gilt das aber nicht. Würde man das auf MVC übertragen, sehe ich beim Regen ein V aber nur so wenig M und C, dass ich die Trennung nicht machen würde.
Naja, wie auch immer...diese Diskussion wird dem Threadstarter vermutlich nicht viel bringen und außerdem muss ich jetzt auch mal langsam in Urlaub...  :wink:


----------



## Beni (30. Sep 2006)

Wenn man mal von einer Tropfen-Klasse ausgeht (wobei ich so eine Klasse doch übertrieben empfinde. Ein double-Array reicht wirklich), dann kann man Logik und Grafik doch ohne Probleme trenne:

```
public class Tropen implements IrgendeinInterface{
  ...
  public void logic( int deltaTimeInMillis ){ // Logik
    y += 0.005 * deltaTimeInMillis; // 5 Einheiten/Sekunde
    if( y > height )
      y = 0;
  }

  public void graphic( Graphics g ){  // Grafik
    g.setColor( Color.BLUE );
    g.fillRect( x, y, 1, 1 );
  }
}
```


----------



## Redfrettchen (30. Sep 2006)

Jup, das gefällt mir 
Ok, wir können aufhören zu diskutieren, wie du schon gesagt hast, es wird dem Threadersteller nicht wirklich weiterbringen (oder doch?  ).
Schönen Urlaub, EgonOlsen!


----------



## LoN_Nemesis (30. Sep 2006)

Diskussionen können einen immer weiterbringen!

Da ich aber wirklich nur Tropfen haben möchte, die von oben nach unten fallen und die nichts an der Umgebung oder dem Hintergrund ändern, werde ich keine neue Klasse machen, sondern einfach ein Array benutzen. Wenn die Tropfen unten angekommen sind, so setze ich sie einfach wieder hoch.


----------



## kaie (2. Okt 2006)

Wieso muss überhaupt irgendwas umkopiert bzw. sortiert werden? Nimm einfach ein int[][]-Array für x/y/v-Informationen und lass die Tropfen fallen. Ein Tropfen, der unten angekommen ist, wird durch einen neuen Tropfen von oben ersetzt. So bleibt die Regendichte immer konstant, und man kann den Speicher- und Rechenbedarf besser abschätzen.

Ich habe gerade mal probeweise was in eine Lösung aus einem anderen Forumsbeitrag eingebaut:


```
import java.awt.*;
import java.net.*;

import javax.swing.*;

public class Spiegelung extends JPanel implements Runnable
{
    private Image bild        = null;
    private int   phase       = 0;
    private int   wellenhoehe = 3;
    
    private int[][] regen = new int[200][3];
    private int regencounter = 0;

    public Spiegelung(String s)
    {
        // Bild laden
        try
        {
            bild = new ImageIcon(new URL(s)).getImage();
        } catch (Exception e)
        {
            System.out.println("Bild nicht gefunden!");
        }

        // JPanel-Groesse setzen
        setPreferredSize(new Dimension(bild.getWidth(null), bild
                .getHeight(null)));

        for( int i=0; i<regen.length; i++ )
            neuerRegen(i);
        // Aktualisier-Thread starten
        new Thread(this).start();
    }

    public void paint(Graphics g)
    {
        // Groesse des Bildes ermitteln
        int w = bild.getWidth(null);
        int h = bild.getHeight(null);

        // Position der Wassergrenze berechnen
        int m = h * 2 / 3;

        // Hintergrund zeichnen
        g.drawImage(bild, 0, 0, null);

        // Spiegelung zeichnen
        for (int y = m; y < h; y++)
        {
            int q = (int) (m - Math.pow(y - m, 1.1) + wellenhoehe
                    * Math.sin(y * .25 + phase * .5));
            g.drawImage(bild, 0, y, w, y + 1, 0, q, w, q + 1, null);
        }

        g.setColor(new Color(0, 0, 255, 20));
        
        for( int i=0; i<regen.length; i++ )
        {
            if( regen[i][1]==0 || regen[i][1]>h+20-regen[i][2]*10 )
            {
                neuerRegen(i);
                regen[i][1]=0;
            }
            g.drawLine(regen[i][0],regen[i][1],regen[i][0]+regen[i][2],regen[i][1]+regen[i][2]*3);
            regen[i][0]+=regen[i][2];
            regen[i][1]+=regen[i][2]*3;
        }
        regencounter = (regencounter+1)%regen.length;

        // halbtransparentes blau über die Spiegelung zeichnen
        g.fillRect(0, m, w, h - m);
    }
    
    private void neuerRegen( int i )
    {
        regen[i][0] = (int)(Math.random()*bild.getWidth(null));
        regen[i][1] = (int)(Math.random()*bild.getHeight(null));
        regen[i][2] = (int)(3*Math.random())+3;
    }

    public void run()
    {
        while (true)
        {
            repaint();
            phase++;
            try
            {
                Thread.sleep(50);
            } catch (Exception e)
            {
            }
        }
    }

    public static void main(String[] args) throws Exception
    {
        // neuen Frame mit einer Spiegelung erzeugen
        JFrame f = new JFrame("Eine Beispielspiegelung");

        String url = "http://www.sz.ruhr-uni-bochum.de/imperia/md/images/sz/greihe75.jpg";
        f.getContentPane().add( new Spiegelung(url) );
        
        f.pack();
        f.setVisible(true);
    }

}
```

Ist zumindest ein Ansatz! Schönen Urlaub an alle, die ihn haben!


----------



## LoN_Nemesis (4. Okt 2006)

Hey kaie,

vielen Dank! Ich hatte mittlerweile eine eigene Lösung fertig, die sehr ähnlich wie deine funktionierte. Leider war ich nicht so schlau wie du, den Regen schräg zu zeichnen. Das sieht gleich viel besser aus 

Ich habe das jetzt mal von dir übernommen, ich hoffe das ist ok?


----------

