Netzwerkspielprogrammierung

Hey Leute.
Ich habe eine Frage, es geht in die Richtung Netzwerkspieleprogrammierung.

Ich wollte mal wieder mit dem Thema anfangen, nur diesmal alles mit gelerntem
in Opengl verwenden. Also ein Netzwerkspiel welches mit Opengl gerendered wird.

Den Rendervorgang sehe ich als nicht notwendig zu beschränken. Das heißt ich lass
eine Grafikanwendung meist mit so viel FPS laufen wie die Grafikkarte eben schafft.
Das bedeutet bei mir bei einem einzigen gezeichneten Kästchen auf dem Bildschirm
an die 10-14000 fps.

Mein Server Client system sieht grob so aus:

Server: Speichert Spieler-Verbindungen, (akzeptierte Sockets), hat für jede
Verbindung eine read() (returned einen String wenn reader.ready()) und eine write(String command) methode.
Hat die Aufgabe zum Beispiel beim Spielstart den Clients “newgame:parameter” zu schicken, und bei annahme
von zB player positionen oder anderen daten die alle clients brauchen diese an letztere weiterzuschicken.

Client: Hat erbenfalls eine read und eine write methode. Schickt seine daten an den Server, der arbeitet damit weiter.
Empfängt zum Beispiel gegenerpositionen und macht diese auch sichtbar.

So. Wenn ich jetzt hingehe und 14000 mal meine Position an den Server schicke… Naja… Ich habs gestern tatsächlich
gemacht, nach n paar sekunden stürzt das Programm einfach ab. Ist natürlich sinnfrei. Besser wäre es in Regelmäßigen
Intervallen Pakete an den Server zu versenden. Und hier stellt sich meine Frage: In welchen? Sollte dieser Intervall fixed sein,
und einfach durch eine art counter repsäntiert werden? Vielleicht sogar in der Game Loop? Oder sollte die Kommunikation in einem
anderen Thread laufen? Wie macht man das am besten?

if(ticksInSeconds % 50 == 0) sendInformatinoToServer();

so etwa? Das wären dann ja 20 Pakete in der Sekunde, ist das optimal? Oder kompletter Quatsch?

Ich finde im Netz irgendwie nicht wirklich was dazu. Als konkrete Codebeispiele oder sowas… bin
vielleicht zu blöd zum googeln…

Naja, danke fürs lesen

*** Edit ***

Vor allem was passiert, wenn jemand nen 150.er Ping hat? dann bekommt der meine Infos ja maximal 6.6 periode mal in der Sekunde, und nicht 20?
Dann sieht der alles verzögert oder wie?..

[QUOTE=mymaksimus]Vor allem was passiert, wenn jemand nen 150.er Ping hat? dann bekommt der meine Infos ja maximal 6.6 periode mal in der Sekunde, und nicht 20?
Dann sieht der alles verzögert oder wie?..[/QUOTE]
Er bekommt trotzdem 20 Pakete pro Sekunde, nur eben um 150ms verzögert. Nur wenn die Bandbreite nicht ausreicht um die Pakete zu übertragen, dann gehen Pakete verloren.

Ich würde einen separaten Thread verwenden, um die Positionsdaten zu übermitteln. Diesen würde ich dann nach der Paketübermittlung schlafen legen, bis das nächste Paket fällig wird.

Okay, und wie arbeitet man dann mit solchen verzögerungen?

Bei Echtzeitspielen lässt sich die Latenz nicht vermeiden. Deshalb versuchen “Onlinespieler” ja auch möglichst geringe Latenzen in ihrer Internetverbindung zu haben (ich erinnere mich da an Werbeargumente von ISPs die “FastPath” für DSL angeboten haben, um Spieler anzulocken).

Eine etablierte Lösungsstrategie um die Effekte zu minimieren kann ich dir nicht nennen, weil ich mich mit Spieleentwicklung noch nicht beschäftigt habe. Ein Lösungsweg **kann **sein, dass die Spieler sich alle in einer Welt befinden, die um die Hälfte der Latenz älter ist. Die im Client ausgelösten Aktionen wirken sich auf eine Welt, die um die Hälfte der Latenz jünger als die Clientwelt ist, aus.
Ob das ein sinnvoller Ansatz ist, kann ich dir aber nicht sagen. Sicherlich findet man haufenweise Literatur zu verschiedenen Ansätzen.

*** Edit ***

