Home | Forum | Downloads | Videos | Artikel | Server-Stats | Impressum

Zurück   UTzone.de > News und Ankündigungen > Artikel > UT: Diverses

Antwort
 
Teilen Artikel Werkzeuge Diesen Artikel durchsuchen
 
Alt
Alles was du schon immer über Replication wissen wolltest
von Wormbo 28.10.2012, 02:16

Alles was du schon immer über Replication wissen wolltest (aber dich nicht zu fragen getraut hast)


Replication ist ein mächtiges Biest, das in der Unreal Engine lauert und selbst von erfahrenen UnrealScript-Programmierern mit einer Menge Respekt behandelt wird. Mit diesem Artikel versuche ich zu erklären wie Replication funktioniert und kann hoffentlich einige Mythen und Missverständnisse aus der Welt schaffen.
(Ich bin nicht der beste Tutorialschreiber, daher sind jede Art von konstruktiver Kritik und hilfreiche Rückmeldungen willkommen.)

Dinge, die ihr beim Lesen bedenken solltet


Die Originalfassung dieses Artikels - und ich bestehe darauf, dass es kein Tutorial ist - habe ich für das Unreal Wiki verfasst. Folglich gehe ich nicht bei jedem Feature ins Detail, weil dazu bereits anderswo ausführliche Informationen verfügbar sind. Auch wird es sich nicht umgehen lassen, dass ich einerseits auf englischsprachige Artikel verlinken werde und andererseits auch eine Reihe von englischen Begriffen verwende. Falls bestimmte Begriffe unklar sein sollten, gibt es einerseits die verschiedensten Wörterbücher im Internet (z.B. LEO.org oder Dict.cc) und andererseits könnt ihr einfach nachfragen, falls die Wörterbücher euch nicht weiterhelfen.
Ihr solltet bereits etwas Erfahrung mit UnrealScript mitbringen und andere Sprachfeatures halbwegs anwenden können, bevor ihr euch an komplexere Themen wie Replication heranwagt. Falls Fragen auftauchen, versucht sie zunächst mit Hilfe der einschlägigen Literatur zum Thema zu beantworten. Wenn das nicht weiterhilft, könnt ihr natürlich auch gern im Forum nachfragen.
Eine Sache, die vielen nicht bewusst ist, ist die Verwandschaft von UTs Demo-Features mit Netzwerkspielen. Wenn man eine Demo aufnimmt, fungiert man als eine Art Server für den Demorecorder, während man beim Abspielen der Client für den als Server agierenden Demoplayer ist. Also selbst wenn man eine reine Offline-Mod schreibt, muss man sich mit Replication auseinandersetzen, wenn man Demos unterstützen möchte. Der Unterschied zu echten Netzwerkspielen ist, dass beim Aufnehmen der "Server" vom Client keine Antwort erwartet (die Daten landen einfach im Demo-File) und der Client beim Demoplayback alle Daten verwirft, die an den Server gesendet werden würden.
Ein weiterer interessanter Fall ist UTV. Ein UTV-Server ist prinzipiell ein Proxy-Server, der für den Gameserver wie ein spezieller Client aussieht, aber für seine eigenen Clients wie ein Server agiert. Die Clients eines UTV-Servers sind Zuschauer, die miteinander per Chat interagieren können, aber vom Primärclient abgesehen bleiben ihre Daten auf dem UTV-Proxy und erreichen nicht den Gameserver. Außerdem verzögert der UTV-Proxy absichtlich alle Daten, die er vom Gameserver bekommt, so dass UTV-Clients von Spielern nicht irgendwie zum Schummeln genutzt werden können.

Hintergrund - Womit man auf der Clientseite startet


Ich werde hier nicht erklären, wie man einen Server aufsetzt und mit einem Client die Verbindung herstellt, das ist anderswo bereits ausführlich erläutert. In diesem Abschnitt geht es darum, was eigentlich alles schon da ist, wenn das Spiel eine Map auf einem Client geladen hat.
Um es kurz zu machen: Man fängt mit den meisten Sachen an, die der Mapper ins Level gesetzt hat. Insbesondere werden alle Nicht-Actor-Objekte (Sounds, Texturen, Meshes) geladen, die in der Map verwendet werden. Einige der Actors, die der Mapper eingefügt hat, werden jedoch fehlen. Um genau zu sein, die Engine löscht alle Actors, bei denen weder bStatic noch bNoDelete auf True gesetzt ist. Levelgeometrie, die meisten Lichter, Mover, NavigationPoints, Keypoints, Emitter, Decorations und viele andere Actors sind davon nicht betroffen. Allerdings sind alle Pawns (vor allem in der Map platzierte Monster, Fahrzeuge und Turrets), Projektile, in Unreal Engine 1 und 2 auch Pickups und Trigger weg sein. BEi allen anderen Actors werden die Werte von Role und RemoteRole vertauscht, bis auf ClientMovers und ähnliche Actors, die als bClientAuthoritative markiert sind. Ziemlich viele der statischen und nicht-löschbaren (non-deletable, bNoDelete=True) Actors bekommen dadurch die Role ROLE_None, aber das heißt nicht, dass sie nicht auf dem Client existieren. Es bedeutet nur, dass ihre Eigenschaften nicht per Replication von Server aktualisiert werden.
Was sonst noch alles passiert, bevor die Replication einsetzt, wird im Artikel What happens at map startup im Unreal Wiki erklärt.


Replication-Grundlagen - Actor-Replication


