Welcome to the new Friends-of-FPC!

Here you can find all kinds of information about the FreePascal Compiler. We have many tutorials and howtos as well as a selection of tools to help you with your programming. We also have some example codes for you. And if you want to contribute some information/ sources/ tools yourself you can do so.
Also we have finally relaunched the FoFPC forum. It's your chance for some Q&A about everything FreePascal.

Friends-of-FPC

Tutorials: Learn how to code with FreePascal.

Source Codes: A collection of examples, miscellaneous source codes and open source stuff.

Tools and Help Files: Intro- duction of some tools that might help you with FPC.

Community

Forum: Ask or answer questions about the FreePascal Compiler, programming or just babble about coding.

Contribute! Contribute your own Tutorial, Source Codes or Tools and send them to us!

Website

About: Information about Friends-of-FPC.org.

Grundlagen der Grafikprogrammierung - Teil 20 - by Venom

2D Effekte - Lehrgang I
Verfaßt im März 2001 von venom

Thema : Sprite rotation und scaling gleichzeitig

Vorausgesetzte Kenntnisse :

  • Satz des Pythagoras (auch hier kurz erklärt)
  • Linienalgorithmus (am Besten von Bresenham)
  • Kenntnisse beim Umgang mit Festkommata Zahlen
  • Fundierte Assembler Kenntnisse

Einleitung I :

Hallo und herzlich willkommen zu meinem ersten Lehrgang über die Programmierung von 2D Effekten. Heute befassen wir uns gleich mit zwei wichtigen Themen in der 2D Programmierung. Bekannt sind sie als SPRITE SCALING und SPRITE ROTATION.

Ich werde versuchen, so gut es geht verständlich zu schreiben und unwichtige Fachbezeichnungen zu vermeiden. Der Text sollte also auch für Neulinge auf diesem Gebiet verständlich bleiben. Falls Ihr Fragen habt könnt Ihr mich jederzeit erreichen, die Adresse ist unten angegeben.

Einleitung II :

Sprites sind eigendlich nichts anderes als 2D Bilder die Ihr in den Speicher Eures Programms geladen habt. Das kann zum Beispiel eine Jump & Run Figur sein, die Ihr mit einem Zeichenprogramm (z.B. Paint Shop) gezeichnet habt und nun mit einer geeigneten Dekodierungsroutine (z.B. PCX oder BMP) in den Speicher laden wollt.

Jedenfalls befindet sich nun das Sprite (Bild) im Arbeitsspeicher und bettelt geradezu um seine Verwendung. Eine Sprite Routine hat nun die Aufgabe, das Sprite in den Graphikspeicher oder in eine virtuelle Seite zu kopieren. Für gewöhnlich wählt man die letztere Variante.

Üblicherweise versieht man seine Sprite Routine noch mit einer CLIPPING-Kontrolle oder Effekten wie z.B. ALPHA BLENDING oder einem schicken TRANSPARENZ Effekt. Diese Effekte werde ich jedoch (falls Nachfrage besteht) in den nächsten Lehrgängen mit Euch erarbeiten, da sie relativ einfach zu realisieren sind.

Wozu SPRITE SCALING und SPRITE ROTATION ?

SPRITE SCALING bedeutet einfach, das Ihr die Größe eines Eurer Sprites verändert. Ihr skaliert z.B. ein Bild mit 100x100 Pixels zu einem Bild mit 200x200 Pixeln. Eine SPRITE SCALING Routine sollte Euch also ermöglichen, ein Bild beliebiger Größe zu einem Bild mit neuer Größe aber gleichem Inhalt zu skalieren.

Habt Ihr erst einmal solch eine Routine fertig, könnt Ihr die Größe Eurer Sprites beliebig verändern und damit tolle Effekte erziehlen. In einem Spiel, welches ich gerade progammiere, skaliere ich zum Beispiel ein 2000x2000 Pixel Bild (das aktuelle Spiellevel) nach 100x100 Pixel, speichere das kleine Bild als neues Sprite ab und verwende es als Radarkarte oben rechts in der Interfaceleiste (C&C läßt grüßen). Aber auch für Intros oder Demos läßt sich die Routine verwenden.

SPRITE ROTATION ist jedoch der notwendigste Effekt für den Umgang mit Sprites in 2D Spielen oder 2D Demos. SPRITE ROTATION bedeutet, daß Ihr Euer Sprite (Bild) 360° drehen könnt. Das ist enorm wichtig, wenn Ihr z.B. einen 2D Shooter aus der Vogelperspektive programmieren wollt.