Eine Anmerkung noch zu dem Ansatz if(ticksInSeconds % 50 == 0) sendInformatinoToServer();
Du sagst, dass 20 Ereignisse pro Sekunde ausgelöst würden. Das impliziert, dass ticksInSeconds nicht in Sekunden, sondern in Millisekunden angegeben ist. Ergo: schlechter Variablenname.
Noch entscheidender ist allerdings, dass du bei der Verwendung von System.currentTimeMillis() je nach Betriebssystem nicht unbedingt eine millisekundengenaue Auflösung hast. Daraus folgt, dass du ggf. sehr lange keinen Wert bekommst, der durch 50 teilbar ist.
Darüber hinaus hast du für den Fall, dass der Wert gerade einmal durch 50 teilbar ist einen Burst, weil du bei 14000 fps pro Millisekunde 14 Schleifendurchläufe hättest. Wenn dann noch die Granularität des Timers schlecht ist, bekommst du ggf. deutlich größere Paketanzahlen.

Das Problem hättest du mit einem weiteren Thread, der nur für Positionsupdates zuständig ist, nicht. Den könntest du einfach schlafen legen, bis das nächste Update fällig ist. Dabei musst du natürlich die Threadsynchronisation beachten.
Wenn du zusätzlich zeitunabhängig noch Aktionspakete übertragen möchtest, würde ich das sofort und nicht erst im regulären Zeitslot machen.

Jop , stimmt, muesste milliseconds heissen.
Ich benutze auch nanotime ^^

Ich denke ich probier einfach mal ein paar sachen aus… mal schauen. Danke dir :wink:

Nun, interessant wäre mal was für eine Art von Spiel du denn vorhast umzusetzen. Soll es ein FPS in Richtung CS werden oder eher was MMO-mäßiges wie WoW ? Oder 'n einfaches “Brettspiel” wie Monopoly ? Denn davon kann man schon mal ableiten welche Daten wie oft übermittelt werden sollten. Auch muss man klären ob alles nur auf dem Server laufen soll (wenn ja : mit welcher Tick-Rate) und die Clients dann mit Interpolation arbeiten oder ob auch die Clients ein “Mitspracherecht” haben.

Bei einem FPS wäre es natürlich schon besser wenn der Server mit einer recht hohen Tick-Rate läuft und dann über UDP die Daten quasi möglichst gespammt werden (mit entsprechender Paket-Nummerierung) damit halt alle immer die neuesten Daten haben. Bei einem MMO wäre es nicht so zeitkritisch (auch im PvP nicht wirklich) und man könnte halt ein fixed-Rate von ein paar Millisekunden machen. Bei nicht-Echtzeit würde sogar TCP völlig ausreichen da es hier dann eher darauf ankommen würde das die Daten korrekt und gesichert ankommen, was ja bei nem UDP-Paket-Spam bei nem Echtzeit ganz anders ist da hier die Daten ja möglichst schnell eintreffen müssen wodurch es dann auch egal ist wenn die Reihenfolge nicht stimmt oder mal was verloren geht was die Clients dann mit Interpolation kompensieren.

Was grundsätzlich zu beachten ist : Daten-Übertragung sollte vom Rendering strikt getrennt sein. Einmal in die Richtung das du das versenden der aktuellen Client-Daten nicht von den FPS abhängig machst und andersrum natürlich das du die FPS auch nicht vom Empfangen abhängig machen darfst sondern mit Interpolation arbeiten musst, also das du die letzten Daten nimmst und damit bis zum nächsten Update das nächste vorrausberechnest. Wenn dann das Update kommst musst dies mit deiner Interpolation abgleichen und ggf nachkorrigieren. Alles nicht so einfach.

Ja, das mit der Interpolation hab ich mir gedacht.
Ich hab überhaupt nicht vor, ein richtiges Spiel zu programmieren, (wenigstens nicht mit multiplayer… noch… aber egal ^^)
aber wollte das halt mal ausprobieren, und zwar eben etwas in die richtung… wo die ganze zeit alles aktualisiert werden muss,
mmo oder was auch immer.

Zum thema udp und tcp ist klar, tcp geht nur wenn das eine art rundenbasiertes spiel ist, bei daten spam,
sprich jederzeit veränderbaren bewegungen usw. braucht man halt udp. Inwiefern meinst du aber jetzt den unterschied
zwischen einem mmo und einem fps? Im prinzip würd ich da ähnlich dran gehen?..

