Feste Spielgeschwindigkeit
-
Ja, so ist die evolution :). Erst hat man immer eine fest logikrate angenommen, an die VSync frequenz des monitors angepasst (ich bin mir sicher ihr wisst was das heute fuer folgen hat :D), dann mit variabler logik framerate, da gab es dann auch mit der zeit spiele die in fps bereichen liefen die so tief oder hoch waren dass garnichts mehr geht (z.b. float underflows im physic system bei zu guter fps). Doom3 hat entsprechend die physic auf 60hz limitiert afaik.
Dann hat man mit dem logik tick weiter gemacht, was so die letzten 5jahre ziemlich weit herum kam.
jetzt faengt man an den systemen ihre individuellen "ticks" zu geben, merk ich hier schoen beim debuggen, wo ich trotz 20fps beim rendern, sowenige "Frames" in der physic habe, dass mein player wie bei einem network lag dauernt zurueck gesetzt wird und die AI bei den raycast die die physic zurueckliefert soviele frames hatte, dass die raycast resultate schon veraltet sind und entsprechend die AI nie den spieler sieht.ich wuerde dennoch weiterhin auf logikframes setzen, da es ein relativ stabiles system bietet. man kann die player camera, den player und vielleicht particle fuers rendern zwischen logikframes interpolieren, damit sieht der spieler immer updates und alles ist stabil.
-
@hustbär
Zunächst einmal kommt es auf die Art des Spieles an ob die Logiksteps länger als die Renderzeit oder kürzer ist.Da die Logiksteps in festen Zeitschritten verarbeitet werden sollen und die Framrate nicht fix sein soll muss zwangsläufig über die Logikschritte interpoliert werden. Sonst wird ein Frame mit dem gleiche Inhalt mehrfach gerendert und das sieht nicht schön aus. Voraussetzung dafür ist natürlich, dass die Logikschritte länger sind als die Framerate ist.
Spätestens im Multiplayer kannst du Logikschritte mit 300Hz komplett vergessen und musst Dir irgendeine schlaue Idee überlegen, wie du zwischen den Schritten am Besten interpolierst. Selbst um Vorhersagen kommst du dann evtl. nicht drumherum.
Und RedPuma hat eine Interpolation beschrieben zwischen zwei Positionen. Hier von einem Integral zu sprechen ist schon starker Tobak.
Interpolation zwischen zwei Punkten:
Pakt = (Palt-Pneu) * t + Pneu
(t von 0.0 bis 1.0)Er berechnet die Zeit die das letzte Frame gebraucht hat und addiert zu der alten Position den berechneten Versatz ( (Palt-Pneu)/N-Schritte.) N ist die Frameanzahl zwischen den einzelnen Logikschritten. Also wird der Teil: (Palt-Pneu) * t durch eine Addition ersetzt. Whoala und wir haben die Interpolation.
@rapso
Dein Post kam eher als meiner.
"...fuers Rendern zwischen Logikframes interpolieren..."
Da schreibst du es ja auch.@Pikkolini
Bist Du Dir sicher, dass die Logikschritte völlig losgelöst sind und nicht etwa noch von der Eingabe abhängen, denn wenn es bei Dir zum Zittern kommt ist zwangsläufig irgendwo eine Pause zwischen den Schritten drin. Evtl. ist die Anzahl der Interpolationsschritte auch falsch.Gruß
Trooper
-
trooper schrieb:
Und RedPuma hat eine Interpolation beschrieben zwischen zwei Positionen. Hier von einem Integral zu sprechen ist schon starker Tobak.
soweit ich das sehe hat redpuma die berechnungsart von z.B. halflife besprochen, diese interpoliert nicht zwischen zwei logikschritten, sondern binden logik und rendering and die aktuelle fps. du interpolierst also nicht, sondern simulierst mit variablen schritweiten.
Interpolation zwischen zwei Punkten:
Pakt = (Palt-Pneu) * t + Pneu
(t von 0.0 bis 1.0)Er berechnet die Zeit die das letzte Frame gebraucht hat und addiert zu der alten Position den berechneten Versatz ( (Palt-Pneu)/N-Schritte.) N ist die Frameanzahl zwischen den einzelnen Logikschritten. Also wird der Teil: (Palt-Pneu) * t durch eine Addition ersetzt. Whoala und wir haben die Interpolation.
das klappt eben nur bei sehr simplen (spricht linearen) bewegungen. hast du z.B. einen sprung wie hustbaer sagte, ist je nach schrittweite ein anderes ergebnis zu erwarten. genau das ist das problem.
@rapso
Dein Post kam eher als meiner.
"...fuers Rendern zwischen Logikframes interpolieren..."
Da schreibst du es ja auch.ich schreibe interpolieren (das macht man zwischen zwei fixen punkten). das ist wirklich was anderes als variable schrittweiten beim simulieren.
waere beides gleich, wuerde man sich hier ja die diskusion sparen
-
rapso schrieb:
das klappt eben nur bei sehr simplen (spricht linearen) bewegungen. hast du z.B. einen sprung wie hustbaer sagte, ist je nach schrittweite ein anderes ergebnis zu erwarten. genau das ist das problem.
RedPuma schrieb:
[...]ebenso bei der von hustbaer geposteten Sprung-Berechnung. Dort hilft es die Zeit zu speichern an der man anfängt zu springen und dann mit der Differenz zu dieser zu rechnen, also mit der Zeit die seit dem Anfang des Sprungs vergangen ist.
/Edit: hab nochmal drüber nachgedacht, die Source-Engine simuliert die Spiel-Logik an sich Serverseitig mit einer bestimmten Tickrate (z.B. 66/s). Im Client wird dann bei voller Framerate interpoliert. Das liegt aber eher an der Multiplayer-Eigenschaft der Engine, selbst beim Singleplayer spielt man praktisch auf nem Server der auf dem eigenen PC läuft. Wenn man im Client mit den interpolierten Werten rechnet dann ist das praktisch kein Unterschied zu der Berechnung mit "echten" Zeitwerten.
Client-seitige Effekte (wie z.B. die angesprochenen Partikeleffekte) werden mit der vollen Framerate des Clients berechnet.
Und selbst im Server muss natürlich auch mit variablen Zeitwerten gerechnet werden da niemand garantieren kann dass dieser die maximalen 66 ticks/s auch wirklich schafft.
-
@rapso
Ich denke dass wir aneinander vorbeireden, denn bei mir wird der Sprung in einzelne Schritte unterteilt und nicht als Ganzes berechnet. Zwischen diesen Einzelschritten wird pro Frame interpoliert. Hier kommt bei jedem Schritt noch die Physik hinzu (außerdem kann man ja neben linear auch noch über Splines oder Hermite interpolieren und schon sind alle Kurven sauber selbst bei komplexen Bewegungen).Im Multiplayer bekommt der Client nur die Zielkoordinaten des Players und lässt ihn selbstständig hinlaufen (auch über Interpolation). Bei mir klappt das wunderbar ohne "Zittern".
"...das ist wirklich was anderes..."
Mathematisch gesehen ist es das gleiche (siehe Beschreibung von mir).
Und mit Nichten ein Integral.//Edit:
@RedPuma
Dein Post kam noch vor meinem.
-
RedPuma schrieb:
rapso schrieb:
das klappt eben nur bei sehr simplen (spricht linearen) bewegungen. hast du z.B. einen sprung wie hustbaer sagte, ist je nach schrittweite ein anderes ergebnis zu erwarten. genau das ist das problem.
RedPuma schrieb:
[...]ebenso bei der von hustbaer geposteten Sprung-Berechnung. Dort hilft es die Zeit zu speichern an der man anfängt zu springen und dann mit der Differenz zu dieser zu rechnen, also mit der Zeit die seit dem Anfang des Sprungs vergangen ist.
/Edit: hab nochmal drüber nachgedacht, die Source-Engine simuliert die Spiel-Logik an sich Serverseitig mit einer bestimmten Tickrate (z.B. 66/s). Im Client wird dann bei voller Framerate interpoliert. Das liegt aber eher an der Multiplayer-Eigenschaft der Engine, selbst beim Singleplayer spielt man praktisch auf nem Server der auf dem eigenen PC läuft. Wenn man im Client mit den interpolierten Werten rechnet dann ist das praktisch kein Unterschied zu der Berechnung mit "echten" Zeitwerten.
Client-seitige Effekte (wie z.B. die angesprochenen Partikeleffekte) werden mit der vollen Framerate des Clients berechnet.
Und selbst im Server muss natürlich auch mit variablen Zeitwerten gerechnet werden da niemand garantieren kann dass dieser die maximalen 66 ticks/s auch wirklich schafft.die HL2 engine simuliert auf mehreren wegen wenn ich mich recht erinnere was mir ein paar source-engine developer erzaehlten
wenn ich mich recht entsinne gibt es
1. auf dem server in fixen zeitschritten logik die leicht voraus laeuft (wegen der hit detection wird wohl die position alle player zu jeden logikstep aufgezeichnet).
2. auf dem client in fixen zeitschritten logik die in der aktuellen zeit laeuft, also serverzeit - latency
3. auf dem client die frame-frame logik.die meisten dinge werden dabei nur auf der frame-frame logik simuliert da sie nicht spielrelevant sind. die wirklichen entscheidungen macht der server und die logik-ticks(2) auf clientseite dienen eigentlich nur um redundanzdaten vom server nicht zu uebertragen, spricht: reine kompression.
am ende ist es ein recht komplexes system, denn der server mit festen logiksteps wird von einem client mit variablen logiksteps gefuettert. dabei ist zwar der server der mit entscheidungsgewalt, aber der client kann unsinn einfuettern die der server dann versucht zu erkennen, wenn die clientseitigen fehler ausserhalb einer gewissen tolleranz sind.
-
Wobei wir damit schon beim Netcode sind wo wir eigentlich nicht hin wollten
-
trooper schrieb:
@hustbär
Und RedPuma hat eine Interpolation beschrieben zwischen zwei Positionen. Hier von einem Integral zu sprechen ist schon starker Tobak.Ich schreibe in dem Zusammenhang auch nicht von einem Integral.
Vielleicht liest du mein Posting einfach nochmal. Die Berechnung der "Flugbahn" beim Springen ist von der Sache her die numerische Lösung eines Integrals. Also wenn man es so macht wie in meinem Beispiel.
Ich schreibe im Übrigen sowieso nirgends von Interpolieren, das kommt immer von anderen.
Interpolieren ist gaga, das macht man erst wenn man muss. Oft genug muss man nicht, dann macht mans nicht.
-
trooper schrieb:
Ich denke dass wir aneinander vorbeireden, denn bei mir wird der Sprung in einzelne Schritte unterteilt und nicht als Ganzes berechnet. Zwischen diesen Einzelschritten wird pro Frame interpoliert.
Haben die Einzelschritte bei dir eine fixe Zeit, oder variable Zeit?
-
So ich habe das jetzt mal mit dem Interpolieren ausprobiert.
Und meine Positionsberechnung sieht jetzt so aus:int dest = 700; x = dest + time * (x - dest);
Immer wenn ich jetzt die rechte Pfeiltaste drücke bewegt sich mein Objekt richtung Zielpunkt.
Allerdings viel zu schnell.
Und jetzt meine wieder eine Frage. Wie schaffe ich es, dass sich das Objekt immer gleich schnell bewegt, egal ob der Zielpunkt 700,800 oder 10000 ist?
-
Indem du den Richtungsvektor ausrechnest, normalisierst, mit der gewünschten Geschwindigkeit multiplizierst, und dann als Geschwindigkeits-Vektor verwendest?
-
Ich habe das folgende codesample mal auf floats geändert da du sonst Probleme bei sehr kleinen Zeitwerten hast.
float dest = 700.0f; //Absolute destination float speed = 50.0f; //Moving 50 pixels per second float nextposition = x + speed*time; if( nextposition > dest ) x = dest; else x = nextposition;
"x" muss hierbei natürlich auch ein float sein.
/Edit: Wenn du "dest" hierbei während der Programmlaufzeit ändern willst und destnew < destold dann solltest du in der if-Abfrage noch hinzufügen ob sich das Ziel links oder rechts von der aktuellen Position befindet.
/Edit2: Ich gehe davon aus dass "time" die Zeit ist, die während eines Frames vergeht (Δt) und nicht die absolute Systemzeit.
-
@RedPuma
Der Code ähnelt ziemlich meinem alten. Ich habe zwar noch nichts ausprobiert, ob der zittert aber antstatt sich 50 Pixel pro Sekunde zu bewegen, bewegt sich das Objekt nur 25 Pixel pro Sekunde.
-
Dann stimmt was mit deiner Zeitvariablen nicht.
-
Ich habe jetzt einmal für Release kompiliert, und schon klappt alles, auch beim Debuggen.
Einziges Problem jetzt ist eben dieses Zittern, wenn ich die Bewegung von den Tastatureingaben abhängig mache.
Nebenbei: Die Positionberechnung mit Interpolation, wie ich sie einmal in meinem Code verwendet habe ist ziemlicher schwachsinn. Angenommen der aktuelle Punkt ist 0, und der Zielpunkt ist 700. Dann würde nach der Rechnung mit der Framezeit von 0,03 Sekunden folgendes rauskommen:
700 + 0,03 * (0 - 700) = 679
Das Ergebnis danach wäre dann 699,37. Also so kann man eine Position nicht ausrechnen. Deswegen stelle ich mir schon die ganze Zeit die Frage, wie das korrekt mit Interpolation geht.
-
Eventuell zitterts weil du in deiner Timer Klasse mit 1/frequency multplizierst. Das könnte vielleicht Rundungsfehler geben da 1/frequency schon eine sehr kleine zahl ist.
/Edit:
Da das Ganze irgendwie ein wenig komplizierter wird kannst du, wenn du willst, mir auch gerne deinen Code per Mail schicken, dann kann ich mal reinschauen. (graefemarius at web . de)
Außerdem würd mich selber interressieren warum des immer noch nicht funktioniert.
-
Pikkolini schrieb:
Dann würde nach der Rechnung mit der Framezeit von 0,03 Sekunden folgendes rauskommen:
700 + 0,03 * (0 - 700) = 679Das ist richtig, denn du hast dich 700 * 0,03 = 21 Einheiten in dieser Zeit bewegt.
-
@Interpolation und allgemeine Diskussion von wegen HL Engine
Habe ich gerade eben gestern gelesen und fands noch ganz interessant:
http://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
http://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization
-
@nurf
Dank dir habe ich jetzt endlich mein Denkfehler entdeckt.
Wenn ich z.B. aus Position 20 stehe muss das x = 20 + 0,03 * (700 - 0) heißen.
Bisher habe ich es immer so gemacht: x = 20 + 0,03 * (700 - 20).
Mein Code sieht jetzt so aus:void object::move(short direct, short pps, double time) { short windowwidth = 800; short windowheight = 600; switch(direct) { case 0x25: { x = x - ((time * windowwidth) * (1.0 / windowwidth)) * pps; if (x < 0) x = 0; break; } case 0x26: { y = y + ((time * windowheight) * (1.0 / windowheight)) * pps; if (y > windowheight) y = windowheight; break; } case 0x27: { x = x + ((time * windowwidth) * (1.0 / windowwidth)) * pps; if (x > windowwidth) x = windowwidth; break; } case 0x28: { y = y - ((time * windowheight) * (1.0 / windowheight)) * pps; if (y < 0) y = 0; break; } } };
Wobei windowheight und windowwidth die Fensterhöhe und breite sind, direct die Richtung (VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN), pps die Pixel pro Sekunde und time die Zeit vom letzten Frame sind/ist.
Die Funktion move rufe ich so auf:if (isKeyDown(VK_LEFT)) { objects.move(VK_LEFT, 10, time); } if (isKeyDown(VK_UP)) { objects.move(VK_UP, 10, time); } if (isKeyDown(VK_RIGHT)) { objects.move(VK_RIGHT, 10, time); } if (isKeyDown(VK_DOWN)) { objects.move(VK_DOWN, 100, time); }
Allerdings bewegt sich das Objekt nur richtig, wenn ich die Pfeiltaste nach unten drücke. Drücke ich nach links, oben oder rechts bewegt sich das Objekt nur sehr langsam.
Bis auf die Geschwindigkeit in manche Richtungen funktioniert bisher alles ohne Zittern so wie es soll.@RedPuma:
Bis auf das Zittern hat ja eigentlich alles funktioniert.
Und danke für das Angebot, doch ich denke das hat sich hiermit erledigt.
Ansonsten hätte ich es gerne angenommen
-
trooper schrieb:
"...das ist wirklich was anderes..."
Mathematisch gesehen ist es das gleiche (siehe Beschreibung von mir).
Und mit Nichten ein Integral.Aber Hallo ist das ein Integral. Das ist eine eingliedrige Taylorreihe die du da berechnest. Nimmst du einen Wegpunkt mehr, hast du sogar 2 Integrale in der Taylorreihe
Es gibt nicht viele Wege, sowas mathematisch korrekt zu machen, und eigentlich führt kein Weg an einer Integration über die Zeit vorbei. Ist nur die Frage, wie exakt man das INtegral berechnet.
//edit was spricht eigentlich gegen 2 Threads, in der der eine konsequent rendert und der andere einfach in festen Abständen seine Logikframes macht? Unter der Annahme, dass Berechnungszeit(logik)<<Berechnungszeit(Grafik) sollte damit doch alles ziemlich gut laufen, oder? Ich meine zumindest, solange man keine uralten PCs noch berücksichtigt. Für die Zukunft ist man damit aber abgesichert.