Also, wie kriegt der Client etwas über die anderen Actors mit? Sie tauchen ja im Spiel auf, nicht wahr? Das hierfür verantwortliche Grundkonzept ist die Actor-Replication. Für jeden relevanten Actor erzeugt die Engine einen "Actor-Channel" zwischen dem Server und dem Zielclient. Die Anzahl der aktiven Channels kann man mit dem Befehl "stat net" anzeigen lassen, dieser liegt in der Regel auf der Taste F6. Die Anzahl der dort angezeigten Channels ist deren Gesamtzahl. Die meisten davon sind Actor-Channels, aber die Engine hat auch andere Arten von Channels, z.B. für Voicechat.
Es gibt eigentlich zwei Arten von Actor-Replication, eine für statische und nicht-löschbare Actors (die auf dem Client nicht beim Laden der Map entsorgt wurden) und eine für Actors, die entweder beim Laden entfernt wurden oder vom Server zur Laufzeit erzeigt werden. Wie bereits erwähnt, existieren die statischen und nicht-löschbaren Actors bereits in der Clientwelt, also stellt ihre Replication-Variante nur den Channel zwischen den entsprechenden Server- und Client-Instanzen her. Dabei sollte erwähnt werden, dass statische Actors nur dann der Replication unterliegen, wenn die bereits vor dem Laden (also im Quellcode) als bAlwaysRelevant markiert wurden.
Die andere Variante ist für Actors die weder bStatic noch bNoDelete sind (ich werde sie als "Laufzeit-Actors" bezeichnen, weil sie zur Laufzeit gespawnt und zerstört werden können) und erfordert etwas mehr Arbeit, da der Zielactor auf dem Client noch nicht exisiert. Im Prinzip sagt der Server dem Client, dass der einen Actor des erforderlichen Typs spawnen soll und auch wo er gespawnt werden soll. Durch das Spawnen des Actors auf dem Client werden alle seine Eigenschaften mit den Standardwerten seiner Klasse initialisiert. Der Artikel What happens when an Actor is spawned im Unreal Wiki erläutert die Details der Actor-Initialisierung. Der wichtigste Teil hier ist, dass die Role- und RemoteRole-Werte des Actors vertauscht werden, bevor jeglicher UnrealScript-Code ausgeführt wird.
Falls das jetzt noch nicht offensichtlich ist: Laufzeit-Actors, die vom Mapper platziert wurden, werden entfernt und dann möglicherweise wieder durch die Actor-Replication gespawnt. Dadurch werden alle ihre Eigenschaften auf die Klassen-Standards zurückgesetzt. Einige der vom Mapper vorgenommenen Änderungen werden später auf andere Weise rekonstruiert, aber jetzt sind sie erst einmal weg.

Netzwerkrelevanz - Welche Actors repliziert werden


Bisher hat sich dieser Artikel hauptsächlich mit der Clientsicht beschäftigt. Wechseln wir also zur Serverseite, um ein sehr wichtiges Thema zu besprechen - Übertragungskapazität. Die Übertragungskapazität ist in der Regel der am stärksten beschränkte Parameter eines Netzwerks. Daten werden sequentiell gesendet, also müssen einige Daten zusätzlich zur eigenen Übertragungszeit auch warten, bis andere Daten übertragen wurden. Das erhöht die Antwortzeiten ("Ping") noch weiter und ist in den meisten Spielen unerwünscht. Spiele können nicht die Zeit reduzieren, die Daten für den Weg brauchen, das ist eine feste Eigenschaft der zugrundeliegenden Netzwerkarchitektur. Sie können jedoch versuchen, die Wartezeit der Daten zu verringern, indem sie die Menge der insgesamt zu übertragenden Daten reduzieren. Die Unreal Engine setzt einige Tricks ein, um die Gesamtmenge der Daten zu reduzieren, aber der beste Weg ist immer, überhaupt keine Daten zu senden.
Um herauszufinden, welche Actors überhaupt zu einem Client repliziert werden müssen, führt die Engine einige Überprüfungen durch, um herauszufinden, ob der Actor für den Client relevant ist. Diese Checks können ungefähr mit den folgenden Regeln zusammengefasst werden, die etwa in dieser Reihenfolge geprüft werden:
  1. Wenn der Actor bAlwaysRelevant ist, ist er für den Client relevant.
  2. Wenn der Client oder sein Viewtarget (z.B. ein anderer Spieler, den der Client als Kamera nutzt) den Actor besitzt, ist er relevant.
  3. Wenn der Client ein UTV-See-All-Zuschauer ist, ist der Actor relevant.
  4. Wenn der Client den AmbientSound des Actors hören kann, ist er relevant.
  5. Wenn der Actor auf einem anderen Actor steht oder an einem seiner Bones befestigt ist, ist er für den Client relevant, wenn es der andere Actor auch ist.
  6. Wenn der Actor bHidden oder bOnlyOwnerSee ist und weder andere Actors blocken kann noch einen Ambientsound besitzt, ist er für den Client nicht relevant.
  7. Wenn der Actor sich in einer Zone mit Distancefog befindet und vom Beobacher weiter als das Ende des Distancefogs entfernt ist, ist er für den Client nicht relevant.
  8. Wenn es BSP-Geometrie zwischen der Kameraposition des Clients und dem Mittelpunkt des Actors (!) gibt, ist der Actor nicht relevant.
  9. Der Server kann entscheiden, ob er die gleiche Prüfung für Terrain wiederholt und/oder die CullDistance berücksichtigt, wenn er weitere Bandbreite einsparen will, wodurch der Actor als nicht relevant für den Client eingestuft werden kann.
  10. Der Actor ist relevant für den Client.