Das könnte ganz interessant sein, hab es eben gefunden :https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

Aber mir ist klar dass das nicht unbedingt alles einfach ist… ^^

Nun, ich persönlich würde einen Unterschied zwischen einem FPS und einem MMO in soweit ziehen als das bei einem MMO der Faktor “zeitkritisch” deutlich schwächer ausfällt, es also nicht nötig ist einen Client quasi im Millisekunden-Takt oder noch schneller extrem viel Daten zuzuschaufeln damit dieser möglichst “LIVE” ist (was auch bei Firewalls gerne zu einer Flood-Protection führt) sondern regelmäßig so alle 20ms - 50ms mal n Paket ausreichend ist und man auch locker mit höheren Latenzen (Pings) leben kann ohne das daraus gleich ein merklicher Nachteil entsteht.

Einfaches Beispiel :

Nehmen wir mal den Klassiker Counter-Strike (passt auch schön zum Valve-Link) : Der Server läuft mit einer bestimmten “Tick-Rate”, also einem bestimmten Zeitinterval in denen die Berechnungen neu durchgeführt und danach an die Clients gesand werden. Ein normaler Standard-Server läuft in der Regel mit einer Tick-Rate von 60Ticks/Sekunde was ca. 16,6ms pro “Tick” entspricht. Eine “normale Durchschnittslatenz” liegt in der Regel irgendwo zwischen 15ms-20ms und guten 60ms-70ms -> bedeutet ein “normaler” Spieler hängt immer ca. 40ms dem Server hinterher, bekommt aber halt trotzdem immer nur alle 16,6ms ein update-Paket. Der Client hat aber seine eigene Zeitrechnung die dank der extrem schlechten Umsetzung sowohl der alten GoldSrc-Engine als auch frühen Source-Builds von den FPS abhingen, so das jemand mit schlechter Hardware einfach mehr “lagg” hatte. Das wurde zwar mitlerweile behoben, führt aber auch gerne mal dazu das durch “zu gute” Hardware quasi “Zukunfts-Laggs” entstehen, also der eigene Rechner schon viel weiter vorausinterpoliert hat als der Server mit seiner Tick-Rate wirklich berechnet hat. Das äußert sich dann dadurch das man z.B. jemanden um die Ecke kommen sieht der laut Server aber dort noch gar nicht sein sollte was beim nächsten Update dann auch korrigiert wird und man halt bei einem Schuss dann trotzdem “vorbei” ist. Warum ? Weils der Server so berechnet hat, und der hat nun mal bei dieser Engine das alleinige Sagen.

Als krasses Gegenbeispiel : WoW. Hier geht es darum das zwei gegnerische Einheiten (PvE/PvP) dem Server ihre Position und aktuelle Aktion melden, der Server das aber nicht unbedingt gegen prüft oder bei kleineren Abweichungen auch einfach mal die Daten vom Client nimmt. Das führt dann gerne mal zu den bekannten Positions-Bugs wo Units bei einem Aktions-Wechsel plötzlich ganz wo anders sind (“rum-porten”). Das betrifft sowohl Spieler als auch Monster. Spontan würde ich das jetzt 1) darauf legen dass das ganze halt deutlich “gemütlicher” abläuft, also mit niedrigerer Tick-Rate und dem nach auch weniger Daten-Verkehr, 2) allerdings auch das es halt auf Grund auch der unglaublichen Masse an Informationen schlicht nicht möglich ist innerhalb eines Ticks ein genaues IST-Abbild der aktuellen Spielwelt zu errechnen so das auch der Server drauf angewiesen ist das die Clients ein Teil dessen übernehmen und gewisse Abweichungen auf beiden Seiten tolleriert werden.
Um beim Beispiel mit dem “um die Ecke treffen” zu bleiben : der Server akzeptiert halt wenn ihm der Client sagt : ich hab Monster xy getötet, schon alleine weil wie gesagt die Kapazitäten fehlen das der Server dieses Ergenis “korrekt” nachprüfen könnte.

Es macht also durch aus einen Unterschied wer was wie schnell berechnen und an den Partner zusenden muss und was passiert wenn beide ein abweichendes Ergebnis ermittelt haben.

Zwar alles sehr subjektiv und eher aus eigener Erfahrung als wirklich durch offizielle Quellen, aber es soll den Unterschied verdeutlichen den ich angesprochen habe, auch wenns mit Sicherheit nicht ganz so auffällig ist wie dargestellt.