Landschaften im "Worms" Stil erstellen Im Internet, genauer gesagt hier fand ich ein Tutorial welches beschreibt wie man recht einfach Landschaften im Worms Stil erstellen kann. Ich möchte auf keinen Fall den Eindruck erwecken als wäre die Idee von mir, lediglich die Umsetzung ist meine Interpretation des beschriebenen Verfahrens. Also, los gehts. Beschreiben wir mal was hier passiert. Als erstes erstellen wir ein Bild und füllen es mit Pink als Hintergrundfarbe. Hier hinein wird nun ein Terrain am unteren Bildschirmrand gezeichnet und einige Wolkenartige Strukturen werden auch noch hinzugefügt damit das Bild nicht so leer ist. Das Terrain sowie die "Wolken" werden in Schwarz dargestellt. Kommen wir zum Code der das ganze realisiert. Diesen habe ich in 2 Bereiche unterteilt. Wie schon erwähnt in das Terrain am unteren Bildschirmrand und eben die "Wolken". Beginnen wir mit dem Terrain. Zunächst, und um sicher zu stellen das keine großen Lücken im Terrain aufreten, wird ein großes Rechteck gezeichnet welches den unteren freien Bereich abdeckt. Das ganze natürlich in schwarzer Farbe. Nun folgt eine Schleife über die gesamte Bildschirmbreite (und darüber hinaus). Bei jedem Step wird eine Zufallszahl für Y ermittelt welche vom unteren Bildschirmrand eine gewisse Höhe haben darf. Hier wird nun jedesmal eine Ellipse mit 100 x 100 Pixeln gezeichnet. Fertig ist das Terrain. Natürlich ziemlich simpel aber das Verfahren kann sich ja jeder ausbauen - hier geht es schließlich um das Prinzip. Color 0, 0, 0: Rect 0, height - 50, width, height, 1 For x = -100 To width + 100 Step 20 y = Rand( height - 150, height ) Oval x, y, 100, 100, 1 NextWeiter gehts mit den "Wolken". Hier wird es schon etwas komplizierter. Als erstes kommt eine Schleife die zwischen 1 und 4 Durchläufe vollführt, also eine zufällige Anzahl an Wolken, erzeugt. Nun ermitteln wir 2 Zufallswerte, jeweils für X und Y. Diese benutzen wir in nachfolgender Schleife als Ursprungskoordinaten um um diesen herum zwischen 6 und 20 Ellipsen in zufälliger Größe zu zeichnen. Fertig ist die Wolke. Auch hier wieder die Anmerkung das es sicher besser geht. For t = 0 To Rand( 1, 3 ) x = Rand( 0, width ) y = Rand( 0, height - 350 ) For i = 0 To Rand( 5, 19 ) r = Rand( -100, 100 ) Oval x+r, y+r, Rand(50, 100), Rand( 50, 150 ), 1 Next NextKommen wir nun aber zum Kern der Sache. Wir tauschen nun unsere schwarze Farbe gegen eine Textur. Diese muß Kachelbar sein, also nahtlos aneinanderpassen. Ich habe hier die Textur der Tutorialseite benutzt: Zunächst legen wir nun ein Bild an welches exakt so groß ist wie unser Landschaftsbild. In dieses wird bei TileBlock die Textur hineingekachelt. stone = LoadImage("stone.png") grabber = CreateImage( width, height ) SetBuffer ImageBuffer( grabber ) TileBlock stone, 0, 0Nun durchlaufen wir wieder eine Schleife die uns das Landschaftsbild ausliest. Finden wir einen schwarzen Pixel wird an diese Position der Pixel aus dem Texturenbild kopiert. Das in den Code noch LockBuffer etc. gehört versteht sich von selbst - ich habe es hier nur weggelassen da es ja ums Prinzip geht. For x = 0 to width - 1 For y = 0 to height - 1 pix = ReadPixelFast( x, y ) If pix = black Then WritePixelFast x, y, ReadPixelFast( x, y, ImageBuffer( grabber ) ) End If Next NextWeiter im Text. Kommen wir nun zum Gras welches sich auf den Oberseiten des Terrains befinden soll und dem was an die Unterseite gehört, ich nenne es mal Sand. Hierzu wird schon beim Durchlaufen der "Texturierungsschleife" geprüft wann ein Pixelwechsel von Pink auf Schwarz und umgekehrt stattfindet. Bei jedem Wechsel wird die entsprechende Aktion ausgeführt, also entweder das Gras oder der Sand gezeichnet. Die Gras bzw. Sandtexturen sind wesentlich kleiner als die Steintextur zum Füllen der Landschaft. Hier ist nur wichtig das die Texturhöhe ein vielfaches von 2 ist. Warum dazu kommen wir gleich, zuerst einmal die Texturen. Auch diese habe ich von der Tutorialseite übernommen: Nun zum Prinzip. Nehmen wir hierzu mal das Gras. Es soll immer dann gezeichnet werden wenn ein Wechsel von Pink auf Schwarz stattfindet. Dabei wird es aber nicht von der Position des aufgefundenen schwarzen Pixels aus nach unten gezeichnet, sondern wir gehen noch einmal die halbe Texturhöhe zurück. Damit dies funktioniert sollte die Texturhöhe eben ein vielfaches von 2 sein. Der Grund hierfür liegt darin das sich später Sand und Gras nicht überschneiden - es sieht halt einfach besser aus. Ausserdem sollen auch vom Gras nur die nicht Pink farbenen Pixel dargestellt werden, so daß man auch "rauhe" Texturen benutzen kann. Ich habe hier Pixel für Pixel kopiert, man könnte das ganze aber natürlich auch per DrawImageRect oder auf andere Weise realisieren. Beim Sand wird ebenso verfahren, nur das hier natürlich bei einem Wechsel von Schwarz auf Pink gezeichnet wird und wir hier noch den unteren Bildschirmrand ausschließen weil es ebenfalls hässlich aussehen würde wenn auch hier die Sandtextur eingezeichnet würde. Alles in allem sieht das Ergebnis danach so aus: Ein recht anständiges Ergebnis wenn man den geringen Aufwand in Betracht zieht. Zu guter letzt noch der gesamte Quellcode. In diesem hab ich noch ein paar kleine "Explosionen" eingebaut welche zeigen sollen wie die fertige Map benutzt werden kann. Da diese nun ein normales maskiertes Image ist, kann auch per ImagesCollide eine Kollisionsabfrage durchgeführt werden. width = 800 height = 600 Graphics width, height, 0, 2 sand = LoadImage("sand.png") grass = LoadImage("grass.png") stone = LoadImage("stone.png") hsand = ImageHeight( sand ) / 2 hgrass = ImageHeight( grass ) / 2 ;--> Erstellt Bild mit gleicher Größe wie Landschaftsbild und fülle mit "stone" Textur. grabber = CreateImage( width, height ) SetBuffer ImageBuffer( grabber ) TileBlock stone, 0, 0 ;--> Erstelle "land" land = CreateImage( width, height ) ;--> Ermittle Referenzpixel SetBuffer ImageBuffer( land ) LockBuffer ImageBuffer( land ) WritePixelFast 0, 0, 0 black = ReadPixelFast( 0, 0 ) WritePixelFast 0, 0, $FF00FF pink = ReadPixelFast( 0, 0 ) UnlockBuffer ImageBuffer( land ) ;--> Beginne Schleife Repeat SeedRnd MilliSecs() SetBuffer ImageBuffer( land ) Color $FF, 0, $FF: Rect 0, 0, width, height, 1 ;--> Berechne Terrain am unteren Bildrand Color 0, 0, 0: Rect 0, height - 50, width, height, 1 For x = -100 To width + 100 Step 20 y = Rand( height - 150, height ) Oval x, y, 100, 100, 1 Next ;--> Berechne Terrain in Bildmitte (Wolkenstrukturen) For t = 0 To Rand( 1, 3 ) x = Rand( 0, width ) y = Rand( 0, height - 350 ) For i = 0 To Rand( 5, 19 ) r = Rand( -100, 100 ) Oval x+r, y+r, Rand(50, 100), Rand( 50, 150 ), 1 Next Next SetBuffer ImageBuffer( land ) DrawBlock land, 0, 0 ;--> locke alle Buffer LockBuffer ImageBuffer( land ) LockBuffer ImageBuffer( sand ) LockBuffer ImageBuffer( grass ) LockBuffer ImageBuffer( grabber ) ;--> Landschaft mit "stone" Textur füllen For x = 0 To width - 1 transition = 0 For y = 0 To height - 1 ;--> Lies Pixel aus. pix = ReadPixelFast( x, y ) ;--> Wenn Schwarz Pixel, ersetze ihn durch "stone" Pixel If pix = black Then ;--> erster Schwarz Pixel nach Pink? Speichere Y-Position If transition = 0 Then ytop = y: transition = 1 WritePixelFast x, y, ReadPixelFast( x, y, ImageBuffer( grabber ) ) End If ;--> Pink Pixel oder Bilrdschirmkante? If pix = pink Or y = height - 1 Then ;--> Erster Pink Pixel nach schwarz? If transition = 1 Then ;--> Zeichne "sand" Textur an unteren Rand wenn NICHT unterer Bildschirmrand If y < height - 1 Then ysand = 0 For y1 = y - hsand * 2 To y - 1 If y1 > -1 And y1 < height Then psand = ReadPixelFast( xsand, ysand, ImageBuffer( sand ) ) If psand <> pink Then WritePixelFast x, y1, psand ysand = ysand + 1 End If Next xsand = xsand + 1: If xsand = ImageWidth( sand ) Then xsand = 0 End If ;--> Zeichne "grass" Textur an oberen Rand wenn NICHT oberer Bildschirmrand If ytop > 0 Then ygrass = 0 For y1 = ytop - hgrass To ytop + hgrass - 1 If y1 > -1 And y1 < height Then pgrass = ReadPixelFast( xgrass, ygrass, ImageBuffer( grass ) ) If pgrass <> pink Then WritePixelFast x, y1, pgrass ygrass = ygrass + 1 End If Next xgrass = xgrass + 1: If xgrass = ImageWidth( grass ) Then xgrass = 0 End If transition = 0 End If End If Next Next UnlockBuffer ImageBuffer( land ) UnlockBuffer ImageBuffer( sand ) UnlockBuffer ImageBuffer( grass ) UnlockBuffer ImageBuffer( grabber ) ;--> Maskieren wir nun das "land" MaskImage land, 0, 0, pink ;--> Erzeuge einen Hintergrund back = CreateImage( width, height ) SetBuffer ImageBuffer( back ) For y = 0 To height blue# = 255 / Float( height ) * y Color 0, 0, blue Rect 0, height-y, width, height-y, 1 Next ;--> Nun machen wir noch einige Explosionen For i=0 To Rand( 5, 9 ) ;--> suche nach einem Punkt im "land" der mit einer Textur belegt ist Repeat x = Rand( 0, width ) y = Rand( 0, height ) If ReadPixel( x, y ) <> pink Then Exit Forever ;--> Zeichne eine Ellipse als "Explosion" Color 0, 0, pink For r = 0 To 100 SetBuffer ImageBuffer( land ) Oval x-r/2, y-r/2, r, r, 1 SetBuffer BackBuffer() DrawBlock back, 0, 0 DrawImage land, 0, 0 Flip Next Next Until KeyHit(1) EndHey...weiterlesen ;-) Der größte Clou kommt nämlich erst jetzt. Als kleiner Tipp: Diese Level werden ja durch "Zufall" erzeugt und wie man weis geschieht in einem Computer nichts wirklich zufällig. Das heißt das ein Level bei gesetztem Startwert für eine Zufallsberechnung jedesmal gleich aussieht. Dies wiederum bedeutet das man z.B. bei einem Multiplayer Spiel nicht ein ganzes Level oder Bild übertragen muß sondern lediglich 4 Byte als Startwert der Zufallsberechnung. Auf allen Rechnern wird dann nämlich das gleiche Level erzeugt. So das wars jetzt aber wirklich. |
|||