# Steuerung des Bewegungsflusses bei einem Netzwerkspiel



## anfänger15 (2. Mrz 2011)

Hallo zusammen

ich bin gerade dabei als Schuleprojekt ein PingPong Spiel übers Netzwerk zu programmieren.
Dabei gibt es einen Server und 2 Clients pro Spiel.

Die Clients senden bei einem Tastendruck diese Information an den Server.
Der Server fürt die Berechnungen aus und sendet dann die Geschwindigkeit mit der sich das Objekt bewegt zu den Clients.

In bestimmten Zeitabständen wird zusätzlich die aktuelle exakte Position des Objekts vom Server an die Clients gesendet.
Die Bewegung von Objekten wird also gleichzeitig von Server und Clients ausgeführt und immer wieder synchronisiert.

Das Problem ist nun, dass der Server den Loop schneller abarbeitet als der Client (Der Client muss die Objekte auch neu zeichnen).
Außerdem kommt es auch vor, dass der Loop einmal schneller und einmal langsamer abgearbeitet wird. Hierfür merke ich mir die Zeit, die das durchlaufen des Loops benötigt hat und errechne mithilfe der Zeitdifferenz zwischen 2 durchläufen und der Bewegungsgeschwindigkeit des Objekts die aktuelle Position. 

Es ist nun kein Problem, wenn der Loop einmal schneller bzw. langsamer durchlaufen wird, jedoch schaffe ich es nicht den "schnelleren" Server mit dem "langsameren" Client zu synchronisieren ohne das bei der Übertragung der aktuellen Position große Sprünge entstehen.

Zum Synchronisieren benötige ich zusätzlich das Delta des Servers, jedoch ist dafür das Netzwerk zu langsam.

Wie wird das in anderen Netzwerkspielen gelöst bzw. hat jemand eine Idee wie ich das lösen könnte?

Vielen Dank


----------



## anfänger15 (4. Mrz 2011)

hat niemand eine Idee?


----------



## Simon_Flagg (4. Mrz 2011)

vl. könnten die clients immer einen request schicken und damit mit dem server sychronisieren...


----------



## anfänger15 (7. Mrz 2011)

Ich habe deinen Vorschlag mit dem request nun getestet, jedoch ist die Netzwerklast zu groß, wenn ich in jedem Loop einen request schicke. Außerdem habe ich auch noch eine zweite Idee von mir versucht, dabei schicke ich das Delta vom Server an die Clients und errechne mir mit dem Delta vom Server bei den Clients ein neues Delta. Das Problem hierbei ist nun auch wieder, das das Netzwerk zu langsam ist. Um dies zu vermeiden errechne ich den Durchschnitt der Deltas vom Server und übertrage nur diesen Durchschnittswert an die Clients. Dies ist aber zu ungenau.

Wenn also noch jemand eine Idee hat bin ich für jede Hilfe dankbar.


----------



## anfänger15 (7. Mrz 2011)

Ich will nicht unhöflich sein, jedoch würde ich das Projekt gerne abschließen und benötige daher bei o.g. Problem eure Hilfe.

Aufgrund der geringen Anworten (1 Antwort) schließe ich daraus, dass so ein Problem bisher noch nie jemand hatte, was ich mir nicht vorstellen kann.

Im schlimmsten Fall müsste ich das Spiel so umbauen, dass einer Server und einer Client vorhanden ist und der Server ebenfalls auf ein Panel zeichnet. Somit wären beide Loops annähernd gleich schnell. Dies will ich aber nur sehr ungern tun, da dadurch die klare Trennung zwischen Server und Client verloren geht. 

Danke für jede Hilfe.


----------



## muckelzwerg (9. Mrz 2011)

Sorry, ich verstehe das genaue Problem noch nicht. 
"Der Client sendet bei Tastendruck <diese> Information an den Server." 
Welche Information meinst Du? 
Was genau versenden Client und Server für Informationen, während des Spiels? Und was berechnen sie auf ihrer Seite?

Deine "Lösung" mit einem zweiten Panel ist, entschuldige "ein dreckiger Hack" , der ganz schnell wieder auf die Nase fliegen wird, wenn sich die Leistung der Rechner oder des Netzwerks ändert.

Kannst Du nochmal genauer beschreiben, welche Berechnungen Client und Server ausführen und welche Informationen übertragen werden?
Grundsätzlich ist das kein so leichtes Thema, allerdings kommt man bei derart kleinen Beispielen oft mit simplen Lösungen aus.
Vielleicht haast Du schon gewonnen, wenn Du die Uhren der Rechner synchronisierst (NTP ...) und alle Nachrichten mit timestamps austauscht.
Du kannst hoffen, dass das Netzwerk keine wirkliche Rolle spielt und es nur ein nichtvorhandenes Synchronisationsprotokoll ist, das Deine Anwendung zerbröselt.

