Wie implementiere ich eine Verzögerung für den Spielercharakter?

7

Die Verwendung der Deltazeit mit Addition und Subtraktion ist einfach.

player.speed += 100 * dt

Multiplikation und Division erschweren die Dinge jedoch ein wenig. Nehmen wir zum Beispiel an, ich möchte, dass der Spieler seine Geschwindigkeit jede Sekunde verdoppelt.

player.speed = player.speed * 2 * dt

Ich kann das nicht tun, weil es den Spieler verlangsamt (es sei denn, die Delta-Zeit ist wirklich hoch). Die Teilung ist der gleiche Weg, nur dass sie die Dinge beschleunigt.

Wie kann ich mit Multiplikation und Division mit Deltazeit umgehen?

Edit : Es sieht so aus, als hätte meine Frage alle verwirrt. Ich wollte wirklich nur die Verzögerung ohne diese schreckliche Menge an Code implementieren können:

else
    if speed > 0 then
        speed = speed - 20 * dt
        if speed < 0 then
            speed = 0
        end
    end
    if speed < 0 then
        speed = speed + 20 * dt
        if speed > 0 then
            speed = 0
        end
    end
end

Weil das viel größer ist als es sein muss. Bisher scheint eine bessere Lösung zu sein:

speed = speed - speed * whatever_number * dt
tesselode
quelle
3
Was versuchst du zu erreichen?
Justin Skiles
Ich möchte, dass der Player langsamer wird, ohne eine Reihe von if-Anweisungen verwenden zu müssen.
Tesselode
Möchten Sie jedes Mal exponentiell beschleunigen und verlangsamen? Sie scheinen etwas verwirrt darüber zu sein, was Sie fragen, da Sie sagen, dass Sie die Geschwindigkeit jede Sekunde verdoppeln möchten, aber Ihr Code zeigt, dass sich die Position verdoppelt.
MichaelHouse
Ja, ich möchte die Geschwindigkeit ändern, nicht die Position. Fest.
Tesselode
2
Mögliches Duplikat von Friction im 2D-Spiel
MichaelHouse

Antworten:

6

Hier möchten Sie etwas mehr Physik verwenden, als nur die Position um einen konstanten Wert zu ändern. Geben Sie Ihrem Spieler eine Geschwindigkeit und eine Beschleunigung. Wenn Sie dann aktualisieren, können Sie Folgendes tun:

player.velocity += player.acceleration * dt;
player.x += player.velocity * dt;

Dann ist es einfach, die Beschleunigung so einzustellen 0, dass eine konstante Geschwindigkeit erhalten bleibt, oder Sie können eine positive Zahl festlegen, um die Geschwindigkeit zu erhöhen, oder eine negative Zahl, um die Geschwindigkeit zu verringern.

Wenn Sie nur eine negative Beschleunigung "abbremsen" möchten, ist das alles Reibung. Reibung ist nur eine weitere Kraft wie das Beschleunigen.

MichaelHouse
quelle
+1, aber ich hatte ein wenig Mühe, die Unterfrage zu beantworten (wie man die Geschwindigkeit jede Sekunde verdoppelt) - ich kann keinen Weg finden, die Geschwindigkeit jede Sekunde genau zu verdoppeln, da nur delta_time verstrichen ist und die aktuelle Geschwindigkeit!
Markus von Broady
Sie würden eine Exponentialfunktion für die Beschleunigung verwenden, um die Geschwindigkeit jede Sekunde zu verdoppeln. Ich bin mir nicht sicher, ob OP das wirklich will. Es wird schnell außer Kontrolle geraten.
MichaelHouse
Ich muss wohl wieder zur Schule gehen.
Markus von Broady
Das OP schien ohnehin davon zu sprechen, die Position jede Sekunde zu verdoppeln ( player.x = player.x * 2), was noch schneller, exponentiell schneller außer Kontrolle geraten würde.
MichaelHouse
Er 'wollte' (weil ich auch denke, dass er das nicht braucht), um seine Geschwindigkeit zu verdoppeln, nicht jede Sekunde zu positionieren, und er wollte es auf delta_time basieren! Ich finde das Problem schwierig, ohne einen "oldSpeed" -Wert zu speichern, der jede Sekunde aktualisiert wird.
Markus von Broady
4

In diesen Fällen sollten Sie Exponentialfunktionen anstelle einer normalen Multiplikation verwenden. Bedeutung:

player.velocity.x = player.velocity.x * pow(CONSTANT_FACTOR, dt);

Es ist ziemlich einfach zu erklären, warum: Wenn Sie schreiben player.velocity.x += CONSTANT * dt, nehmen Sie an, dass Ihr Spiel CONSTANT * 1/60mit 60 fps läuft, und erhöhen die Geschwindigkeit 60 Mal pro Sekunde, was bedeutet, dass sich Ihre Geschwindigkeit in jeder Sekunde erhöht CONSTANT * 1/60 * 60 = CONSTANT.

Jetzt möchten Sie, dass die Spielgeschwindigkeit zwei- oder dreimal pro Sekunde erreicht wird. Nach einer Sekunde können Sie new_velocity = old_velocity * CONSTANTdiese in kleinere Multiplikationen aufteilen und schreiben

new_velocity = old_velocity * CONSTANT ^ (1/60) * CONSTANT ^ (1/60) * ... 
             = old_velocity * CONSTANT ^ (1/60 * 60) 
             = old_velocity * CONSTANT ^  1 = old_velocity * CONSTANT

Dies bedeutet, dass Sie in jedem Frame die Geschwindigkeit mit multiplizieren müssen CONSTANT^dt

BEARBEITEN

Basierend auf den Änderungen, die Sie in Ihrer Frage vorgenommen haben, müssen Sie diese exponentielle Methode nicht wirklich implementieren. Angenommen, Sie simulieren Reibung, können Sie auch eine physikalischere Methode implementieren:

Physikalische Reibung verringert die Geschwindigkeit linear, vorausgesetzt, die Objektmasse ändert sich nicht im Laufe der Zeit. Es übt eine Kraft auf das Objekt aus, genau entgegengesetzt zur aktuellen Geschwindigkeit des Objekts. Nehmen wir zum Beispiel an, Reibung reduziert die Geschwindigkeit immer um zwei Einheiten pro Sekunde. Sie müssen die aktuelle Richtung der Geschwindigkeit ermitteln, sie umkehren und einen Vektor der Länge zwei in dieser Richtung multipliziert mit dt reduzieren. Hier ist ein Code, der Ihnen das Verständnis erleichtert:

Velocity_Direction = Velocity / Length(Velocity)
Friction_Power = 2
Friction_Direction = -Velocity_Direction
New_Velocity = Old_Velocity + Friction_Direction * dt

Es gibt eine kleine Kleinigkeit, die Sie vielleicht bemerken möchten: Wenn der Code bei diesem Code zu niedrig ist, zittert das Objekt an seiner Position. Das liegt daran, dass Fricton_Direction * dtes größer als die Geschwindigkeit selbst ist, was dazu führt, dass sich das Objekt im nächsten Schritt genau in die entgegengesetzte Richtung bewegt, anstatt still zu stehen. Um dies zu beheben, müssen Sie eine if-Anweisung hinzufügen, um zu überprüfen, ob die Geschwindigkeit zu niedrig ist und das Objekt vollständig gestoppt werden soll. Ich mache es so:

Friction_Power = 2

if(Length(Velocity) > dt * Friction_Power)
    Velocity_Direction = Velocity / Length(Velocity)
    Friction_Direction = -Velocity_Direction
    New_Velocity = Old_Velocity + Friction_Direction * dt
else
    New_Velocity = Vector(0)

Beachten Sie, dass dies Lengtheine Funktion ist, die einen Vektor als Eingabe erhält und dessen Länge zurückgibt. Zum Beispiel, wenn Ihr Spiel eine 2D-Welt hatLength (Velocity) = Sqrt(Sqr(Velocity.x) + Sqr(Velocity.y))

Ali1S232
quelle
+1, es funktioniert! v *= Math.pow ( 2, dt );die Geschwindigkeit jede Sekunde zu verdoppeln (dt ist Bruchteil einer Sekunde)
Markus von Broady
4

Wie andere angemerkt haben, lautet die korrekte Formel für die Verzögerung aufgrund von Trockenreibung

velocity += constant * dt * -dir( velocity )

Dabei wird dir(v)ein Vektor mit Längeneinheiten zurückgegeben, der in dieselbe Richtung wie zeigt v. (Für eindimensionale Bewegungen, dir(v) = 1ob v > 0und dir(v) = -1wenn v < 0.) Eine Möglichkeit, sie zu berechnen, ist as

