Bewegungen über Netzwerk synchron halten



  • Hallo zusammen,

    ich versuche gerade ein sehr einfaches "spiel" netzwerkfähig zu machen.
    Es gibt zwei Rechtecke, die von je einem Spieler mit der tastatur gesteuert werden können.
    Außerdem ist einer der Spieler der Server und einer der Client.
    Ich möchte jetzt, dass beide spieler ihre rechtecke bewegen können und das beim jeweils anderen sichtbar wird.
    Langfristig soll es natürlich viel mehr als nur zwei rechtecke und auch mehr als nur 2 spieler geben, aber ich wollte erstmal klein anfangen zum ausprobieren.

    Mein erster Ansatz funktioniert so, dass der Server die gesamte spiellogik ausführt und dem client alle daten die er zur anzeige benötigt schickt. Der Client schickt dem server seine Tastureingaben, und erhält periodisch updates vom server.

    Ich sende im moment per tcp 20x pro sekunde von server einen neuen "gamestate" und 20x pro sekunde vom client zum server die aktuellen eingaben.

    Beim server läuft die logik mit 100 logikframes pro sekunde.

    Beim server sieht das ganze gut aus, aber beim client hat das einen ruckelhaften eindruck.
    Ist auch nicht verwunderlich bei 20 updates pro sekunde + delay.

    Dann habe ich meinen ansatz überarbeitet und wollte den client selbst logik ausführen lassen. Der client interpoliert also neue gamestates und zeigt neu berechnete positionen an, um die verzögerung unsichtbar zumachen, solange bis er vom server den "wahren" neuen gamestate erhalten hat.
    Ich habe das dann zunächst mal so gemacht, dass der client wenn er einen neuen gamestate erhält seinen eigenen sofort durch den server-gamestate überschreibt.
    Das hat leider einen sehr komischen effekt, so dass man immer ein wenig hin und herspringt, die positionen beim server stimmen nicht ganz mit denen des clients überein.
    Das liegt wohl hauptsächlich am delay, einmal hat das senden von eingaben vom client zum server verzögerung und auch das senden des gamestates zurück, so dass dann differenzen auftreten.

    Ich muss meine lösungen noch irgendwie bearbeiten, damit das ganze richtig funktioniert.
    Eine idee von mir war nicht sofort den clientgamestate mit dem servergamestate zu überschreiben, sondern langsam dahinzuinterpolieren, allerdings ist das aufwändig und einige sachen wie z.B. teleportationen (gibts bisher natürlich nicht) sind schwer zu interpolieren.

    Ich habe noch etwas anderes probiert:
    Ich habe bei jedem logikframe einen turncounter hochgezählt und beim senden des serverstates zum client diesen turncounter mitgeschickt. Es treten idr. nur differenzen von 2 turns (heißt 2 logikframes bzw. 20ms) auf, was eigentlich nicht viel ausmachen sollte.
    Ich habe das dann so gemacht, dass wenn ich einen neuen gamestate vom server empfange und merke, dass er 2 logikframes in der vergangenheit liegt, dass ich dann noch 2 logikframes sofort simuliere bevor ich etwas anzeige, aber das hat keine sichtbare besserung gebracht.

    Ich hoffe, dass das ganze so verständlich war mit logikframes usw 🙂

    Hat jemand vorschläge?
    Links zu tutorials o.ä. sind natürlich auch gut. Bisher habe ich ein bischen zu sowas gefunden, aber nichts wirklich konkretes, sondern entweder reinen netzwerkcode oder nur eine ganz grobe beschreibung der prinzipien mit stichwörtern wie interpolation...

    Vielen Dank schonmal!



  • TCP ist für so etwas nicht geeignet. Für kleine Datenmengen, welche unkritisch sind ist UDP einiges besser, da es viel weniger Overhead mit sich bringt. Probier das mal. Das sollte bereits recht viel ausmachen.

    //EDIT
    Das gehört wohl eher ins Spiele-Unterforum, als hier hin. 😉
    / oder web. ^^



  • Dieser Thread wurde von Moderator/in CStoll aus dem Forum C++ (auch C++0x) in das Forum Webzeugs verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • Mit udp werd ich mal versuchen.

    Ich dachte aber eigentlich nicht, dass die verbindung das problem ist, ich habe bisher nur minimale datenmengen und sende 20 mal pro sekunde, ich kann auch mal versuchen das höher zu stellen, aber wenn man jetzt größere datenmengen und udp hat dann kommt das doch vermutlich wieder so raus, wie es jetzt ist, oder?

    Ich dachte eigentlich das das problem weniger die netzwerkgeschichte selbst ist, sondern sachen wie interpolation gebraucht werden, was weniger mit webzeugs zu tun hat.



  • Macht schon einiges aus. Nicht nur, dass TCP mehr Daten braucht, sondern es braucht auch mehr Processing auf den Hosts (auch Router). Und das kann bei einer höheren Frequenz durchaus was ausmachen.
    Das eigentliche Problem hast du dann natürlich immernoch, aber es sollte sich schon recht verringert haben.

    Ich habe jetzt noch nicht direkt Erfahrung mit der Behebeung des Problems, aber grundsätzlich machst du das denke ich schon richtig.

    Vielleicht hilft noch das hier:
    http://en.wikipedia.org/wiki/Client-side_prediction

    Das hier könnte für dich auch interessant sein: http://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

    //Spiele-Unterforum wäre besser gewesen, ja. 🙂

    //EDIT:
    Also was TCP<->UPD anbelangt kann ich aus eigener Erfahrung sprechen. Habe im lokalen Netzwerk mit dem Telefon zuerst mittels TCP ein Spiel auf dem PC gesteuert, was kaum auszuhalten war aber dann auf UDP gewechselt, was dann ganz smooth ging.



  • Also ich habe das bei mir so geregelt, dass der Server grundsätzlich die echte Logik ausführt und der Client gewisses Spielerverhalten vorhersagt (Client-side prediction, siehe Link), was in vielen Fällen gut funktioniert aufgrund von zeitlicher Lokalität. Um das Ruckeln auf Client-Seite zu unterbinden, gestatte ich eine gewisse Fehlertoleranz zwischen Spielzustand von Client und Server, die bei einem Servertick etwas angepasst wird, sodass kein direktes Ruckeln auftritt und der Fehler pro Tick aber minimiert wird / konstant gehalten wird.

    Und bei Echtzeitnetzwerkverkehr benutzt man eigentlich grundsätzlich UDP. Natürlich muss man das Spielprotokoll dann entsprechend anpassen, weil manche Pakete nicht / doppelt / in unterschiedlicher Reihenfolge am Ziel ankommen. Besonders ersterer Fall ist manchmal annehmbar, manchmal nicht.



  • So,
    ich habe inzwischen das ganze mal mit UDP ausprobiert.
    Allerdings funktioniert das ganze bisher sehr schlecht, viel schlechter als mit TCP.
    Mir ist klar, dass das nicht an UDP selbst liegt, sondern an meiner Verwendung, aber im Moment weiß ich nicht wirklich weiter...
    Sobald das mit der Verbindung über UDP funktioniert, werde ich am interpolieren weiterarbeiten, inzwischen habe ich einen ganz guten artikel hier gefunden:
    http://gafferongames.com/game-physics/networked-physics/

    Hier ist der gesamte Code:
    http://codepad.org/qsznZjow

    Hier noch zwei wichtige Stellen:

    void Game::sendGameState()
    {
    	sf::Packet toSend;
    	toSend << myServerPlayer.xPos << myServerPlayer.yPos << myServerPlayer.input
    		<< myClientPlayer.xPos << myClientPlayer.yPos;
    	sf::Socket::Status status = mySocket.Send(toSend, myIPAddress, PORT);
    	switch(status)
    	{
    	case sf::Socket::Disconnected:
    		cout << "Disconnected" << endl;
    		myIsConnected = false;
    		break;
    	case sf::Socket::Error:
    		cout << "Error sending" << endl;
    		break;
    	case sf::Socket::Done:
    		break;
    	case sf::Socket::NotReady:
    		//try again next time;
    		break;
    	}
    }
    
    void Game::receiveAndSyncGameState()
    {
    	sf::Packet toReceive;
    	unsigned short port = PORT;
    	sf::Socket::Status status = mySocket.Receive(toReceive, myIPAddress, port);
    	switch(status)
    	{
    	case sf::Socket::Disconnected:
    		cout << "Disconnected" << endl;
    		myIsConnected = false;
    		break;
    	case sf::Socket::Error:
    		cout << "Error receiving game state" << endl;
    		break;
    	case sf::Socket::Done:
    		toReceive >> myServerPlayer.xPos >> myServerPlayer.yPos >> myServerPlayer.input
    		>> myClientPlayer.xPos >> myClientPlayer.yPos;
    		//no interpolation yet
    		break;
    	case sf::Socket::NotReady:
    		//try again next time;
    		break;
    	}
    }
    

    Also was passiert ist, dass beim Client das Rechteck des Servers immer zwischen zwei Positionen hin und herspringt, evtl. hat das was mit UDP zu tun und ich muss irgenwelche "Sicherungen" einbauen?
    Oder ist es einfach nur ein ganz blöder bug den ich eingebaut hab? ^^

    Außerdem kommt es zu verzögerung und zu socket errors wenn ich mit hoher rate verschicke (im moment steht auf 100, aber getestet hab ich vorher mit 20/s, da hats auch nicht besonders gut funktioniert).
    Natürlich muss ich die Rate des Updates noch von der Netzwerk verschickungsrate entkoppeln, steht auch in dem kommentaren, aber das kommt später.

    Ich bin mir auch nicht ganz sicher, ob meine verwendung der asynchronen sockets so "richtig" ist.

    Auf jeden Fall bin ich mit dem Ergebnis bisher nicht zufrieden, selbst mit TCP war es besser.

    Falls ihr das ganze kompilieren wollt, braucht ihr SFML.
    Bei Bedarf lade ich gerne die .exe Datei hoch.

    Wäre schön, wenn ihr mir helfen könnt 🙂



  • Sry für doppelpost, kann leider nicht editieren, weil ich unregistiert bin.
    Sollte mich echt mal registrieren^^

    Bei mir läd codepad nicht mehr, deswegen hier nochma der link bei pastebin vom gesamten code:
    http://pastebin.com/2ERzSk1n

    Sind übrigens insgesamt 330 zeilen, vieles aber davon geschweifte klammern und leerzeilen, ist nicht soviel wies sich vll anhört also guckts euch ruhig an 🙂

    Vielen Dank schonmal für eure Hilfe!



  • Ich habs inzwischen mit boost::asio getestet und habe sf::Packet zum konvertieren der Daten benutzt, jetzt funktioniert das ganze wunderbar.
    Also lags wohl an sfml oder an meiner benutzung von sfml (async udp).


Anmelden zum Antworten