Bewegung FPS unabhängig

Übersicht BlitzBasic Allgemein

Neue Antwort erstellen

 

BBPro2

Betreff: Bewegung FPS unabhängig

BeitragDi, Aug 21, 2012 8:54
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo,

ich versuche mir gerade ein vernünftiges System für FPS unabhängige Bewegung zu überlegen.

Beispiel: Breakout (unten ein paddle mit dem man den ball immer wieder nach oben haut um die dortigen blöcke kaputt zu schießen)

Problem: Bewegung des Balls

nun möchte ich natürlich nicht, dass der Ball pro Schleifendurchlauf um x# viele Pixel bewegt wird,
da das abhängig von den FPS und damit von der Rechenpower zu unterschiedlichen Ergebnissen führt.

Bei 30 fps würde die Addition 30x pro Sekunde ausgeführt werden, bei 60 fps 60x.
Der Ball wäre bei 30fps also deutlich langsamer unterwegs
Demnach wäre das Spiel auf schwachen PCs nur "halb so schwer"

Deswegen bewege ich den Ball um x# viele Pixel PRO SEKUNDE. (abhg. von Millisecs())
Soweit so gut, ein ziemliches Standardverfahren afaik.


Nun zum Problem:
Was wenn der PC - aus welchem Grund auch immer - kurz hart laggt ?
Sagen wir zwischen dem einen und dem nächste Update liegt eine halbe Sekunde.
Millisecs () läuft offensichtlich normal weiter, aber es wird kein Update ausgeführt.
Beim nächsten mal wird der Ball also eine extreme Strecke zurücklegen (z.B. 100 pixel bei einer Geschwindigkeit von 200px/s)

Nun kann es aber passieren, dass der Ball ein oder mehrere male in dieser halben Sekunde abgeprallt wäre. Ich kann also nicht einfach die x und y koordinaten dementsprechend anpassen sondern muss seinen Weg überprüfen.
Ist er irgendwo abgeprallt muss ich entsprechend handeln, sonst könnte er sich falsch verhalten / durch Wände/Schläger/etc. durchfliegen

Eine Idee die ich hatte war es sein "Verhalten pro Millisekunde" zu untersuchen.
Also wenn seit dem letzten Update 500ms vergangen sind dann
führe 500 mal die Berechnung aus "was passiert nach 1ms ? handle entsprechend !"

Dann ist mir aber aufgefallen, dass hierbei Probleme enstehen wenn die "Verhlaten pro Millisekunde" länger zum rechnen braucht als eben diese Millisekunde.

Beim ersten mal würden z.B. 50 "Schritte" ausgerechnet werden - hierfür würde es sagen wir 100ms brauchen.
Beim nächsten mal wären also 100ms vergangen -> 100 Schritte -> 200ms Berechnungszeit -> 200ms vergangen -> 200 Schritte ...

Das ganze scheint also inpraktikabel.

Alternativ in "Was passiert nach 10ms ?" zu arbeiten führt zu ungewünschten Nebeneffekten, da eine Maschine die im Schnitt 11ms braucht um von einem Durchlauf zum nächste zu kommen sich anders verhalten würde (eine andere Geschwindigkeit hätte) als eine die im Schnitt 14ms braucht.


Die einzige "echte" Lösung die mir einfällt ist es irgendwie "den Weg" des Balles zu überprüfen und dann rekursiv seinen Weg zu bestimmen (bei jedem abprallen rekursiv weiterarbeiten)
Die scheint mir aber recht kompliziert für ein so einfaches Problem.

Richtig kompliziert wird es zudem wenn sich der Schläger in der Zeit eigentlich auch bewegt hätte. Verfolge ich dann einfach nur Wege müsste ich wissen wo der Schläger zu welchem Zeitpunkt wäre usw.

Ich weiß, dass ich hier kompliziert denke, aber ich will eine wirklich KORREKTE lösung finden und nicht etwas das nur funktioniert wenn wir keine ekligen, langen lags an den falschen stellen haben.