Ohne solch eine Routine könnt Ihr Euren Panzer oder Eure Figur höchstens nach rechts, links, unten und oben marschieren lassen. Aber das wollt Ihr ja gerade nicht, Ihr wollt dem Spieler ermöglichen, sich in 360° durch die 2D Level bewegen zu können.

Dafür muß sich natürlich auch das Bild von Eurem Panzer in die entsprechende Richtung drehen. Und da kommt Ihr um ein bischen Mathematik (ich höre Euch stöhnen) nicht herum.

Keine Panik ! Zwei Effekte, aber nur EIN Algorithmus !!!

So, nun aber zum wichtigen Teil. Ich hoffe das Wort "Mathematik" hat Euch ebend nicht verjagt. Aber keine Panik, ich werde Euch jetzt erklären, wie Ihr mit wenig Mathematik und etwas Einfallsreichtum eine Routine programmiert, die zwei "Fliegen mit einer Klappe schlägt" und außerdem verfraggt schnell ist. Ich habe diese Routine vor ca. 4 Monaten entwickelt und werde sie Euch nun erklären. Also rubbelt Euch noch mal die müden Augen und konzentriert Euch, es ist nicht alzu schwer. Also here we go...

Es gibt sicherlich viele Wege ein Sprite zu skalieren oder ein Sprite zu rotieren, aber es gibt nur wenige die...ja die beides auf einmal können !!! Also atmet auf, Ihr müßt nicht beides programmieren, lediglich EINE Routine mit der Ihr ein Sprite drehen und gleichzeitig seine Größe verändern könnt.

Als kleinen Ansporn solltet Ihr Euch vielleicht eine kleine Demo von mir ansehen, welche Ihr von meiner Homepage downloaden könnt. Dir URL ist : www.venomsoftware.de.vu Dort könnt Ihr Euch unter DEMOS die Demo_01 runterladen. Sie nimmt nur ca. 800 Kb ein und demonstriert unter Anderem genau das, was Ihr jetzt von mir lernen werdet.

SPRITE ROTATION und SCALING zur gleichen Zeit

Also los geht's ! Stellt Euch ein quadratisches Sprite (Bild) mit den Ecken A,B,C und D vor.

Ein Sprite liegt bekanntlich linear IM SPEICHER, man kann sich also die einzelnen Pixel als lange Kette oder Zahlenstrahl vorstellen. Dargestellt wird diese Kette (alle Pixel) jedoch als Rechteck, in unserem Falle sogar als Quadrat. Das Quadrat (Sprite) besteht also aus mehreren horizontalen Linien, dessen Längen (a) gleich der Spritebreite sind. Die Höhe des Sprites entspricht der Anzahl dieser horizontalen Linien. Das bedeutet wiederum, daß man sich ein Sprite AUF DEM BILDSCHIRM so vorzustellen hat :

Auf dem Bildschirm :                     Im Speicher :

Man erkennt gut, daß das Quadrat aus mehreren horizontalen Linien besteht, dessen einzelne Pixel die Farbinformation des Bildes tragen. Im Speicher liegen diese horizontalen Linien aber wie gesagt natürlich linear, also aneinandergereiht vor. Soweit alles klar ? Ok dann weiter.

Stell Euch weiterhin die horizontalen Linien im Bild vor. Ihr wollt jetzt dieses Sprite zum Beispiel 45° nach links drehen. Das würde dann so aussehen :

Was muß nun passieren ? Ganz klar. Horizontale Linien ade ! Oder doch nicht ? Auf dem Bildschirm vielleicht schon, aber nicht im Speicher. Der Speicherinhalt bleibt immer gleich. Also was ist zu tun ?

Nun jetzt macht man folgendes. Man berechnet die neuen Koordinaten der vier Eckpunkte A,B,C und D. Das geht einfach mit Sinus und Cosin.

X,Y seien die Koordinaten des Mittelpunktes unseres Sprites. In der Abbildung (s.o.) also der Ursprung der zwei Pfeile. Da wir ein quadrat betrachen, ist der Abstand vom Ursprung X,Y zu jedem Punkt A,B,C oder D gleich.

Also berechnen wir ihn nur einmal für A und benutzen ihn ebenfalls für B,C und D. Groß X und Y seien wie gesagt die Koordinaten des Mittelpunktes unseres Bildes. Die simple Formel für die neuen x und y Koordinaten eines jeden Punktes lautet nach Pythagoras in Pascal code :

