paint-brush
Bringen Sie Ihrem Charakter bei, in Flammen zu laufenvon@eugene-kleshnin
3,868 Lesungen
3,868 Lesungen

Bringen Sie Ihrem Charakter bei, in Flammen zu laufen

von Eugene Kleshnin11m2023/02/28
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

Diese Artikelserie ist meine Reise zum Erlernen von Flame (und Flutter) und zum Erstellen eines einfachen Plattformspiels. Ich werde versuchen, es ziemlich detailliert zu gestalten, damit es für jeden nützlich sein sollte, der sich gerade erst mit Flame oder Spieleentwicklern im Allgemeinen beschäftigt. Im ersten Teil erstellen wir ein neues Flame-Projekt, laden alle Assets, fügen einen Spielercharakter hinzu und bringen ihm das Laufen bei.

People Mentioned

Mention Thumbnail
featured image - Bringen Sie Ihrem Charakter bei, in Flammen zu laufen
Eugene Kleshnin HackerNoon profile picture
0-item

Ich wollte schon immer Videospiele machen. Meine allererste Android-App, die mir dabei geholfen hat, meinen ersten Job zu bekommen, war ein einfaches Spiel, das mit Android-Ansichten erstellt wurde. Danach gab es viele Versuche, mithilfe einer Game-Engine ein aufwändigeres Spiel zu erstellen, die jedoch alle am Zeitmangel oder an der Komplexität eines Frameworks scheiterten. Aber als ich zum ersten Mal von der auf Flutter basierenden Flame-Engine hörte, war ich sofort von ihrer Einfachheit und plattformübergreifenden Unterstützung fasziniert und beschloss, ein Spiel damit zu entwickeln.


Ich wollte mit etwas Einfachem, aber dennoch Anspruchsvollem beginnen, um ein Gefühl für den Motor zu bekommen. Diese Artikelserie ist meine Reise zum Erlernen von Flame (und Flutter) und zum Erstellen eines einfachen Plattformspiels. Ich werde versuchen, es ziemlich detailliert zu gestalten, sodass es für jeden nützlich sein sollte, der sich gerade erst mit Flame oder Spieleentwicklern im Allgemeinen beschäftigt.


Im Laufe von 4 Artikeln werde ich ein 2D-Side-Scrolling-Spiel erstellen, das Folgendes umfasst:

  • Ein Charakter, der rennen und springen kann

  • Eine Kamera, die dem Spieler folgt

  • Scrollende Levelkarte mit Boden und Plattformen

  • Parallaxenhintergrund

  • Münzen, die der Spieler sammeln kann, und HUD, das die Anzahl der Münzen anzeigt

  • Bildschirm gewinnen


Komplettes Spiel


Im ersten Teil erstellen wir ein neues Flame-Projekt, laden alle Assets, fügen einen Spielercharakter hinzu und bringen ihm das Laufen bei.


Projektaufbau

Lassen Sie uns zunächst ein neues Projekt erstellen. Das offizielle Tutorial zum Bare Flame-Spiel beschreibt alle Schritte dazu hervorragend. Befolgen Sie es also einfach.

Eines muss noch hinzugefügt werden: Wenn Sie die Datei pubspec.yaml einrichten, können Sie die Bibliotheksversionen auf die neueste verfügbare Version aktualisieren oder sie unverändert lassen, da das Caret-Zeichen (^) vor einer Version sicherstellt, dass Ihre App die neueste Version verwendet -brechende Version. ( Caret-Syntax )

Wenn Sie alle Schritte befolgt haben, sollte Ihre main.dart Datei so aussehen:

 import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; void main() { final game = FlameGame(); runApp(GameWidget(game: game)); }

Vermögenswerte

Bevor wir fortfahren, müssen wir die Ressourcen vorbereiten, die für das Spiel verwendet werden. Assets sind Bilder, Animationen, Sounds usw. Für die Zwecke dieser Serie verwenden wir nur Bilder, die in der Spieleentwicklung auch Sprites genannt werden.


Der einfachste Weg, ein Plattform-Level zu erstellen, ist die Verwendung von Kachelkarten und Kachel-Sprites. Das bedeutet, dass die Ebene im Grunde ein Raster ist, in dem jede Zelle angibt, welches Objekt, welchen Boden oder welche Plattform sie darstellt. Später, wenn das Spiel läuft, werden die Informationen aus jeder Zelle dem entsprechenden Kachel-Sprite zugeordnet.


