JavaScript-Tutorium:Physics: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
|||
(6 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
=Physics= | =Physics= | ||
Unter Physics versteht man in der Informatik die Simulation physikalischer Gesetze wie Geschwindigkeit und Erdanziehung. | Unter Physics versteht man in der Informatik die Simulation physikalischer Gesetze wie Geschwindigkeit und Erdanziehung. | ||
In der Spieleprogrammierung werden | In der Spieleprogrammierung werden Physics verwendet um Spielmechaniken natürlicher wirken zu lassen. | ||
Dieses Tutorium erklärt simple | |||
Dieses Tutorium erklärt simple Formeln und Berechnungen für die Entwicklung von Simulationen in JavaScript. | |||
==Allgemeines== | ==Allgemeines== | ||
===Zugehörigkeit=== | ===Zugehörigkeit=== | ||
Bei einer Modellierung nach | |||
Versteht man das Model so dass es neben den Daten | Bei einer Modellierung nach Model-View-Controller stellt sich die Frage wo der Code für physikalische Simulationen hingehört. | ||
Die Gegebenheit dass ein Spiel physikalischen Gesetzen folgt kann also wie jede andere Regel des Spiels verstanden werden. | Versteht man das Model so, dass es neben den Daten auch die Logik enthält, so schliesst dies den Physics-Code eindeutig ein. | ||
Die Gegebenheit, dass ein Spiel physikalischen Gesetzen folgt, kann also wie jede andere Regel des Spiels verstanden werden. | |||
===Abstrakte Einheiten=== | ===Abstrakte Einheiten=== | ||
Gegebenenfalls ist es | Gegebenenfalls ist es sinnvoll ein Koordinatensystem einzuführen und Spielelemente mit Dimensionen zu versehen. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var Player = function(x, y, width, height) { | var Player = function(x, y, width, height) { | ||
this.x = x; | |||
this.y = y; | |||
this.width = width; | |||
this.height = height; | |||
}; | }; | ||
</source> | |||
Wichtig ist dabei, dass diese Einheiten keinen direkten Bezug zu Pixel haben. | |||
Sollen das Model und die View mit unterschiedlichen Zahlensystem arbeiten, kann ein Skalierungsfaktor verwendet werden. | |||
<source lang="javascript"> | |||
var renderPlayer = function(player, scaleFactor) { | var renderPlayer = function(player, scaleFactor) { | ||
var div = document.createElement('div'); | var div = document.createElement('div'); | ||
Zeile 46: | Zeile 55: | ||
Selbst Spiele wie "Tic Tac Toe" arbeiten mit Koordinatensystemen, oftmals | Selbst Spiele wie "Tic Tac Toe" arbeiten mit Koordinatensystemen, oftmals vereinfacht als multidimensionales Array dargestellt. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var TicTacToe = function() { | var TicTacToe = function() { | ||
this._spielfeld = [ | |||
['', '', ''], | ['', '', ''], | ||
['', '', ''], | ['', '', ''], | ||
Zeile 58: | Zeile 66: | ||
}; | }; | ||
</source> | </source> | ||
===Welt und Elemente=== | ===Welt und Elemente=== | ||
Innerhalb einer | |||
Innerhalb einer Physics-Simulation ist es sinnvoll das Modell in eine Welt und deren enthaltenen Elemente aufzuteilen. | |||
<source lang="javascript"> | <source lang="javascript"> | ||
var Element = function(x, y, width, height) { | var Element = function(x, y, width, height) { | ||
this.x = x; | |||
this.y = y; | |||
this.width = width; | |||
this.height = height; | |||
}; | }; | ||
</source> | |||
Dabei kapseln Elemente ihre physikalischen Eigenschaften, während die Welt regelmäßig ihre Berechnungen auf diese anwendet. | |||
Die Berechnungen werden mithilfe von eigenständigen Timern durchgeführt, ähnlich wie beim Rendering. | |||
<source lang="javascript"> | |||
var World = function(elements) { | |||
var self = this; | var self = this; | ||
self._elements = elements; | |||
self._elements = | |||
self._updateElements = function() { | self._updateElements = function() { | ||
Zeile 92: | Zeile 100: | ||
self._updateElementsRepeatedly = function() { | self._updateElementsRepeatedly = function() { | ||
self._updateElements(); | self._updateElements(); | ||
setTimeout(self._updateElementsRepeatedly, 15); | |||
}; | }; | ||
self._updateElementsRepeatedly(); | self._updateElementsRepeatedly(); | ||
}; | }; | ||
</source> | </source> | ||
==Ortsänderungen== | ==Ortsänderungen== | ||
Nahezu jedes Spiel welches eine physikalischen Simulation beinhaltet arbeitet mit der Veränderung vom Ort einzelner Elemente. | |||
Nahezu jedes Spiel, welches eine physikalischen Simulation beinhaltet, arbeitet mit der Veränderung vom Ort einzelner Elemente. | |||
===Geschwindigkeit=== | ===Geschwindigkeit=== | ||
Eine simple Simulation von Geschwindigkeit kann bereits erreicht werden indem man die Koordinaten von Objekten verändert. | Eine simple Simulation von Geschwindigkeit kann bereits erreicht werden indem man die Koordinaten von Objekten verändert. | ||
Zeile 116: | Zeile 127: | ||
</source> | </source> | ||
Mithilfe von Geschwindigkeitsattributen können | |||
Mithilfe von Geschwindigkeitsattributen können Objekte selbst entscheiden wie sich ihre Koordinaten verändern. | |||
<source lang="javascript"> | <source lang="javascript"> | ||
var Element = function(x, y) { | var Element = function(x, y) { | ||
this.x = x; | |||
this.y = y; | |||
this.vx = 0; | |||
this.vy = 0; | |||
} | } | ||
Zeile 131: | Zeile 141: | ||
element.vx = 1; | element.vx = 1; | ||
element.vy = 1; | element.vy = 1; | ||
</source> | |||
<source lang="javascript"> | |||
self._updateElements = function() { | self._updateElements = function() { | ||
for (var i = 0; i < self. | for (var i = 0; i < self._elements.length; i++) { | ||
self. | self._elements[i].x += self._elements[i].vx; | ||
self. | self._elements[i].y += self._elements[i].vy; | ||
} | } | ||
}; | }; | ||
</source> | </source> | ||
Verknüpft man die Steuerung der Geschwindigkeitswerte | '''Anmerkung:''' Es ist Konvention Geschwindigkeitsattribute mit einem "v" zu versehen, welches für "Velocity" steht. | ||
Verknüpft man die Steuerung der Geschwindigkeitswerte mit dem Keyboard erreicht man eine einfache Spielersteuerung. | |||
<source lang="javascript"> | <source lang="javascript"> | ||
Zeile 160: | Zeile 175: | ||
===Beschleunigung=== | ===Beschleunigung=== | ||
Neben der Geschwindigkeit ist die Beschleunigung ein weiterer wichtiger Faktor für physikalische Simulationen in Spielen. | Neben der Geschwindigkeit ist die Beschleunigung ein weiterer wichtiger Faktor für physikalische Simulationen in Spielen. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var Element = function(x, y) { | var Element = function(x, y) { | ||
this.x = x; | |||
this.y = y; | |||
this.vx = 0; | |||
this.vy = 0; | |||
this.ax = 0; | |||
this.ay = 0; | |||
} | } | ||
Zeile 178: | Zeile 192: | ||
element.vx = 1; | element.vx = 1; | ||
element.ay = .1; | element.ay = .1; | ||
</source> | |||
Beschleunigung verhält sich zu Geschwindigkeit wie Geschwindigkeit zu Ort, also eine konstante Änderung der Geschwindigkeit. | |||
<source lang="javascript"> | |||
self._updateElements = function() { | self._updateElements = function() { | ||
var element = null; | var element = null; | ||
Zeile 190: | Zeile 209: | ||
}; | }; | ||
</source> | </source> | ||
===Erdanziehung=== | ===Erdanziehung=== | ||
Zeile 199: | Zeile 219: | ||
self._updateElements = function() { | self._updateElements = function() { | ||
var element = null; | var element = null; | ||
for (var i = 0; i < self. | for (var i = 0; i < self._elements.length; i++) { | ||
element = self. | element = self._elements[i]; | ||
element.vx += element.ax; | element.vx += element.ax; | ||
element.vy += element.ay + self._gy; | element.vy += element.ay + self._gy; | ||
Zeile 211: | Zeile 231: | ||
===Unbewegliche Objekte=== | ===Unbewegliche Objekte=== | ||
Häufig gibt es in Simulationen auch unbewegliche Objekte welche nicht von Ortsänderungen betroffen sein sollen. | Häufig gibt es in Simulationen auch unbewegliche Objekte welche nicht von Ortsänderungen betroffen sein sollen. | ||
Zeile 218: | Zeile 239: | ||
self._updateElements = function() { | self._updateElements = function() { | ||
var element = null; | var element = null; | ||
for (var i = 0; i < self. | for (var i = 0; i < self._elements.length; i++) { | ||
element = self. | element = self._elements[i]; | ||
if (!element.isImmovable) { | if (!element.isImmovable) { | ||
element.vx += element.ax; | element.vx += element.ax; | ||
Zeile 232: | Zeile 253: | ||
==Kollisionen== | ==Kollisionen== | ||
In vielen Spielen ist es das Ziel mit dem Spieler bestimmte Elemente zu treffen und anderen Elementen wiederum auszuweichen. | In vielen Spielen ist es das Ziel mit dem Spieler bestimmte Elemente zu treffen und anderen Elementen wiederum auszuweichen. | ||
Zeile 238: | Zeile 260: | ||
===Begrenzungen=== | ===Begrenzungen=== | ||
Ein spezielles Element mit dem der Spieler in fast jedem Spiel kollideren kann ist die Welt selbst bzw. deren Begrenzung. | Ein spezielles Element mit dem der Spieler in fast jedem Spiel kollideren kann ist die Welt selbst bzw. deren Begrenzung. | ||
Zeile 245: | Zeile 268: | ||
<source lang="javascript"> | <source lang="javascript"> | ||
if (element.x < 0) { | |||
element.x = 0; | |||
element.vx = element.ax = 0; | |||
} | |||
if (element.x + element.width > self._width) { | |||
element.x = self._width - element.width; | |||
element.vx = element.ax = 0; | |||
} | |||
if (element.y < 0) { | |||
element.y = 0; | |||
element.vy = element.ay = 0; | |||
} | |||
if (element.y + element.height > self._height) { | |||
element.y = self._height - element.height; | |||
element.vy = element.ay = 0; | |||
} | |||
</source> | </source> | ||
'''Tipp:''' Man sollte ebenso darauf achten die x- und y-Position des mit der Welt kollidierenden Elements anzupassen. | '''Tipp:''' Man sollte ebenso darauf achten die x- und y-Position des mit der Welt kollidierenden Elements anzupassen. | ||
Zeile 274: | Zeile 292: | ||
===Kollisionserkennung=== | ===Kollisionserkennung=== | ||
Eine | |||
Dabei berechnet man für | Eine Möglichkeit um Kollisionen zwischen Objekten zu erkennen ist die Verwendung von "Bounding Boxes". | ||
Dabei berechnet man für jedes Objektes das kleinst mögliche Rechteck und verwendet dies zur Kollisionserkennung. | |||
Die Berechnung um festzustellen ob sich zwei nicht gedrehte Rechtecke überschneiden gestaltet | Die Berechnung um festzustellen ob sich zwei nicht gedrehte Rechtecke überschneiden gestaltet ist sehr einfach. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
self. | self._areElementsColliding = function(a, b) { | ||
if ((a.x > b.x + b.width) || (b.x > a.x + a.width)) { | if ((a.x > b.x + b.width) || (b.x > a.x + a.width)) { | ||
return false; | return false; | ||
Zeile 291: | Zeile 309: | ||
</source> | </source> | ||
Ebenso ist die Kollisionserkennung für zwei | |||
Kombiniert man jedoch Kreise und Rechtecke erhöht sich die Komplexität | Ebenso ist die Kollisionserkennung für zwei Kreisformen relativ einfach zu berechnen. | ||
Kombiniert man jedoch Kreise und Rechtecke erhöht sich die Komplexität aufgrund der Fallunterscheidungen. | |||
Es ist empfehlenswert Kreise ebenfalls als Rechtecke aufzufassen solange keine exakte Erkennung notwendig ist. | |||
Für die Kollisionserkennung aller Elemente innerhalb einer Welt kann man eine verschachtelte <code>for</code>-Schleife nutzen. | Für die Kollisionserkennung aller Elemente innerhalb einer Welt kann man eine verschachtelte <code>for</code>-Schleife nutzen. | ||
Zeile 303: | Zeile 323: | ||
a = self._elements[i]; | a = self._elements[i]; | ||
for (var j = i + 1; j < self._elements.length; i++) { | for (var j = i + 1; j < self._elements.length; i++) { | ||
b = self._elements[ | b = self._elements[j]; | ||
// TODO: Your code goes here | // TODO: Your code goes here | ||
} | } | ||
Zeile 313: | Zeile 333: | ||
=== | ===Kollisionstiefe=== | ||
Um zu wissen aus welcher Richtung die Elemente sich angenähert haben kann die Kollisionstiefe der Achsen berechnet werden. | |||
<source lang="javascript"> | <source lang="javascript"> | ||
self. | self._getCollisionDepth = function(a, b) { | ||
var xValues = [a.x, a.x + a.width, b.x, b.x + b.width]; | var xValues = [a.x, a.x + a.width, b.x, b.x + b.width]; | ||
var yValues = [a.y, a.y + a.height, b.y, b.y + b.height]; | var yValues = [a.y, a.y + a.height, b.y, b.y + b.height]; | ||
var sort = function( | var sort = function(firstValue, secondValue) { | ||
return firstValue - secondValue; | |||
}; | |||
xValues.sort(sort); | xValues.sort(sort); | ||
yValues.sort(sort); | yValues.sort(sort); | ||
return { | |||
x: xValues[2] - xValues[1], | |||
y: yValues[2] - yValues[1] | |||
}; | |||
}; | }; | ||
</source> | </source> | ||
Beträgt die x-Tiefe einen niedrigeren Wert als die y-Tiefe nähern sich die Elemente horizontal an, im anderen Fall vertikal. | |||
Auf diese Weise können | Bei einer horizontalen Annäherung zweier Elemente bezeichnet man die y-Achse als die Kollisionsachse, andernfalls die x-Achse. | ||
<source lang="javascript"> | |||
var collisionDepth = self._getCollisionDepth(a, b); | |||
var collisionAxis = collisionDepth.x < collisionDepth.y ? 'y' : 'x'; | |||
</source> | |||
===Reaktion auf Kollision=== | |||
Abhängig davon welche Elemente wie kollidieren und welche Spielregeln existieren kann es unterschiedliche Reaktionen geben. | |||
====Umkehrung von Geschwindigkeit==== | |||
Bei beweglichen Elementen in einer Kollision ist es sinnvoll die Geschwindigkeitsanteile entlang der Kollisionsachse umzukehren. | |||
<source lang="javascript"> | |||
var collisionDepth = self._getCollisionDepth(a, b); | |||
var collisionAxis = collisionDepth.x > collisionDepth.y ? 'y' : 'x'; | |||
if (collisionAxis == 'y') { | |||
a.vx *= -1; | |||
b.vx *= -1; | |||
} | |||
else { | |||
a.vy *= -1; | |||
b.vy *= -1; | |||
} | |||
</source> | |||
'''Beispiel:''' | |||
Ein Ball fliegt mit einer Geschwindigkeit vx von 2 und vy von 1, also nach rechts unten, gegen ein unbewegliches Hindernis unter ihm. | |||
Für die Kollisionstiefe ergibt sich im x-Anteil einen größeren Wert als für y, dementsprechend ist x die Kollisionsachse. | |||
Der y-Anteil der Geschwindigkeit vom Ball wird umgekehrt auf -1, sodass der Ball sich weiter nach rechts oben bewegt. | |||
Die Geschwindigkeit des Hindernisses braucht nicht umgekehrt werden, da es sich um ein unbewegliches Element handelt. | |||
====Auflösung von Kollision==== | |||
Die Umkehrung der Geschwindigkeitsanteile führt in der Regel dazu, dass sich die Kollision wieder von selbst auflöst. | |||
Sollen die Elemente jedoch angehalten werden, muss die entstandene Überschneidung rückgängig gemacht werden. | |||
Dazu werden die Elemente je nach ihrer Geschwindigkeit und Beweglichkeit um Anteile der Kollisionstiefe auseinander gezogen. | |||
<source lang="javascript"> | |||
var collisionDepth = self._getCollisionDepth(a, b); | |||
var collisionAxis = collisionDepth.x > collisionDepth.y ? 'y' : 'x'; | |||
if (collisionAxis == 'y') { | |||
if (a.vx > 0) { | |||
a.x -= (collisionDepth.x / 2); | |||
b.x += (collisionDepth.x / 2); | |||
} | |||
else { | |||
a.x += (collisionDepth.x / 2); | |||
b.x -= (collisionDepth.x / 2); | |||
} | |||
} | |||
else { | |||
if (a.vy > 0) { | |||
a.y -= (collisionDepth.y / 2); | |||
b.y += (collisionDepth.y / 2); | |||
} | |||
else { | |||
a.y += (collisionDepth.y / 2); | |||
b.y -= (collisionDepth.y / 2); | |||
} | |||
} | |||
</source> | |||
Gibt es nur ein bewegliches Element wird nur dieses um die Kollisionstiefe entgegen der Geschwindigkeitsrichtung zurückbewegt. | |||
<source lang="javascript"> | |||
var collisionDepth = self._getCollisionDepth(a, b); | |||
var collisionAxis = collisionDepth.x > collisionDepth.y ? 'y' : 'x'; | |||
if (collisionAxis == 'y') { | |||
if (a.isImmovable) { | |||
b.x += b.vx > 0 ? -collisionDepth.x : collisionDepth.x; | |||
} | |||
else if (b.isImmovable) { | |||
a.x += a.vx > 0 ? -collisionDepth.x : collisionDepth.x; | |||
} | |||
} | |||
else { | |||
if (a.isImmovable) { | |||
b.y += b.vy > 0 ? -collisionDepth.y : collisionDepth.y; | |||
} | |||
else if (b.isImmovable) { | |||
a.y += a.vy > 0 ? -collisionDepth.y : collisionDepth.y; | |||
} | |||
} | |||
</source> | |||
====Ereignisse==== | |||
Unabhgängig davon wie und ob auf Kollisionen reagiert wird ist es sinnvoll bei jeder Kollision ein Ereignis zu erzeugen. | |||
Auf diese Weise können anderen Programmkomponenten diese Information zu verarbeiten und z.B. visuell darzustellen. | |||
'''Tipp:''' Nutzen Sie den <code>EventDispatcher</code> um Ereignisse zu erzeugen wenn Kollisionen auftreten. | '''Tipp:''' Nutzen Sie den <code>EventDispatcher</code> um Ereignisse zu erzeugen wenn Kollisionen auftreten. |
Aktuelle Version vom 22. Oktober 2015, 21:48 Uhr
Physics
Unter Physics versteht man in der Informatik die Simulation physikalischer Gesetze wie Geschwindigkeit und Erdanziehung. In der Spieleprogrammierung werden Physics verwendet um Spielmechaniken natürlicher wirken zu lassen.
Dieses Tutorium erklärt simple Formeln und Berechnungen für die Entwicklung von Simulationen in JavaScript.
Allgemeines
Zugehörigkeit
Bei einer Modellierung nach Model-View-Controller stellt sich die Frage wo der Code für physikalische Simulationen hingehört. Versteht man das Model so, dass es neben den Daten auch die Logik enthält, so schliesst dies den Physics-Code eindeutig ein. Die Gegebenheit, dass ein Spiel physikalischen Gesetzen folgt, kann also wie jede andere Regel des Spiels verstanden werden.
Abstrakte Einheiten
Gegebenenfalls ist es sinnvoll ein Koordinatensystem einzuführen und Spielelemente mit Dimensionen zu versehen.
var Player = function(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
Wichtig ist dabei, dass diese Einheiten keinen direkten Bezug zu Pixel haben.
Sollen das Model und die View mit unterschiedlichen Zahlensystem arbeiten, kann ein Skalierungsfaktor verwendet werden.
var renderPlayer = function(player, scaleFactor) {
var div = document.createElement('div');
div.style.position = 'absolute';
div.style.backgroundColor = '#F00';
div.style.width = player.width * scaleFactor + 'px';
div.style.height = player.height * scaleFactor + 'px';
div.style.left = player.x * scaleFactor + 'px';
div.style.top = player.y * scaleFactor+ 'px';
document.querySelector('body').appendChild(div);
};
var player1 = new Player(10, 10, 10, 10);
renderPlayer(player1, 5);
Selbst Spiele wie "Tic Tac Toe" arbeiten mit Koordinatensystemen, oftmals vereinfacht als multidimensionales Array dargestellt.
var TicTacToe = function() {
this._spielfeld = [
['', '', ''],
['', '', ''],
['', '', '']
];
};
Welt und Elemente
Innerhalb einer Physics-Simulation ist es sinnvoll das Modell in eine Welt und deren enthaltenen Elemente aufzuteilen.
var Element = function(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
Dabei kapseln Elemente ihre physikalischen Eigenschaften, während die Welt regelmäßig ihre Berechnungen auf diese anwendet.
Die Berechnungen werden mithilfe von eigenständigen Timern durchgeführt, ähnlich wie beim Rendering.
var World = function(elements) {
var self = this;
self._elements = elements;
self._updateElements = function() {
for (var i = 0; i < self._elements.length; i++) {
// TODO: Your code goes here
}
};
self._updateElementsRepeatedly = function() {
self._updateElements();
setTimeout(self._updateElementsRepeatedly, 15);
};
self._updateElementsRepeatedly();
};
Ortsänderungen
Nahezu jedes Spiel, welches eine physikalischen Simulation beinhaltet, arbeitet mit der Veränderung vom Ort einzelner Elemente.
Geschwindigkeit
Eine simple Simulation von Geschwindigkeit kann bereits erreicht werden indem man die Koordinaten von Objekten verändert.
self._updateElements = function() {
for (var i = 0; i < self._objects.length; i++) {
self._objects[i].x += 1;
self._objects[i].y += 1;
}
};
Mithilfe von Geschwindigkeitsattributen können Objekte selbst entscheiden wie sich ihre Koordinaten verändern.
var Element = function(x, y) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
}
var element = new Element(0, 0);
element.vx = 1;
element.vy = 1;
self._updateElements = function() {
for (var i = 0; i < self._elements.length; i++) {
self._elements[i].x += self._elements[i].vx;
self._elements[i].y += self._elements[i].vy;
}
};
Anmerkung: Es ist Konvention Geschwindigkeitsattribute mit einem "v" zu versehen, welches für "Velocity" steht.
Verknüpft man die Steuerung der Geschwindigkeitswerte mit dem Keyboard erreicht man eine einfache Spielersteuerung.
var Controller = function(element) {
document.addEventLister('keydown', function(event) {
if (event.keyCode == 37) {
element.vx = -0.5;
}
if (event.keyCode == 39) {
element.vx = 0.5;
}
});
document.addEventLister('keyup', function() {
element.vx = 0;
});
};
Beschleunigung
Neben der Geschwindigkeit ist die Beschleunigung ein weiterer wichtiger Faktor für physikalische Simulationen in Spielen.
var Element = function(x, y) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.ax = 0;
this.ay = 0;
}
var element = new Element(0, 0);
element.vx = 1;
element.ay = .1;
Beschleunigung verhält sich zu Geschwindigkeit wie Geschwindigkeit zu Ort, also eine konstante Änderung der Geschwindigkeit.
self._updateElements = function() {
var element = null;
for (var i = 0; i < self._elements.length; i++) {
var element = self._elements[i];
element.vx += element.ax;
element.vy += element.ay;
element.x += element.vx;
element.y += element.vy;
}
};
Erdanziehung
Ein häufiger Einsatzzweck der Beschleunigung ist die vereinfachte Simulation einer global angewendeten Erdanziehungskraft.
self._gy = 0.1;
self._updateElements = function() {
var element = null;
for (var i = 0; i < self._elements.length; i++) {
element = self._elements[i];
element.vx += element.ax;
element.vy += element.ay + self._gy;
element.y += element.vy;
element.y += element.vy;
}
};
Unbewegliche Objekte
Häufig gibt es in Simulationen auch unbewegliche Objekte welche nicht von Ortsänderungen betroffen sein sollen. Diese können einfach mit einer boolschen Eigenschaft versehen werden welche dann in der Welt abgefragt wird.
self._updateElements = function() {
var element = null;
for (var i = 0; i < self._elements.length; i++) {
element = self._elements[i];
if (!element.isImmovable) {
element.vx += element.ax;
element.vy += element.ay + self._gy;
element.y += element.vy;
element.y += element.vy;
}
}
};
Kollisionen
In vielen Spielen ist es das Ziel mit dem Spieler bestimmte Elemente zu treffen und anderen Elementen wiederum auszuweichen. Diese Gegebenheit lässt sich in der Regel mittels vereinfachter Kollisionserkennung und Kollisionauflösung umsetzen.
Begrenzungen
Ein spezielles Element mit dem der Spieler in fast jedem Spiel kollideren kann ist die Welt selbst bzw. deren Begrenzung. Da es sich normalerwise um eine rechteckige Begrenzung handelt kann leicht überprüft werden ob eine Kollision stattfindet. Darf der Spieler die Welt nicht verlassen so können seine physikalischen Attribute einfach entsprechend angepasst werden.
if (element.x < 0) {
element.x = 0;
element.vx = element.ax = 0;
}
if (element.x + element.width > self._width) {
element.x = self._width - element.width;
element.vx = element.ax = 0;
}
if (element.y < 0) {
element.y = 0;
element.vy = element.ay = 0;
}
if (element.y + element.height > self._height) {
element.y = self._height - element.height;
element.vy = element.ay = 0;
}
Tipp: Man sollte ebenso darauf achten die x- und y-Position des mit der Welt kollidierenden Elements anzupassen.
Kollisionserkennung
Eine Möglichkeit um Kollisionen zwischen Objekten zu erkennen ist die Verwendung von "Bounding Boxes". Dabei berechnet man für jedes Objektes das kleinst mögliche Rechteck und verwendet dies zur Kollisionserkennung. Die Berechnung um festzustellen ob sich zwei nicht gedrehte Rechtecke überschneiden gestaltet ist sehr einfach.
self._areElementsColliding = function(a, b) {
if ((a.x > b.x + b.width) || (b.x > a.x + a.width)) {
return false;
}
else if ((a.y > b.y + b.height) || (b.y > a.y + a.height)) {
return false;
}
return true;
};
Ebenso ist die Kollisionserkennung für zwei Kreisformen relativ einfach zu berechnen.
Kombiniert man jedoch Kreise und Rechtecke erhöht sich die Komplexität aufgrund der Fallunterscheidungen.
Es ist empfehlenswert Kreise ebenfalls als Rechtecke aufzufassen solange keine exakte Erkennung notwendig ist.
Für die Kollisionserkennung aller Elemente innerhalb einer Welt kann man eine verschachtelte for
-Schleife nutzen.
self._detectCollisions = function() {
var a = null, b = null;
for (var i = 0; i < self._elements.length - 1; i++) {
a = self._elements[i];
for (var j = i + 1; j < self._elements.length; i++) {
b = self._elements[j];
// TODO: Your code goes here
}
}
};
Auf diese Weise wird jede mögliche Zweierkombination von Elementen garantiert auch nur einmal durchlaufen.
Kollisionstiefe
Um zu wissen aus welcher Richtung die Elemente sich angenähert haben kann die Kollisionstiefe der Achsen berechnet werden.
self._getCollisionDepth = function(a, b) {
var xValues = [a.x, a.x + a.width, b.x, b.x + b.width];
var yValues = [a.y, a.y + a.height, b.y, b.y + b.height];
var sort = function(firstValue, secondValue) {
return firstValue - secondValue;
};
xValues.sort(sort);
yValues.sort(sort);
return {
x: xValues[2] - xValues[1],
y: yValues[2] - yValues[1]
};
};
Beträgt die x-Tiefe einen niedrigeren Wert als die y-Tiefe nähern sich die Elemente horizontal an, im anderen Fall vertikal.
Bei einer horizontalen Annäherung zweier Elemente bezeichnet man die y-Achse als die Kollisionsachse, andernfalls die x-Achse.
var collisionDepth = self._getCollisionDepth(a, b);
var collisionAxis = collisionDepth.x < collisionDepth.y ? 'y' : 'x';
Reaktion auf Kollision
Abhängig davon welche Elemente wie kollidieren und welche Spielregeln existieren kann es unterschiedliche Reaktionen geben.
Umkehrung von Geschwindigkeit
Bei beweglichen Elementen in einer Kollision ist es sinnvoll die Geschwindigkeitsanteile entlang der Kollisionsachse umzukehren.
var collisionDepth = self._getCollisionDepth(a, b);
var collisionAxis = collisionDepth.x > collisionDepth.y ? 'y' : 'x';
if (collisionAxis == 'y') {
a.vx *= -1;
b.vx *= -1;
}
else {
a.vy *= -1;
b.vy *= -1;
}
Beispiel:
Ein Ball fliegt mit einer Geschwindigkeit vx von 2 und vy von 1, also nach rechts unten, gegen ein unbewegliches Hindernis unter ihm. Für die Kollisionstiefe ergibt sich im x-Anteil einen größeren Wert als für y, dementsprechend ist x die Kollisionsachse.
Der y-Anteil der Geschwindigkeit vom Ball wird umgekehrt auf -1, sodass der Ball sich weiter nach rechts oben bewegt. Die Geschwindigkeit des Hindernisses braucht nicht umgekehrt werden, da es sich um ein unbewegliches Element handelt.
Auflösung von Kollision
Die Umkehrung der Geschwindigkeitsanteile führt in der Regel dazu, dass sich die Kollision wieder von selbst auflöst. Sollen die Elemente jedoch angehalten werden, muss die entstandene Überschneidung rückgängig gemacht werden. Dazu werden die Elemente je nach ihrer Geschwindigkeit und Beweglichkeit um Anteile der Kollisionstiefe auseinander gezogen.
var collisionDepth = self._getCollisionDepth(a, b);
var collisionAxis = collisionDepth.x > collisionDepth.y ? 'y' : 'x';
if (collisionAxis == 'y') {
if (a.vx > 0) {
a.x -= (collisionDepth.x / 2);
b.x += (collisionDepth.x / 2);
}
else {
a.x += (collisionDepth.x / 2);
b.x -= (collisionDepth.x / 2);
}
}
else {
if (a.vy > 0) {
a.y -= (collisionDepth.y / 2);
b.y += (collisionDepth.y / 2);
}
else {
a.y += (collisionDepth.y / 2);
b.y -= (collisionDepth.y / 2);
}
}
Gibt es nur ein bewegliches Element wird nur dieses um die Kollisionstiefe entgegen der Geschwindigkeitsrichtung zurückbewegt.
var collisionDepth = self._getCollisionDepth(a, b);
var collisionAxis = collisionDepth.x > collisionDepth.y ? 'y' : 'x';
if (collisionAxis == 'y') {
if (a.isImmovable) {
b.x += b.vx > 0 ? -collisionDepth.x : collisionDepth.x;
}
else if (b.isImmovable) {
a.x += a.vx > 0 ? -collisionDepth.x : collisionDepth.x;
}
}
else {
if (a.isImmovable) {
b.y += b.vy > 0 ? -collisionDepth.y : collisionDepth.y;
}
else if (b.isImmovable) {
a.y += a.vy > 0 ? -collisionDepth.y : collisionDepth.y;
}
}
Ereignisse
Unabhgängig davon wie und ob auf Kollisionen reagiert wird ist es sinnvoll bei jeder Kollision ein Ereignis zu erzeugen. Auf diese Weise können anderen Programmkomponenten diese Information zu verarbeiten und z.B. visuell darzustellen.
Tipp: Nutzen Sie den EventDispatcher
um Ereignisse zu erzeugen wenn Kollisionen auftreten.