Obwohl sie verhindern, dass ein Actor gerendert wird, beeinflussen Antiportals nicht die Netzwerkrelevanz. (Vermutlich weil die Prüfung jedes Actors gegen jedes einzelne Antiportal für jeden Client viel zu aufwendig wäre.)
Wenn ein Actor einmal relevant war, wird er weiterhin als relevant betrachtet, bis die obigen Regeln ihn über einen Zeitraum von wenigen Sekunden als nicht relevant einstufen. Die genaue Dauer wird über RelevantTimeout=... unter [IpDrv.TcpNetDrv] in der Hauptkonfigurationsdatei des Servers in UE1/2 bzw. der Engine.ini in UE3 eingestellt. Der Standardwer ist 5 Sekunden und stellt eine gute Balance dar zwischen dem Entsorgen nicht relevanter Actors und der Vermeidung häufiger Neustarts der Replication bei Actors, die öfters zwischen relevant und nicht relevant wechseln.
Wenn ein vorher relevanter Actor wirklich nicht mehr relevant ist, wird sien Channel zum Client geschlossen und der Actor wird auf dem Client zerstört. (Der Artikel What happens when an Actor is destroyed im Unreal Wiki liefert dazu mehr Detail.) Wenn der Actor später wieder relevant wird, wird er als komplett neuer Actor gespawnt. Wenn ein Actor auf dem Server zerstört wird, werden siene Channels zu allen Clients geschlossen, so dass die entsprechenden Actor-Instanzen auf allen Clients ebenfalls zerstört werden.
Es gibt noch zwei weitere Wege einen Actor-Channel zu schließen, ohne dass die Client-Instanz zerstört wird. Wenn das passiert, übernimmt der Client die "Simulation" des Actor-Verhaltens ohne weitere Hilfe des Servers. Ein Weg ist die Eigenschaft bNetTeporary, die den Actor-Channel sofort nach der Übertragung des Initialisierungspakets an Eigenschaften. (siehe weiter unten) Dieser Modus wird meist für Projektile verwendet, deren Bewegung sich nach dem Spawnen nicht mehr ändert, von einem möglichen Gravitationseinfluss abgesehen. Projektile, die andere Interaktionen als Explodieren oder Abprallen erlauben verwenden bNetTeporary normalerweise nicht. Dazu gehören Projektile, die anbeschossen werden können (z.B. Shock-Altfire, Redeemer oder AVRiL), die ein Ziel verfolgen können (z.B. eingelockte Raketen oder Spidermines) oder die einfach an einem Ziel haften bleiben (z.B. Bio oder Sticky Grenades). bNetTeporaryhat auch den Vorteil, dass sich der Server ncht merken muss, welche Variablenwerte er an die Clients geschickt hat, aber dazu später mehr.
Der andere Weg ist die bTearOff-Eigenschaft, die ebenfalls den Actor-Channel schließt, dabei aber die Werte der Role- und RemoteRole-Eigenschaften wieder zurück tauscht, so dass die clientseitige Instanz eine "autoritative" Instanz wird, d.h. sie verhält sich, als ob der Actor nicht durch Replication sondern durch den Client gespawnt wurde. Im Gegensatz zu bNetTemporary, dass nur im DefaultProperties-Block gesetzt werden kann, muss bTearOff vom Server zur Laufzeit gesetzt werden, um die Replication zu allen Clients gleichzeitig "abzureißen". Auf allen Clients, für die der Actor zu dem Zeitpunkt relevant war, wird das Event TornOff() für den Actor aufgerufen. Sobald ein Actor "abgerissen" ist, wird er nicht mehr zu neuen Clients repliziert, für die er relevant werden könnte.

Variablenreplikation - Eigenschaften auf Clients aktualisieren


So, nun wisst ihr, wie Actors auf den Client gelangen. Jetzt ist es Zeit sich Gedanken zumachen, wie man Modifikationen durch das Netzwerk bekommt. Zur Erinnerung: Wenn ein replizierter Actor auf dem Client gespawnt wird, beginnt er mit den Standardwerten seiner Klasse und die einzigen Informationen vom Server sind die Position des Actors und eventuell seine Rotation, sofern die eine Rolle spielt. Alle anderen Informationen (inklusive zukünftiger Updates der Position oder Rotation) werden separat durch das übertragen, was man Variablenreplikation nennt.
Replizierte Eigenschaften werden immer vom Server zu allen oder einer bestimmten Teilmenge der Clients repliziert, allerdings natürlich nur zu solchen Clients, zu denen ein Channel für den Actor existiert. In der Unreal Engine 1 gab es auch die Möglichkeit, Variablen von dem Client, dem der Actor gehört, Variablen an den Server zu senden, aber dieses Feature wurde aus späteren Engine-Generationen entfernt, so dass die Werte dort immer mittels replizierten Funktionsaufrufen gesendet werden müssen. (Das schauen wir uns später an.) Ein Überbleibsel dieser Zweiwege-Replication ist, dass praktisch alle Variablen-Replikationsbedingungen im Code von Epic den Term Role == ROLE_Authority enthalten.

Replikationsbedingungen