Abstand eines jeden Punktes A,B,C und D zum Mittelpunkt :

Abstand := Sqrt( ( (a div 2) * (a div 2) ) * 2 );

Neue x- und y-Koordinaten von A sind :

xA := X + Trunc( Cosin[NeuerWinkel] * Abstand );
yA := Y - Trunc( Sinus[NeuerWinkel] * Abstand );

Ok, was haben wir gemacht. Zuerst haben wir den Abstand berechnet. Wir haben einfach die Breite unseres Sprites halbiert (a div 2) und mit sich selbst multipliziert (a div 2) * (a div 2). Wir haben also die Quadratzahl von der halbierten Breite unseres Sprites gebildet.

Die halbierte Breite unseres Sprites ist in der Abbildung (s.u.) wird durch den Pfeil dargestellt. Da es sich ja um ein Quadrat handelt, haben wir einfach das ganze mit 2 multipliziert ( ( (a div 2) * (a div 2) ) * 2 ), denn der Abstand von der Pfeilspitze zum Punkt A ist ja gleich dem Abstand den wir gerade berechnet haben, also vom Mittelpunkt bis zur Pfeilspitze.

Warum das ganze ? Ganz einfach, der Satz des Pythagoras lautet ja k² = q² + p² ! k ist unser Abstand vom Mittelpunkt X,Y zum Punkt A. q² und p² sind gleich (da Quadrat), bei uns also beide (a div 2) * (a div 2). Da q² und p² gleich sind können wir also statt q² + p² einfach (a div 2) * (a div 2) einfach mit 2 multiplizieren (natürlich besser shiften mit shl 1).

Anschließen ziehen wir noch aus ( ( (a div 2) * (a div 2) ) * 2 ) die Wurzel, weil wir ja nicht k² sondern nur k haben wollen Sqrt( ( (a div 2) * (a div 2) ) * 2 ). Und schon haben wir den Abstand eines jeden Punktes zum Mittelpunkt. Hier nochmal die Formel :

  k        >     q² bzw. p²    <         

Abstand := Sqrt( ( (a div 2) * (a div 2) ) * 2 );

Zu den anderen beiden Formeln muß ich glaube ich nichts sagen. Wie man Cosin und Sinus anwendet sollte Euch klar sein. Jedenfalls können wir nun mit diesen beiden Formeln und unserem errechneten Abstand...

xA := X + Trunc( Cosin[NeuerWinkel] * Abstand );
yA := Y - Trunc( Sinus[NeuerWinkel] * Abstand );

...jede neue X und Y Koordinate für unsere Punkte A,B,C und D nach einer Drehung (hier : 45 ° nach links) berechnen. Sinus und Cosin sind natürlich Arrays von 0..360, welche die jeweiligen Sin- und Cos-Werte für den gesuchten Radius enthalten. Man sollte immer solche Look-up tables erstellen und nicht jedesmal den Wert für einen Winkel neu mit den Pascal-Funktionen sin und cos berechnen. Aber das wißt Ihr sicherlich schon längst... J ..hoffe ich.

So, nun aber weiter. Ihr habt jetzt also die neuen Koordinaten für A,B,C und D berechnet :

Nun macht Ihr folgendes, Ihr schnappt Euch eine richtig schnelle Routine zum Zeichnen von Linien (am besten mit einem Bresenham Algorithmus !!!). Diese Routine verwendet Ihr jetzt als Schleife von Punkt A bis zum Punkt B. Ihr müßt Euch also vorstellen, das Ihr in dieser Routine vom Punkt A bis zum Punkt B (hier bei 45 ° abwärts) wandert.

Überall da, wo Eure Linienroutine jetzt eigendlich einen Punkt (Pixel) auf der Linie b setzen würde, startet Ihr stattdessen eine neue Linienroutine die von diesem Punkt aus bis zum gegenüber liegenden Punkt auf der Linie c läuft. Ich hoffe Ihr könnt Euch das vorstellen, macht Euch dies unbedingt klar !

Ihr lauft also salopp gesagt jeden Pixel zwischen A und B ab und zieht bei jedem Pixel von diesem aus bis zum gegenüber liegenden Pixel eine neue Linie.

