Mit GSAP Game-UI mit Canvas animieren

Avatar of Opher Vishnia
Opher Vishnia am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Das Jahr war 1995; Toy Story kam in die Kinos, Kinder sammelten besessen kleine Pappkreise und Kiss From a Rose wurde von allen schlecht gesungen. Ich war ein schlaksiger Zehnjähriger und wie jedes andere relativ große Kind wurde ich oft mit „Du musst doch super im Basketball sein!“ angesprochen. Also übte ich stundenlang auf dem Feld meiner Grundschule. Schließlich erkannte ich, sehr zum Entsetzen von Tanten und anderen Wangenkneifern, dass es zwar von Vorteil sein mag, vertikalen Raum einzunehmen, dies aber kein Garant für Erfolg beim Basketball ist.

21 Jahre später. Jetzt ein großer und schlaksiger Entwickler, immer noch schlecht im Basketball, stand ich vor einem Projekt: dem Design und der Implementierung eines Full-Motion-Video-Web-Basketballspiels für die NBA-Mannschaft Detroit Pistons. Bälle werfen ist eine Sache; Pixel werfen – das ist endlich eine Basketball-Herausforderung, die ich meistern kann!

Während der Entwicklung des Spiels verwendete ich viele coole Dinge wie Canvas, SVG und CSS-Animationen, Gestenerkennung und einen Videostream, der dynamisch on-the-fly erstellt wird. Es ist wirklich erstaunlich, was wir heutzutage nur mit einem Browser machen können. Probieren Sie es aus, probieren Sie es aus.

In diesem Artikel möchte ich mich darauf konzentrieren und Ihnen zeigen, wie ich die Animation für die Superpower-Anzeige mit Vanilla JS in Verbindung mit GSAP implementiert habe. Dies ist die Bewegungsreferenz, die ich während der Implementierung der Animation verwendet habe und die in After Effects erstellt wurde

In 1on1 werden die Benutzer mit Kombopunkten belohnt, wenn sie erfolgreich einen Zug machen. Die Anzeige befindet sich in der oberen linken Ecke des Bildschirms und ihre Aufgabe ist es, dem Benutzer die Anzahl der Kombopunkte anzuzeigen, dargestellt durch die Anzahl der roten Segmente. Zu bestimmten Zeiten im Spiel wird die Superpower-Anzeige aktiv und benachrichtigt den Benutzer, dass er sie anklicken kann, damit sein In-Game-Avatar einen Spezialzug ausführt.

Die grundlegende Struktur der Superpower wird mit einem Canvas-Element und etwas einfacher Geometrie erreicht

Sehen Sie sich den Pen Pistons Superpower: Structure von Opher Vishnia (@OpherV) auf CodePen an.

Im Wesentlichen gibt es hier zwei Hauptkomponenten – das zentrale Bild und die Anzeige-Segmente. Das Bild ist der einfache Teil, es ist nur eine triviale Verwendung von Canvas' drawImage. Die Anzeige-Segmente sind das Interessante. Ich habe eine allgemeine options-Definition mit einigen Eigenschaften erstellt, mit denen später gespielt werden kann, wie z. B. die Anzahl der Segmente, der Radius, die Breite usw. Dann durchlaufe ich ein Array von segment-Objekten und verwende deren Eigenschaften (strokeStyle, lineWidth), um die tatsächlichen Segmente mit der Canvas-Bogenfunktion zu zeichnen. Bisher so gut – aber wo bleibt die Animation?

Ich habe überlegt, ob ich ein Canvas-Animations-Framework verwenden soll, habe mich aber letztendlich dagegen entschieden. Dies liegt daran, dass ich mehrere Arten von Animationen im Projekt benötigte: Canvas, SVG und CSS/DOM, und kein einziges Framework kann alles. Darüber hinaus mussten alle Animationen reibungslos über ein spielendes Video laufen, sowohl auf Desktop als auch auf Mobilgeräten mit unterschiedlichen Fähigkeiten und Netzwerkbedingungen. Das bedeutet, dass die Leistung von größter Bedeutung war, und ich wollte genau wissen, welcher Code die Animation antreibt. Glücklicherweise erlaubt mir GSAP (auch bekannt als Greensock, AKA TweenMax, AKA TweenLite) genau das.

GSAP ist cool. Es ermöglicht Ihnen, praktisch alles zu animieren! Der Trick ist, dass die Animations-API nicht nur DOM/SVG-Objekte akzeptiert, sondern auch beliebige JS-Datenstrukturen, deren Eigenschaften Sie dann „animieren“ können.

Animate all the thing! Meme

Die Grundidee ist, dass Sie GSAP verwenden, um die Eigenschaften dieser Objekte im Laufe der Zeit zu ändern. Diese Werte geben an, wie die Benutzeroberfläche zu einem bestimmten Zeitpunkt aussieht. Bei jedem requestAnimationFrame führen Sie einen Zeichenaufruf an das Canvas aus, um den Zustand der Benutzeroberfläche basierend auf diesen Objekten zu zeichnen.

function render() { 
 //draw the animation state
 drawComboGui();
 
 //draw the image
 ctx.drawImage(...);
 //render on the next frame as well
 window.requestAnimationFrame(render)
}
render();

Hier ist eine Aufschlüsselung der verschiedenen implementierten Animationen

Anzeige füllt sich

Sehen Sie sich den Pen Pistons Superpower: Gauge fill von Opher Vishnia (@OpherV) auf CodePen an.

