Bewegungsvorhersage für Nichtschützen


35

Ich arbeite an einem isometrischen 2D-Spiel mit moderatem Multiplayer-Modus, bei dem ungefähr 20 bis 30 Spieler gleichzeitig mit einem permanenten Server verbunden sind. Ich hatte einige Schwierigkeiten, eine gute Implementierung für die Bewegungsvorhersage zu finden.

Physik / Bewegung

Das Spiel hat keine echte physische Implementierung, sondern verwendet die Grundprinzipien, um Bewegung zu implementieren. Anstatt fortlaufend Eingaben abzufragen, werden Statusänderungen (z. B. / Maus runter / rauf / Verschieben) verwendet, um den Status der Charakterentität zu ändern, die der Spieler kontrolliert. Die Richtung des Spielers (dh / Nordosten) wird mit einer konstanten Geschwindigkeit kombiniert und in einen wahren 3D-Vektor umgewandelt - die Geschwindigkeit der Entität.

In der Hauptspielschleife wird "Update" vor "Draw" aufgerufen. Die Aktualisierungslogik löst eine "Physikaktualisierungsaufgabe" aus, die alle Entitäten mit einer Geschwindigkeit ungleich Null verfolgt, wobei eine sehr grundlegende Integration zum Ändern der Entitätenposition verwendet wird. Beispiel: entity.Position + = entity.Velocity.Scale (ElapsedTime.Seconds) (wobei "Seconds" ein Gleitkommawert ist, der gleiche Ansatz jedoch für Ganzzahlwerte in Millisekunden funktioniert).

Der entscheidende Punkt ist, dass keine Interpolation für die Bewegung verwendet wird - die rudimentäre Physik-Engine kennt kein Konzept eines "vorherigen Zustands" oder "aktuellen Zustands", sondern nur eine Position und eine Geschwindigkeit.

Statusänderungs- und Aktualisierungspakete

Wenn sich die Geschwindigkeit des Charakters, den der Spieler kontrolliert, ändert, wird ein "Avatar bewegen" -Paket an den Server gesendet, das den Aktionstyp (Stehen, Gehen, Laufen), die Richtung (Nordosten) und die aktuelle Position des Charakters enthält. Dies unterscheidet sich von der Funktionsweise von 3D-Ego-Spielen. In einem 3D-Spiel kann sich die Geschwindigkeit (Richtung) von Bild zu Bild ändern, wenn sich der Spieler bewegt. Das Senden jeder Zustandsänderung würde effektiv ein Paket pro Rahmen übertragen, was zu teuer wäre. Stattdessen scheinen 3D-Spiele Statusänderungen zu ignorieren und "Statusaktualisierungs" -Pakete in einem festen Intervall zu senden - etwa alle 80-150 ms.

Da Geschwindigkeits- und Richtungsaktualisierungen in meinem Spiel viel seltener vorkommen, kann ich jede Statusänderung nicht mehr senden. Obwohl alle physikalischen Simulationen mit der gleichen Geschwindigkeit und deterministisch ablaufen, ist die Latenz nach wie vor ein Problem. Aus diesem Grund sende ich routinemäßig Positionsaktualisierungspakete aus (ähnlich wie bei einem 3D-Spiel), allerdings viel seltener - derzeit alle 250 ms, aber ich vermute, mit einer guten Vorhersage kann ich es leicht in Richtung 500 ms steigern. Das größte Problem ist, dass ich jetzt von der Norm abgewichen bin - alle anderen Online-Dokumentationen, Anleitungen und Beispiele senden Routine-Updates und interpolieren zwischen den beiden Zuständen. Es scheint mit meiner Architektur nicht kompatibel zu sein, und ich muss einen besseren Algorithmus für die Bewegungsvorhersage entwickeln, der einer (sehr einfachen) "vernetzten Physik" -Architektur näher kommt.

Der Server empfängt dann das Paket und bestimmt die Geschwindigkeit des Spielers anhand seines Bewegungstyps anhand eines Skripts (Kann der Spieler laufen? Ermittelt die Laufgeschwindigkeit des Spielers). Sobald es die Geschwindigkeit hat, kombiniert es sie mit der Richtung, um einen Vektor zu erhalten - die Geschwindigkeit des Objekts. Einige Cheat-Erkennungen und grundlegende Überprüfungen werden durchgeführt, und die Entität auf der Serverseite wird mit der aktuellen Geschwindigkeit, Richtung und Position aktualisiert. Grundlegende Drosselung wird auch durchgeführt, um zu verhindern, dass Spieler den Server mit Bewegungsanforderungen überfluten.