Ok, soweit so gut. Jetzt kommt der Effekt überhaupt. Wärend Ihr also die Linie a entlang wandert und jede Linie bis zur Linie c zieht, lest Ihr aus jeder horizontalen Linie Eures Sprites (linear im Speicher) die Farbinformationen aus (Pixel) und setzt sie auf den Bildschirm.

Das ist der fundamentale Trick an der Sache, Ihr lest also wärend Ihr die Pfeile bis zur Linie c (linkes Bild) entlangwander aus der zugehörigen horizontalen Linie Eures Sprites (Pfeile im rechten Bild) die Farbinformationen (Pixel) und setzt sie auf das Bild.

Natürlich ist der Abstand von einem Punkt auf der Linie a bis zu seinem Gegenüber auf der Linie c nicht immer gleich und so muß man folgenden Trick anwänden, damit die horzontale Linie auch in die neue vertikale Linie "paßt". Und da kommen wir zum skalieren. Wir dividieren einfach die Länge unserer horizontalen Linie (Spritebreite) durch die Länge unserer vertikalen Linie.

Dafür müssen wir natürlich die Länge der vertikalen Linie berechnen, das geht ganz einfach wieder mit einer Linienroutine die bei jedem Punkt wo sie eigendlich einen Pixel setzen sollte eine Zählvariable erhöht und am Ende die Länge der abgelaufenen Strecke präsentiert.

WICHTIG ! Während wir mit unserer modifizierten Linienroutine die Länge der Linie berechnen, speichern wir gleich jeden x und y Abstand der aktuellen Position in der vertikalen Linie bis zum Startpunkt der Linienroutine ab. Am besten in einem geeignet großen Array. Was das uns bringt ?

Ganz einfach, statt jedesmal für's Zeichnen einer vertikalen Linie vom aktuellen Punkt auf der Linie b bis zum gegenüber liegenden Punkt eine Linienroutine zu benutzen, machen wir das nur einmal am Anfang und benutzen die Werte die wir dabei für die x,y Koordinaten aller Pixel auf dieser Linie ermittelt habe auch für alle anderen folgenden vertikalen Linien.

Dieser Trick muß Euch unbedingt klar sein, da er viel Rechenzeit einspart !!! Denn die Steigung jeder vertikalen Linie ist ja gleich, also merken wir uns alle Punkte (x,y-Werte) unserer ersten vertikalen Linie und verwenden diese auch für die folgenden. Lediglich den Startpunkt verändern wir, da wir ja von A nach B abwärts wandern.

Wir starten also am Anfang bei der ersten vertikalen Linie (der lange Pfeil) mit einer Linienroutine und speichern während wir die Variable für die Länge erhöhen jetzt z.B. die Differenz von x2-x1 sowie y2-y1 ab. Davon nehmen wir den absoluten Wert (immer positiv).

Wenn wir jetzt die Linie L2 abarbeiten wollen, verwenden wir nicht mehr eine Linienroutine, sondern berechnen die folgenden Pixel durch unsere Differenzwerte, die wir mit der ersten Linienroutine ermittelt haben.

Also währe der zweite Punkt (xneu,yneu) der Linie L2 einfach :

xneu:= X + Abs(x2-x1);
yneu:= Y - Abs(y2-y1);

Es ist klar, daß man Abs(x2-x1) und Abs(y2-y1) in einem Array gespeichert hat, während man die modifizierte Linienroutine zum Berechnen der Linienlänge ausgeführt hat.

Soviel dazu. Wir wollten nun aber die horizontale Linie auf die vertikale Linie anpassen, da beide nicht immer gleichlang sind. Dafür hatten wir mittels der ersten Linienroutine (für die erste vertikale Linie) schon die Länge jeder vertikalen Linie berechnet, während wir die Differenzen gespeichert haben.

Also dividieren wir die Spritebreite durch die Länge der vertikalen Linie. Was stellt nun das Ergebnis davon dar ? Ganz easy, das ist einfach ein Faktor den wir beim ablaufen unserer vertikalen Linien berücksichtigen müssen. Stellen wir uns vor unsere Spritebreite ist 200 und unsere neue vertikale Linie ist 100 Pixel lang. Nach unserer Berechnung ergibt sich :

200 : 100 = 2

Wenn wir jetzt unsere vertikale Linie entlangwandern, und aus unserem Sprite (aus den horizontalen Linien) die Pixel auslesen, setzen wir statt jeden Pixel, nur jeden 2'ten Pixel aus dem Quellsprite. Bildlich sieht das so aus :