Mit dieser Technik erstellte Spielgrafiken können sehr aufwändig oder sehr einfach sein. In Super Mario Bros sieht man beispielsweise, dass sich viele Elemente wiederholen. Das liegt daran, dass es für jedes Bodenplättchen im Spielfeld nur ein Bodenbild gibt, das es darstellt. Wir werden den gleichen Ansatz verfolgen und für jedes statische Objekt, das wir haben, ein einzelnes Bild vorbereiten.


Das Level besteht aus sich wiederholenden Kacheln


Wir möchten auch, dass einige der Objekte, wie zum Beispiel der Spielercharakter und die Münzen, animiert werden. Animationen werden normalerweise als eine Reihe von Standbildern gespeichert, die jeweils ein einzelnes Bild darstellen. Bei der Wiedergabe einer Animation werden Einzelbilder nacheinander abgespielt, wodurch die Illusion entsteht, dass sich das Objekt bewegt.


Die wichtigste Frage ist nun, woher man das Vermögen bekommt. Natürlich können Sie sie selbst zeichnen oder einen Künstler damit beauftragen. Außerdem gibt es viele großartige Künstler, die Spielressourcen zu Open Source beigetragen haben. Ich werde das Arcade Platformer Assets-Paket von GrafxKid verwenden.


Typischerweise gibt es Bildressourcen in zwei Formen: Sprite-Sheets und einzelne Sprites. Ersteres ist ein großes Bild, das alle Spielinhalte in einem enthält. Dann geben die Spieleentwickler die genaue Position des benötigten Sprites an und die Spiel-Engine schneidet es aus dem Blatt. Für dieses Spiel werde ich einzelne Sprites verwenden (mit Ausnahme von Animationen, es ist einfacher, sie als ein Bild beizubehalten), da ich nicht alle im Sprite-Sheet bereitgestellten Elemente benötige.



Ein einzelnes Sprite, das den Boden darstellt


Spritesheet mit 6 Sprites für die Player-Animationen


Animation ausführen


Unabhängig davon, ob Sie Sprites selbst erstellen oder von einem Künstler beziehen, müssen Sie sie möglicherweise in Scheiben schneiden, damit sie besser für die Spiel-Engine geeignet sind. Sie können speziell für diesen Zweck erstellte Tools (z. B. Texture Packer) oder einen beliebigen grafischen Editor verwenden. Ich habe Adobe Photoshop verwendet, da die Sprites in diesem Sprite-Sheet einen ungleichen Abstand zwischen ihnen haben, was es für automatische Tools schwierig machte, Bilder zu extrahieren, sodass ich es manuell tun musste.


Möglicherweise möchten Sie auch die Größe der Assets erhöhen, aber wenn es sich nicht um ein Vektorbild handelt, könnte das resultierende Sprite unscharf werden. Eine Problemumgehung, die sich meiner Meinung nach hervorragend für Pixelkunst eignet, besteht darin, die Größenänderungsmethode Nearest Neighbour (hard edges) in Photoshop zu verwenden (oder „Interpolation“ in Gimp auf „Keine“ zu setzen). Wenn Ihr Asset jedoch detaillierter ist, wird es wahrscheinlich nicht funktionieren.


Nachdem Sie die Erklärungen aus dem Weg geräumt haben, laden Sie die von mir vorbereiteten Assets herunter oder bereiten Sie Ihre eigenen vor und fügen Sie sie dem Ordner assets/images Ihres Projekts hinzu.


Wenn Sie neue Assets hinzufügen, müssen Sie diese wie folgt in der Datei pubspec.yaml registrieren:

 flutter: assets: - assets/images/

Und der Tipp für die Zukunft: Wenn Sie bereits registrierte Assets aktualisieren, müssen Sie das Spiel neu starten, um die Änderungen zu sehen.


Jetzt laden wir die Assets tatsächlich in das Spiel. Ich mag es, alle Asset-Namen an einem Ort zu haben, was für ein kleines Spiel großartig ist, da es einfacher ist, den Überblick über alles zu behalten und es bei Bedarf zu ändern. Erstellen wir also eine neue Datei im lib Verzeichnis: assets.dart

 const String THE_BOY = "theboy.png"; const String GROUND = "ground.png"; const String PLATFORM = "platform.png"; const String MIST = "mist.png"; const String CLOUDS = "clouds.png"; const String HILLS = "hills.png"; const String COIN = "coin.png"; const String HUD = "hud.png"; const List<String> SPRITES = [THE_BOY, GROUND, PLATFORM, MIST, CLOUDS, HILLS, COIN, HUD];