dir(v) = v / abs(v)

wo abs(v)gibt die Länge oder Größe des Vektors v.

Um zu verhindern, dass Objekte nach dem Anhalten zittern (und um zu vermeiden, dass sie durch Null geteilt werden, wenn wir versuchen, die Richtung eines perfekt stationären Objekts zu berechnen), sollten wir auch sicherstellen, dass die Geschwindigkeitsänderung niemals die ursprüngliche Größe der Geschwindigkeit überschreitet. Das heißt, Reibung sollte niemals dazu führen, dass sich ein Objekt rückwärts bewegt. Alles in allem ist eine vernünftige Implementierung von Reibung in einem Spiel:

friction = constant * dt;
speed = abs( velocity );
if ( friction < speed ) {
    delta_v = friction * -( velocity / speed );
} else {
    delta_v = -velocity;    // the object stops, or was stopped already
}
velocity += delta_v;

Beachten Sie, dass dies auch dann funktioniert, wenn sich das Objekt in mehr als einer Dimension bewegt, sodass es sich velocityum einen Vektor handelt. (Natürlich kann der tatsächliche Code dafür mit Vektoren etwas anders aussehen, je nachdem, welche Notation Ihre Sprache für die Vektorarithmetik verwendet.)

Sie können vor oder nach diesem Code weitere Geschwindigkeitsänderungen hinzufügen. Wenn Sie sie vorher hinzufügen, können ausreichend kleine Kräfte durch Reibung vollständig aufgehoben werden, was tatsächlich realistisch ist. Für weiteren Realismus möchten Sie möglicherweise statische Reibung implementieren, indem Sie die Konstante in der Reibungsberechnung variieren lassen, je nachdem, ob die Geschwindigkeit des Objekts zu Beginn dieses Zeitschritts ungleich Null war oder nicht.

Wenn Sie wirklich genau sein möchten, sollten Sie bei der Aktualisierung der Objektposition die Beschleunigung während des Zeitschritts berücksichtigen. Das heißt, anstatt nur zu tun

velocity += delta_v;
position += velocity * dt;

du solltest tun

velocity += delta_v;
position += ( velocity - delta_v / 2 ) * dt;

Wo velocity - delta_v / 2ist der Durchschnitt der Geschwindigkeiten vor und nach dem Hinzufügen delta_v.

Diese letztere Annäherung an Newtons Bewegungsgesetze ist tatsächlich genau, solange sie acceleration = delta_v / dtkonstant ist, und ist auf jeden Fall eine bessere Annäherung als die erstere, selbst für Änderungen delta_v. Bei Spielen mit einem kleinen und konstanten Zeitschritt dtist der Unterschied jedoch im Allgemeinen nicht erkennbar, zumindest ohne Vergleich nebeneinander. Der Hauptvorteil der genaueren Form besteht darin, dass Objekttrajektorien weniger empfindlich auf Änderungen in reagieren dt.

Ich sollte auch darauf hinweisen, dass (trockene) Reibung nicht die einzige Kraft ist, die sich bewegende Objekte verlangsamen kann. Beispielsweise erfahren Objekte, die sich durch Wasser oder Luft bewegen, einen Luftwiderstand , der einer Formel folgt, die im Allgemeinen ungefähr so ​​aussieht

delta_v = ( a * speed + b ) * -velocity * dt

wo aund bsind Konstanten, die von vielen Dingen abhängen, wie der Dichte und Viskosität der Flüssigkeit und der Masse, Größe und Form des Objekts, das sich durch sie bewegt. (Siehe den Wikipedia-Link oben für Details; aoben entspricht Newton Drag, während bStokes Drag entspricht.)

Für makroskopische Objekte, die sich durch ziemlich nichtviskose Flüssigkeiten wie Wasser oder Luft bewegen, bsollte sie sehr klein oder sogar Null sein, während eine viskose Flüssigkeit wie Lava oder Melasse eine höhere erfordert b. Spielen Sie für Spielzwecke einfach mit den Werten herum, bis Sie den gewünschten Effekt erzielen.