Nach dem Aktualisieren seiner eigenen Entität sendet der Server ein "Avatar-Positionsaktualisierungs" -Paket an alle anderen Spieler in Reichweite. Das Positionsaktualisierungspaket wird verwendet, um die clientseitigen Physiksimulationen (Weltzustand) der entfernten Clients zu aktualisieren und eine Vorhersage und eine Verzögerungskompensation durchzuführen.

Vorhersage und Lag Compensation

Wie oben erwähnt, sind Kunden für ihre eigene Position maßgeblich. Außer im Falle von Betrug oder Anomalien wird der Avatar des Kunden niemals vom Server neu positioniert. Für den Avatar des Kunden ist keine Extrapolation ("Jetzt verschieben und später korrigieren") erforderlich - was der Spieler sieht, ist korrekt. Es ist jedoch eine Extrapolation oder Interpolation für alle entfernten Entitäten erforderlich, die sich bewegen. In der lokalen Simulations- / Physik-Engine des Kunden ist eindeutig eine Art Vorhersage und / oder Verzögerungskompensation erforderlich.

Probleme

Ich habe mit verschiedenen Algorithmen zu kämpfen und habe eine Reihe von Fragen und Problemen:

  1. Soll ich extrapolieren, interpolieren oder beides? Mein "Bauchgefühl" ist, dass ich eine reine Extrapolation basierend auf der Geschwindigkeit verwenden sollte. Die Zustandsänderung wird vom Client empfangen, der Client berechnet eine "vorhergesagte" Geschwindigkeit, die die Verzögerung ausgleicht, und das reguläre Physiksystem erledigt den Rest. Es fühlt sich jedoch im Widerspruch zu allen anderen Beispielcodes und Artikeln an - sie scheinen alle eine Reihe von Zuständen zu speichern und eine Interpolation ohne eine Physik-Engine durchzuführen.

  2. Wenn ein Paket eintrifft, habe ich versucht, die Position des Pakets mit der Geschwindigkeit des Pakets über einen festgelegten Zeitraum (z. B. 200 ms) zu interpolieren. Ich nehme dann die Differenz zwischen der interpolierten Position und der aktuellen "Fehler" -Position, um einen neuen Vektor zu berechnen und diesen anstelle der gesendeten Geschwindigkeit auf dem Objekt zu platzieren. Die Annahme ist jedoch, dass in diesem Zeitintervall ein anderes Paket eintrifft, und es ist unglaublich schwierig zu "erraten", wann das nächste Paket eintrifft - zumal nicht alle in festen Intervallen eintreffen (dh auch / state-Änderungen). Ist das Konzept grundlegend fehlerhaft oder ist es korrekt, erfordert jedoch einige Korrekturen / Anpassungen?

  3. Was passiert, wenn ein Remote-Player stoppt? Ich kann die Entität sofort stoppen, aber sie wird an der "falschen" Stelle positioniert, bis sie sich wieder bewegt. Wenn ich einen Vektor abschätze oder zu interpolieren versuche, habe ich ein Problem, weil ich den vorherigen Status nicht speichere - die Physik-Engine kann nicht sagen, dass Sie anhalten müssen, nachdem Sie Position X erreicht haben. Es versteht einfach eine Geschwindigkeit, nichts Komplexeres. Ich zögere es, der Entities- oder Physics-Engine die "Packet Movement State" -Informationen hinzuzufügen, da dies gegen grundlegende Designprinzipien verstößt und den Netzwerkcode über den Rest der Game-Engine verteilt.

  4. Was soll passieren, wenn Entitäten kollidieren? Es gibt drei Szenarien: Der steuernde Spieler kollidiert lokal, zwei Entitäten kollidieren auf dem Server während einer Positionsaktualisierung oder eine Remote-Entitätsaktualisierung kollidiert auf dem lokalen Client. In allen Fällen bin ich mir nicht sicher, wie ich mit der Kollision umgehen soll - abgesehen vom Schummeln sind beide Zustände "korrekt", jedoch zu unterschiedlichen Zeitpunkten. Im Fall einer entfernten Entität ist es nicht sinnvoll, sie durch eine Wand zu ziehen. Daher führe ich auf dem lokalen Client eine Kollisionserkennung durch und veranlasse, dass sie "stoppt". Basierend auf dem obigen Punkt 2 könnte ich einen "korrigierten Vektor" berechnen, der kontinuierlich versucht, das Objekt "durch die Wand" zu bewegen, was niemals gelingt - der entfernte Avatar bleibt dort hängen, bis der Fehler zu hoch wird und "einrastet" Position. Wie funktionieren Spiele um dieses Problem herum?