Wie würdet ihr vorgehen ? Gibt es dafür Standard-Vorgehensweisen die mir nicht bekannt sind ?

Midimaster

BeitragDi, Aug 21, 2012 9:26
Antworten mit Zitat
Benutzer-Profile anzeigen
Also , du wirst sicherlich einige Beiträge hier bekommen, die dir zu Delta-Timing raten....

Meine Meinung dazu ist, das es selten nötig ist. Ich stelle Dir hier ein Konzept vor, bei dem man innerhalb des Spielcodes überhaupt gar nicht an Zeitprobleme denken muss. Ich glaube man nennt es "fixed step delta timing".

Zunächst mal: Den Timer so hoch zustellen, dass der Programmcode schon länger dauert als ein Timer-Tick ist keine gute Lösung und auch unnötig. Ich habe hier mit FPS von 60, 120 oder 200 meist sehr gute Ergebnisse erzielt.

Die Idee ist folgende:

Für mein Konzept ist es total wichtig, alle Handlungen (Action-Code) von allen Mal-Aktionen (Grafik-Code) getrennt zu halten. Aber dies sollte man eh immer machen.

Du hast einen Timer mit 60 FPS. Darauf richtest Du deine Spielbewegungen ein. Schafft der Computer die Step-Rate ist alles in Butter. Schafft er sie einmal nicht, dann wiederholst Du den Action-Code einfach ein weiteres Mal.


BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
SetBuffer BackBuffer()
FPS=CreateTimer(60) ; probier hier andere Werte aus

TIMESTEP=5 ; 10mses

Global X%, Y%, Xadd%=1, Yadd%=1
Repeat
If ActionTimer<MilliSecs()-500
ActionTimer=MilliSecs()
ElseIf ActionTimer<MilliSecs()
While ActionTimer<MilliSecs()
ActionTimer=ActionTimer+TIMESTEP
AllAction()
Wend
AllGrafik()
EndIf
While MouseDown(1)
Wend
WaitTimer FPS
Until KeyHit(1)

Function AllAction()
;DebugLog "action"
X=X+Xadd
Y=Y+Yadd
If X<0 Or X>800 Then Xadd=-Xadd
If Y<0 Or Y>600 Then Yadd=-Yadd
End Function

Function AllGrafik()
;DebugLog "Grafik" + x
Cls
Oval X,Y,5,5
Flip 0
End Function



wie Du im Code erkennen kannst, ist die einzige Stelle wo was von Timern steht in der Hauptschleife.

Eine Unterbrechung von einer halben Sekunde wird übrigens immer als PAUSE gewertet. Für soclhe Fälle sollte dein Spiel stehen bleiben und der Ball anschließend an der gleichen Stelle weitermachen. Würde man die "verlorengegangenen Spielzüge" schnell nachholen, wäre das dem Spieler gegenüber unfair. da er ja keine Reaktionsmöglichkeit hätte. Im code oben lässt sich so ein Fall mit MausDown() simulieren.
Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe
 

BBPro2

BeitragDi, Aug 21, 2012 9:58
Antworten mit Zitat
Benutzer-Profile anzeigen
Hi,

erstmal danke für die schnelle und kompetente hilfe.

ein paar fragen hät ich aber noch:

1) If ActionTimer<MilliSecs()-500
ActionTimer=MilliSecs()

hier setzt du "halbe sekunde seit letztem update ? das ist zu viel ! wir 'pausieren'" um, richtig ?
die halbe sekunde ist eine art erfahrungswert von dir nehme ich an ?

2) TIMESTEP=5 ; 10mses

so wie ich das system verstanden habe bedeutet timestep = 5 , dass pro berechnetem step
5ms vergangen sind - demnach wäre der kommentar falsch ?