Moment, was ist eine "Replikationsbedingung"? Nun, wie bereits erwähnt kann Variablenreplikation auf eine Teilmenge der relevanten Clients eingeschränkt werden. Die Teilmenge wird durch einen Ausdruck vom Typ bool ausgewählt, den man Replikationsbedingung nennt. Replikationsbedingungen werden in einem speziellen Bereich des Quellcodes, dem Replication-Block, angegeben. Jede Klasse darf nur einen Replication-Block enthalten. Darin darf es eine oder mehrere Replikationsbedingungen geben, die jeweils auf eine oder mehrere Variablen oder Funktionen angewendet werden. Für eine Variable oder Funktion darf es maximal eine Replikationsbedingung geben und für geerbte Variablen und Funktionen darf man keine Bedingung angeben.
Ein typischer Replication-Block in der UE2 könnte wie folgt aussehen:
Code:
replication
{
  reliable if (bNetOwner)
    DieseVariableInteressiertNurDenBesitzer;
 
  reliable if (bNetInitial)
    DieseVariableWirdNurEinmalGesendet;
}
In der UE3 würde er ähnlich sein, nur dass die Schlüsselwörter "reliable" bzw. "unreliable" fehlen. Diese Schlüsselwörter haben keinerlei Auswirkungen auf Variablenreplikation, sie existieren nur weil sie die Replikation von Funktionsaufrufen beeinflussen, aber das kommt später.
Rein technisch handelt es sich bei dem Bool-Ausdruck zwischen den Klammen nach dem "if" um ganz normalen UnrealScript-Code, der auch Funktionen aufrufen könnte. In der Praxis wird das aber niemand tun, weil der Zeitpunkt und die Häufigkeit, in der Replikationsbedingunen ausgewertet werden, nicht vorhersagbar sind. Außerdem passiert das tief im Netzwerkcode und sollte so schnell wie möglich ablaufen. Daher sind bei einigen Klassen die Replikationsbedingungen in Native-Code implementiert, was durch das Schlüsselwort NativeReplication in der Klassendeklaration ausgedrückt wird. Diese Klassen haben trotzdem einen Replication-Block, so dass man trotzdem sieht, unter welchen Umständen Variablen repliziert werden. NativeReplication betrifft außerdem nur die Variablenreplikation, nicht replizierte Funktionsaufrufe.
Also, was für Bedingunen kann man denn so verwenden? Hier sind ein paar Eigenschaften, die nützlich sein könnten:
  • bNetInitial - Ist nur während des Initialisierungspakets wahr, für Variablen, die zusätzlich zu den Spawn-Informationen übermittelt werden.
  • bNetDirty (UE2/3) - Ist wahr, wenn sich Variablen des Actors geändert haben. Um ehrlich zu sein, ich bin nicht ganz sicher, warum es das gibt, da Variablen sowieso nur repliziert werden, wenn sich ihr Wert gegenüber dem zuletzt übertragenen geändert hat.
  • bNetOwner - Ist wahr, wenn der Actor dem Zielclient gehört.
  • bDemoRecording - Ist wahr, wenn zum Demorecorder statt einer "echten" Netzwerkverbindung repliziert wird.
  • bClientDemoRecording - Ist wahr, wenn die Demo auf einem Netzwerkclient aufgenommen wird, falsch wenn die Demo offline aufgenommen wird oder kein Demorecording läuft.
  • bRepClientDemo - Ist auf dem Server wahr, wenn der Client, dem der Actor gehört gerade eine Demo aufnimmt.
  • Level.ReplicationViewer (UE2) - Der PlayerController des Clients, zu dem repliziert wird.
  • Level.ReplicationViewTarget (UE2) - Das aktuelle Viewtarget des ReplicationViewers.
  • WorldInfo.ReplicationViewers (UE3) - Ein dynamisches Array mit Informationen über den/die PlayerController auf dem Zielclient, ihre Viewtargets, Kameraposition und -richtung. Das ist ein Array, weil die UE3 prinzipiell mehr als einen Spieler per Splitscreen auf einem Client unterstützt.
  • Role - Die lokale Netzwerkrolle dieses Actors. Wird eighentlich nur in der UE1 oder bei replizierten Funktionsaufrufen benötigt.
Wenn man sich in den Replication-Blöcken der Standardklassen umschaut, findet man auch andere Variablen. Zum Beispiel wird das Mesh eines Actors nur dann repliziert, wenn der DrawType den Wert DT_Mesh hat.

Was wird repliziert, und wann?


Also wann genau passiert Variablenreplikation? Die kurze Antwort lautet: "Zwischen Weltupdates, sofern sich irgendwas ändert." Allerdings prüft der Server nicht alle Actors in jedem Tick. Jede Actor-Klasse hat eine NetUpdateFrequency, die angibt, wie oft pro Sekunde der Actor auf geänderte replizierte Variablen geprüft werden soll. Der erste Check passiert natürlich sobald der Actor für einen Client relevant wird und bei bNetTemporary-Actors ist das auch schon das letzte Mal. Für alle anderen relevanten Actors wiederholt die Engine die Checks auf geänderte Variablen etwa alle 1/NetUpdateFrequency Sekunden. Normalerweise steht nur begrenzte Bandbreite zur Verfügung, so dass die Engine die verschiedenen Actors priorisieren muss. Das passiert über die NetPriority-Eigenschaft. Je höher die Priorität eines Actors ist, desto wahrscheinlicher wird er geupdatet. Niederpriore Actors werden allerdings nicht "verhungern", denn je länger ein Actor auf seinen Check warten muss, desto wahrscheinlicher wird es, dass er beim nächsten Mal drankommt.
In der Unreal Engine 1 kann man nicht kontrollieren, zu welchem Zeitpunkt ein Updatecheck passiert. In der Unreal Engine 2 kann man einen früheren Check erzwingen (oder ihn zumindest nachdrücklich empfehlen), indem man die NetUpdateTime auf einen Wert in der Vergangenheit, beispielsweise Level.TimeSeconds - 1, setzt. Die Unreal Engine 3 stellt die Eigenschaft bForceNetUpdate zur Verfügung, die man für ein sofortiges Update auf True setzen kann.
Der Server mekrt sich, was jeder Client über die Actors weiß, die zu ihm repliziert wurden. Die initiale Annahme über das Wissen des Client wird aus den serverseitigen Standardwerten der jeweiligen Klasse ermittelt, was lokalisierte und konfigurierbare Werte einschließt, sie aus den Lokalisierungs- und Konfigurationsdateien des Servers gelesen wurden. Mit anderen Worten, Variablen, die als config oder globalconfig deklariert wurden, werden möglicherweise anfangs nicht repliziert, weil der Server glaubt, der Client kennt sie bereits. Daher ist es dringend zu empfehlen, separate Variablen zum Replizieren von konfigurierbaren Werten zu nutzen. Ähnlich ist es, wenn man zur Laufzeit die Standardwerte von Variablen auf dem Server ändert und dann einen neuen Actor der Klasse spawnt - der Server weiß nicht, dass sich die Standardwerte geändert haben und nimmt an, dass der Client darüber Bescheid weiß.
Jedes Mal, wenn eine Variable an den Client gesendet wird, merkt sich der Server den Wert für diesen Client. Dadurch dürfte eine Menge Speicher verbraucht werden, aber der Server spart eine Menge Bandbreite, da er den gleiche Wert nicht nochmal replizieren muss. Mann muss sich nur folgendes Szenario überlegen: Der Server hat einen bestimmten Wert zum Client repliziert, dann wird die Variable auf dem Server mehrfach verändert und hat zum Schluss wieder den gleichen Wert wie der, den der Server bereits gesendet hat. Keine der Veränderungen wurden repliziert, weil sie zu schnell waren, aber der Server hat sich gemerkt, dass Eigenschaften des Actors verändert wurden. Jetzt ist es wieder Zeit für eine Überprüfung auf zu replizierende Werte. Der Server schaut sich also an, welche Werte er dem Client zuletzt geschickt hat und stellt fest, dass sich eigentlich keine replizierbare Variable verändert hat. Um Bandbreite zu sparen sendet der Server diesen Wert also nicht noch einmal, da der Client ihn bereits kennt.

