/tech: Champions Kontext geben

von RiotAaronMike, Lucida

/tech ist eine neue Reihe von Artikeln, die die Technik hinter League of Legends erkundet. Wenn dir dieser Beitrag gefällt, sieh dir doch auch den „Riot Games“-Technikblog an, um noch tiefer in die komplexen Hintergründe der Spielsysteme zu tauchen.

Hallo da draußen, AaronMike und Lucida hier! Wir sind Softwareentwickler in Leagues Team für grundlegende Spielmechanik und wollen dir ein wenig über ein System namens „Contextual Action Component“ (kurz CAC – Komponente für kontextbezogene Aktion) erzählen, das den Persönlichkeiten der Champions eine zusätzliche interessante Ebene verleiht. Im Wesentlichen ist CAC ein System, mit dem die Entwickler kontextbezogene Interaktionen für Champions kreieren können und diese so natürlicher auf Ereignisse in der Kluft reagieren.


Einblicke in das System

Lass uns zuerst Poppys „Verspotten“-Emote als Beispiel betrachten: Wie läuft es ab, wenn sie allein ist, und was sagt sie, wenn ihr goldgeflügelter Verbündeter Galio in der Nähe steht?


Verspotten ohne Galio

Verspotten in Anwesenheit von Galio

Wenn Poppy ihr „Verspotten“ für sich oder in der Nähe von Champions ohne bestimmte Verbindung einsetzt, sagt sie einen von einer Handvoll allgemeiner Sprüche. Wenn Poppy in der Nähe ihres Verbündeten Galio steht, scherzt sie stattdessen mit ihm und er antwortet sogar, falls er nach ihrem Spruch noch da ist. Das scheint heutzutage zwar selbstverständlich, aber als League 2010 erschien, war diese Art von Interaktion nicht möglich. Varianten konnten nur dadurch erzeugt werden, indem das Soundsystem eine zufällige Aufnahme aus einer Liste auswählte. Die Audiodesigner mussten deshalb allgemein gehaltene Dialoge verwenden, damit nicht in bestimmten Situationen unpassende Sätze abgespielt werden.


Lux’ „Geschwisterrivalität! Klingt spaßig!“ zum Beispiel ergibt Sinn, wenn sie sich neben Garen befindet. Aber neben Katarina? Eher nicht. In Leagues altem Audiosystem wäre ein solcher Satz nicht erlaubt gewesen, da das System ihn nicht zur richtigen Gelegenheit auswählen konnte. Dadurch gingen uns wertvolle Möglichkeiten verloren, die die Persönlichkeit eines Champions und seine Beziehung zu anderen Champions hätten zeigen können!

Und genau hier kommt das CAC ins Spiel. Es ist der Kern solcher Interaktionen und ermöglicht Poppy und Lux eine kontextuelle Kommunikation anhand von Echtzeit-Informationen z. .B über nahe Verbündete, welchen Gegenstand sie gekauft haben oder welchen Champion sie gerade getötet haben. Es macht außerdem Pulsfeuer-Caitlyns einzigartigen Penta-Spruch möglich und lässt Xayah und Rakan in der Kluft herumturteln.


Blick unter die Haube

Das CAC wurde für einen einfachen Einsatz konzipiert: die Ausführung von verschiedenen Aktionen (Handlungen) basierend auf den Situationen im Spiel. Die Struktur des Systems kann folgendermaßen dargestellt werden:

  • – Eine Situation
    • – Regel 1
      • – Bedingungen
      • – Aktion
    • – Regel 2
      • – Bedingungen
      • – Aktion
    • – Weitere Regeln

Eine Situation ist ein festgelegtes Konstrukt in unserem Spiel, wie Championtötung, Gebäudeangriff oder Gegenstandskauf. Im Lauf der letzten Jahre haben wir zahlreiche Situationen zu League hinzugefügt. Jede Situation kann eine Liste an Regeln enthalten, die wiederum eine Liste an Bedingungen besitzt sowie eine bestimmte Aktion. Wenn eine Situation eintrifft, wird eine Regel, deren Bedingungen zutreffen, gewählt und dann wird ihre Aktion ausgeführt. Werden mehrere Regeln erfüllt, wird entweder die erste gewählt oder – falls vorgegeben – eine zufällige Wahl getroffen. Bedingungen sind festgelegte Kontexte, wie „eigener LP-Bereich“, „Name des Zielchampions“, „Kartenort“, „Fähigkeitsstufe“ usw. Die Sprüche der Aktionen können für unterschiedliche Gegenüber passend gewählt werden, einschließlich sich selbst, Verbündete, Gegner und Zuschauer.

