JavaScript-Tutorium:Physics: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(5 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 solche Simulationen verwendet um gewisse Spielmechaniken natürlicher wirken zu lassen.
In der Spieleprogrammierung werden Physics verwendet um Spielmechaniken natürlicher wirken zu lassen.
Dieses Tutorium erklärt simple nützliche Formeln und Berechnungen für die Entwicklung von Simulationen in JavaScript.
 
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 dem MVC-Paradigma stellt sich die Frage wo der Code für physikalische Simulationen hingehört.
 
Versteht man das Model so dass es neben den Daten ebenso die Logik enthält so schliesst dies den Physics-Code eindeutig ein.
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===


Obwohl im Model nicht mit Pixeln gearbeitet werden soll spricht nichts dagegen abstrakte numerische Einheiten zu verwenden.
 
Gegebenenfalls ist es durchaus sinnvoll ein Koordinatensystem einzuführen und Spielelemente mit Dimensionen zu versehen.
Gegebenenfalls ist es sinnvoll ein Koordinatensystem einzuführen und Spielelemente mit Dimensionen zu versehen.
Wichtig ist dabei nur dass diese Einheiten keinen direkten Bezug zu Pixel haben selbst wenn der Umrechungsfaktor eins beträgt.


<source lang="javascript">
<source lang="javascript">
var Player = function(x, y, width, height) {
var Player = function(x, y, width, height) {
   var self = this;
   this.x = x;
  self.x = x;
   this.y = y;
   self.y = y;
   this.width = width;
   self.width = width;
   this.height = height;
   self.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 aber als multidimensionales Array dargestellt,
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() {
   var self = this;
   this._spielfeld = [
  self._spielfeld = [
     ['', '', ''],
     ['', '', ''],
     ['', '', ''],
     ['', '', ''],
Zeile 58: Zeile 66:
};
};
</source>
</source>


===Welt und Elemente===
===Welt und Elemente===


Innerhalb einer physikalischen Simulation ist es sinnvoll das Modell in eine Welt und deren enthaltenen Elemente aufzuteilen.
 
Dabei kapseön Elemente ihre physikalischen Eigenschaften während die Welt regelmäßig ihre Berechnungen auf diese anwendet.
Innerhalb einer Physics-Simulation ist es sinnvoll das Modell in eine Welt und deren enthaltenen Elemente aufzuteilen.
Dies wird durchgeführt mithilfe von eigenständigen Timern (z.B. durch <code>setTimeout()</code>) ähnlich wie beim Rendering von Objekten.


<source lang="javascript">
<source lang="javascript">
var Element = function(x, y, width, height) {
var Element = function(x, y, width, height) {
   var self = this;
   this.x = x;
  self.x = x;
   this.y = y;
   self.y = y;
   this.width = width;
   self.width = width;
   this.height = height;
   self.height = height;
};
};
</source>
Dabei kapseln Elemente ihre physikalischen Eigenschaften, während die Welt regelmäßig ihre Berechnungen auf diese anwendet.


var World = function(width, height) {
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._currentTimeout = null;
   self._elements = elements;
   self._elements = [];
 
  self.addElement = function(element) {
    self._elements.push(element);
  };


   self._updateElements = function() {
   self._updateElements = function() {
Zeile 92: Zeile 100:
   self._updateElementsRepeatedly = function() {
   self._updateElementsRepeatedly = function() {
     self._updateElements();
     self._updateElements();
     self._currentTimeout = setTimeout(self._updateElementsRepeatedly, 15);
     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 109: Zeile 120:
<source lang="javascript">
<source lang="javascript">
self._updateElements = function() {
self._updateElements = function() {
   for (var i = 0; i < self._elements.length; i++) {
   for (var i = 0; i < self._objects.length; i++) {
     self._elements[i].x += 1;
     self._objects[i].x += 1;
     self._elements[i].y += 1;
     self._objects[i].y += 1;
   }
   }
};
};
</source>
</source>


Mithilfe von Geschwindigkeitsattributen können dann Objekte selbst entscheiden wie sich ihre Koordinaten verändern sollen.
 
'''Anmerkung:''' Es ist Konvention Geschwindigkeitsattribute mit einem "v" zu versehen, welches für "Velocity" steht.
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) {
   var self = this;
   this.x = x;
  self.x = x;
   this.y = y;
   self.y = y;
   this.vx = 0;
   self.vx = 0;
   this.vy = 0;
   self.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._elements.length; i++) {
   for (var i = 0; i < self._elements.length; i++) {
Zeile 140: Zeile 152:
</source>
</source>


Verknüpft man die Steuerung der Geschwindigkeitswerte nun mit dem Keyboard erreicht man eine einfache Spielersteuerung.
'''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.
Beschleunigung verhält sich zu Geschwindigkeit wie Geschwindigkeit zu Ort, also eine konstante Änderung der Geschwindigkeit.


<source lang="javascript">
<source lang="javascript">
var Element = function(x, y) {
var Element = function(x, y) {
   var self = this;
   this.x = x;
  self.x = x;
   this.y = y;
   self.y = y;
   this.vx = 0;
   self.vx = 0;
   this.vy = 0;
   self.vy = 0;
   this.ax = 0;
   self.ax = 0;
   this.ay = 0;
   self.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 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 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">
self._keepElementsInsideWorld = function() {
if (element.x < 0) {
  var element = null;
  element.x = 0;
  for (var i = 0; i < self._elements.length; i++) {
  element.vx = element.ax = 0;
    element = self._elements[i];
}
    if (element.x < 0) {
if (element.x + element.width > self._width) {
      element.x = 0;
  element.x = self._width - element.width;
      element.vx = element.ax = 0;
  element.vx = element.ax = 0;
    }
}
    if (element.x + element.width > self._width) {
if (element.y < 0) {
      element.x = self._width - element.width;
  element.y = 0;
      element.vx = element.ax = 0;
  element.vy = element.ay = 0;
    }
}
    if (element.y < 0) {
if (element.y + element.height > self._height) {
      element.y = 0;
  element.y = self._height - element.height;
      element.vy = element.ay = 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 einfache Weise um Kollisionen zwischen beliebigen Objekten erkennen zu können ist die Verwendung von "Bounding Boxes".
 
Dabei berechnet man für Objekte beliebiger Form die kleinst möglichen Rechtecke und verwendet diese zur Kollisionserkennung.
Eine Möglichkeit um Kollisionen zwischen Objekten zu erkennen ist die Verwendung von "Bounding Boxes".
Möchte man diese nicht extra berechnen interpretiert man einfach alle Elemente innerhalb einer Simulation als Rechtecke.
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 sich als sehr einfach.
Die Berechnung um festzustellen ob sich zwei nicht gedrehte Rechtecke überschneiden gestaltet ist sehr einfach.


<source lang="javascript">
<source lang="javascript">
Zeile 291: Zeile 309:
</source>
</source>


Ebenso ist die Kollisionserkennung für zwei beliebig große Kreisformen relativ einfach zu berechnen.
 
Kombiniert man jedoch Kreise und Rechtecke erhöht sich die Komplexität alleine aufgrund der Fallunterscheidungen.
Ebenso ist die Kollisionserkennung für zwei Kreisformen relativ einfach zu berechnen.
Deshalb ist es empfehlenswert Kreise ebenfalls als Rechtecke aufzufassen solange keine exakte Erkennung notwendig ist.
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[i];
       b = self._elements[j];
       // TODO: Your code goes here
       // TODO: Your code goes here
     }
     }
Zeile 313: Zeile 333:




===Reaktion auf Kollision===
===Kollisionstiefe===
 


Abhängig davon welche Elemente kollidieren und welche Spielregeln existieren kann es unterschiedliche Reaktionen geben.
Um zu wissen aus welcher Richtung die Elemente sich angenähert haben kann die Kollisionstiefe der Achsen berechnet werden.
Für eine realistisch wirkende physikalische Simulation können die relevanten Geschwindigkeitsanteile umgekehrt werden.
Dazu muss festgestellt werden welche die Kollisionsachse ist, also aus welcher Richtung die Elemente sich angenähert haben.


<source lang="javascript">
<source lang="javascript">
self._getCollisionAxis = function(a, b) {
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(a, b) { return a - b; };
   var sort = function(firstValue, secondValue) {
    return firstValue - secondValue;
  };
   xValues.sort(sort);
   xValues.sort(sort);
   yValues.sort(sort);
   yValues.sort(sort);
   var overlappingX = xValues[2] - xValues[1];
   return {
  var overlappingY = yValues[2] - yValues[1];
    x: xValues[2] - xValues[1],
   return overlappingX > overlappingY ? 'x' : 'y';
    y: yValues[2] - yValues[1]
   };
};
};
</source>
</source>


Ist die Kollisionsachse die y-Achse so werden die x-Geschwindigkeiten umgekehrt, bei der x-Achse die y-Geschwindigkeiten.


Unabhängig davon ob die Geschwindigkeit angepasst werden ist es sinnvoll für jede Kollision ein Ereignis zu erzeugen.
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 andere Spiel- und Anwendungskomponenten dieses Ereignis verarbeiten und andere Logiken ausführen.
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, 22: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.