Lassen Sie uns die Anatomie dieser Animation besprechen. Im Ruhezustand sind alle noch nicht gefüllten Segmente der Anzeige grau und dünn, und die gefüllten Segmente sind rot und etwas dicker. Sobald das nächste Segment der Anzeige gefüllt ist, ändern alle vorherigen aktiven Segmente ihre Farbe zu Weiß, werden dicker und beginnen zu leuchten. Das folgende Segment wird dann gefüllt, und schließlich hören alle Segmente auf zu leuchten und kehren zu ihrer ursprünglichen, aktiven Breite zurück.

Erinnern Sie sich an das Array von Segmentobjekten? Hier kommen sie mit GSAP ins Spiel. Die Funktion addActiveSegment ist das Herzstück des Ganzen, wo wir TweenMax.fromTo verwenden, um Eigenschaften wie lineWidth und anglePercent zu animieren. Das GSAP colorProps-Plugin ermöglicht uns sanfte Übergänge bei Farbeigenschaften wie strokeStyle und activeStrokeStyle. Ich verwende die Eigenschaft delay, um die verschiedenen Komponenten dieser Animation zu timen.

TweenMax.fromTo(segments[index], expandAnimLength, {
  anglePercent: 0,
  colorProps:{strokeStyle: options.activeStrokeStyle},
}, 
{
  anglePercent: 1,
  colorProps:{strokeStyle: options.activeStrokeStyle},
  ease: Power0.easeIn,
  delay: growAnimLength
});

Wie bereits erwähnt, ruft die render-Funktion bei jedem requestAnimationFrame, idealerweise 60 Mal pro Sekunde, drawComboGui auf. In drawComboGui löschen wir zuerst das Canvas von allen zuvor gezeichneten Daten, bevor wir den aktuellen Zustand zeichnen.

Um den Leuchteffekt zu erzeugen, habe ich zwei Segmente übereinander gezeichnet. Das untere Segment verwendet shadowBlur auf dem Canvas-Pfad, und das obere hat keine Schattenunschärfe. Dadurch scheint das unscharfe Element hinter dem nicht unscharfen hervor, was den Leuchteffekt erzeugt.

Es gibt mehrere zusätzliche Animationen für die Superpower-Anzeige. Die Zustände „Superpower aktiviert“ und „Superpower deaktiviert“ sind konzeptionell sehr ähnlich zur hier besprochenen Anzeige-Füllung. Sie werden durch Animation der Breite des Bildes und der aktiven Segmente implementiert.

superpower charged
Superpower aufgeladen
Superpower discharged
Superpower entladen

Die Animationen Superpower aufgeladen und Superpower entladen erfordern andere Techniken wie die Animation von Bild-Sprites und die Anwendung eines Echtzeit-Blur-Filters. Das geht jetzt etwas über den Rahmen hinaus, wird aber in einem zukünftigen Artikel behandelt!

Es gibt ein großes Problem bei der Implementierung von Benutzeroberflächen für Spiele. Spiele sind extrem zustandsorientiert. In jedem Moment kann sich der Zustand des Spiels und damit seine Benutzeroberfläche ändern. Im speziellen Fall der Superpower-Anzeige bedeutet dies, dass die Anzeige jederzeit gefüllt, aktiviert, entladen oder deaktiviert werden kann. Das kann auch mitten in einer Animation geschehen! Was macht man aber in diesem Fall?

Sie haben zwei Optionen – eine besteht darin, jede laufende Animation zu stoppen und abrupt zum neuen Animationszustand zu wechseln. Das Problem bei diesem Ansatz ist, dass die Erfahrung für den Benutzer sehr aufreibend ist, sie vom Spiel entkoppelt und letztendlich mehr Rauschen als Informationen vermittelt. Das Gegenteil von dem, was eine gute Schnittstelle tun sollte.

Die andere Option ist, die Animationen zu einer Warteschlange hinzuzufügen, sodass jede Animation ausgeführt wird, bevor die letzte beginnt. Das wird etwas knifflig, da eine Animation aus kleineren Teilanimationen bestehen kann, aber dank der Timeline-Funktion von GSAP wird die Aufgabe, all diese Zustände und Animationen zu verwalten, viel überschaubarer. Anstatt TweenMax.to aufzurufen, initialisieren Sie ein Timeline-Instanzobjekt und verwenden es für die Aufrufe. Standardmäßig definieren diese Aufrufe neue Animationen, die am Ende der Timeline beginnen und eine Animationswarteschlange bilden, aber dies ist sehr konfigurierbar! Sie könnten zum Beispiel eine Animation definieren, die mit einem Offset relativ zum Ende der Timeline beginnt, oder an einer absoluten Position auf der Timeline. Dies ermöglicht es Ihnen auch, die Verwendung der Verzögerungseigenschaft zur Berechnung der Animationen in der Warteschlange zu vermeiden, was bei mehreren Animationen umständlich werden kann.

Hier ist eine Implementierung der Anzeige-Füllungsanimation mit GSAPs Timeline. Versuchen Sie, auf die Schaltfläche „Segment hinzufügen“ zu klicken, während eine Animation bereits läuft.

Sehen Sie sich den Pen Pistons Superpower: Gauge fill with Timeline von Opher Vishnia (@OpherV) auf CodePen an.

Ich hoffe, das hilft Ihnen bei einigen Herausforderungen und Problemen, auf die Sie bei Ihrem Spiel/Ihrer Website/Ihrem Projekt stoßen. Wenn Sie Fragen haben oder wissen möchten, wie ich andere UI-Elemente im Spiel umgesetzt habe, können Sie mich gerne auf Twitter kontaktieren!

Epilog

Obwohl ich immer noch ab und zu von tief hängenden Ästen getroffen werde und meine Wurffähigkeiten zu wünschen übrig lassen, kann mir niemand etwas vormachen, wenn es darum geht, schnell Tastenkombinationen zu drücken. Andre Drummond hat nichts gegen mich.