Und dann erstellen Sie eine weitere Datei, die in Zukunft die gesamte Spiellogik enthalten wird: game.dart

 import 'package:flame/game.dart'; import 'assets.dart' as Assets; class PlatformerGame extends FlameGame { @override Future<void> onLoad() async { await images.loadAll(Assets.SPRITES); } }


PlatformerGame ist die Hauptklasse, die unser Spiel repräsentiert. Sie erweitert FlameGame , die Basisspielklasse, die in der Flame-Engine verwendet wird. Was wiederum Component erweitert – den Grundbaustein von Flame. Alles in Ihrem Spiel, einschließlich Bilder, Benutzeroberfläche oder Effekte, sind Komponenten. Jede Component verfügt über eine asynchrone Methode onLoad , die bei der Komponenteninitialisierung aufgerufen wird. Normalerweise befindet sich dort die gesamte Komponenten-Setup-Logik.


Schließlich haben wir unsere zuvor erstellte Datei assets.dart importiert und as Assets hinzugefügt, um explizit anzugeben, woher unsere Assets-Konstanten stammen. Und nutzte die Methode images.loadAll , um alle in der SPRITES Liste aufgeführten Assets in den Spielbild-Cache zu laden.


Dann müssen wir unser neues PlatformerGame aus main.dart erstellen. Ändern Sie die Datei wie folgt:

 import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; import 'game.dart'; void main() { runApp( const GameWidget<PlatformerGame>.controlled( gameFactory: PlatformerGame.new, ), ); }

Alle Vorbereitungen sind abgeschlossen und der spaßige Teil beginnt.


Spielercharakter hinzufügen

Erstellen Sie einen neuen Ordner lib/actors/ und darin eine neue Datei theboy.dart . Dies wird die Komponente sein, die den Spielercharakter repräsentiert: Der Junge.

 import '../game.dart'; import '../assets.dart' as Assets; import 'package:flame/components.dart'; class TheBoy extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { TheBoy({ required super.position, // Position on the screen }) : super( size: Vector2.all(48), // Size of the component anchor: Anchor.bottomCenter // ); @override Future<void> onLoad() async { animation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, // For now we only need idle animation, so we load only 1 frame textureSize: Vector2.all(20), // Size of a single sprite in the sprite sheet stepTime: 0.12, // Time between frames, since it's a single frame not that important ), ); } }

Die Klasse erweitert SpriteAnimationComponent eine Komponente, die für animierte Sprites verwendet wird und über ein Mixin HasGameRef verfügt, das es uns ermöglicht, auf das Spielobjekt zu verweisen, um Bilder aus dem Spielcache zu laden oder später globale Variablen abzurufen.


In unserer onLoad Methode erstellen wir eine neue SpriteAnimation aus dem THE_BOY Sprite-Sheet, das wir in der Datei assets.dart deklariert haben.


Jetzt fügen wir unseren Spieler zum Spiel hinzu! Kehren Sie zur Datei game.dart zurück und fügen Sie am Ende der onLoad Methode Folgendes hinzu:

 final theBoy = TheBoy(position: Vector2(size.x / 2, size.y / 2)); add(theBoy);

Wenn Sie das Spiel jetzt ausführen, sollten wir The Boy treffen können!


Lernen Sie den Jungen kennen

Spielerbewegung

Zuerst müssen wir die Möglichkeit hinzufügen, The Boy über die Tastatur zu steuern. Fügen wir das HasKeyboardHandlerComponents Mixin zur Datei game.dart hinzu.

 class PlatformerGame extends FlameGame with HasKeyboardHandlerComponents


Als nächstes kehren wir zum Mixin theboy.dart und KeyboardHandler zurück:

 class TheBoy extends SpriteAnimationComponent with KeyboardHandler, HasGameRef<PlatformerGame>


Fügen Sie dann einige neue Klassenvariablen zur TheBoy Komponente hinzu:

 final double _moveSpeed = 300; // Max player's move speed int _horizontalDirection = 0; // Current direction the player is facing final Vector2 _velocity = Vector2.zero(); // Current player's speed


Abschließend überschreiben wir die Methode onKeyEvent , die das Abhören von Tastatureingaben ermöglicht:

 @override bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) { _horizontalDirection = 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyA) || keysPressed.contains(LogicalKeyboardKey.arrowLeft)) ? -1 : 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyD) || keysPressed.contains(LogicalKeyboardKey.arrowRight)) ? 1 : 0; return true; }