Wertkompression


Wie bereits im Abschnitt über Relevanz erwähnt, hat die Engine ein paar Tricks, um die zu sendende Datenmenge zu reduzieren. Einer dieser Tricks ist, dass bestimmte Werttypen vor dem Senden komprmiert und nach dem Empfang dekomprimiert werden. Diese Kompressin ist allerdinghs nicht verlustfrei, sondern äöndert die Daten, die der Client erhält. Einfache Datentypen sind davon nicht betroffen, nur bestimmte Structs:
  • Vector - Die Komponenten werden auf die nächste Ganzzahl gerundet und als solche gesendet. Dadurch benötigen kleine Vektoren nur mehrere Bits bis wenige Bytes, während die drei unkomprimierten Gleitkommawerte 12 Bytes benötigt hätten. Falls eine höhere Genauigkeit benötigt wird, sollte der Vektor mit einer Konstante multipliziert werden, bevor er an die replizierte Variable zugewiesen wird.
  • Rotator - Nur die Bits 9 bis 16 der Komponenten werden übertragen, was der Operation C & 0xff00 entspricht. Dadurch wird die Datenmenge von 12 auf ungefähr 3 Bytes reduziert. (Komponenten mit dem Wert 0 nehmen nur ein einziges Bit in Anspruch, so dass der Null-Rotator die Minmalgröße von 3 Bits braucht.) Durch die Kompression werden replizierte Rotator-Werte auf Rotationen beschränkt und macht sie nutzlos für Rotationsraten. Die Komponenten von Rotationsraten könnte man zum Replizieren aber in die Komponenten einer Vektorvariablen kopieren. Vorsicht: Dafür sollte man auf keinen Fall Typecasting verwenden, da das Ergebnis ein Einheitsvektor ist. Dadurch ignoriert man nicht nur die Roll-Komponente, sondern unterwirft den Vektor auch starken Rundungsfehlern durch dessen eigene Kompression.
  • Quat - Die Einge nimmt hier an, dass man ausschließlich Einheitsquaternionen repliziert, so dass die W-Komponente komplett ignoriert werden kann. Sie wird auf der anderen Seite aufgrund der Annahme aus den Werten für X, Y und Z errechnet. Ein Quat benötigt daher 12 statt 16 Bytes beim Replizieren.
  • CompressedPosition (UE2) - Das Struct besteht aus Vektoren für Position und Geschwindigkeit sowie einem Rotator für die Rotation. Die Vektoren und die Pitch- und Yaw-Komponenten des Rotators werden wie üblich komprimiert, die Roll-Komponente komplett ignoriert.
  • Plane - Die Kopmponenten werden auf ganzzahlige, vorzeichenbehaftete 16 Bit-Werte gerundet und auf den Wertebereich [-32768,32767] beschränkt. Das entspricht einer Reduktion um 50%, eignet sich aber nicht für die Ebenenrepräsentation durch Normalenvektor und Abstand zum Ursprung. Eine Kugel kann man aber so durch Mittelpunkt und Radius relativ unbeeinflusst darstellen.

Replizierte Werte auf dem Client erkennen


Meistens werden Werte nur repliziert, damit sie auf dem Client zur Verfügung stehen. Manchmal möchte man jedoch auf bestimmte Änderungen sofort reagieren. Je nach Engine-Generation stehen dafür verschiedene Möglichkeiten zur Verfügung.
In der Unreal Engine 1 ist man komplett auf sich gestellt, da es keinerlei Benachrichtigung gibt. Man muss also eine Kopie des zu überwachenden Wertes speichern und diese regelmäßig mit dem Original vergleichen, beispielsweise in Tick() oder Timer().
Die Unreal Engine 2 gibt zumindest Bescheid, dass sie replizierte Variablenwerte empfangen hat, aber sie sagt nicht, welche Variablen repliziert wurden. Man muss bNetNotify auf dem Client auf True setzen, um PostNetReceive()-Aufrufe zu erhalten wenn ein Paket neuer Variablenwerte angekommen ist. Dabei sollte erwähnt werden, dass für einzelne, seltene Ereignisse der Wert von bClientTrigger umgeschaltet werden kann. Dadurch wird auf dem Client das ClientTrigger()-Ereigniss aufgerufen, sobald die Änderung auf dem Client ankommt. (auch ohne bNetNotify)
In der Unreal Engine 3 muss man dann endlich nicht mehr selbst nachforschen, welche Variable sich geändert hat. Um hier Replication-Benachtichtigungen zu erhalten, braucht eine entsprechende Variable nur mit dem Schlüsselwort RepNotify deklariert zu werden und die Engine ruft das ReplicatedEvent() m it dem Namen der Variablen als Parameter auf, wenn ein Wert für diese Variable empfangen wurde.
Man sollte beachten, dass Variablen nicht sofort repliziert werden, wenn ihr Wert geändert wird. Die Engine stellt in der Regel sicher, dass mindestens 1/NetUpdateFrequency Sekunden zwischen den Aktualisierungen eines Actors liegen. Außerdem werden Actors mit höherer NetPriority bevorzugt, wenn nicht genug Platz für die geänderten Werte in allen relevanten Actors vorhanden ist. Actors mit geringerer Priorität müssen möglicherweise länger auf Updates ihrer Variablen warten.
Für sofortige Replikation auf Kosten der Möglichkeit, mehr als einen Zielclient anzusprechen, kann man stattdessen auch replizierte Funktionsaufrufe nutzen.