1
Was hat ein 3D- oder 2D-Spiel mit welcher Art von Server zu tun? und warum funktioniert ein nicht autorisierender Server für Ihr Spiel nicht?
AttackingHobo

1
@ Roy T. Bandbreiten-Kompromisse. Die Bandbreite ist die wertvollste Ressource in heutigen Computersystemen.
FxIII

1
Das ist nur unwahr, Online-Spiele werden hauptsächlich von der Reaktionszeit bestimmt. Bei einer 10-Mbit-Leitung (1,25 MB / s) beträgt die Latenz zwischen Server und Client 20 ms. Das Senden eines 1,25-KB-Pakets dauert 20 ms + 1 ms. Das Senden eines Pakets mit 12,5 KB dauert 30 ms. Auf einer doppelt so schnellen Leitung dauert ein 1,25-kb-Paket immer noch 20 ms + 0,5 ms und 20 ms + 5 ms für das 12-kb-Paket. Die Latenz ist der begrenzende Faktor, nicht die Bandbreite. Wie auch immer, ich weiß nicht, wie viele Daten es gibt, aber das Senden von 50 Vektoren 3 (25-fache Position + 25-fache Drehung) ist nur 600 Byte, das Senden alle 20 ms kostet 30 kb / s. (+ Paketaufwand).
Roy T.

2
Quake Engine hat Vorhersage seit der ersten Version. Die Bebenvorhersage wird dort und an einigen anderen Orten beschrieben. Hör zu.
user712092

1
Machst du diese Position + = Geschwindigkeit * Deltazeit für jede Entität parallel (unbedingt: im Code hast du 2 Arrays physikalischer Parameter von Entitäten, einen Frame auseinander, aktualisierst die ältere um neuer zu sein und tauschst sie aus)? Es gibt einige Probleme mit der Iteration von Sean Barret, der die Basis der Thief 1-Engine erstellt hat .
User712092

Antworten:


3

Das einzige, was zu sagen ist, ist, dass 2D, Isometrie, 3D, sie alle gleich sind, wenn es um dieses Problem geht. Da Sie viele Beispiele für 3D sehen und nur ein mit 2D-Oktanten begrenztes Eingabesystem mit augenblicklicher Geschwindigkeit verwenden, bedeutet dies nicht, dass Sie Netzwerkprinzipien verwerfen können, die sich in den letzten 20 Jahren entwickelt haben.

Designprinzipien müssen verdammt sein, wenn das Gameplay gefährdet ist!

Wenn Sie frühere und aktuelle Informationen verwerfen, werden die wenigen Informationen verworfen, die Ihr Problem lösen könnten. Zu diesen Daten würde ich Zeitstempel und berechnete Verzögerungen hinzufügen, damit die Extrapolation besser vorhersagen kann, wo sich dieser Spieler befindet, und die Interpolation Geschwindigkeitsänderungen mit der Zeit besser ausgleichen kann.

Dies ist ein wichtiger Grund, warum Server offenbar viele Statusinformationen senden und keine Eingaben steuern. Ein weiterer wichtiger Grund hängt davon ab, welches Protokoll Sie verwenden. UDP mit akzeptiertem Paketverlust und Auslieferung nicht in Ordnung? TCP mit versicherter Zustellung und Wiederholungsversuchen? Mit jedem Protokoll erhalten Sie Pakete zu seltsamen Zeiten, verzögert oder in einer Flut von Aktivitäten übereinander gestapelt. All diese seltsamen Pakete müssen in einen Kontext passen, damit der Client herausfinden kann, was los ist.