Ansonsten wirst Du Dich da nämlich erst reinwühlen müssen.
Gamasutra - Features - Dead Reckoning: Latency Hiding for Networked Games
http://www8.cs.umu.se/education/examina/Rapporter/HaishuZhang.pdf
architecture - Networking Pong Clone - Game Development - Stack Exchange
Simple online pong game network synchronization - Stack Overflow
...


----------



## anfänger15 (9. Mrz 2011)

Ok ich versuchs nochmal zu erklären.

Ich habe einen Server und 2 Clients. 
Wird nun vom Spieler1 eine Taste gedrückt sendet der Client dem Server welche Taste gedrückt wurde.
Nun schaut der Server was beim Drücken der Taste passieren soll z.B. Spieler1 nach unten bewegen. Der Server verändert also die Geschwindigkeit des Spieler1 auf der Y-Achse und sendet die Geschwindigkeit, mit der sich der Spieler1 bewegt an beide Clients.
Die Änderung der Geschwindigkeit wird von beiden Clients empfangen und entsprechend gesetzt.

Es werden nun bis zur nächsten Bewegungsänderung von Spieler1 keine Daten mehr zwischen den Clients und dem Server ausgetauscht. Es wird lediglich zum synchronisieren der Clients mit dem Server in regelmäßigen Abständen die aktuelle Position (X,Y Koordinaten) vom Server an beide Clients gesendet.

Die Berechnung der aktuellen Position von Spieler1 wird nun unabhängig voneinander und anhand der Geschwindigkeit mit der sich Spieler1 auf der Y-Achse bewegt von beiden Clients und dem Server errechnet.

Zum berechnen der aktuellen Position von Spieler1 wird beim Client und Server die aktuelle Position mit der Geschwindigkeit verrechnet (z.B. Spieler1.Y = Spieler1.Y + Spieler1.SpeedY). Zusätzlich wird noch die Dauer des Durchlaufes eines Loops berücksichtigt.

Das Problem ist nun, dass der Server weniger Aufgaben (muss nicht auf ein Panel zeichnen) in seinem Loop hat als die Clients und daher den Loop öfters durchlauft. Folglich findet also die Berechnung der aktuellen Position auf dem Server öfters statt als auf den Clients. Aufgrund dessen hat sich der Spieler1 auf dem Server bereits viel weiter bewegt als bei den Clients. Es wird zwar wieder in regelmäßigen Abständen die aktuelle Position von Spieler1 des Servers an die Clients gesendet, jedoch ist die Differenz von Spieler1 auf dem Server und der Position von Spieler1 auf den Clients so groß, dass das Spiel nicht spielbar ist.

Ich habe das Spiel in einem kleinen Heimnetzwerk getestet und kann daher ausschließen, das das Netzwerk zu langsam ist. Mir ist bewusst, dass ich beim senden der akteullen Position vom Server an die Clients eine Verzögerung durch das Netzwerk habe und infolge dessen beim Empfangen so einer "Positionsnachricht" es bei den Clients "ruckelt", jedoch kann ich ausschließen das dies der Grund für die großen Sprünge ist.

Hoffe ich konnte es nun verständlicher erklären. Sollte noch etwas unklar sein einfach schreiben.

Danke


----------



## muckelzwerg (9. Mrz 2011)

Da verbergen sich vermutlich einige Fehler. Aber erstmal ist da eine GANZ wichtige Sache, die Du ändern musst.
Die Position darf nicht von der Anzahl oder der Geschwindigkeit des Schleifendurchlaufs abhängen.
Du kannst nicht einfach in jedem Durchlauf einen bestimmten Wert aufaddieren. Ich schätze mal, Deine Geschwindigkeit, ist gar keine, sondern nur eine Zahl ohne Einheit, richtig?
Das heißt Du hättest zwar ganz gern, dass diese Zahl die Geschwindigkeit ist, sie ist es aber nicht. Die Geschwindigkeit der Objekte ist in diesem Fall die Geschwindigkeit des Schleifendurchlaufs und die Zahl die Du da verwendest nur ein zusätzlicher Faktor.
Läuft die Schleife langsam oder schnell, ist auch Dein Objekt langsam oder schnell. Läuft die Schleife gar nicht, oder pausiert für ein paar Takte, dann bewegt sich Dein Objekt nicht weiter und wird danach auch nicht springen.
Wenn die Schleife mal mehr zu tun hat (Panel zeichnen) wird natürlich Dein Objekt langsamer.