Einschränkungen


Nicht alle Typen sind replizierbar, andere lassen sich nur unter bestimmten Umständen replizieren. Zum Beispiel können dynamische Arrays überhaupt nicht repliziert werden. Jeder zu replizierene Variablenwert muss zumindest allein in ein einzelnes Netzwerkpaket passen, um repliziert zu werden, aber wenn mehrere Werte eines Actors gemeinsam hineinpassen, werden sie auch gemeinsam übertragen, wodurch etwas Overhead eingespart wird.
Strings und Structs können nur als Ganzes repliziert werden, während die Elemente eines statischen Arrays bei der Replication als separate Variablen behandelt werden. Somit kann ein statisches Array mit hunderten Elementen durchaus korrekt repliziert werden, während ein langer String oder komplextes Struct nicht übertragen wird. Dabei muss man beachten, dass statische Arrays in Structs unter die "als Ganzes"-Regel fallen, während dynamische Arrays in einem Struct bei der Replikation als leer angesehen und von der Übermittlung ausgeschlossen werden.
Actor- und Objekt-Referenzen erfordern ebenfalls etwas Aufmerksamkeit. Actor-Referenzen können nur repliziert werden, wenn der referenzierte Actor entweder bStatic oder bNoDelete ist oder gerade für den Zielclient relevant ist. Referenzen auf Nicht-Actor-Objekte, wie beispielsweise Sounds, Texturen oder Meshes, erreichen den Client nur, wenn das Objekt nicht zur Laufzeit angelegt wurde. Nicht-Actor-Objekte (nicht Referenzen, sondern das Objekt selbst) werden generell [b]nicht[/i] repliziert, so dass man immer ienen Actor braucht, um eine "Verbindung" zwischen dem Server und einem Client aufzubauen.
Aus dem Artikel sollte es bereits hervorgegangen sein, aber nur für den Fall: Es gibt keine Möglichkeit einer direkten Replikation zwischen Clients. Clients können ausschießlich mit dem Server kommunizieren.

Replikation von Funktionsaufrufen - Senden von Nachrichten zwischen Server und Client


Das Wort "Nachrichten" sollte in einem wesentlich allgemeineren Zusammenhang vertanden werden, als nur einfache Textnachrichten. UnrealScript-Funktionen können bis zu 16 Parameter besitzen und jeder Parameter kann einen der vielen eingebauten und selbstdefinierten Datentypen haben. Replizierte Funktionsaufrufe können fast die gesamte Bandbreite an Features nutzen, die man sich vorstellen kann. Für Parameter existieren die gleichen Beschränkungen wie für die Variablenreplikation, mit zwei Ergänzungen: Der gesamte Funktionsaufruf muss mit all seinen Parametern in ein einzelnes Netzwerkpaket passen, und nur das erste Element eines Parameters, der als statisches Array definiert ist, wird repliziert, während die anderen Elemente als entsprechende Null-Werte empfangen werden. Falls das Limit der Paketgröße erreicht wird, muss man einen Weg finden, die Daten in separate Aufrufe aufzuteilen. Falls ein statisches Array repliziert werden soll, kann man es in ein Struct verpacken. Das erleichtert es auch, das statische Array durch einfache Zuweisungen zu speichern, da Structs immer als Ganzes kopiert werden, während das bei statischen Arrays nur in Funktionsaufdrufen möglich ist.
Ok, schauen wir uns an, wie Funktionsaufrufe repliziert werden. Hier gibt es Unterschiede zwischen den Unreal Engines 1 und 2 und der Unreal Engine 3. Die Engine-Generationen 1 und 2 nutzen den Replication-Block, um anzugeben, wann der Funktionsaufruf zur Gegenstelle repliziert werden soll. In der Regel wird dabei entweder Role == ROLE_Authority angegeben, um die Funktion vom Server zum Client zu senden, und Role != ROLE_Authority, um sie vom Client zum Server zu schicken. Andere Terme kommen in der Replikationsbedingung praktisch gar nicht vor. (Einzige mir bekannte Ausnahme: Funktionen, die nur an den Demorecorder geschickt werden sollen.) Wichtig: Funktionsreplikation impliziert immer bNetOwner, d.h. Funktionsaufrufe werden nur repliziert, wenn der aufrufende Actor dem beteiligten Client gehört, und der Aufruf kann nur zwischen Server und Besitzer-Client repliziert werden.
Die Unreal Engine 3 nutzt den REplication-Block nicht mehr für Replickationsbedingungen von Funktionsaufrufen. Stattdessen stellt sie verschiedene Schlüsselwörter für die Funktionsdeklaration zur Verfügung, die rt und Richtung der Replikation angeben. Das Schlüsselwort server bedeutet, dass wenn sie auf dem Client aufgerufen wird, dem der Actor gehört, sie auf dem Server ausgeführt werden soll, während das Schlüsselwort client bedeutet, dass der Server den Aufruf an den Client senden soll, dem der Actor gehört. Weil es einfach Sinn macht, schließt das Schlüsselwort client die Bedeutung des Schlüsselwortes simulated mit ein, um sicherzustellen, dass die Funktion auf dem Client auch ausgeführt werden kann, wenn der Aufruf dort ankommt. Ein weiteres Schlüsselwort ist demorecording und bedeutet, dass der Aufruf an den Demorecorder geschickt werden soll.

Aufrufen replizierter Funktionen