Jetzt ist _horizontalDirection gleich 1, wenn sich der Spieler nach rechts bewegt, -1, wenn sich der Spieler nach links bewegt, und 0, wenn sich der Spieler nicht bewegt. Allerdings können wir es noch nicht auf dem Bildschirm sehen, da die Position des Spielers noch nicht verändert ist. Lassen Sie uns das beheben, indem wir die update Methode hinzufügen.


Jetzt muss ich erklären, was die Spielschleife ist. Im Grunde bedeutet es, dass das Spiel in einer Endlosschleife läuft. In jeder Iteration wird der aktuelle Status in der Methode render Component's gerendert und dann ein neuer Status in der Methode update berechnet. Der dt Parameter in der Signatur der Methode ist die Zeit in Millisekunden seit der letzten Statusaktualisierung. Fügen Sie in diesem Sinne Folgendes zu theboy.dart hinzu:

 @override void update(double dt) { super.update(dt); _velocity.x = _horizontalDirection * _moveSpeed; position += _velocity * dt; }

Für jeden Spielschleifenzyklus aktualisieren wir die horizontale Geschwindigkeit unter Verwendung der aktuellen Richtung und der Höchstgeschwindigkeit. Dann ändern wir die Sprite-Position mit dem aktualisierten Wert multipliziert mit dt .


Warum brauchen wir den letzten Teil? Wenn Sie die Position nur mit der Geschwindigkeit aktualisieren, fliegt das Sprite in den Weltraum. Aber können wir einfach den kleineren Geschwindigkeitswert verwenden, fragen Sie sich vielleicht? Das ist möglich, aber die Art und Weise, wie sich der Spieler bewegt, wird je nach Bildrate pro Sekunde (FPS) unterschiedlich sein. Die Anzahl der Frames (oder Spielschleifen) pro Sekunde hängt von der Spielleistung und der Hardware ab, auf der es ausgeführt wird. Je besser die Geräteleistung, desto höher die FPS und desto schneller bewegt sich der Spieler. Um dies zu vermeiden, machen wir die Geschwindigkeit von der Zeit abhängig, die seit dem letzten Frame vergangen ist. Auf diese Weise bewegt sich das Sprite bei jedem FPS ähnlich.


Okay, wenn wir das Spiel jetzt ausführen, sollten wir Folgendes sehen:


Der Junge ändert seine Position entlang der X-Achse


Großartig, jetzt lasst uns den Jungen dazu bringen, sich umzudrehen, wenn er nach links geht. Fügen Sie dies am Ende der update Methode hinzu:

 if ((_horizontalDirection < 0 && scale.x > 0) || (_horizontalDirection > 0 && scale.x < 0)) { flipHorizontally(); }


Ziemlich einfache Logik: Wir prüfen, ob sich die aktuelle Richtung (der Pfeil, den der Benutzer drückt) von der Richtung des Sprites unterscheidet, und drehen dann das Sprite entlang der horizontalen Achse.


Fügen wir nun auch eine Laufanimation hinzu. Definieren Sie zunächst zwei neue Klassenvariablen:

 late final SpriteAnimation _runAnimation; late final SpriteAnimation _idleAnimation;


Dann aktualisieren Sie onLoad wie folgt:

 @override Future<void> onLoad() async { _idleAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, textureSize: Vector2.all(20), stepTime: 0.12, ), ); _runAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(20), stepTime: 0.12, ), ); animation = _idleAnimation; }

Hier haben wir zuvor hinzugefügte Leerlaufanimationen zur Klassenvariablen extrahiert und eine neue Laufanimationsvariable definiert.


Als nächstes fügen wir eine neue updateAnimation Methode hinzu:

 void updateAnimation() { if (_horizontalDirection == 0) { animation = _idleAnimation; } else { animation = _runAnimation; } }


Rufen Sie schließlich diese Methode am Ende der update Methode auf und führen Sie das Spiel aus.

Animation ausführen und Sprite umdrehen


Abschluss

Das war’s für den ersten Teil. Wir haben gelernt, wie man ein Flame-Spiel einrichtet, wo man Assets findet, wie man sie in das Spiel lädt und wie man einen fantastischen animierten Charakter erstellt und ihn auf der Grundlage von Tastatureingaben bewegt. Den Code für diesen Teil finden Sie auf meinem Github .


Im nächsten Artikel beschreibe ich, wie man mit Tiled ein Spiellevel erstellt, wie man die Flame-Kamera steuert und wie man einen Parallaxenhintergrund hinzufügt. Bleiben Sie dran!

Ressourcen

Am Ende jedes Teils füge ich eine Liste großartiger Entwickler und Ressourcen hinzu, von denen ich gelernt habe.