Auch wenn Ihre Eingaben auf 8 Richtungen beschränkt sind, kann die tatsächliche Änderung jederzeit erfolgen - die Durchsetzung eines 250-ms-Zyklus wird schnelle Spieler nur frustrieren. 30 Spieler sind nichts Besonderes für einen Server. Wenn Sie von Tausenden sprechen ... selbst dann sind Gruppen von ihnen auf mehrere Boxen aufgeteilt, sodass einzelne Server nur eine angemessene Last tragen.

Haben Sie jemals ein Profil für eine Physik-Engine wie Havok oder Bullet erstellt? Sie sind wirklich ziemlich optimiert und sehr, sehr schnell. Möglicherweise geraten Sie in die Falle, dass die Operation ABC langsam ist und etwas optimiert, das sie nicht benötigt.


Definitiver Salbei-Rat hier! Es ist leicht, das große Ganze aus den Augen zu verlieren. In diesem Fall verwende ich TCP. Das Thema "8-Richtungen" ist in Bezug auf Eingaben weniger ein Problem - es ist eher ein Problem bei der Interpolation und Extrapolation. Die Grafiken sind auf diese Winkel beschränkt und verwenden animierte Sprites - das Gameplay sieht "komisch" aus, wenn sich der Spieler in einem anderen Winkel oder einer anderen Geschwindigkeit bewegt, die zu weit von der Norm entfernt ist.
ShadowChaser

1

Ihr Server ist also im Wesentlichen ein "Schiedsrichter"? In diesem Fall glaube ich, dass alles in Ihrem Klienten deterministisch sein muss; Sie müssen sicherstellen, dass auf jedem Client immer das gleiche Ergebnis erzielt wird.

Bei Ihrer ersten Frage sehe ich nicht ein, wie Sie vorhersagen können, in welche Richtung der Spieler das nächste Mal abbiegen wird, wenn der lokale Spieler die Richtung des anderen Spielers erhält, abgesehen davon, dass er seine Bewegung im Laufe der Zeit verzögern und Kollisionen anwenden kann Umgebung mit 8 Richtungen.

Wenn Sie die Aktualisierung der "realen Position" jedes Spielers erhalten (möglicherweise könnten Sie versuchen, auf dem Server zu taumeln), müssen Sie die Position und Richtung des Spielers interpolieren. Wenn die "erratene" Position sehr falsch ist (dh der Spieler hat die Richtung unmittelbar nach dem Senden des letzten Richtungspakets komplett geändert), besteht eine große Lücke. Dies bedeutet, dass entweder der Spieler die Position überspringt oder Sie zur nächsten erratenen Position interpolieren können . Dies sorgt für eine gleichmäßigere Interpolation im Laufe der Zeit.

Wenn Entitäten kollidieren und Sie ein deterministisches System erstellen können, kann jeder Spieler die Kollision lokal simulieren, und die Ergebnisse sollten nicht zu weit von der Realität entfernt sein. Jeder lokale Computer sollte die Kollision für beide Spieler simulieren. In diesem Fall muss sichergestellt werden, dass der endgültige Status nicht blockiert und akzeptabel ist.

Auf der Serverseite kann ein Schiedsrichterserver immer noch einfache Berechnungen durchführen, um beispielsweise die Geschwindigkeit eines Spielers über kurze Zeiträume hinweg zu überprüfen und als einfachen Anti-Cheat-Mechanismus zu verwenden. Wenn Sie die Überwachung jedes Spielers über 1 Sekunde hintereinander durchlaufen, ist Ihre Cheat-Erkennung skalierbar. Das Auffinden von Cheatern dauert jedoch länger.


Danke - das hört sich ziemlich nach dem an, was ich brauche, besonders auf der Serverseite. Ein interessanter Punkt ist, dass, obwohl die Spieler in 8 Richtungen gesperrt sind, die interne Bewegung ein 3D-Vektor ist. Ich habe in den letzten Tagen ein bisschen mehr darüber nachgedacht und ich glaube, ich habe Probleme mit der Tatsache, dass ich überhaupt keine Interpolation implementiert habe. Ich verwende einfach eine sehr grundlegende Integration, indem ich die Geschwindigkeit einstelle und die Position auf der Basis von aktualisiere Vektor jedes Update.
ShadowChaser