außerdem: welchen wert für timestep würdest du empfehlen ?
finde 5 irgendwie zu klein, da (so wie ich das im moment verstehe zumindest) selbst bei der
maximalen fps von 60 gleich dreimal die hauptschleife ausgewertet werden würde
(60fps -> ~16ms pro durchlauf)
wenn allActions () halbwegs viele operationen durchführt führt das zu dem von mir beschriebenen stau oder?
ich glaube so etwas wie 16 wäre ein geeigneter wert oder ?
im falle von "perfekten" 16ms wird die hauptschleife dann im schnitt genau 1x durchlaufen - läuft der pc nur auf 30fps berechnet er halt jedes 2. mal (nach 32ms) dafür gleich 2 mal das ganze
Problematisch wird es glaube ich aber, wenn der pc rein "rechenmässig" nur 30 fps schafft.
setze ich timestep auf 15 versucht er alle 15ms einmal allActions () zu berechnen.
schafft er das aber aufgrund von cpu nicht versucht er es trotzdem weiter.
es kommt zum benannten stau.
beim nächsten mal will er 2x die funktionaufrufen, hat dafür aber nur 15ms zeit.
das schafft er nicht usw.
demnach verlangt man mit diesem system eine mindest-fps die die cpu schaffen muss. andernfalls wird das programm (quasi) unausführbar

3) wenn ich an timestep rumspiele wird das spiel ja schneller/langsamer wenn ich in allActions ()
mit absoluten werten arbeite, was du ja suggerierst wenn ich dich richtig verstehe.
ich denke dort dennoch mit "delta-timing" zu arbeiten wäre die bessere alternative, da man
dann an timestep rumspielen kann ohne ablaufgeschwindigkeit der applikation zu verändern.
hierzu müsste man wohl einfach die änderungswerte "pro Millisekunde" definieren und dementsprechend
in allActions () jede Änderung immer mit Timestep multiplizieren
(da ein solcher aufruf ja genau pro 'timestep' millisekunden ausgeführt wird)

4) alles in allem sieht das ziemlich 1:1 nach dem aus was ich vorgeschlagen hatte nur dass es
auf (in diesem fall hier) 5 ms / "step" arbeitet statt 1ms wie bei mir.
das problem, dass wenn ein allActions() step im schnitt länger als 5ms braucht sich alles nach hinten
staut besteht hier wohl auch oder ?

alles in allem aber defintiv ein interessanter ansatz über den ich mal ein wenig nachdenken werde
  • Zuletzt bearbeitet von BBPro2 am Di, Aug 21, 2012 10:24, insgesamt 2-mal bearbeitet

Lobby

BeitragDi, Aug 21, 2012 10:01
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich würde durchaus "Verhalten pro Zeit" bevorzugen, da es einfacher ist dabei ein kontinuirliches Updaten zu verwenden. Einen deltaTime-Faktor zu nutzen scheint mir zudem recht einfach und nachvollziehbar.

Nun zu deinem Problem des Lags bzw. Ruckelns. Eine sehr elegante Lösung scheint mir, deltaTime auf einen Maximalwert zu begrenzen (z.b. 30ms bei 60 FPS), denn ob man es glaubt oder nicht, der Spieler ist ebenfalls nicht sonderlich daran interessiert, dass weiter aktualisiert wird, ohne, das er es sehen könnte oder einen Einfluss darauf hat. Diese Obergrenze kann man an den geplanten FPS orientieren, muss man aber nicht. Wenn ein Spiel dauerhaft an dieser Obergrenze kratzt, sollte man sich überlegen, ob das Spiel für diesen Rechner überhaupt geeignet ist (42ms würden der deltaTime bei 24 FPS entsprechen, ich denke höher sollte man die deltaTime nicht setzen lassen).

Wie du siehst gebe ich deltaTime übrigens gerne in Millisekunden an, aber das ist sicherlich Geschmackssache. Man muss lediglich bedenken, dass man so die Geschwindigkeiten in Schritt pro Millisekunde angibt.
  • Zuletzt bearbeitet von Lobby am Di, Aug 21, 2012 10:09, insgesamt einmal bearbeitet
 

BBPro2

BeitragDi, Aug 21, 2012 10:08
Antworten mit Zitat
Benutzer-Profile anzeigen
@lobby

diese idee hatte ich auch schon.
mein problem bei dem ansatz:

welche obergrenze sollte verwendet werden ?
sie muss klein genug sein damit keine seltsamen effekte auftreten (ball fliegt durch schläger etc)
aber auch hoch genug damit das spiel bei weniger fps nicht "echt" ausgebremst wird.

also wir wollen ja nicht, dass jemand mit nem schrott pc und 10fps einen unfairen vorteil hat weil das spiel
langsamer updated.
dies könnte benutzt (es gibt programme die deine cpu leistung und damit fps beschränken) werden um
eine bessere highscore zu erreichen.
(spielt man halt mit 1fps und hat alle zeit der welt zu handeln)
genau das will ich aber unter keinen umständen zulassen.

lösung wäre natürlich das spiel ab einer gewissen fps-untergrenze einfach "unspielbar" zu machen und mit einer netten nachricht a la "your pc suckz" zu schließen

durchaus etwas was ich in betracht gezogen habe und immer noch ziehe, aber ich wollte mir eben mal ein paar weitere vorschläge und ideen hier einholen.
vielleicht kommen ja noch ein paar weitere meinungen zu diesem ansatz, danke auf jeden fall für deinen beitrag

Lobby

BeitragDi, Aug 21, 2012 10:12
Antworten mit Zitat
Benutzer-Profile anzeigen
Wie geschrieben, ich finde unter 24 FPS sollte die Renderrate nicht sinken können, schon 24 FPS sind recht wenig und sollten einem die Freude an einem Spiel doch etwas lindern (insbesondere, wenn es auf schnelle Reaktionen ankommt). Warum machst du dir Gedanken darüber, dass jemand mit einer schlechten Bildwiederholungsrate im Nachteil sein könnte? Wenn ein Rechner so dermaßen schlecht ist, ist es ab einem gewissen Punkt nicht mehr an dir, Gleichberechtigung zu schaffen (kannst du auch gar nicht, da ja auch die Usereingaben sich nicht mehr sofort auf die Welt auswirken können).

Blitzcraft

BeitragDi, Aug 21, 2012 10:18
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich kenne eigentlich kaum ein PC bei dem ein Breakoutspiel groß laggt.
Also könntest du das spiel einfach mit einer netten Nachricht beenden falls die FPS auffällig niedrig ist.
Screenshot aus meinem ersten Projekt
 

BBPro2

BeitragDi, Aug 21, 2012 10:28
Antworten mit Zitat
Benutzer-Profile anzeigen
@Lobby

ich mache mir die gedanken aus 2 gründen
1) (mit Abstand Haupgrund) weil es mich theoretisch interessiert und ich ein Produkt abliefern
möchte, dass IMMER Korrekt arbeitet und nicht "normalerweise"
2) Weil man es wie gesagt ausnutzen kann um höhere Highscores zu erreichen.
(Who cares bei einem Breakout-Clone, aber wie gesagt - das Problem stellt sich ja auch bei anderen -
womöglich wichtigeren Problemen)

@Blitzcraft

Zunächst ist Breakout hier nur ein Beispiel - das Problem lässt sich auf jedes beliebige und womöglich wichtigere Problem übertragen.
Außerdem können Lags von anderen Programmen hervorgerufen werden. Man weiß nie was gerade noch
alles auf einem PC am laufen ist und wann es was in welchem Ausmaß berechnet und die CPU für das eigene Programm lahm legt.
Das Programm zu beenden bei zu niedriger FPS ist vermutlich bei dem ein oder anderen Ansatz teil der Lösung.

Midimaster

BeitragDi, Aug 21, 2012 10:51
Antworten mit Zitat
Benutzer-Profile anzeigen
Der Code soll eine Experimentierumgebung sein. Die Werte kannst Du völlig beliebig setzen. Je kleiner der Wert TIMESTEP% ist, desto schneller läuft die Action im Spiel. Du hast recht, er sollte nicht unter die benötigte Action-Zeit sinken.

