Autor Beitrag
umpani
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 389



BeitragVerfasst: Sa 25.10.03 16:28 
Generierung einer 2D-Landschaft

Inspiriert von der Diskussion in der Multimedia-Sparte, habe ich mich entschlossen, hier ein kleines Tutorial zur Generierung von 2D-Landschaften zu schreiben.

Es geht um eine Landschaft wie in Scorched Earth. Sie besteht aus Bergen, auf denen Panzer stehen, die sich becshießen. (Ein bekanntes Beispiel ist das bei MS-DOS mitgelieferte Gorillas.bas (QBasic))

WIr müssen es also erreichen, dass zufallsgenerierte Berge schnell und effizient generiert werden.

An die Berge haben wir folgende Ansprüche:

    - Variable Anzahl
    - Variable Steigungen
    - Variable höhe
    - schnelle Generierung

Ausgangbasis soll Timage Komponente beliebiger Größe sein (am besten in der Höhe größer 100) und ein Button.

Dann benötigen wir 2 Variablen vür die X- und die Y-Achse des Bildes
ausblenden Delphi-Quelltext
1:
var x,y:integer;					

EIne Variable, in der gespeichert wird, in welche richtung die Berge gehen (nach oben, oder nach unten) und eine, in der ein Zufallswert gespeichert wird.
ausblenden Delphi-Quelltext
1:
2:
    plusminus:boolean;
    auswahl:integer;

Zuerst muss das Image gelöscht und die Variablen initialisiert werden.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
   bild.Picture.Free;
   bild.Picture := tpicture.Create;
   randomize;
   y := 100//Die Höhe, in der der erste Berg startet
   steigung := 1;   //Steigung von 1 bedeutet 45grad

Wir beginnen nun damit, dass wir jede Spalte des Bildes durchlaufen.

Und für jede Spalte muss erneut per zufall generiert werden, ob der Berg eine neue Richtung einschlagen, oder die derzeitige beibehalten soll.

in 99 der hindert Fälle behält der Berg die Richtung bei und in einem fall ändert er es. Verringert man die Variable "bergspitzen", so wird es wahrscheinlicher, dass sich die Steigungsrichtung ändert.

In der Var plusminus wird die Steigungsrichtung gespeichert. Es gibt nur "nach oben" oder "nach unten" also ist sie vom Typ boolean.

Bei jedem Durchgang wird nun je nach Strigungsrichtung etwas von abgezogen bzw. dazugezählt.

am ende wird noch strich auf dem jeweiligen x Wert in y-Länge gezeichnet. Aber da der Berg so auf dem Kopüfstehen würde, muss der y-Wert von der Bild-Höhe abgezogen werden.

Das Ergebnis kann so aussehen.:

user defined image

Und der Quelltext dazu:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
procedure TForm1.BitBtn2Click(Sender: TObject);
var x:integer;
    steigung,y:real;
    plusminus:boolean;
    auswahl:integer;
    bergspitzen:integer;

const berg = 50;

begin
   bild.Picture.Free;
   bild.Picture := tpicture.Create;
   randomize;
   y := 100//Startpunkt
   steigung := 1;   //Startsteigung
   bergspitzen := 100//Wahrscheinlichkeit von vielen Bergspitzen

for x := 1 to bild.Width do
  begin
    auswahl := random(bergspitzen+1);  
      if auswahl = bergspitzen then  //wahrscheinlichkeit:1 zu bergspitzen
        if plusminus = true then
         begin
          plusminus := false;
        end
      else
        begin
          plusminus := true;
        end;

      if plusminus = true then
        begin
          y := y + steigung;
          if y > bild.Height-50 then
            y := bild.Height-50;
          if y < 0 then
            y := 0;
        end
      else
        begin
          y := y - steigung;
          if y < 0 then
            y := 0;
          if y > bild.Height-100 then
            y := bild.Height-100;
        end;
   bild.Canvas.Polygon([point(x,bild.Height),point(x,bild.Height-round(y))]);
  end;
  bild.repaint;
end;

end.


Problem:

Sehen wir uns unsere Ansprüche an, so muss ich sagen, dass unser erstes Ergebnis dem nicht Gerecht wird. Wir haben etwas generiert, was Ähnlichkeiten von Bergen hat, jedoch ist die Steigung immer 1 und die Oberfläche sehr glatt.

Wir müssen die Steigung also Abhängig vom Zufall machen.

Weiterhin wollen wir auch Steigungen haben, die kleiner als 45 Grad sind. Z.Z. ist die Steigung jedoch eine integer Variable und damit die kleinste Steigung "1" (also 45Grad). Erst unter 1 (z.B. 0.2) wir die Steigung flacher. Die Var "steigung" wird also, ebenso wie die Var y zu Real-Werten.

Weiterhin wird bei Jeder Änderung der Steigungsrichtung die Steigung neu per Zufall generiert.

Das Ergebnis kann dann so aussehen:

user defined image

Und der Quelltext dazu:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
procedure TForm1.BitBtn1Click(Sender: TObject);
var x:integer;
    steigung,y:real;
    plusminus:boolean;
    auswahl:integer;
    bergspitzen:integer;
const berg = 50;
begin
   bild.Picture.Free;
   bild.Picture := tpicture.Create;
   randomize;
   y := random(bild.Height div 2); //Startpunkt
   steigung := random(20) / (random(30)+1);   //Startsteigung
   bergspitzen := random(berg); //Wahrscheinlichkeit von vielen Bergspitzen