Wenn eine replizierte Funktion aufgerufen wird und die Replikationsbedingung zutrifft, werden der Aufruf und die übergebenen Parameterwerte sofort zur Gegenseite gesendet. Wenn die Replikationsbedingung nicht zutrifft, wird die Funktion stattdessen lokal aufgerufen. Das bedeutet, falls der Actor keinen Besitzer hat, oder dieser keinem Client gehört, wird der Funktionsaufruf behandelt, als ob die Funktion nicht als repliziert definiert wurde. Für Funktionen, die vom Client zum Server repliziert werden sollen, bedeutet das in der Regel, dass der Aufruf ignoriert wird, da die Funktion nicht mit dem Schlüsselwort simulated deklariert wurde. Aufrufe vom Server, die auf dem Server bleiben haben keinen solchen "Sicherheitsschalten" und werden tatsächlich auf dem Server ausgeführt.
Obwohl replizierte Funktionen einen Rückgabetyp definieren dürfen, gibt ein erfolgreich replizierter Aufruf einen Null-Wert zurück. Der Code wird nicht auf die Ausführung der Funktion und em Empfang eines Rückgabewertes warten. Wenn eine replizierte Funktion einen Wert zurückschicken soll, muss sie diesen als Parameter an eine weitere replizierte Funktion übergeben, die in die jeweils andere Richtung geschickt wird. Ähnlich sieht es mit out-Parametern aus, deren Wert sich beim replizierten Funktionen nicht ändert. Auf der ausführenden Gegenseite werden out-Parameter oder Rückgabewerte weggeworfen, nachdem die Funktion ausgeführt wurde.
Zu beachten ist auch, dass Parameter den gleichen Kompressionsstrategien wie Variablen unterworfen sind. Außerdem werden Parameter mit Nullwerten aus den Replikationsdaten gestrichen um Bandbreite zu sparen. Das gilt aber nur für ganze Parameter, nicht für einzelne Elemente eines Parameterstructs.

Zuverlässigkeit


Die Replikation von Funktionsaufrufe kann entweder "zuverlässig" oder "unzuverlässig" erfolgen, was jeweils durch das Schlüsselwort reliable bzw. unreliable in der Replikationsbedingung der UE1/2 oder als optionales Schlü+sselwort bei der Funktionsdeklaration in der UE3 angegeben wird. Wenn eine Funktion als "zuverlässig" markiert wurde, stellt die Engine sicher, dass Aufrufe in der korrekten Reihenfolge im Vergleich zu anderen zuverlässigen Netzwerkereignissen erfolgt, insbesondere zu anderen zuverlässigen Funktionsaufrufen. Aber auch das Öffnen und Schließen vn Actor-Channels sind zuverlässige Ereignisse. Mit anderen Worten, sofern sie aufgerufen wurde, nachdem der Server den Actor-Channel geöffnet hat, werden zuverlässige Funktionesaufrufe garantiert vom Client verarbeitet, so lange der Actor existiert, und sie werden auch garantiert in der gleichen Reihenfolge verarbeitet, wie sie auf dem Server aufgerufen wurden.
Warum ist die Reihenfolge so wichtig? Ich erspare euch die Details, doch werden wir uns ein wenig in die technischen Umstände vor wagen müssen, um diese Frage zu beantworten. Die Unreal Engine nutzt ds UDP-Protokoll zur Datenübertragung. Dieses Protokoll baut eigentlich keine Verbindung auf, sondern verschickt nur einzelne Pakete an eine Zieladresse. ES garantiert nicht einmal, dass die Pekete ankommen, und schon gar nicht, dass sie in der ursprünglichen Sendereihenfolge ankommen. Aufgrund der Funktionsweise des Internets können verschiedene Pakete unterschiedliche Wege nehmen und sich ggenseitig überholen. Sie können verworfen oder sogar dupliziert werden.
Das klingt wie ein Albtraum, aber das Fehlen jeglicher Überprüfungen hat auch einen großen Vorteil. Das TCP-Protokoll würde garantierte Reihenfolge und Datenintegrität implementieren, aber all seine Checks erzeugen eine Menge Overhead und bremsen die Übertragung. Bei Dateitransfers mag das kein Problem sein (HTTP, FTP und die verschiedenen Mailprotokolle basieren auf TCP), aber für ein Spiel, das niedrige Reaktionszeiten benötigt, wäre es eine Katastrophe. Also schluckt die Engine die bittere Pille und prüft selbst auf verlorene, duplizierte und umsortierte Pakete. Diese Checks werden aber nur für wichtige Dinge vorgenommen, wie beispielsweise Öffnen und Schließen von Actor-Channels oder zuverlässig replizierte Funktionsaufrufe. Durch Paketloss können aber selbst zuverlässige Funktionssaufrufe verloren gehen, aber die Aufrufe, die ankommen, werden garantiert in der korrekten Reihenfolge ausgeführt.
Unzuverlässige Funktionsaufrufe werden unter Umständen nicht einmal gesendet, wenn die Bandbreite voll ausgeschöpft ist. Wenn sie gesendet werden, gehen sie möglicherweise verloren, werden dupliziert oder in einer anderen Reihenfolge ausgeführt. Im schlimmsten Fall können sie sogar ankommen, bevor der Channel geöffnet oder nachdem er geschlossen wurde, wodurch sie verworfen werden. Im Code werden unzuverlässige Funktionen zum replizieren von Sounds, weniger wichtigen Visualeffects und (das mag überraschen) Spielereingaben verwendet. Falls mal ein Paket mit Eingabeinfos verloren geht, ist das normalerweise kein großes Problem, da der Server Bewegungen extrapoliert und der Client bei Korrekturen relativ viel Freiheit hat. Eine Sprung- oder Schuss-Eingabe zu verlieren mag ärgerlich sein, aber bei der riesigen Anzahl der Eingabepakete hat die unzuverlässige Replikation große Vorteile gegenüber der zuverlässigen, z.B. bessere Reaktionszeiten. Duplizierte oder umsortierte Pakete werden mittels eines Zeitstempels im Funktionsaufruf herausgefischt, so dass veraltete Pakete vom Server verworfen werden können.
Wenn also die Entscheidung ansteht, ob eine Funktion zuverlässig oder unzuverlässig sein soll, stellt man sich folgende Fragen: Wäre es wirklich so schlimm, wenn der Aufruf unterwegs verloren geht oder in der falschen Reihenfolge ankommt? Und wenn ja, wären die Vorteile einer zuverlässigen Funktion es wert, die Reaktionszeit möglicherweise durch den zusätzlichen Überprüfungsaufwand zu erhöhen?