Hier ein Beispiel, wie Camille mit dem „Programm Camille“-Skin Ashe mit dem „PROJEKT: Ashe“-Skin verspottet:


Wie der Hauptteil von Leagues Codebasis ist dieses System in C++ geschrieben und verwendet unseren Spieldatenserver für die Konfiguration. Hier ist ein abgekürzter Auszug des Codes, der aufgerufen wird, wenn ein Champion einen anderen Champion tötet:


// Wird aufgerufen, wenn ein Champion einen anderen Champion tötet.
void HandleChampionKillSituation(Champion* killer, Champion* victim,
  uint8_t killerMultikill = 0)
{
  ContextualActionComponent& cac = killer->GetContextualActionComponent();

  // Überprüfen, ob der siegreiche Champ eine „KillChampion“-Situation hat.
  const ContextualSituation* situation = cac.FindSituation(kKillChampion);
  if (situation != nullptr) {
    // Festlegen der relevanten Daten
    ContextualFacts& facts = cac.GetFacts();
    facts.mKiller = killer;
    facts.mVictim = victim;
    facts.mKillerMultiKillSize = killerMultikill;

    // Versuchen, eine Regel für die Tötungsdaten zu finden
    const ContextualRule* rule = situation->PickRule(facts);
    if (rule) {  // Eine zutreffende Regel wurde gefunden.
      if (rule->ExecuteAudioAction(facts)) {
        // Den anderen CACs mitteilen, dass der siegreiche Champ gerade diesen Vorgang ausgelöst hat.
        cac.NotifyAllCacsOfPlayedAction(rule->GetAudioSituationTrigger());
      }

      // Zurücksetzen der momentanen Daten
      facts.mKiller = killer;
      facts.mVictim = nullptr;
      facts.mKillerMultiKillSize = 0;
    }
  }
}

Dieser Auszug veranschaulicht, wie das System festlegt, welche Aktion aufgrund des Kontexts durchgeführt werden soll. Die PickRule-Funktion geht jede Regel für die „KillChampion“-Situation durch, bis sie die Regel findet, die alle Bedingungen erfüllt und führt dann die entsprechende(n) Aktion(en) aus.


Das Autorensystem

Der folgende Screenshot zeigt eine Regel, die wir für Spieler festgelegt haben, die talentiert genug sind (oder das Glück haben), eine Fünffachtötung mit Pulsfeuer-Caitlyn zu erreichen:


Jedes Mal, wenn Pulsfeuer-Caitlyn einen gegnerischen Champion tötet, durchläuft das CAC die Regeln in der „KillChampion“-Situation. Diese Regel besagt: Wenn das die fünfte Tötung in einem Blutrausch ist, soll die Aufnahme vonKillChampion3DPentakill für sich selbst (den Spieler) und die Gegner des Spielers abgespielt werden. Beachte, dass bei dieser Regel das Auftreten auf 3-mal beschränkt ist (den Tippfehler im Dialog bitte ignorieren, den werde ich gleich korrigieren), damit sie nur bei den ersten drei erfolgreichen Fünffachtötungen abgespielt wird – wie wir alle wissen, wird es ab dem 4. Mal etwas nervig.


Vorteile

In der Vergangenheit wurde Audio direkt durch Ereignisse in verschiedenen Systemen des gesamten Spiels ausgelöst. Diese Ereignisse schließen z. B. Partikeleffekte, Animationen, Einsatz von Fähigkeiten, Benutzereingaben usw. mit ein. Wenn also ein Spieler seinen Champion bewegt, erstellt der Spielclient ein Audioereignis, das „Champion_VO_MoveCommand“ oder so ähnlich heißt, und versucht, die entsprechende Audiodatei abzuspielen. Da die ursprünglichen Auslöser den Spielkontext nicht interpretieren konnten, waren für sie situationsabhängige Interaktionen nicht möglich.

