Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!
1. Vorwort
1.1 Einleitung
1.2 Themen in den Tutorials
1.3 Umgebung / Gestaltung der Engine
1.4 Vorkenntnisse 2. Hauptteil: 1. Teil des Tutorials (Partikel und Kräfte)
2.1 Vektoren
2.2 Partikel[list]
- Was sind Partikel in der SimpleEngine?
- Bestandteile eines Partikels
- Berechnen der Position / Geschwindigkeit
-> Einschub: Kräfte
- Zurück zur Geschwindigkeits-Berechnung[/list]3. Beispielprogramm #1 4. Schlusswort
===
1. Vorwort
1.1 Einleitung
In dieser Tutorial-Serie möchte ich Möglichkeiten zeigen, wie man
eine einfache Physik-Engine erstellen kann. Dabei werde
ich versuchen, alles leicht verständlich zu halten.
Die Engine wird für den dreidimensionalen Raum entwickelt.
1.2 Themen in den Tutorials
Im ersten Teil geht es um die Grundlagen, den Aufbau der Engine
und grundlegende Dinge. Dazu gehören Kräfte, Partikel und
wichtige Formeln der Physik. In den folgenden Teilen werden
dann unter anderem noch nützliche Klassen (Sprungfedern, feste Verbindungen, etc.),
grössere Objekte und Kollisionen behandelt.
1.3 Umgebung / Gestaltung der Engine
Als Entwicklungsumgebung werde ich Visual C++ 2005 verwenden.
Für die Beispiele verwende ich dazu DirectX, allerdings werde
ich die grafische Theorie wenn möglich ausserhalb der Tutorials
und lediglich im Source Code halten.
Wichtig ist, dass die Engine unabhängig von jeglichen Spiele-Daten läuft.
Sie soll also überall einsatzbereit und nicht an ein bestimmtes Spiel
gebunden sein. Ich werde für die Engine eine Win32-DLL erstellen
und sie ganz einfach mal SimpleEngine nennen.
Zu jedem Teil der Serie wird es ein Beispielprogramm geben. Dort sollte
man keine grafischen Highlights erwarten, denn die Programme dienen
lediglich der Demonstration der Physik.
1.4 Vorkenntnisse
Um das Tutorial zu verstehen, sollte man sich zumindest mit
Vektoren und später mit Matrizen auskennen. Ich gehe davon aus, dass hier
die meisten diese Kenntnisse haben, aber man kann nie sicher sein.
Natürlich schaden auch allgemeinere mathematische Kenntnisse nicht.
Nützlich sind auch generelle Erfahrungen mit der Spieleprogrammierung.
2. Hauptteil: 1. Teil des Tutorials (Partikel und Kräfte)
2.1 Vektoren
Für unsere Engine sind Vektoren sehr wichtig. Deshalb habe ich eine
Klasse für 3D-Vektoren implementiert (Vector3). Wie bereits gesagt sollte man
sich mit ihnen auskennen, daher werde ich auf die Implementierungs-
Details hier nicht eingehen. Der Code sollte allerdings ausreichend
kommentiert sein, falls man sich doch für die Implementierung interessieren sollte.
2.2 Partikel
Was sind Partikel in der SimpleEngine?
Wenn ich hier von Partikeln spreche, meine ich nicht nur winzige Objekte,
die im Raum herumschwirren. Es kann sich dabei auch um Pistolenkugeln,
Bälle oder Granaten handeln. Aber eben auch um Rauchpartikel oder ähnliches.
In der Regel sind Partikel recht klein gehalten.
Bestandteile eines Partikels
Wir wissen nun, was wir hier unter einem Partikel verstehen. Aber
was gehört eigentlich zu einem Partikel? Was muss ein Partikel können?
Unsere Partikel können eigentlich gar nicht viel. Sie existieren nur
und werden anhand von Kräften bewegt. Auf diese werde ich ein wenig später
noch eingehen.
Nehmen wir beispielsweise eine Metallkugel. Damit wir etwas mit ihr
anfangen können, benötigt sie Masse, eine Position und eine Bewegungsgeschwindigkeit.
Zur Position sollte es nicht viel zu sagen geben.
Die Masse ist für die Berechnungen wichtig. Schliesslich unterscheiden sich
die Auswirkungen von Kräften auf eine Metallkugel physikalisch doch ziemlich
von einer leichten Plastikkugel. Wir werden hier die inverse Masse verwenden,
da uns dies einige Vorteile bringt. (Einmal bei Divisionen durch 0,
und einmal bei der Tatsache, dass wir somit unendliche Masse simulieren
können (unbewegliche Partikel))
Bei der Bewegungsgeschwindigkeit handelt es sich um einen 3D-Vektor,
der die Bewegungsrichtung eines Partikels angibt. Wie der Name schon sagt,
ist auch die Geschwindigkeit enthalten: Je länger der Vektor, desto höher
die Geschwindigkeit des Partikels.
Beginnen wir mit der Implementierung dieses bisher einfachen Partikels:
(Getter / Setter nicht aufgelistet)
C-/C++-Quelltext
1
2
3
4
5
6
7
8
9
class Particle
{
private:float m_InverseMass; // Inverse Masse
Vector3 m_Velocity; // Bewegungsgeschwindigkeit; Wird bei jedem Durchlauf berechnet
Vector3 m_Position; // Position; Wird bei jedem Durchlauf berechnet
public:void Move(float time); // Berechnet die Position / Geschwindigkeit neu
};
Wenn wir die Masse eines Partikels mit dem normalen Wert setzen möchten,
tun wir dies über den Setter SetMass(float mass).
In diesem wird die Masse in die inverse Masse umgerechnet:
Diese Informationen werden in jedem Frame neu berechnet.
Das geschieht in der Move(float time)-Methode eines Partikels.
time: Vergangene Zeit seit dem letzten Frame in Sekunden
Zuerst wird die Position berechnet. Dazu addieren wir zur Position
einfach nur den Vektor der Bewegungsgeschwindigkeit. Damit das zeitlich
auch korrekt abläuft, multiplizieren wir den Richtungsvektor vorher mit der vergangenen Zeit.
C-/C++-Quelltext
1
2
// Position berechnen
m_Position = m_Position + m_Velocity * time;
Für die Geschwindigkeitsberechnung benötigen wir eine Beschleunigung.
Und für die Beschleunigung brauchen wir Kräfte.
Einschub: Kräfte
Was für Kräfte werden wir hier einsetzen?
Nun, wenn wir einmal kurz zum Kühlschrank schweben möchten,
werden wir merken, dass wir gar nicht schweben können.
(Es sei denn, man hat ein Schwebe-Gerät oder sowas. )
Woran liegt das? An der Schwerkraft! Damit haben wir
eine der wichtigsten Kräfte entdeckt.
Ebenfalls vorstellbar wären Windkräfte, eine Strömung
im Wasser und ähnliches. Was man eben gerade so braucht,
um die Partikel zu bewegen.
Bei Kräften handelt es sich hier wieder um 3D-Vektoren.
Wir können Kräfte auf Partikel anwenden, fügen sie ihnen also
hinzu. Die Klasse Particle bekommt nun also noch einen 3D-Vektor
mit den Kräften. Neue Kräfte werden wir einfach zu diesem Vektor
addieren.
Die Vektoren müssen dabei nicht normalisiert sein; je stärker
eine Kraft, desto länger der Vektor.
Nach jedem Frame werden hier die Kräfte wieder entfernt.
Dazu haben wir im Partikel eine neue Methode, die die Komponenten
des Vektors wieder auf 0.0 setzt.
Anmerkung:
Wenn immer dieselbe Kraft auf einen Partikel einwirkt, kann
man diesen Schritt natürlich auch weglassen, um Performance zu
sparen. Ein Beispiel dafür wäre, wenn beispielsweise nur die
Gravitationskraft auf den Partikel einwirkt und man diese nicht
in jedem Frame neu setzen möchte.
Um einem Partikel eine Kraft hinzuzufügen, rufen wir die Methode
AddForce auf, welche wie folgt implementiert ist:
Wobei m_ForceAccum die schlussendlich wirkende Kraft
auf den Partikel ist. Mit ClearForceAccum() entfernen wir
die Gesamtkraft wieder. (Resp.: Setzen sie auf 0)
Nun haben wir Kräfte, die unseren Partikel beschleunigen können,
also können wir jetzt die Beschleunigung berechnen.
Wie bereits gesagt, hat die Masse Auswirkungen auf die Stärke
einer Kraft. Unsere Beschleunigung berechnen wir, indem wir
die Kräfte mit der inversen Masse multiplizieren.
Anmerkung:
Würden wir nicht mit der inversen Masse arbeiten,
wäre die Formel hier: Beschleunigung = Kraft / Masse.
So ist auch die ursprüngliche Formel nach Newton:
a = F / m
a: Beschleunigung in m/s/s F: Kraft m: Masse (kg)
In unserer Engine entspricht eine Einheit im Koordinatensystem einem Meter.
Das vereinfach die Anwendung der Formeln (und meiner Meinung nach auch
die gesamte Grössenordnung in einem Spiel. )
Die berechnete Beschleunigung addieren wir dann (nach der Multiplikation
mit der vergangenen Zeit) zur bisherigen Geschwindigkeit.
Die gesamte Methode Move(float time):
Temporäre Anmerkung:
Bestimmt ist nun endgültig aufgeflogen, dass ich im Vector3
keinen +=-Operator implementiert habe... Das war Faulheit,
und ich bitte um Verzeihung. Wird aber geändert.
3. Beispielprogramm #1
Für das Beispielprogramm habe ich mir zuerst überlegt,
einfach ein paar fallende Partikel darzustellen. Aber
das wäre dann doch zu langweilig.
Wir werden eine Kugel haben (hier mit der Masse 1.5) und diese
mit einem Windantrieb in die Luft heben. Die Stärke des Antriebs
können wir mit den Pfeiltasten steuern.
Anmerkung:
Das ganze wurde hier nun nicht so sauber programmiert. Beispielsweise
hätte man den Ventilator in eine eigene Klasse packen können. Aus
Zeitgründen und der Einfachheit halber habe ich aber wirklich alles sehr
simpel gehalten. Mit den interessanteren Themen kommen dann aber
auch die interessanteren Beispiele.
Dazu habe ich einmal ein Grundgerüst für die Beispielprogramme
zusammengestellt. Diese drei Dateien / Klassen dienen lediglich dem
Erstellen des Fensters und dem Rendern:
- main.cpp: Aufbau des Fensters, Hauptschleife
- Graphics: Initialisierung von D3D, Rendern
- TestParticle: Laden eines Partikelmeshs und Rendern
Die Physik kommt dann in der Klasse FallingParticles zum Einsatz.
Sie stellt sozusagen die "Game"-Klasse dar.
C-/C++-Quelltext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FallingParticles
{
private:
Graphics* m_Graphics; // Zeiger auf Graphics (Singleton)
TestParticle m_Particle; // Testpartikel (lediglich zum Rendern)
Vector3 m_TestVent; // Position des 'Ventilators'
Vector3 m_VentForce; // Kraft des 'Ventilators'
Vector3 m_Gravity; // Gravitations-Kraft
public:
FallingParticles();
bool Load();
void Move(float time);
void Render();
void Present();
};
In jedem Frame werden Render, Move und Present aufgerufen.
Move wird dabei auch hier die vergangene Zeit seit dem letzten
Frame in Sekunden mitgegeben.
Für den Gravitations-Vektor habe ich in der Engine eine
Datei "SPUtil.h" erstellt, welche bisher lediglich die
Gravitations-Konstante GRAVITY enthält. Diese beträgt 9.81.
(Durchschnittliche Gravitationsbeschleunigung an der Erdoberfläche)
Diese Kraft fügen wir dem Partikel hinzu.
(Wenn man nicht möchte, dass die Schwerkraft von der Masse
beeinflusst wird, sollte man diese Kraft separat in die Partikel-
Klasse nehmen. Da die Konstante eigentlich schon in m/s/s vorhanden
ist und nicht mehr berechnet werden müsste, könnte man sie dann
direkt der Beschleunigung hinzufügen.Ich fand den Effekt aber
nicht schlecht. )
Man kann auch mal etwas mit der Gravitation herumspielen.
Beispielsweise, wenn man eine geringe Gravitation möchte oder
wenn man einfach nur man an die Decke gehen möchte.
Diese sollte durch die Kommentare recht selbsterklärend sein.
Je weiter der Ball vom Ventilator entfernt ist, desto mehr wird
die Kraft abgeschwächt. Hier habe ich dazu wirklich simple Berechnungen
verwendet, besonders beim Abprall des Balls. Es wurden einfach
Werte genommen, die das Ganze akzeptabel aussehen lassen.
In diesem Fall haben diese Werte also nichts mit der Physik-Engine
zu tun und haben auch keine besondere Bedeutung.
Download des bisherigen "Engine"-Codes und des Beispielprojekts: Klick
4. Schlusswort
Das wäre es dann auch schon mit dem ersten Teil.
Im nächsten Teil werden wir mehr über die Kräfteverwaltung
erfahren, sowie über Sprungfedern und vielleicht auch feste Verbindungen.
Also, nach ein paar Grundlagen zu den interessanteren Dingen!
Korrekturen und Meinungen bitte posten.
Ich denke, besonders Korrekturen sind noch nötig. *Kopf verloren*
Und bevor ich mich an weitere Teile wage:
Besteht überhaupt Interesse?
Ich bin mal gespannt,wie weit das geht. Weil Physik kann ja wirklich komplex werden. Wenn es hinterher so weit ist, mit Modellen, die sich polygongenau bewegen (z.B. Kisten stapeln, bis der Turm umkippt oder so) wird es richtig interessantw erden. Von daher fände ich einen Ausblick, was noch kommen wird schön.
Jop, das Thema ist sicherlich interessant, ich warte mal auf den nächsten Teil. In diesem war ja nichts wirklich Spannendes dabei, mal schauen was nach der Einführung kommt.
Positiv ist auf jeden Fall, dass ich mal keine Rechtschreibfehler gefunden hab, denn das nervt bei etwas "längeren" Texten dann doch.
Richtig schönes Tutorial, verständlich und gut formuliert
Oo Thx!
@Jonathan_Klein:
Kisten stapeln u.Ä. wird sicherlich vorkommen.
Zu viel über das Kommende kann ich noch nicht sagen,
vorkommende Teile werden aber sein:
- Sprungfedern und Verbindungen
- Kollisionen (und damit grössere Objekte)
- Evtl. Rag Doll
(Je nach Beliebtheit. )
So jetzt hatte ich auch mal Zeit zum lesen und gebe nun mal Senf und Ketchup dazu ab
Erstmal vorab: Ich bin sehr positiv überrascht (was ich übrigends sehr selten bin) über das Tutorial und es war auch sehr angenehm zu lesen. Es war sehr verständlich (bis auf ein paar Dinge zu denen ich gleich noch kommen werde) und beinhaltet gute Erklärungen zu einem Thema, wofür es sehr sehr wenig gute Tutorials gibt. Dein Tutorial gehört auf jedenfall in die obere Liga, wenn du es so fortführst.
Ein paar Dinge haben mich jedoch doch etwas gestört:
Mir fehlt ein Index, du gliederst zwar sehr schön, aber der Index fehlt halt zur Gliederung. Ich persönlich finde es immer sehr angenehm wenn ein Index drin ist, damit ich "in etwa" weiß wo etwas steht, wieviel ich ungefähr scrollen muss usw.
Was mich sehr gestört hat beim lesen (auf der Couch, wo ich im Firefox gezoomt habe) ist, dass du irgendwie nach jeder Zeile einen Absatzt machst und nicht durchgehend schreibst. Das empfinde ich auch auf meinem 22" Flachbildschirm sehr "unschön" und das zerstört irgendwie den "look'n feel"
Etwas sehr verwirrend war beim Lesen auch, dass du erst von "Bewegungsgeschwindigkeit" und dann von "Richtungsvektor" sprichst. Natürlich ist beides das Selbe, aber du machst keinen sauberen Übergang von dem einen Wort zum anderen Wort. Ich habe mich sogar selbst dabei erwischt als ich mich fragte: "Momente mal? Noch ein Vektor? Er hat doch nur Bewegungsgeschwindigkeit und Position?"
Als du mit dem Ventilator-Beispiel gekommen bist, und ich noch mal das AddForce gesehen habe, stellte ich mir folgende Frage: "Hat eine Kraft nicht auch eine Position und ggf. sogar eine Masse? Denn deine ganze Berechnung so von wegen Entfernung usw. hätte man glaube ich leichter in eine Klasse "Force" auslagern können, welche in dem Beispiel den Ventilator beschreibt.
Was bedeutet m/s/s?
Du benutzt in deinem Beispiel die Konstante 0,75 bei der Invertierung des Richtungsvektors. Woher kommt diese Konstante? Ist das genau so eine wie die Erdanziehung?