JavaScript-Tutorium:Physics: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 196: Zeile 196:


<source lang="javascript">
<source lang="javascript">
self._gy = 0.1;
self._ay = 0.1;
self._updateElements = function() {
self._updateElements = function() {
   var element = null;
   var element = null;
Zeile 202: Zeile 202:
     element = self._elements[i];
     element = self._elements[i];
     element.vx += element.ax;
     element.vx += element.ax;
     element.vy += element.ay + self._gy;
     element.vy += element.ay + self._ay;
     element.y += element.vy;
     element.y += element.vy;
     element.y += element.vy;
     element.y += element.vy;

Version vom 1. Dezember 2014, 11:58 Uhr

Physics

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. Dieses Tutorium erklärt simple nützliche Formeln und Berechnungen für die Entwicklung von Simulationen in JavaScript.


Allgemeines

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. Die Gegebenheit dass ein Spiel physikalischen Gesetzen folgt kann also wie jede andere Regel des Spiels verstanden werden.


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. Wichtig ist dabei nur dass diese Einheiten keinen direkten Bezug zu Pixel haben selbst wenn der Umrechungsfaktor eins beträgt.

var Player = function(x, y, width, height) {
  var self = this;
  self.x = x;
  self.y = y;
  self.width = width;
  self.height = height;
};

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 aber als multidimensionales Array dargestellt,

var TicTacToe = function() {
  var self = this;
  self._spielfeld = [
    ['', '', ''],
    ['', '', ''],
    ['', '', '']
  ];
};

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. Dies wird durchgeführt mithilfe von eigenständigen Timern (z.B. durch setTimeout()) ähnlich wie beim Rendering von Objekten.

var Element = function(x, y, width, height) {
  var self = this;
  self.x = x;
  self.y = y;
  self.width = width;
  self.height = height;
};

var World = function(width, height) {

  var self = this;
  self._currentTimeout = null;
  self._elements = [];

  self.addElement = function(element) {
    self._elements.push(element);
  };

  self._updateElements = function() {
    for (var i = 0; i < self._elements.length; i++) {
      // TODO: Your code goes here
    }
  };

  self._updateElementsRepeatedly = function() {
    self._updateElements();
    self._currentTimeout = 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._elements.length; i++) {
    self._elements[i].x += 1;
    self._elements[i].y += 1;
  }
};

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.

var Element = function(x, y) {
  var self = this;
  self.x = x;
  self.y = y;
  self.vx = 0;
  self.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;
  }
};

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

var Element = function(x, y) {
  var self = this;
  self.x = x;
  self.y = y;
  self.vx = 0;
  self.vy = 0;
  self.ax = 0;
  self.ay = 0;
}

var element = new Element(0, 0);
element.vx = 1;
element.ay = .1;

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._ay = 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._ay;
    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.


self._keepElementsInsideWorld = function() {
  var element = null;
  for (var i = 0; i < self._elements.length; i++) {
    element = self._elements[i];
    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 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. Möchte man diese nicht extra berechnen interpretiert man einfach alle Elemente innerhalb einer Simulation als Rechtecke. Die Berechnung um festzustellen ob sich zwei nicht gedrehte Rechtecke überschneiden gestaltet sich als 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 beliebig große Kreisformen relativ einfach zu berechnen. Kombiniert man jedoch Kreise und Rechtecke erhöht sich die Komplexität alleine aufgrund der Fallunterscheidungen. Deshalb ist es 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[i];
      // TODO: Your code goes here
    }
  }
};

Auf diese Weise wird jede mögliche Zweierkombination von Elementen garantiert auch nur einmal durchlaufen.


Reaktion auf Kollision

Abhängig davon welche Elemente kollidieren und welche Spielregeln existieren kann es unterschiedliche Reaktionen geben. 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.

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

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. Auf diese Weise können andere Spiel- und Anwendungskomponenten dieses Ereignis verarbeiten und andere Logiken ausführen.

Tipp: Nutzen Sie den EventDispatcher um Ereignisse zu erzeugen wenn Kollisionen auftreten.