for x := 1 to bild.Width do
  begin
    auswahl := random(bergspitzen+1);
      if auswahl = bergspitzen then
        if plusminus = true then
         begin
          plusminus := false;
          steigung := random(10) / (random(30)+1);
        end
      else
        begin
          plusminus := true;
          steigung := random(10) / (random(30)+1);
        end;
      if plusminus = true then
        begin
          y := y + steigung + (random(2)-1);
          if y > bild.Height-50 then
            y := bild.Height-50;
          if y < 0 then
            y := 0;
        end
      else
        begin
          y := y - steigung - (random(2)-1);
          if y < 0 then
            y := 0;
          if y > bild.Height-100 then
            y := bild.Height-100;
        end;
   bild.Canvas.Polygon([point(x,bild.Height),point(x,bild.Height-round(y))]);
  end;
  bild.repaint;
end;

Zum Abschluss muss ich noch sagen, dass es auch noch die Möglichkeiten über Sinuskurven gibt. "thebe", von dem auch das nachfolgende Bild ist hat diesen Weg vorgeschlagen. Hier muss jeder selber wissen, was er bevorzugt.

user defined image

Die weiteren Veränderungen dieser Generierung sind der Phantasie überlassen.

Gruß Umpani

_________________
Lernen, ohne zu denken, ist eitel; denken, ohne zu lernen, ist gefährlich. Konfuzius
Lhid
ontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic starofftopic star
Beiträge: 831



BeitragVerfasst: So 23.11.03 11:59 
Meinung und mich grundlegend geändert-> alte beiträge gelöscht


Zuletzt bearbeitet von Lhid am Sa 26.09.09 13:43, insgesamt 2-mal bearbeitet
umpani Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 389



BeitragVerfasst: So 23.11.03 18:35 
In der Multimedia Sparte gab es ein Topic mit ähnlichem Namen, dort hat "Thebe" eine Möglichkeit mit Sinuskurven beschrieben. Schau mal dort nach.

Gruß Umpani

_________________
Lernen, ohne zu denken, ist eitel; denken, ohne zu lernen, ist gefährlich. Konfuzius
obbschtkuche
Gast
Erhaltene Danke: 1



BeitragVerfasst: So 23.11.03 18:44 
Gothicware
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 77

Win 98, Win 2000, Win XP, BeOs-R5, Zeta 1.0(war nicht gut, also verkauft), KnoppiX, VM-Ware
D4 Client/Server, Turbo Basic, QBasic, Atari-Basic
BeitragVerfasst: Mo 23.08.04 04:02 
Habs mal ausprobiert, und festgestellt, das selbst im zweitem Sourcecode immer noch hässliche Abfälle, und Krasse Spitzen Auftretten, die Sehr unnatürlich wirken.

So enstehen solche Bilder:
user defined image
Fehler des alten Codes


Auserdem tretten Lücken oder Löcher auf. Wenn man das ganze für ein Spiel nutzen will, ist das nur dann Sinvoll, wenn man ein Fluss haben will. Oder so was in der Art.
Die Const Cluster, gibt an, wie gross die Einzelnen Unterschiede Ausfallen sollen.
Ein kleine rWert, generiert ein Flaches Land, und ein Grosser, ein Spitzgebirge. (Siehe unten).
Also hab ichs mal verbessert, und etwas Sin() und Cos() eingebaut.

Also Hier der Neue Code:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
procedure TForm1.BitBtn1Click(Sender: TObject);
var x:integer;
    steigung,y:real;
    plusminus:boolean;
    auswahl:integer;
    bergspitzen:integer;


const berg = 100;
const cluster = 0.8// sollte Vorsichtig vergrössert werden. Einfach Testen.

begin
   bild.Picture.Free;
   bild.Picture := tpicture.Create;
   randomize;
   y := random(Bild.Height div 2) + (Bild.Height div 4); //Startpunkt zufällig festlegen
   steigung := -0.21;   //Startsteigung
   bergspitzen := 50//Wahrscheinlichkeit von vielen Bergspitzen

for x := 0 to bild.Width do // mit 0 beginnen, da ein Canvas, von 0 bis Width geht. Oder
// for x := 1 to bild.Width +1 do , und beim Positionieren wieder 1 abziehn
  begin
    auswahl := random(bergspitzen+1);
      if auswahl = bergspitzen then  //wahrscheinlichkeit:1 zu bergspitzen
        if not plusminus then plusminus := true else Plusminus := false;

      if plusminus then
        begin
          steigung := cluster + sin(x / ((cluster * 10) + random(10)));
          y := y + steigung;
        end
      else
        begin
          steigung := cluster + cos(x / ((cluster * 10) + random(10)));
          y := y - steigung;
        end;
   { Hier könnte man auch mit Min(,) und Max(,) arbeiten. ;-)
     Damit keine "löcher" entstehn, wird der Maximale und Minimale Wert
     relativ zur Bildgrösse festgelegt. }

   if y > ((bild.Height div 8) * 7then plusminus := false;
   if y > ((bild.Height div 8) * 7then y := ((bild.Height div 8) * 7);
   if y < (bild.Height div 8then plusminus := true;
   if y < (bild.Height div 8then y := (bild.Height div 8);
   bild.Canvas.MoveTo(x,bild.Height);
   bild.Canvas.lineto(x,bild.Height-round(y));
   // Weiss nicht, was Sinnvoller von beidem ist, aber mit lineto(,) ist es einfacher zuverstehn. ;-P
   // bild.Canvas.Polygon([point(x,bild.Height),point(x,bild.Height-round(y))]);
  end;
  bild.repaint;
end;



Und Drei Beispiele.

user defined image
Bild 1[Berg=100, Cluster=0.8]

user defined image
Bild 1[Berg=1000, Cluster=1.1]

user defined image
Bild 1[Berg=10, Cluster=0.2]

Wenn man etwas Blur betreibt, oder mehr Rundet, bekommt man auch einzelne verPixelungen noch weg. :)

MfG Gothicware

Moderiert von user profile iconraziel: Code- durch Delphi-Tags ersetzt