Ich bin nicht sicher, wie ich das mit Interpolation oder Vorhersage kombinieren soll. Ich habe versucht, die im Paket gesendete aktualisierte Position zu übernehmen, sie über einen festgelegten Zeitraum (z. B. 200 ms) zu integrieren und dann den Vektor (die Geschwindigkeit) zu bestimmen, der zum Erreichen dieses Punkts in 200 ms erforderlich ist. Mit anderen Worten, unabhängig von der aktuellen falschen Position des Spielers auf der Clientseite sollten sie in 200 ms immer noch dieselbe "geschätzte korrekte Position" erreichen. Es endete damit, dass mein Charakter in verrückte Richtungen geschickt wurde - ich gehe davon aus, dass die 200 ms wirklich die Zeit für das nächste Paket sein sollten, die ich nicht einschätzen kann.
ShadowChaser

Haben Sie sichergestellt, dass Sie zuerst die richtige Position bei t bis t + 1 integriert haben, bevor Sie die falsche Position bei t + 1 in die erratene richtige Position integriert haben?
Jonathan Connell

Ja - Ich habe doppelt überprüft, ob ich die richtige Position für die ursprüngliche Integration verwendet habe. Ursprünglich war das ein Fehler, aber die Behebung schien noch keine spürbare Verbesserung zu bewirken. Mein Verdacht ist die "+1" - es muss sehr abhängig von der Zeit zwischen den Paketen sein. Es gibt zwei Probleme: Senden Sie Statusänderungen zusätzlich zu regelmäßigen Updates (250 ms), und ich kann nicht vorhersagen, wann diese auftreten werden. Außerdem zögere ich, ein bestimmtes Intervall festzulegen, da es sinnvoll ist, dass der Server weniger Aktualisierungen für Einheiten sendet, die weiter vom Player entfernt sind. Die Zeit zwischen Paketen kann sich ändern.
ShadowChaser

1
Ja, ein fester Zeitschritt ist wahrscheinlich keine gute Idee. Ich mache mir allerdings Sorgen, dass die Unregelmäßigkeit der Bewegung in 8 Richtungen sehr schwer vorherzusagen ist (wenn nicht unmöglich?). Trotzdem können Sie in der Lage sein , zu versuchen , die durchschnittliche Latenzzeit des Clients zu verwenden , t + 1, vorherzusagen und eine Schwelle , über der Sie immer „teleportieren“ die anderen Spieler zu ihren neuen Positionen.
Jonathan Connell

0

Können Sie Geschwindigkeit nicht in Ihre Zustandsänderungsnachrichten aufnehmen und diese verwenden, um Bewegungen vorherzusagen? Angenommen, die Geschwindigkeit ändert sich erst, wenn Sie eine Meldung erhalten, die besagt, dass sie sich geändert hat. Ich denke, Sie senden bereits Positionen. Wenn also etwas "übersteuert", haben Sie beim nächsten Update ohnehin die richtige Position. Während der Aktualisierung können Sie dann die Positionen schrittweise ändern, indem Sie die Geschwindigkeit der letzten Nachricht verwenden und die Position überschreiben, wenn eine Nachricht mit einer neuen Position empfangen wird. Dies bedeutet auch, dass Sie eine Nachricht senden müssen, wenn sich die Position nicht ändert, die Geschwindigkeit jedoch (wenn dies in Ihrem Spiel sogar ein gültiger Fall ist), aber wenn überhaupt, hat dies keine großen Auswirkungen auf Ihre Bandbreitennutzung.

Die Interpolation sollte hier keine Rolle spielen, z. B. wenn Sie wissen, wo sich etwas in Zukunft befindet, ob Sie es haben, welche Methode Sie verwenden usw. Sind Sie vielleicht mit der Extrapolation verwechselt? (wofür ich einen einfachen Ansatz beschreibe)


-1

Meine ersten Fragen wären: Was ist falsch daran, ein Modell zu verwenden, bei dem der Server die Berechtigung hat? Warum ist es wichtig, ob die Umgebung 2D oder 3D ist? Es würde Ihren Cheat-Schutz viel einfacher machen, wenn Ihr Server autorisierend wäre.