Direkte Ereignisse können nicht einmal im Ansatz das bewirken, was das CAC leisten kann. Die Kombination von Situationen und Regeln erlaubt es, sehr spezifische Interaktionen zu schreiben. Vor der Einführung dieses Systems hatten wir ein paar spezifische Interaktionen im Spiel, aber wir mussten uns darauf verlassen, dass sie zufällig mit der passenden Häufigkeit eingesetzt wurden. Zac hat zum Beispiel zwei allgemeine Sprüche beim Verspotten: „Ich komme groß raus“ und „Es geht nicht darum, wie viel mal heben kann. Es geht darum, wie gut man aussieht.“ Beim Verspotten wählt das Spiel zufällig einen der Sprüche aus. Jetzt haben wir das Werkzeug, ihr Auftreten auf passende Gelegenheiten abzustimmen. So können wir gewollt seltene und detaillierte Interaktionen einbauen, anstatt sie allein dem Zufall zu überlassen.


Xayah und Rakan

Anfang 2016 begannen wir mit der Entwicklung unseres ersten Championpaares im Universum von League of Legends. Unser Ziel war es, diese beiden Champions wie ein echtes Paar miteinander kommunizieren und nicht nur austauschbare, gewöhnliche Interaktionen ausführen zu lassen. Was, wenn wir wollen, dass Rakan während einer Tanznummer Xayah in die Luft heben kann? Was, wenn wir wollen, dass Xayah Rakan mit ein paar (liebevollen) Scherzen neckt? Was, wenn Rakan Xayah vor drohender Gefahr warnen will?

Um dieses „was, wenn …“ in die Realität umzusetzen, mussten wir das CAC mit einigen neuen Aktionen und Situationen versorgen.


Xayah und Rakan


Animation

Dem Animationssystem fehlten einige der nötigen Kontexte, die die Animatoren für das gewünschte Tänzchen oder den gemeinsamen Rückruf brauchen. Wir haben das CAC also um eine neue Aktion für die Kontrolle von Championanimationen erweitert, um das Endergebnis erreichen zu können. Wenn Xayah irgendetwas macht – oder nichts, wenn sie inaktiv ist – sendet sie eine PlayAnimation-Anfrage an das Animationssystem mit dem gewünschten Animationsnamen. Wir haben diese Abfolge modifiziert, so dass das CAC diese Anfragen abfängt und überprüft, ob irgendwelche kontextabhängigen Bedingungen erfüllt werden. Findet sich eine Übereinstimmung, wird die Animation mit einer Animation ausgetauscht, die dem Kontext besser entspricht. Die Anfrage wird dann weiter zur Ausführung an das Animationssystem geschickt.


CAC-Interaktionen

Der Tanz war die nächste Herausforderung. Wie lassen Xayah und Rakan einander wissen, dass sie zusammen tanzen möchten? Das haben wir umgesetzt, indem wir eine neue Situation hinzugefügt haben, die ausgelöst wird, wenn ein anderer Champion eine CAC-Aktion ausführt. Alle CACs im Spiel werden über die abgeschlossene Aktion sowie dem momentanen Spielkontext informiert und können dann entscheiden, ob eine Reaktion notwendig ist.


Von links nach rechts: Beide sind inaktiv, Xayah beginnt ihren Solotanz, Rakan schließt sich dem Tanz an und sie führen ihren gemeinsamen Tanz vor.


Kontextabhängige Signale

Ein weiterer großartiger Vorteil bot sich darin, die Anfragen für Signale durch das CAC umzuleiten. Jetzt ist das Pärchen (neben den normalen Signalen) außerdem dazu in der Lage, etwas wie „Pass auf, Schatz!“ beim Gefahr-Signal oder „Sie sind nicht hier.“ beim „Gegner fehlt“-Signal zu sagen.


Technische Bedenken


Schummelschutz

Cheats und Hacks sind stets ein zentrales Anliegen, wenn wir neue Systeme wie das CAC in League of Legends aufnehmen. Eine Form von Hacking ist die Beschaffung von zusätzlichen Informationen, die das Spiel den Spielern nicht direkt bietet, um sich Vorteile im Kampf zu verschaffen. Ein Cheater könnte ein Kontextsystem dazu ausnutzen, diese Informationen herauszufinden. Stell dir bloß vor, dass Elise jedes Mal „Meine Spinnensinne schlagen aus …“ sagt, wenn sich dein Team im hohen Gras versteckt. Um einen solchen Missbrauch zu verhindern, haben wir das CAC so entworfen, dass es nur auf Informationen reagiert, die der Client bereits hat – und nichts weiter (anders gesagt, es sieht nur, was du auch siehst).


Leistung