Meine Erfahrungen sagen allerdings, dass nur selten Action-Zeiten von länger als 5 msec vorkommen. Eher liegt dies meist bei 1msec. Die meiste Zeit geht doch bei der Grafik drauf. Der Kommentar meint: "Probier auch 10"
Auch die 16 ist ein vernünftiger Wert. Je kleiner Du den Wert setzt, desto feiner werden "Begegnungen" erkannt, oder physikalische Abläufe weicher berechnet.


Die PAUSE-Zeit muss eben so gewählt werden, dass ein "Windows-Nachdenken" von 120msec (was schon mal vorkommt) nicht gleicht zum Spielstop führt. sicherlich kann man hier auch 250msec einsetzen. Am besten ausprobieren.


Noch ein Gedanke zu echtem Delta:

Um bei echtem Delta sicherzustellen, dass die Physik stimmt, habe ich manchmal dann schon das Addieren in mehrere Steps zerlegt, um sichzustellen, dass die Kollisionen wirklich entdeckt werden.

vorher:
es ist nur eine Frage der Zeit bis der Ball durch die rote wand schlupft:
BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
SetBuffer BackBuffer()
FPS=CreateTimer(60) ; bei 200 passiert nix!

Global Delta#

Global X#=10, Y#, Xadd#=.2, Yadd#=.2

DeltaZeit=MilliSecs()


Repeat
Delta=MilliSecs()-DeltaZeit
DeltaZeit=MilliSecs()
AllAction Delta
AllGrafik()
WaitTimer FPS
Until KeyHit(1)

Function AllAction(locDelta#)
;DebugLog "action"


X=X+Xadd * locDelta
Y=Y+Yadd * locDelta

; rote Wand:
If X>399 And X<401 Then Xadd=-Xadd


If X<0 Or X>800 Then Xadd=-Xadd*1.02
If Y<0 Or Y>600 Then Yadd=-Yadd*1.02
End Function

Function AllGrafik()
;DebugLog "Grafik" + x
Cls
Oval X,Y,15,15
Color 255,0,0
Rect 400,0,1,600
Text 50,400,delta
Flip 0
End Function



Nachher:
BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
SetBuffer BackBuffer()
FPS=CreateTimer(20) ; jetzt passier auch bei 20 nix!

Global X#=10, Y#, Xadd#=.2, Yadd#=.2

DeltaZeit=MilliSecs()

Global Delta#

Repeat
Delta=MilliSecs()-DeltaZeit
DeltaZeit=MilliSecs()
While Delta>5
AllAction 5
Delta=Delta-5
Wend
AllAction Delta
AllGrafik()
WaitTimer FPS
Until KeyHit(1)

Function AllAction(locDelta#)
;DebugLog "action"

DeltaXadd#=Xadd * locDelta
DeltaYadd#=Yadd * locDelta

X=X+DeltaXadd
Y=Y+DeltaYadd

; rote Wand:
If X>399 And X<401 Then Xadd=-Xadd


If X<0 Or X>800 Then Xadd=-Xadd*1.02
If Y<0 Or Y>600 Then Yadd=-Yadd*1.02
End Function

Function AllGrafik()
;DebugLog "Grafik" + x
Cls
Oval X,Y,15,15
Color 255,0,0
Rect 400,0,1,600
Text 50,400,delta
Flip 0
End Function


Gewinner des BCC #53 mit "Gitarrist vs Fussballer" http://www.midimaster.de/downl...ssball.exe
 

BBPro2

BeitragDi, Aug 21, 2012 11:09
Antworten mit Zitat
Benutzer-Profile anzeigen
jap genau so hatte ich mir das vorgestellt.

der 2. code scheint mir eine sehr gute und vollständige lösung des problems darzustellen.

natürlich muss timestep jetzt noch vernünftig gewählt werden
(klein genug damit keine effekte auftreten die man nicht will, groß genug damit jeder pc
auf den man abzielt das auch schafft und es zu keinem sich selbst verstärkenden stau kommt)

dank dir, ich denke so oder so ähnlich werde ich es im endeffekt auch umsetzen.
sollte ich auf weitere probleme/ansätze/ideen stoßen werde ich diese ebenfalls hier posten

Neue Antwort erstellen


Übersicht BlitzBasic Allgemein

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group