Jump to content
Unity Insider Forum

Unet Multiplayer FPS - Umsetzungsfragen


Recommended Posts

Moin,

ich bastel zur Zeit an einem First Person Shooter - bzw. an einem Toolkit dafür - und möchte den auch Multiplayerfähig machen, kenn mich allerdings noch nicht wirklich mit Unet oder Networking in Unity allgemein aus. Ich hab mir zwar schon ziemlich viel zu dem Thema durchgelesen und mir diverse Tutorials angeschaut aber es macht irgendwie jeder anders und viele bewegen sich sehr nah am Unity Tutorial, das quasi nur die absoluten Basics abdeckt und man nicht mal transform Movement interpolieren kann.
Deswegen mal ein paar Fragen wie man das wohl am besten umsetzen würde was ich vorhabe, mit dem Hintergedanken, dass das Spiel in einem kompetitiven Umfeld genutzt werden können sollte. Sprich: Spielern sollte es so schwer gemacht werden zu cheaten, wie es mit vernünftigem Aufwand nur möglich ist und Delays und Lags sollten ebenfalls mit vernünftigem Aufwand so gering wie möglich gehalten werden. "Der Netcode soll gut werden" wie man heutzutage vielleicht sagen würde ^^

Ich habe oft gesehen, dass auf dem Player die Position und Rotation genommen wird und die wird dem Server dann mitgeteilt, der das Update dann an alle anderen Clients überträgt.
Der Ansatz hat natürlich einige Vorteile. Der Code ist total unkompliziert und kurz und funktioniert unabhängig von Input/AI oder dem Controller (Rigidbody, CC, CustomCC, ...) und man kann sehr einfach eine Sendrate einstellen, die eben besagt, dass der Spieler alle x Millisekunden ein Positionsupdate machen soll/darf.
Nun hat der Ansatz aber auch zumindest einen sehr offensichtlichen Nachteil: Als ambitionierter Spieler mit schlechten Absichten kann man das System unglaublich einfach ausnutzen, es werden einem keine Steine in den Weg gelegt Teleporthacks oder Speedhacks zu benutzen. 
Das kann man zwar wohl abschwächen indem man den Server die Updates validieren lässt, allerdings hilft das nur sehr begrenzt. Angenommen ich möchte da Fahrzeuge reinbringen oder eine Teleportation (was ich tatsächlich auch schon drin habe) gibt es zig Spezialfälle und 100%ig korrekt kann ich das nie auswerten. 

Der andere Ansatz wäre dem Server (fast) komplette Autorität zu gewähren und die User schicken nur ihren Input und der Server berechnet dann alles und schickt die Resultate an alle anderen Spieler. Das ist was ich so gelesen habe und auch wenn man mal logisch überlegt der sinnvollere Ansatz, allerdings auch der deutlich kompliziertere.
Wie wird dann zum Beispiel eine Sendrate umgesetzt? Irgendwie müsste der User dann sämtlichen Input sammeln und im nächsten Sendframe an den Server schicken, der den Input dann mit zumindest derselben Reihenfolge abarbeiten sollte oder? 
Angenommen mein Server läuft aber jetzt mit 60fps und ein Spieler hat jetzt zum Beispiel 28fps während ein anderer 104fps hat. Wie führe ich da das ganze dann zusammen? 
Ich denke zum Senden selbst wäre FixedUpdate nicht verkehrt oder eine Coroutine, die nach Ablauf von (1 / sendrate) Sekunden den Input losschickt, richtig?
 
Aber welchen Input schicke ich denn dann überhaupt los? Denn der Spieler mit 104fps hat u.U. viiel mehr Input angesammelt als der mit 28fps? Schaue ich dann quasi jeden Frame an und speichere mir alle Aktionen dazu ab und sage "Frame 1 hat 16.32ms gedauert, Frame 2 hat 15.87ms gedauert, Frame 3 hat 17.28ms gedauert" und rechne dann auf dem Server mit den abgespeicherten deltaTimes vom Client? Könnte das nicht ebenfalls potenzielles Cheaten ermöglichen? Wenn der User den Input manipulieren kann und sagt jeder Frame indem Movement getätigt wurde ist jetzt 3x so lange, dann würde der Server den Player auch 3x so weit bewegen als er es eigentlich dürfte. Wobei der Server eigentlich relativ einfach überprüfen könnte, ob die angegebene Gesamt-deltaTime mit der vergangenen Zeit seit dem letzten Tick übereinstimmt. Wenn es länger ist kann das nicht stimmen und den Input verwerfen oder anders behandeln.

Und wie werte ich das dann am besten auf dem Server genau aus? Das leichteste wäre wahrscheinlich den Input zu cachen und dann in FixedUpdate (oder einer Coroutine, die auf den Tick eingestellt ist) einfach der Reihe nach im gleichen FixedFrame abzuarbeiten und nach Abarbeiten allen Inputs den Clients ein Update zu schicken. 


Und als zweites: Wie könnte ich einen Dedizierten Server aufsetzen? Einfach einen Host starten, der selbst keinen Player spawnt oder ist da noch mehr dahinter?


Schon mal im Voraus vielen Dank für Antworten, bin um jede Hilfe froh :)
Grüße,
Tiwaz

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 weeks later...

Hallo,

Zuerst zur zweiten Frage, da die etwas einfacher zu beantworten ist:

Im neuen UNet (mit der High Level API) gibt es Server, Host und Client. Ein Server ist ein dedizierter Server,  ein Host ist dagegen ein Server mit einem lokalen Spieler.
Das einfachste Setup ist ein GameObject mit einem angehängten NetworkManager-Script und zusätzlich dem NetworkManagerHUD-Script zu erstellen. Dann wird ein Prefab mit einem NetworkIdentity-Script benötigt, welches man beim NetworkManager als Spielerobjekt zuweist. Ausserdem muss man noch eine Online- und eine Offline-Szene erstellen und dem NetworkManager zuweisen. Sobald man dann das Spiel startet und Server/Host im GUI auswählt, wird die Online-Szene geladen und beim Host das Spieler-Objekt erzeugt (beim Server natürlich nicht, da es noch keinen Client gibt).

Und nun zur anderen Frage:

Grundsätzlich ist UNet ein Server/Client-Modell und es ist durchaus möglich die Autorität dem Server zu überlassen. In Tutorials zeigt und empfiehlt Unity jedoch meistens das Modell mit der Client-Autorität, vermutlich weil es für Anfänger halt wesentlich einfacher ist. Die Server-Autorität ist wirklich ein komplexes Thema (zumindest bei schnellen Action-Games).

In Spielen wie Half-Life etc. wird meistens die Kombination von Client Prediction (auf dem Client) und Lag Compensation (auf dem Server) angewendet. Es gibt dazu auch von Valve einen Artikel, den ich sehr empfehlen kann: https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization

Grob zusammengefasst: Der Client sammelt tatsächlich alle Inputs zusammen mit der deltaTime und der absoluten Zeit bzw. Netzwerkzeit. Server und Client teilen sich die exakt gleiche Physik und Update-Logik. Der Client führt die Physik für jedes Input sofort lokal aus (Spieler bewegt sich lokal ohne Lag). Ausserdem schickt er diese Inputs an den Server, welcher die gleichen Inputs für die gleiche deltaTime abarbeitet und in Form von Status-Updates (Position etc.) bestätigt. Der Client verwirft die gesammelten Inputs nicht sofort nach dem Senden, sondern behält diese noch etwas länger. Empfängt der Client nun vom Server ein Status-Update, dann steht dort drin neben der Position etc. auch welches das letzte vom Client abgearbeitete Input war (z.B. eine Id, oder die Netzwerkzeit). Da der letzte verarbeitete Input aufgrund des Lags in der Vergangenheit liegen muss, heisst das auch dass der Client in der zwischenzeit vielleicht 5 weitere Inputs gesammelt und verarbeitet hat. Also setzt man den Spieler nun an die vom Server gemeldete Position, aber arbeitet die 5 Inputs die zuvor schonmal abgearbeitet wurden noch einmal ausgehend von der Server-Position ab.

Das heisst im Endeffekt: Der Server korrigiert den Client zwar, der Client versucht jedoch das Lag zu umgehen, indem er aufgrund der Inputs vorausrechnet wo er sich eigentlich befinden müsste, wenn er einen Ping von 0ms hätte. Falls es bei den Korrekturen zu kleinen Rucklern kommt, kann man sich auch die Abweichung merken und diese über die Zeit ausgleichen statt sofort (Interpolation).

Die Lag Compensation findet hingegen auf dem Server statt. Diese dient dazu, dass Spieler exakt auf andere Spieler zielen können und nicht vorauszielen müssen. Es wird davon ausgegangen, dass alle nicht lokalen Spieler beim Client interpoliert werden (das heisst z.B. 100ms in der Vergangenheit dargestellt werden, dafür flüssig, ohne Ruckler). Der lokale Spieler befindet sich aufgrund der Client Prediction in der Gegenwart (vom Server aus gesehen, sogar in der Zukunft), während sich nicht lokale Spieler 100ms in der Vergangenheit befinden...

Der Server speichert zu diesem Zweck für eine bestimmte Zeit lang alle Welt-Updates (Positionen aller Spieler in der Welt, Zeitpunkt etc.). Empfängt er nun von einem Client ein Input das verarbeitet werden soll, versetzt er zuerst alle anderen Spieler in die Vergangenheit zurück (sei es für Kollisionen oder Raycasts). Dafür muss man die Latenz des Clients kennen, sowie dessen verwendete Interpolationszeit für nicht lokale Spieler (z.B. 100ms). Man rechnet also die Input-Zeit minus die Latenz minus die Interpolationszeit. In diese Zeit versetzt man alle anderen Spieler zurück, indem man die zwei zwischengespeicherten Welt-Updates heraussucht die direkt vor dieser Zeit und danach liegen. Zwischen diesen beiden Updates werden die Positionen der anderen Spieler aufgrund der berechneten Zeit dann interpoliert. Sind alle anderen Spieler zurückversetzt wird nun das Input verarbeitet. Danach werden die anderen Spieler jedoch wieder an die ursprüngliche Position (Gegenwart) versetzt. Den lokalen Spieler lässt man wo er ist.

Also schon sehr komplex das Ganze...

Hier noch ein paar Unity-spezifische Tipps wie man die Server-Autorität mit der High Level API umsetzen kann:

  • Es empfiehlt sich eine eigene Klasse zu erstellen die vom NetworkManager ableitet und einige Methoden überschreibt.
  • Unity bietet die Möglichkeit von ClientRPCs (Server zu Client) und Commands (Client zu Server). Es ist natürlich verlockend die Client Inputs via Commands zu senden.
    Aus Gründen der Performance empfiehlt es sich jedoch eigene "Messages" zu erstellen, die von der Klasse MessageBase ableiten.
  • Im eigenen NetworkManager kann man die Methode OnStartServer überschreiben und dort die Message registrieren mit: NetworkServer.RegisterHandler(...)
  • Beim Client (z.B. innerhalb eines NetworkBehaviours) kann man die eigenen Messages dann senden via connectionToServer.Send(messageId, message)

Ich hoffe das hilft dir etwas weiter ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...