Wir wollen den Entwicklern die nötige Freiheit und Benutzerfreundlichkeit bieten, damit sie Leagues Charaktere zum Leben erwecken können. Aber wir wollen vermeiden, dass die Leistung auf den Spieler-PCs in Mitleidenschaft gezogen wird, egal ob sie auf einer mit Stickstoff gekühlten Monstermaschine spielen oder einem etwas schwachbrüstigen Laptop. Es war bei jedem Schritt der Entwicklung unser Ziel, das System so ressourcenschonend und leistungsfähig wie möglich zu halten. Eine Kombination aus Entscheidungen bezüglich des Codes und bewährten Methoden erlaubt uns, das zu erreichen:

  1. Die Situationen sind in einer HashMap gespeichert, mit String Hash als Schlüsseltyp. Mit dieser Struktur können wir schnell eine Situation von einem CAC-Objekt abrufen. Wenn ein Champion keine Daten für eine bestimmte Situation hat, kehrt die „Handle“-Funktion einfach zurück. Da jeder Champion über eine überschaubare Menge an relevanten Situationen verfügt, kosten die meisten Situationen sehr wenig.
  2. Wir ziehen spezifische Situationen den allgemeinen vor. Normalerweise würden wir allgemeine, wiederverwendbare Lösungen bevorzugen, die mehrere Probleme auf einen Schlag abhaken, aber hier handelt es sich um einen speziellen Fall. Allgemeinere Situationen beinhalten mehr Regeln, von der jede andere Bedingungen enthält, die vom Prozessor verarbeitet werden müssen. Eine allgemeine Situation in einige spezifische Situation zu teilen, senkt die Anzahl an Regeln und verbessert die Leistung. Situationen ohne Regeln können sogar direkt zurückkehren. Wir haben beispielsweise vier spezifische Tötungssituationen: KillChampion (Champion getötet), KillTurret (Turm zerstört), KillNeutralMinion (neutrale Einheit getötet) und KillWard (Auge zerstört). KillChampion hat oft die meisten Varianten, aber tritt nur ein paar Mal im Spiel auf. KillNeutralMinion hat die wenigsten Varianten, aber tritt häufiger auf. Wenn wir eine allgemeine Situation wie KillTarget (Ziel getötet) für alle diese Fälle verwenden würden, müssten wir jedes Mal, wenn eines dieser vier Arten von Zielen getötet wird, eine riesige Liste mit Regeln analysieren.
  3. Wir überprüfen zuerst einfache, aber wichtige Fakten oder Bedingungen. Falls die Bedingungen nicht erfüllt werden, können komplexere Durchläufe im Prozess übersprungen werden.
    1. Audiosituationen werden übersprungen, falls der Benutzer bereits etwas sagt. League erlaubt es nicht, dass für den gleichen Charakter mehrere Sätze gleichzeitig abgespielt werden. Das ist für uns eine gute Gelegenheit zum Optimieren. Wenn das CAC erkennt, dass der Benutzer spricht, kann es neue Audiosituationen ignorieren. Werden Emotes ununterbrochen ausgelöst, ist das CAC trotzdem sehr effizient, da es einen großen Teil des Prozesses überspringt, sobald der Champion zu sprechen beginnt.
    2. Eine andere wichtige Bedingung ist die Abklingzeit von Situationen. Wenn der Champion für eine kurze Zeit nach der letzten Ausführung nicht auf eine Situation reagieren soll, gibt es keinen Grund, die Situation zu verarbeiten.
  4. Situationen mit großer Häufigkeit werden wenn möglich vermieden. Wenn eine Situation tatsächlich oft eintritt, können Leistungseinbußen auf verschiedene Arten verhindert werden:
    1. Die Situation erhält eine Abklingzeit, damit der Spielclient sie nicht bei jedem Auftreten überprüfen muss.
    2. Situationen von großer Häufigkeit haben nur wenige Regeln, damit sie schneller ablaufen.

Fazit

Dank dem CAC erkennen die Audio- und Animationssysteme besser kontextbezogene Situationen im Spiel, was unseren kreativen Kollegen viele neue Möglichkeiten eröffnet. Sie sind so dazu in der Lage, die Persönlichkeiten der Champions herauszuarbeiten und die Welt von League of Legends wachsen zu lassen. Jeder einzelne Satz, um den wir die Sprachausgabe erweitern, hat das Potential, jemandem ein Lächeln auf das Gesicht zu zaubern und die Spieler ihren Lieblingschampions näher zu bringen.



6 months ago