Ok, was ist jetzt eigentlich mit diesem "simulated"-Schlüsselwort?


Ach ja, dieser komische Funktionsmodifizierer. Übrigens kann der auf auf States angewandt werden, um dort State-Code in der gleichen Weise zu beeinflussen. Erinnert ihr euch an Role und RemoteRole zurück und die Tatsache, dass sie auf dem Client vertauscht werden? (Das stand in den ersten paar Abschnitten dieses Artikels.) Nun, das Schlüsselwort simulated, oder eigentlich dessen Abwesenheit, hat mit dem Wert der Role zu tun. Actor-Instanzen (im Gegensatz zu statischen Funktionen und Nicht-Actor-Objekten) führen Code in ihren Funktionen und States nur dann aus, wenn entweder die Role des Actors höher als ROLE_SimulatedProxy ist, oder die Funktion bzw. der State als simulated deklariert wurde.
Offline und auf einem Server haben alle Actors ROLE_Authority, und das gleiche gilt für "Laufzeit-Actors" (also bStatic und bNoDelete beides False), die auf dem Client mit der Spawn()-Funktion angelegt, also nicht durch Replication empfangen wurden. Dann sind da ja auch noch die bStatic- und bNoDelete-Actors mit bClientAuthoritative, deren Role und RemoteRole nicht vertausch wurden, sowie die "abgerissenen" Actors, deren Role und RemoteRole zurück getauscht wurden und deshlb ebenfalls ROLE_Authority haben.
Jetzt sagt die Regel aber "entweder simulated oder Role höher als ROLE_SimulatedProxy", jedoch ist ROLE_Authority nicht die einzige Rolle, die dieser Regel entspricht. Es gibt auch noch ROLE_AutonomousProxy, welche vom lokalen PlayerController und seinem Pawn genutzt wird. Die Rolle wird auf dem Server als RemoteRole gesetzt, jedoch von der Replication-Magie auf anderen Clients zu ROLE_SimulatedProxy degradiert, so dass nur der Eigentümer den Role-Wert bekommt.
Andererseits gibt es auch ROLE_DumbProxy (zumindest in UE 1/2) und ROLE_None. Erinnert ihr euch, dass einige vom Mapper platzierte Actors auf dem Client ROLE_None als Wert für Role erhalten? Das bedeutet nur, dass man keine Replication verwenden kann, aber nichts würde verhindern, simulated Funktionen an diesen Actors aufzurufen, falls sie welche haben.
Mit Zitat antworten
Hits 7877 Kommentare 3
Total Comments 3

Kommentare

Alt 28.10.2012, 08:19   #2
Razorkill.UTzone
Chefanzünder
 
Benutzerbild von Razorkill
 
Registriert seit: 14.11.2009
Ort: Ennepetal
Alter: 58
Beiträge: 3.192
Beigetretene Cups: 4
Cup Siege: 0
Downloads: 42
Uploads: 2
Standard

Respekt! Heftig viel Arbeit.

Hab ehrlicherweise nur die ersten zwei Absätze gelesen.Bin gerade erst aufgestanden und da klappt das Nachvollziehen komplexer Texte noch nicht so recht.Den Rest lese ich dann nachher
__________________

Nature One 2017 We call it Home


Es sind die schmutzigen Jungs,die Herzen brechen....Mit freundlichen Grüßen,DAS HANDWERK
Razorkill ist offline   Mit Zitat antworten
Alt 06.11.2012, 14:48   #3
Sly.
UT-Freak
 
Benutzerbild von Sly.
 
Registriert seit: 14.04.2010
Ort: zu Hause
Beiträge: 3.229
Beigetretene Cups: 0
Cup Siege: 0
Downloads: 62
Uploads: 7
Standard

Einer der ausführlichsten deutschen Artikel zum Thema UScript, gute Arbeit, Wormbo!
Sly. ist offline   Mit Zitat antworten
Alt 06.11.2012, 19:32   #4
slice
Donator
 
Benutzerbild von slice
 
Registriert seit: 11.02.2010
Beiträge: 1.951
Beigetretene Cups: 0
Cup Siege: 0
Downloads: 56
Uploads: 0
Standard

Hut ab!
slice ist offline   Mit Zitat antworten
Antwort

Stichworte
network, netzwerk, replication, unrealscript


Im Moment aktive Benutzer, die diesen Artikel betrachten: 1 (0 Registrierte Benutzer und 1 Gäste)
 
Artikel Werkzeuge Diesen Artikel durchsuchen
Diesen Artikel durchsuchen:

Erweiterte Suche

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge hochzuladen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist an.

Gehe zu

Ähnliche Artikel
Thema Autor Forum Antworten Letzter Beitrag
Maus immer gleich schnell slice UT2004 5 01.08.2012 23:57
Hilfe mit CD-Key, Crack schon versucht, neuen Crack benötigt Skinsuite Archiv 1 13.06.2010 20:12
New Video: Replication X Donzi VideoEcke 0 10.12.2009 20:05


Alle Zeitangaben in WEZ +1. Es ist jetzt 15:32 Uhr.

Powered by vBulletin® Version 3.8.11 (Deutsch)
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
CopyRight-Licence © 1999 - 2024 by UTzone.de