Geschwindigkeit bedeutet "Weg / Zeit". Ein Objekt hat eine Position, eine Bewegungsrichtung und eine Bewegeungsgeschwindigkeit.
Praktischerweise kann man Richtung und Geschwindigkeit in einem Vektor zusammenfassen. Die Richtung des Vektors entspricht der Bewegungsrichtung, die Länge des Vektors entspricht der Geschwindigkeit (z.B.)
Man kann es natürlich auch "getrennt" speichern. In Deinem Fall wird es wohl sowieso nur zwei Richtungen geben, wir reden doch von Pong oder?

Die Geschwindigkeit der Spieler/Schläger/Paddel ... musst Du in Weg/Zeit angeben. Die zurückgelegte Strecke errechnet sich dann aus der vergangenen Zeit seit der letzten Berechnung und dem Wert der Geschwindigkeit.
Führst Du die Berechnung hundert mal in der Sekunde aus, dann bekommst Du hundert Bewegungsschritte die alle ein Hundertstel des Weges pro Sekunde ausmachen.
An der Stelle musst Du schauen, ob Du Probleme mit Rundungsfehlern und zu kleinen Zahlen bekommst. Wenn Du Pech hast, werden die Einzelschritte so klein, dass beim Verändern der Position nichts mehr passiert, bzw. Ungenauigkeiten auftreten.
So ein "Kettenmaß" ist eigentlich nicht so besonders toll. Stattdessen ist es angenehmer immer auf einen gesicherten Startzeitpunkt mit einer gesichrten Startzeit zurückzurechnen.
Von diesem ausgehend wird dann mit der seither verstrichenen Zeit die aktuelle Position berechnet.
Damit auch hier kein langes Kettenmaß und eine zu komplexe Berechnung entsteht, aktualisierst Du den letzten sicheren Stand hin und wieder.
Beispeilsweise bei der Geschwindigkeitsänderung und/oder Richtungsänderung.
Das ist dann der "Keyframe".


----------



## anfänger15 (9. Mrz 2011)

anfänger15 hat gesagt.:
			
		

> Zum berechnen der aktuellen Position von Spieler1 wird beim Client und Server die aktuelle Position mit der Geschwindigkeit verrechnet (z.B. Spieler1.Y = Spieler1.Y + Spieler1.SpeedY). Zusätzlich wird noch die Dauer des Durchlaufes eines Loops berücksichtigt.



Eigentlich müsste dies schon eine Geschwindigkeit sein.
Die genaue Berechnung sieht so aus:

 Spieler1.Y = Spieler1.Y + (Spieler1.SpeedY * (Delta / 1e7))

Delta wird pro Schleifendurchlauf berechnet und entspricht der Differnz der vergangenen Millisekunden zwischen dem letzten Aufruf und dem jetzigen Aufruf.


----------



## Illuvatar (9. Mrz 2011)

Das ist etwas anderes als was du vorher geschrieben hast - und wenn du es so machst, bewegen sich die Spieler beim Server und beim Client gleich schnell, unabhängig von der Anzahl Schleifendurchläufe...

Abgesehen von eventuellen Ungenauigkeiten. Wie berechest du 
	
	
	
	





```
delta
```
, mit 
	
	
	
	





```
System.currentTimeMillis()
```
? Das kann, je nach System, recht ungenau sein - zu empfehlen ist 
	
	
	
	





```
System.nanoTime()
```
.
Außerdem kannst du deine Berechnungen noch hierdurch verbessern:


			
				muckelzwerg hat gesagt.:
			
		

> So ein "Kettenmaß" ist eigentlich nicht so besonders toll. Stattdessen ist es angenehmer immer auf einen gesicherten Startzeitpunkt mit einer gesichrten Startzeit zurückzurechnen.
> Von diesem ausgehend wird dann mit der seither verstrichenen Zeit die aktuelle Position berechnet.
> Damit auch hier kein langes Kettenmaß und eine zu komplexe Berechnung entsteht, aktualisierst Du den letzten sicheren Stand hin und wieder.
> Beispeilsweise bei der Geschwindigkeitsänderung und/oder Richtungsänderung.
> Das ist dann der "Keyframe".


----------



## muckelzwerg (9. Mrz 2011)

Da hat Illuvatar völlig recht. Was für Wiederholraten hast Du? Ich würde currentTimeMillis() nichtmal bei 30ms wirklich vertrauen.