Man sieht, daß nur die Pixel 1,3,5 und 7 aus unserem Quellsprite (aus unserer horizontalen Linie) auf die Ziellinie gesetzt werden. Wir haben also die ursprünglich horizontale Linie auf die Größe der vertikalen Ziellinie skaliert !

Und hier kommt der Trick der auch gleich das SPRITE SCALING einschließt. Da also sowieso jede horzizontale Linie zu seiner jeweiligen vertikalen Linie skaliert wird, paßt sich die horizontale Linie ja immer an die Länge der vertikalen Linie an !

Also kann man auch einfach ganz am Anfang die Werte für die Breite des Sprites manipulieren. Erinnert Ihr Euch noch ? Wir hatten den Abstand vom Mittelpunkt bis zum Punkt A wie folgt berechnet :

Abstand := Sqrt( ( (a div 2) * (a div 2) ) * 2 );

a war ja unsere Spritebreite. Um das Sprite nun zu skalieren setzt Ihr einfach für a die neue Breite ein !!! Das wars schon !!! In unserem Falle würdet Ihr also 100 Pixel für a wählen, anstatt 200 Pixel und Eure Routine würde automatisch das Sprite skalieren, da sich ja eh jede horizontale Linie an die Länge der neuen vertikalen Linie angepaßt wird !!!

Tätärätäää ! Wir haben fertig. Wir haben eine Routine erschaffen, die ein Sprite rotiert und gleichzeitig skaliert !

Es ist natürlich klar, daß Ihr das Auslesen der Pixelinformationen aus jeder horizontalen Linie und das gleichzeitige schreiben dieser Information in die neue vertikale Linie realisiert, indem Ihr einfach esi immer mit dem Skalierungsfaktor (bei uns 2 gewesen weil 200 : 100...) addiert.

Es ist also ein Index in das Quellsprite. Jedesmal wenn beim Auslesen einer horizontalen Linie an dessen Ende angelangt seit, müßt Ihr Euch den neuen esi Wert "merken" und bei der nächsten horizontalen Linie als Startwerte nehmen.

Warum ist die Routine so schnell ?

Die Routine die ich entwickelt habe ist aus mehreren Gründen sehr schnell. Zum Einen berechnet sie nicht einzeln für jeden Pixels die neuen Koordinaten, lediglich 4 Pixel (die Eckpunkte) werden am Anfang einmal berechnet !!!

Zum Anderen erfolgt das Füllen des neuen (rotierten) Quadrats durch lineares Auslesen der Farbwerte aus dem Quellsprite und die Skalierung erfolgt mittels Festkommata Rechnungen.

Außerdem läuft der gesammte Prozeß nur in einer Schleife ab. Nämlich die Linienroutine die vom Punkt A bis zum Punkt B wandert. Die vertikalen Linien zur gegenüber liegenden Linie c werden nicht neu berechnet, da ihre Steigung gleich ist und jede Koordinate für jeden Pixel schon am Anfang berechnet worden ist und einfach addiert wird.

Nachwort, Kontaktinformation und der Source zum Lehrgang

Zum Schreiben dieses Lehrgangs bin ich durch die Leute von www.sundancerinc.de gekommen. Die Seite ist sehr übersichtlich und bietet vor allem auch gute Programmierdokus auf Deutsch.

Ich persönlich habe zwar mit englischen Dokus kein Problem, lese aber natürlich lieber die Deutschen Texte. Vor allem kann man sich bei Fragen an jemanden wenden, der einem auch auf Deutsch zurückschreibt J.

Falls ich auf diesen Lehrgang hin weitere Nachfrage nach einer Fortsetzung bekomme bin ich gerne dazu bereit. Falls Ihr Fragen zu dieser Doku habt oder sonstiges loswerden wollt, könnt Ihr mir einfach eine Mail hinterlassen oder mir per Post schreiben.

Auf meiner Homepage http://www.venomsoftware.de könnt Ihr Euch meine alte Graphikunit VenomGFX (GO32V2 Version) für Free Pascal runterladen. Die Unit ist PUBLIC DOMAIN und enthält neben 120 anderen Graphikroutinen die Sourceroutine zu diesem Lehrgang. Also eine Routine zum Rotieren und gleichzeitigem Skalieren eines Sprites.

HOMEPAGE :
http://www.venomsoftware.de

Verfaßt im März 2001 von venom.

Back to previous page

Useful Links









Link to us