Einfache Spielphysik JavaScript

Registriert
Sep. 2018
Beiträge
172
Moin. Ich brauche mal eure Hilfe beim Programmieren mit JavaScript. Ich muss demnächst einen Vortrag über "einfache Spielphysik" halten, also sowas wie Schwerkraft, Schwingungen und Vibrationen. Vibrationen waren recht einfach zu erstellen, jedoch tue ich mich mit der Schwerkraft ein bisschen schwer. Ich hoffe es gibt ein paar die mal drüber gucken können. Anbei ein Dopbbox Link zu meinem Ansatz: https://www.dropbox.com/s/86ea31c34qakknc/Spielphysik.rar?dl=0
 
  • Gefällt mir
Reaktionen: QXARE, FranzvonAssisi und DrMedDenRasen93
@Bagbag Ich will auch gar nicht, dass mir das jemand fertig programmiert. Ich will einfach nur herausfinden ob meine Lösung gut so ist oder ob ich es anders machen soll. Konkrete Frage: Kann ich meinen Lösungsansatz so verwenden oder muss ich mit mathematischen Formeln arbeiten um die Schwerkraft zu programmieren?
 
Ob sie gut ist oder nicht, hängt ganz von deinen Anforderungen ab. Du siehst ja selbst, dass die Figur sich nach unten bewegt und dabei immer schneller wird. Wenn es dir so reicht, ist es gut - wenn nicht, dann "sollst" du es anders machen.

Im Prinzip hast du doch jetzt schon eine Formel für die Schwerkraft. Alle 1000/60 ms (grob) erhöht sich die Geschwindigkeit auf der Y-Achse um 1.

Ein weiterer Tipp: Kommentare wie // neue Figur (= Instanz) erzeugen kannst du dir sparen.
Was geschieht sollte am Code erssichtlich sein - ist es das nicht, ist es möglicherweise kein guter Code. Wieso etwas geschieht, das kann man kommentieren - sollte in den meisten Fällen aber auch nicht nötig sein.
 
Zuletzt bearbeitet:
Code:
this.x = this.x + this.vx;
Kannst Du im Prinzip so machen. Physikalisch korrekter wäre, wenn Du eine Zeitdifferenz definierst, den Strecke = Geschwindigkeit * Zeit:
Code:
this.x = this.x + this.vx * dt;
Beschleunigung sehe ich den dem Code keine. Die Formel ist aber auch einfach: Geschwindigkeit = Beschleunigung * Zeit. Wenn Du für Y eine Beschleunigung ay definierst, dann sieht das etwa so aus:
Code:
this.vy = this.vy + ay*dt;
this.y = this.y + this.vy * dt;

Edit:
Ah OK, Lebendig.vy = Lebendig.vy +1; sollte die Beschleunigung sein. Geht prinzipiell auch.
 
  • Gefällt mir
Reaktionen: DrMedDenRasen93
michi.o schrieb:
Ah OK, Lebendig.vy = Lebendig.vy +1; sollte die Beschleunigung sein. Geht prinzipiell auch.

Prinzipiell, aber feste Werte würde ich niemals nutzen, speziell nicht in JS mit timeouts oder intervals! Nur im absoluten Idealfall sind die genau, i.d.R schwankt es aber um einige ms, aber auch Schwankungen im 3-Stelligen Bereich gibt es nicht selten, speziell in Browser. Immer time delta nutzen!

Idealerweise nimmt man dann noch requestAnimationFrame() statt timeouts. Falls man die "fps" limitieren will, damit es z.B. auf 144hz Monitoren trotzdem mit 60 updated, kann man z.B. das hier machen:
Javascript:
const interval = 1000/60; // fps festlegen
// Alle Geschwindigkeiten, Beschleunigungen etc. in 1/ms angeben, statt 1/frame
// Dein ay war 1/frame, mit v = vy/frame, daher entspricht 1/interval² dann deinem alten 1 bei angenommenen 60fps
const ay = 1/interval/interval; // 0.0036

var lastIntervalTick = 0;
var lastAnimUpdate = 0;
function animate(timestamp) {
    // timestamp ist ms mit µs Genauigkeit, kein unix timestamp!
    requestAnimationFrame(animate);

    let intervalDelta = timestamp - lastIntervalTick;
    if (intervalDelta >= interval) {
        // Anpassung mit modulo damit fps möglichst eingehalten wird.
        // 144hz Monitor sind z.B. 6.94ms, 60fps interval sind 16.6ms, 3 * 6.94ms = 20.82ms
        // 1000/20.82 = 48fps, aber -(20.82%16.6) verschiebt nächstes update 4.21ms nach vorne => 60fps (oder mehr)
        lastIntervalTick = timestamp - (intervalDelta % interval);
   
        // delta für Berechnungen, da intervalDelta verschoben ist!
        let dt = timestamp - lastAnimUpdate;
        lastAnimUpdate = timestamp;
   
        // ay wird mehr oder weniger µs genau verändert
        // => gleichmäßige Beschleunigung, vollständig unabhängig von den eigentlichen fps
        Lebendig.vy += ay * dt;
        // Gleiches für die Position
        Lebendig.x += Lebendig.vx * dt;
        Lebendig.y += Lebendig.vy * dt;
    }
}
Benutze ich so ähnlich in einem aktuellen Hobbyprojekt, da 144 updates/sec oft einfach unnötig sind, speziell z.B. bei einer Charaktervorschau im Menü :D
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: DrMedDenRasen93
Hallo TE,
da ich denke, dass du aus diesem Thread mehr herausziehen könntest, schreibe mal ein paar generelle Zeilen.
  1. Falls du weiterhin beim Programmieren bleiben solltest, lies unbedingt Clean Code von Martin Fowler
  2. Schreib Code so, dass JEDER Programmierer den Code extrem schnell verstehen kann. Sehr viel erreichst du alleine durch gute Namen und durch Abstraktion.