Die meisten Proben, die ich gesehen habe, haben die Bewegungsvorhersage eng mit den Entitäten selbst verknüpft. Beispiel: Speichern des vorherigen Status zusammen mit dem aktuellen Status. Ich möchte das vermeiden und nur Entitäten mit ihrem "aktuellen Status" behalten. Gibt es eine bessere Möglichkeit, damit umzugehen?

Bei der Vorhersage müssen mehrere Zustände (oder zumindest Deltas) auf dem Client verwaltet werden, damit der vom Server empfangene autorisierende Zustand / Delta mit dem des Clients verglichen und die erforderlichen vorgenommen werden können Korrekturen. Die Idee ist, so viel wie möglich deterministisch zu halten, um den Umfang der erforderlichen Korrekturen zu minimieren. Wenn Sie die vorherigen Zustände nicht beibehalten, können Sie nicht wissen, ob auf dem Server etwas anderes passiert ist.

Was soll passieren, wenn der Spieler anhält? Ich kann nicht auf die richtige Position interpolieren, da sie möglicherweise rückwärts oder in eine andere seltsame Richtung gehen müssen, wenn ihre Position zu weit voraus ist.

Warum müssen Sie interpolieren? Der autorisierende Server sollte alle fehlerhaften Bewegungen außer Kraft setzen.

Was soll passieren, wenn Entitäten kollidieren? Wenn der aktuelle Spieler mit etwas kollidiert, ist die Antwort einfach: Halten Sie den Spieler einfach an, sich zu bewegen. Was passiert jedoch, wenn zwei Entitäten denselben Speicherplatz auf dem Server belegen? Was ist, wenn die lokale Vorhersage dazu führt, dass eine entfernte Entität mit dem Spieler oder einer anderen Entität kollidiert - beende ich sie auch? Wenn die Vorhersage das Unglück hatte, sie vor eine Wand zu stecken, die der Spieler umrundet hat, kann die Vorhersage dies niemals kompensieren, und sobald der Fehler zu hoch wird, springt die Entität an die neue Position.

Dies sind Situationen, in denen ein Konflikt zwischen dem Server und dem Client auftreten würde. Deshalb müssen Sie die Status auf dem Client beibehalten, damit der Server alle Fehler beheben kann.

Entschuldigung für die schnellen Antworten, ich muss los. Lesen Sie diesen Artikel , er erwähnt Schützen, sollte aber für jedes Spiel funktionieren, das Echtzeit-Networking erfordert.


Ein paar Antworten: * Wenn der Server autorisiert ist, ist er dafür verantwortlich, alle sich bewegenden Einheiten zu verfolgen und ihre Positionen in regelmäßigen Abständen zu aktualisieren. Mit anderen Worten, es muss die Physik-Engine laufen - was teuer werden könnte. Skalierbarkeit ist ein wichtiges Entwurfsziel von mir. * Ich muss auf der Client-Seite interpolieren, andernfalls bewirkt jede an die Clients gesendete Server-Aktualisierung, dass die Entitäten springen. Im Moment wird meine Interpolation in der Physik-Engine durchgeführt - es wird nur die Geschwindigkeit eingestellt. Es gibt keine Zustände oder Deltas.
ShadowChaser

Ich habe alle Artikel von Glenn gelesen, aber er gibt in den Kommentaren an, dass sie sich nur an Schützen richten (dh / hohe Aktualisierungsfrequenzen). In einigen seiner Artikel geht es um maßgebliche Kunden, die von mir angestrebte Implementierung. Ich möchte keine Interpolation / Physik auf dem Server durchführen, aber ich bin bereit, meine Meinung zu ändern, wenn dies wirklich der einzige Weg ist :)
ShadowChaser

1
-1. Was Sie geschrieben haben, berührt das Thema nur vage. fühlt sich mehrdeutig. Antworten fühlen sich unterdurchschnittlich an, wenn sie im Wesentlichen "diesen langen Artikel lesen", aber keine nützlichen Informationen aus dem vorliegenden Artikel enthalten.
AttackingHobo

1
@ AttackingHobo Da müsste ich dir zustimmen. Ich sagte, ich hätte es eilig, aber das ist keine Entschuldigung. Wenn ich keine Zeit gehabt hätte, wäre es besser gewesen, es in Ruhe zu lassen. Lektion gelernt.
Gyan aka Gary Buyn
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.