Beachten Sie, dass im Gegensatz zur Trockenreibung die Widerstandskräfte ein sich bewegendes Objekt niemals vollständig zum Stillstand bringen, sodass wir uns im Allgemeinen keine Gedanken über versehentliche Richtungsumkehrungen machen müssen. Die Ausnahme ist if (a * speed + b) * dt > 1, was passieren kann, wenn dtes zu groß ist oder wenn das Objekt irgendwie eine ungewöhnlich hohe Geschwindigkeit erreicht; In diesem Fall besteht die Lösung entweder darin, sich dynamisch anzupassen dt, um sich für sich schnell bewegende Objekte zu verkleinern, oder einen Bewegungsintegrator höherer Ordnung zu verwenden (der wirklich über den Rahmen dieses Beitrags hinausgeht).

Ilmari Karonen
quelle
3

Ohne mehr in Ihre Frage zu lesen und Ihre Frage so zu beantworten, wie sie ist, ist dies eine Vereinfachung Ihres Codes:

speed = (speed>0)?MAX(speed - 20 * dt, 0):MIN(speed + 20 * dt, 0)

Um jedoch ein realistischeres (dh Newtonsches) Ergebnis zu erzielen, sollten Sie, wie andere angegeben haben, eine "Beschleunigungs" -Variable implementieren und Ihre Entität wie folgt aktualisieren:

position = position + velocity * dt + acceleration * dt * dt * 1/2
velocity = velocity + acceleration * dt

Hier wäre die Geschwindigkeit (vermutlich) Ihre "Geschwindigkeits" -Variable. (Wenn ich mich aus der Physik richtig erinnere, ist "Geschwindigkeit" technisch ABS (Geschwindigkeit), sollte daher immer> = 0 sein)

Jing
quelle
Sehr gute Antwort +1
AturSams
0

Um diesen Code zu verkürzen, können Sie Folgendes tun:

//define a constant somewhere in your code so you could adjust the value later
DECCELLARATION_RATE = 20;
//Checks if the speed is positive or negative so we will decrease it's absolute value:
sign = (speed >= 0? 1 : -1);
//Decrease the absolute value of the speed while maintaing it's sign
speed -= sign * DECCELLARATION_RATE * dt
//If the object is moving too slowly, the above command will change the speed's sign and with it the direction of the movement. 
//What we want to do is stop the object in this case.
if(sign != (speed >= 0? 1 : -1)) speed = 0;

Der Operator (condition ? value_if_true : value_if_false ) wird als ternärer Operator bezeichnet. Mehr dazu lesen Sie hier:

http://en.wikipedia.org/wiki/Ternary_conditional_operation

AturSams
quelle
1. Sie antworten funktioniert nur für 1D Welt. 2. Falls wir davon ausgehen, dass OP eine 1D-Antwort wollte, haben sowohl ich als auch Byte diese Antwort bereits gegeben.
Ali1S232
3
Der von Ihnen angegebene Link führt zur allgemeinen Seite mit Fragezeichen. Warum verbringt Ihre Antwort mehr Platz damit, ternäre Operatoren zu erklären, ohne Ihren Bewegungscode zu erklären?
Justin Skiles
@ JustinSkiles Danke, ich habe den Link repariert. Er fragte, wie er seinen Code kurz und einfach machen könne. Ich bot ein Tool an, das als ternärer Operator bezeichnet wurde. Ich vermute, das OP ist nicht damit vertraut, also habe ich im Detail erklärt, was es tut. Ich habe die Antwort bearbeitet, um einige Kommentare hinzuzufügen, die den Code selbst erklären. Ich fand es sehr kurz und ziemlich klar, sobald Sie wissen, was ein ternärer Operator tut.
AturSams
@Gajoo All die Dinge, die Sie geschrieben haben, ändern nichts an der Tatsache, dass 1. diese Antwort für die Manipulation der Geschwindigkeit der x-Achse in einem 2D-Plattformspiel geeignet ist. 2. Es passt perfekt zur bearbeiteten Frage. 3. Sie haben Byte56 höchstwahrscheinlich ursprünglich und mich jetzt herabgestuft, weil Sie wettbewerbsfähig sind. Keine Ihrer Erklärungen zeigt, warum diese Antwort bei der Betrachtung der Frage weniger nützlich ist. Außerdem befasst sich der letzte Teil Ihrer Antwort mit genau der gleichen Situation. Sie müssen also denken, dass es etwas nützlich ist.
AturSams
3
+1 ist vielleicht nicht die beste Antwort, aber es zeigt die Lösung auf eine andere Art und Weise und ist daher eine positive Eingabe, die mit Sicherheit nicht -3 verdient.
Markus von Broady