Zu deinem Code im Speziellen:
Abstraktion:
alter Code:

Javascript:
var imageLebendig = new Image();
imageLebendig.src = 'assets/pics/lebendig.png';

var imageTot = new Image();
imageTot.src = 'assets/pics/tot.png';

var imageBalken = new Image();
imageBalken.src = 'assets/pics/balken.png';

neuer Code:
Javascript:
function createImage({ imagePath }) {
  const image = new Image();
  image.src = imagePath;
  return image;
}

const imageAlive = createImage({ imagePath: 'assets/pics/lebendig.png' });
const imageDead = createImage({ imagePath: 'assets/pics/tot.png' });
const imageObstacle = createImage({ imagePath: 'assets/pics/balken.png' });

Ich habe die Logik der Bilderstellung in eine Funktion abstrahiert.
Somit kann ich in einer Zeile ein neues Bild erstellen, OHNE die Implementierung andauernd neu zu schreiben. Ein neues Bild = eine neue Zeile Code. Das Argument der Funktion ist dabei ein Objekt, sodass eindeutig ist, was das Argument überhaupt ist (der Pfad zu einem Bild).

Beispiel zum Kontext:

Javascript:
function setupGameContext() {
  const canvas = document.getElementById('gc');
  const context = canvas.getContext('2d');
  context.fillStyle = 'blue';
  return context;
}

const context = setupGameContext();


Naming:

Beispiel 1:
Eine Zeile wie this.x = this.x + this.vx ist schwer zu verstehen. Nur weil ich weiß, dass v wahrscheinlich für velocity steht, kann ich RATEN, dass damit Geschwindigkeit gemeint ist. Und das auch nur, weil du einen Kommentar dazu schreiben musstest.
Anstatt this.vx wäre this.speedOnXAxis sehr viel aussagekräftiger.
Dann kannst du den Kommentar sogar löschen, weil der Name alles erklärt.

Beispiel 2:
Die Zeile var Lebendig = new makeFigur(imageLebendig, 200, 100); // neuen Feind m. Elfen Bild verstehe ich selbst mit Kommentar nicht sofort. Das liegt daran, dass 200 und 100 Argumente sind, die ich in der makeFigur Funktion nachschlagen muss.
const figureAlive = new createFigure({ image: imageAlive, xPositon: 200, yPosition: 100 }); wäre hier sehr viel aussagekräftiger.
 
  • Gefällt mir
Reaktionen: BeBur und DrMedDenRasen93
Danke für die vielen Tipps!

@Bagbag Wie würde denn die Alternative zu meiner "Schwerkraft" aussehen? Wie muss ich das machen?

Edit: Was mir einfallen würde: Eine If-Schleife erstellen in der man sagt, dass das Objekt an einem bestimmten y-Wert einfach wieder nach unten geht. Mich würde sehr interessieren wie du es machen würdest damit es auch relativ realistisch wirkt.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: blöderidiot
Ah, eine if-Schleife, die will ich sehen :D

Im Prinzip hat es @coolmodi ja schon gezeigt, wie das geht.

So wäre mein primitiver Ansatz.
Javascript:
const gravity = 9.81;
const flyUpAcceleration = -20;

const character = {
  x: 0,
  y: 0,
  velocityX: 0,
  velocityY: 0
};

requestAnimationFrame(gameLoop);

let lastTimestamp = 0;
function gameLoop(timestamp) {
  const timeDelta = (timestamp - lastTimestamp) / 1000;
  lastTimestamp = timestamp;

  const accelerationY = getAccelerationY();

  applyAccelerationY(character, accelerationY, timeDelta);
  render(character);
  requestAnimationFrame(gameLoop);
}

function getAccelerationY() {
  let accelerationY = 0;

  accelerationY += gravity;

  if (isFlyingUp()) {
    accelerationY += flyUpAcceleration;
  }

  return accelerationY;
}

function applyAccelerationY(character, accelerationY, timeDelta) {
  const accelerationYDelta = accelerationY * timeDelta;
  character.velocityY += accelerationYDelta;

  const velocityYDelta = character.velocityY * timeDelta;
  character.y += velocityYDelta;
}

function isFlyingUp() {
  return true; // check for key pressed or whatever
}
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Ebrithil und DrMedDenRasen93
Naja, wenn die Geschwindigkeit 10 m/s ist, aber die letzte Berechnung erst 0.5s her ist, hat es sich seit dem nur 5m bewegt.
 
Passiert doch alles im selben frame bin der Meinung das es so richtig wäre.
Code:
character.velocityY += accelerationY * timeDelta;
character.y += character.velocityY;
 
Nein, wenn du dich mit 10 m/s fortbewegst (velocityY) und angenommen nicht beschleunigst (accelerationY = 0) und 100 fps hast, wärst du nach einer Sekunde schon 1 km weiter, da du 100 mal 10 m hinzugefügt hast.

velocityY wird ja nicht zurückgesetzt, sondern jeden frame um accelerationYDelta addiert.
 
Hast vollkommen recht gehört hinten dran mir war so als steht da velocityY = velocitY * deltaTime; hat mich aus dem Konzept gebracht.
 
Zurück
Oben