Dies ist der letzte Teil meiner vierteiligen Serie, in der ich lerne, wie man ein einfaches Plattformspiel mit der Flame-Engine erstellt. Wir wissen bereits, wie man einen animierten Spielercharakter hinzufügt, den ich „The Boy“ getauft habe, wie man mit dem Tiled-Editor ein scrollbares Spiellevel erstellt und wie man mithilfe der Kollisionserkennung Schwerkraft und Springen hinzufügt (Teile 1 , 2 , 3 ).
In diesem Teil fügen wir Münzen hinzu, die der Charakter sammeln kann, ein HUD, das anzeigt, wie viele Münzen der Spieler hat, und einen Siegesbildschirm, den wir anzeigen, sobald alle Münzen gesammelt sind.
Wir müssen dem Spiel mitteilen, wo Münzen (oder auch andere Spielobjekte) erscheinen sollen. Wie Sie vielleicht schon erraten haben, werden wir den Tiled-Editor verwenden, um eine weitere Objektebene hinzuzufügen, ähnlich wie wir Plattformen hinzugefügt haben, jedoch mit zwei Unterschieden:
Mit den Plattformen haben wir Sprite-Bilder in die Level-Daten integriert. Diese Methode ist für Münzen nicht sehr geeignet, da wir das Objekt entfernen möchten, sobald der Spieler es eingesammelt hat. Wir werden also lediglich Spawnpunkte hinzufügen, um zu wissen, wo Münzen im Spiel erscheinen sollen, und das Rendern erfolgt mithilfe von Flame-Komponenten.
Für die Plattformen haben wir Rechtecke beliebiger Größe verwendet, aber für die Münzen werden wir Spawnpunkte in der Größe einer Kachel hinzufügen. Wenn Sie jedoch eine Reihe von Münzen nacheinander hinzufügen möchten (Hallo Mario), können Sie dies ganz einfach tun, indem Sie den Spielcode ändern und beim Hinzufügen von Münzobjekten die Größe eines Spawnpunkts berücksichtigen. Für die Zwecke dieser Serie gehen wir jedoch davon aus, dass die Spawn-Punkte der Münzen 1x1 betragen.
Öffnen Sie unser Level im Tiled-Editor und erstellen Sie eine neue Objektebene namens Coins. Fügen Sie dann mit dem Rechteck-Werkzeug mehrere Spawnpunkte auf der Karte hinzu, damit wir mithilfe der Spiel-Engine Münzkomponenten hinzufügen können. Mein Level sieht jetzt so aus:
Ich sollte hinzufügen, dass unser Spiel ziemlich einfach ist und wir wissen, dass diese leeren Rechtecke im Laufe der Spiellaufzeit in Münzen umgewandelt werden. Wenn wir jedoch weitere Objekttypen hinzufügen möchten, wird es schwierig, diese zu unterscheiden. Glücklicherweise verfügt Tiled dafür über ein Tool namens „Insert Tile“, das für jedes Objekt visuelle Hinweise hinzufügen kann, diese Bilder werden jedoch nicht im Spiel gerendert.
Alles klar, speichere das Level und kehre zur IDE zurück. Fügen wir unsere Coin
Klasse zum Ordner /objects/
hinzu.
class Coin extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { late final SpriteAnimation spinAnimation; late final SpriteAnimation collectAnimation; Coin(Vector2 position) : super(position: position, size: Vector2.all(48)); @override Future<void> onLoad() async { spinAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(16), stepTime: 0.12, ), ); collectAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.range( start: 4, end: 7, amount: 8, textureSize: Vector2.all(16), stepTimes: List.filled(4, 0.12), loop: false ), ); animation = spinAnimation; final hitbox = RectangleHitbox() ..collisionType = CollisionType.passive; add(hitbox); return super.onLoad(); } }
Wir haben 2 verschiedene Animationen zum Drehen und Sammeln und eine RectangleHitbox
, um später Kollisionen mit dem Spieler zu überprüfen.
Kehren Sie als Nächstes zu game.dart
zurück und ändern Sie die Methode spawnObjects
, um unsere Münzen zu erzeugen:
final coins = tileMap.getLayer<ObjectGroup>("Coins"); for (final coin in coins!.objects) { add(Coin(Vector2(coin.x, coin.y))); }
Führen Sie das Spiel aus und sehen Sie sich hinzugefügte Münzen an:
Kehren Sie zu coin.dart
zurück und fügen Sie die collect
hinzu :
void collect() { animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; }
Wenn diese Methode aufgerufen wird, schalten wir die Drehanimation auf die Sammelanimation um und entfernen diese Komponente, sobald sie fertig ist, aus dem Spiel.
Gehen Sie dann zur Klasse theboy.dart
und überschreiben Sie die Methode onCollisionStart
:
@override void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) { if (other is Coin) { other.collect(); } super.onCollisionStart(intersectionPoints, other); }
Der Grund, warum wir onCollisionStart
anstelle von onCollision
verwenden, besteht darin, dass der Kollisionsrückruf nur einmal ausgelöst werden soll.
Bei einer Kollision mit dem Jungen verschwinden jetzt Münzen. Fügen wir die Benutzeroberfläche hinzu, um die Anzahl der gesammelten Münzen zu verfolgen.
HUD oder Heads-up-Display ist einfach eine Statusleiste, die alle Informationen über das Spiel anzeigt: Trefferpunkte, Munition usw. Wir werden für jede gesammelte Münze ein Münzsymbol anzeigen.
Der Einfachheit halber speichere ich die Anzahl der Münzen in einer Variablen, aber für komplexere Schnittstellen sollten Sie die Verwendung eines Flame_bloc- Pakets in Betracht ziehen, mit dem Sie den Spielstatus auf bequeme Weise aktualisieren und beobachten können.
Fügen Sie eine neue Klasse hinzu, die HUD-Logik enthalten soll: lib/hud.dart
class Hud extends PositionComponent with HasGameRef<PlatformerGame> { Hud() { positionType = PositionType.viewport; } void onCoinsNumberUpdated(int total) { final coin = SpriteComponent.fromImage( game.images.fromCache(Assets.HUD), position: Vector2((50 * total).toDouble(), 50), size: Vector2.all(48)); add(coin); } }
Zwei interessante Dinge hier:
positionType
auf PositionType.viewport
, um unser HUD an der Bildschirmecke festzuhalten. Wenn wir das nicht tun, bewegt sich das HUD aufgrund der Kamerabewegung mit dem Level.onCoinsNumberUpdated
wird jedes Mal aufgerufen, wenn der Spieler die Münze sammelt. Es verwendet total
, um den Versatz des nächsten Münzsymbols zu berechnen, und fügt dann ein neues Münz-Sprite an der berechneten Position hinzu.
Kehren Sie als Nächstes zur Datei game.dart
zurück und fügen Sie neue Klassenvariablen hinzu:
int _coins = 0; // Keeps track of collected coins late final Hud hud; // Reference to the HUD, to update it when the player collects a coin
Fügen Sie dann die Hud
Komponente am Ende der onLoad
Methode hinzu:
hud = Hud(); add(hud);
Und fügen Sie eine neue Methode hinzu:
void onCoinCollected() { _coins++; hud.onCoinsNumberUpdated(_coins); }
Rufen Sie es schließlich über collect
-Methode von Coin
auf :
void collect() { game.onCoinCollected(); animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; }
Genial, unser HUD zeigt jetzt an, wie viele Münzen wir gesammelt haben!
Das Letzte, was ich hinzufügen möchte, ist der Gewinnbildschirm, der angezeigt wird, sobald der Spieler alle Münzen gesammelt hat.
Fügen Sie der PlatformerGame
Klasse eine neue Konstante hinzu:
late int _totalCoins;
Und weisen Sie ihm die Anzahl der Münzen zu, die wir im Level haben. Fügen Sie diese Zeile am Ende der spawnObjects
Methode hinzu:
_totalCoins = coins.objects.length;
Und fügen Sie dies am Ende der onCoinCollected
Methode hinzu.
Beachten Sie, dass Sie möglicherweise import 'package:flutter/material.dart';
manuell.
if (_coins == _totalCoins) { final text = TextComponent( text: 'U WIN!', textRenderer: TextPaint( style: TextStyle( fontSize: 200, fontWeight: FontWeight.bold, color: Colors.white, ), ), anchor: Anchor.center, position: camera.viewport.effectiveSize / 2, )..positionType = PositionType.viewport; add(text); Future.delayed(Duration(milliseconds: 200), () => { pauseEngine() }); }
Hier überprüfe ich, ob der Münzzähler der Anzahl der Münzen im Level entspricht, und füge, wenn ja, oben auf dem Spielbildschirm ein Win-Label hinzu. Unterbrechen Sie dann das Spiel, um die Bewegung des Spielers zu stoppen. Ich habe außerdem eine Verzögerung von 200 ms hinzugefügt, damit das Etikett vor dem Anhalten gerendert wird.
Es sollte so aussehen:
Und das ist das Spiel! Natürlich sieht es jetzt noch nicht nach einem fertigen Spiel aus, aber nach allem, was ich erklärt habe, sollte es ziemlich einfach sein, weitere Level, Feinde oder andere Sammelgegenstände hinzuzufügen.
Dieser Teil schließt die Serie ab.
Die Flame-Engine hat noch viel mehr zu bieten, was ich nicht behandelt habe, einschließlich der Physik-Engine Forge2D, Partikel, Effekte, Spielmenüs, Audio usw. Aber nach Abschluss dieser Serie habe ich ein Verständnis dafür, wie die Engine ist, und ich Sie haben ein Verständnis dafür, wie man komplexere Spiele erstellt.
Flame ist ein leistungsstarkes und dennoch einfach zu verwendendes und zu erlernendes Tool. Es ist modular aufgebaut, sodass andere coole Dinge wie Box2D integriert werden können, und es wird aktiv gepflegt. Einer der größten Vorteile von Flame besteht jedoch darin, dass es auf Flutter aufbaut, was bedeutet, dass es mit etwas zusätzlichem Aufwand Multiplattform-Unterstützung bietet. Da es sich jedoch um eine Flutter-Erweiterung handelt, bestehen alle Flutter-Probleme auch in Flame fort. Beispielsweise besteht der Antialiasing-Fehler von Flutter seit mehreren Jahren ohne Lösung, und Sie werden ihn möglicherweise auch in dem von uns erstellten Spiel bemerken. Aber insgesamt ist es ein großartiges Tool zum Erstellen von Spielen, das einen Versuch wert ist.
Den vollständigen Code für dieses Tutorial finden Sie in meinem Github
Am Ende jedes Teils füge ich eine Liste großartiger Entwickler und Ressourcen hinzu, von denen ich gelernt habe.