Flutter – Flameでゲーム作成 (キャラクターの移動)
2023.07.23
この記事は最終更新日から1年以上が経過しています。
Flameは、2Dゲーム開発に特化したFlutterの拡張パッケージとなります。Flameを使用することで、簡単に美しい2Dゲームやインタラクティブなアプリケーションを作成することができます。
Flameは、Flutterと組み合わせて使用することで、高速でパフォーマンスの良いゲームを開発することができます。また、シンプルなAPIと使いやすさが特徴であり、ゲーム開発者にとって便利なツールとなっています。
Flameは、以下のような機能を提供しています。
- ゲームループの管理
- 統合タッチイベントのサポート
- アニメーションの作成
- 物理エンジンのサポート
- カメラの制御
- パーティクルエフェクトの作成
etc..
Flameは、FlutterのWidgetとして実装されており、Flutterの他のWidgetと同様に、簡単に使用することができます。FlutterのWidgetの知識があれば、Flameを使用することは簡単です。
Flameの開発には、Dart言語を使用します。Dartは、Flutterの開発に使用されるプログラミング言語であり、JavaScriptやJavaといった言語に似た構文を持っています。Dartは、静的型付け言語であり、高速で安全なコードを書くことができます。
FlutterとFlameを使用することで、美しい2Dゲームを作成することができます。Flutterは、Android、iOS、Webなどの多くのプラットフォームをサポートしており、Flameはそれらのプラットフォームで動作します。Flameを使用することで、Flutterを使用した2Dゲームの作成がより簡単になります。
公式サイト
公式ドキュメント(1.8.0)
https://docs.flame-engine.org/1.8.0/
Pub.Dev(1.8.0)
https://pub.dev/packages/flame/install
といった感じで、ざっっと概要は述べたところで、実際に使っていきましょう。
一つのゲーム作成までと行きたいところですが、そうなると結構ボリューミィになってしまうので、今回はとりあえず、キャラクターを表示し動かすところまでやっていきましょう。
では、早速パッケージを追加して行きましょう。
pubコマンドを用いて、pub devよりパッケージを追加します。
flutter pub add flame
pubspec.yaml
dependencies: flutter: sdk: flutter flame: 1.6.0 flutter: assets: - assets/images/
追加できていればオッケー。
手動で記述し、パッケージを追加することも可能です。
$ flutter pub get
main.dart
import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import '〇〇.dart'; void main() { final game = 〇〇Game(); runApp( GameWidget(game: game), ); }
flameパッケージ追加後、importを行い、GameWidgetを使用して内容を作成していきます。
実際のソースはこの様な形となりました。
import 'package:flame/flame.dart'; import 'package:flutter/material.dart'; import 'main_game_page.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(const App()); } class App extends StatelessWidget { const App({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, title: 'SpriteAnime', home: MainGamePage(), ); } }
ページ単位で切り出せる様に、main_game_page.dartを設け、StatefulWidgetとして作成し、実際のflameのゲーム処理に関しては、更にgame.dartとファイルの切り出し。
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flame/palette.dart'; import 'package:flame/game.dart'; import 'helpers/joypad.dart'; import 'game.dart'; class MainGamePage extends StatefulWidget { const MainGamePage({Key? key}) : super(key: key); @override MainGameState createState() => MainGameState(); } class MainGameState extends State<MainGamePage> { MainGame game = MainGame(); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color.fromRGBO(0, 0, 0, 1), body: Stack( children: [ GameWidget(game: game), Align( alignment: Alignment.bottomRight, child: Padding( padding: const EdgeInsets.all(32.0), child: Joypad(onDirectionChanged: game.onJoyPadDirectionChanged), ), ) ], ) ); } }
枠組みはこの様な形で、キャラクターを動かすために、player componentを作成していきます。
キャラクターを動かす
今回のゴールとして、キャラクターを表示し、移動させるところまで行いたいと思います。イメージは以下の様にウィンドウ上にキャラクターが表示し、2D RPG的な動きで上下左右キーボードの操作で移動できる形となります。
説明はいいから、ソースを先に確認されたい方は、サンプルのソースはこちらとなります。
https://github.com/flame-games/player_move
git cloneを行い、fluter runコマンドで実行することができます。
スプライトの読み込み
上方向にキャラクターが移動する際は、上方向の表示、下方向の場合は下向きの表示など各表示似合わせてキャラクターも表示するように、まずはキャラクターのスプライト画像の読み込みを行います。使用したスプライト画像はこちらのものとなります。
キャラクタースプライト画像
Retro Character Sprite Sheet
スプライト読み込み
components/player.dart ファイルを作成。
今回は、flameのSpriteAnimationComponentを用いて作成するので、SpriteAnimationComponentを継承したPlayer Classを作成。
components/player.dart
class Player extends SpriteAnimationComponent with HasGameRef {
キャラクターのアニメーションを行う、_loadAnimations関数を作成。
onLoadをoverrideし、_loadAnimations関数を実行します。
@override Future<void> onLoad() async { super.onLoad(); await _loadAnimations().then((_) => {animation = _standingAnimation}); }
Future<void> _loadAnimations() async { final spriteSheet = SpriteSheet( image: await gameRef.images.load('sp_player.png'), srcSize: Vector2(84.0, 110.0), ); ....
FlameのSpriteSheetを用いて画像を読み込みます。
この際、Player Classでwith HasGameRefを行うことによって、gameRefを使用可能となっていますので、gameRef.images.loadで対象のスプライト画像を読み込みし、スプライト画像をクリッピングするサイズの指定を行います。
スプライトアニメーション
スプライトアニメーションが行われるように、アニメーションの設定を行います。
スプライト画像のキャラクターは、1行目が下向き(Down)、2行目が上向き(Up)…という形となっておりますので、それに合わせ設定。
_runDownAnimation = spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, to: 4);
spriteSheetのcreateAnimationメソッドで設定を行います。
row 0は、スプライト画像の1行目に該当し、stepTimeは スプライト画像が4stepで用意されているので4を指定。これを _runDownAnimation変数として扱います。
その他の方向も作成します。
アニメーションの速度とキャラクターの移動速度を_playerSpeed、_animationSpeedの定数として用意しておきます。
final double _playerSpeed = 300.0; final double _animationSpeed = 0.15; ... late final SpriteAnimation _runDownAnimation; ...
キャラクターの移動
サンプルにはJoypadによる実装も入っておりますが、今回の説明としてはキーボード入力のみとしておきます。
キーボードイベントが取得できるように、game.dartファイルを作成していきます。
MainGame ClassはFlameGameを継承し、KeyboardEventsも扱えるように withで指定しておきます。
game.dart
class MainGame extends FlameGame with KeyboardEvents {
KeyboardEventsのonKeyEventをoverrideします。RawKeyEventとSet<LogicalKeyboardKey>を受け取ります。
@override KeyEventResult onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
キャラクターの方向(キーボードが押されている方向)のDirection enumを作成。
enum Direction { up, down, left, right, none }
今回は、上: w、左: a、右: d、下: sのキーに割り当てたいので、event.logicalKeyがLogicalKeyboardKeyのkeyAやkeyDと同等かでkeyDirection変数に格納しております。
キーアップする(離す)ことで、キャラクター移動も止め、アイドルアニメーション割当ができるように、isKeyDownとkeyDirectionで判定します。
キーアップ(押されていない状態)だと、_player.directionにはDirection.noneが割当てられ、それ以外はそれぞれの方向が割当てられます。
game.dart
@override KeyEventResult onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) { final isKeyDown = event is RawKeyDownEvent; Direction? keyDirection = null; if (event.logicalKey == LogicalKeyboardKey.keyA) { keyDirection = Direction.left; } else if (event.logicalKey == LogicalKeyboardKey.keyD) { keyDirection = Direction.right; } else if (event.logicalKey == LogicalKeyboardKey.keyW) { keyDirection = Direction.up; } else if (event.logicalKey == LogicalKeyboardKey.keyS) { keyDirection = Direction.down; } if (isKeyDown && keyDirection != null) { _player.direction = keyDirection; } else if (_player.direction == keyDirection) { _player.direction = Direction.none; } return super.onKeyEvent(event, keysPressed); }
再び、Player componentを修正。
ゲームサイクルのupdate内で、キャラクター移動に関することを監視したいので、
updateをoverrideし、キャラクター移動に関する関数、movePlayerを実行します。
movePlayer関数内で、playerのdirectionをチェックし、Direction.upやDirection.downなどenumと比較し、それぞれの向きによって用意した関数を引数deltaを渡し実行します。
また、animation = _runUpAnimation;で、Player自身のanimationを最初の方に作成した各方向それぞれ方向別に用意した、spriteSheetのアニメーションを渡し、キャラクターアニメーションも実行します。
components/player.dart
@override void update(double delta) { super.update(delta); movePlayer(delta); } void movePlayer(double delta) { switch (direction) { case Direction.up: animation = _runUpAnimation; moveUp(delta); break; case Direction.down: animation = _runDownAnimation; moveDown(delta); break; case Direction.left: animation = _runLeftAnimation; moveLeft(delta); break; case Direction.right: animation = _runRightAnimation; moveRight(delta); break; case Direction.none: animation = _standingAnimation; break; } }
上であれば、moveUp関数を実行。
Player Class(SpriteAnimationComponent)のpositionを変更していきます。
上方向であれば、y座標の値がマイナスとなるので、deltaと_playerSpeedを用いてy座標を減算し、Player自身を上に移動させていきます。
void moveUp(double delta) { position.add(Vector2(0, delta * -_playerSpeed)); }
キャラクターを実際に画面表示するには、Player componentをMainGame classのプロパティとして保持、onLoad時に addします。
game.dart
final Player _player = Player(); @override Future<void> onLoad() async { super.onLoad(); add(_player); }
これで一通り必要最低限のキャラクター移動の実装ができましたので、動かしてみましょう。
と、Flatter、Flameを色々触っていたのが4ヶ月前と、時間の流れの速さを感じており、振り返りがてら備忘録としてFlatter、Flame周りについて書いて行こうかと思っております。
4ヶ月前は Flameのバージョンも1.6.0が最新だったのですが、現在では1.8.1が最新となっており、こちらの開発の速さも感じれます。
開発も活発に行われていてこれからも期待できそうなFlutterのゲームエンジンとなっております。
次回は、マップ移動やゲーム作成について触れられればと思っております。
ではではぁ。