Du kannst es ja sehr leicht testen.
Gib Dir eine feste Geschwindigkeit vor und lass die Schleife mal langsamer und schneller laufen.
Zum Beispiel "10 Einheiten pro Sekunde" (ich kenn Dein Bezugsystem nicht)
Dann lass die Schleife mit unterschiedlichen Wartezeiten laufen und die "Bruchstücke" der Bewegung anzeigen.
Nach 10 Sekunden musst Du da 100 Einheiten haben. Egal ob die Schleife einmal, 10mal, 100mal oder 300mal durchgelaufen ist.
Dafür brauchst Du kein Netzwerk, das kannst Du alles lokal testen.


----------



## anfänger15 (10. Mrz 2011)

Ich bin aufgrund der unterschiedlichen Positionen zwischen dem Server und dem Client davon ausgegangen, dass diese durch die unterschiedlichen Laufzeiten der Threads zustande kommen. Nach einigen Tests habe ich nun herausgefunden, das es doch am Netzwerk liegt. Es dauert zu lange bis der Client dem Server sendet welche Taste er gedruckt hat und der Server darauf dem Client die aktuelle Geschindigkeit zusendet. Da der Server nun schon früher mit den Berechnungen der Geschindigkeit anfägt ist er dem Client ein paar Pixel vorraus. 
Ich werde nun also doch die Zeit, die das senden von Informationen zwischen Server und Client übrs Netzwerk braucht, berücksichtigen müssen.
Habt ihr noch ideen, was ich zwischen dem Client/Server Datenaustausch ändern/verbessern könnte.
Meine Idee, weshalb der Client nur die gedrücken Tasten sendet, war um zu verhindern, dass das Spiel zu einfach manipuliert werden kann. Dies wäre beim senden der aktuellen Geschwindigkeit durch den client kein Problem, da so jeder eine sehr hohe Geschwindigkeit senden könnte.

Danke für eure Hilfe. Das Forum hier ist echt klasse.


----------



## muckelzwerg (10. Mrz 2011)

Uhren synchronisieren, da gibt es bestimmt ein "inline-ntp" für Java, oder was Vergleichbares.
Und dann bekommen die Pakete einen timestamp vom Versenden. Der dient als Basis der Berechnung seit wann sich das Objekt anders bewegt.

Dann miss aber auch mal die Netzwerkdrift. Rätselraten bringt da nicht viel. Du musst das Leck halt richtig finden, bevor Du es reparieren kannst.
Evlt. verschickst Du ja auch "unpraktisch" und must da nochmal was an der Rate etc. ändern.


----------



## anfänger15 (11. Mrz 2011)

Ok nochmals danke für die super Hilfe.


----------



## muckelzwerg (12. Mrz 2011)

Läuft es denn jetzt?


----------



## anfänger15 (13. Mrz 2011)

Es läuft nun soweit, dass es "spielbar" ist jedoch "wackelt" der Ball immer noch beim setzten der Position auf den Clients, da diese durch die Übertragung übers Netzwerk veraltet sind. Die Lösung unter Berücksichtigung der Übertragungsdauer des Netzwerkes kenne ich nun und kann mithilfe deiner geposteten Links das Problem lösen. 
Leider habe ich die nächsten Tage wenig Zeit, sodass ich dies nicht sofort umsetzen kann, sollte ich jedoch Probleme bei der Umsetzung haben werde ich mich wieder melden.


----------



## muckelzwerg (14. Mrz 2011)

Es wäre bestimmt hilfreich, wenn Du am Schluss mal ein bisschen zusammenfasst, welche Probleme aufgetreten sind und wie Du sie gelöst hast.
Da würden sicher einige Einsteiger froh sein, wenn sie sehen, wie weit man bereits bei einem so einfachen Spiel manchmal gehen muss.
"Man denkt da leicht "das ist so simpel, das geht schon irgendwie" und dann ist es aber doch nicht so.


----------



## Friedhelm (18. Mrz 2011)

muckelzwerg hat gesagt.:


> Es wäre bestimmt hilfreich, wenn Du am Schluss mal ein bisschen zusammenfasst, welche Probleme aufgetreten sind und wie Du sie gelöst hast.
> Da würden sicher einige Einsteiger froh sein, wenn sie sehen, wie weit man bereits bei einem so einfachen Spiel manchmal gehen muss.



Dem kann ich beipflichten.


----------



## Firephoenix (18. Mrz 2011)

Hi,
eine andere alternative wie man das realisieren könnte:

der server hat booleans die die clients beliebig steuern können (z.b. boolean spieler1hoch).
die spieler senden an den server per hoch/runter nur anfragen diese variablen zu ändern.
der server arbeitet einmal pro tick das spiel ab und bewegt die spieler entsprechend der variablen.
dann schickt er allen spielern den neuen spielstand usw.

Ich hab das ganze nie implementiert, aber das wäre mein Ansatz dafür, vielleicht hilft es ja trotzdem.
Gruß


----------

