このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

Archives Details

Flutter – Flameでゲーム作成 (キャラクターの移動)

Flutter

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ゲームの作成がより簡単になります。

公式サイト

https://flame-engine.org/

公式ドキュメント(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

isaiah658

 

スプライト読み込み

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のゲームエンジンとなっております。

次回は、マップ移動やゲーム作成について触れられればと思っております。

ではではぁ。

Comment

Related Article

Flutter – Flameでゲーム作成 (キャラクターの移動)

2023.07.23

Flutterで作る ChatGPT Prompt Manager

2023.07.12

【Flutter】CheckboxListTileのチェックボックスをカスタマイズ

2022.10.01

CATEGORY LIST

LATEST NEWS

Mac VSCodeで、SFML C++開発環境を作る。

C++

2024.09.09

Rust-SDL2 examplesをすべて試す

Rust

2024.09.01

JavaScriptで、DOMを放り投げる処理

JavaScript

2024.07.27

Rustで創る MOS 6502 CPU その2

Rust

2024.07.23

Rustで創る MOS 6502 CPU その1

Rust

2024.07.19

汎用 3D mesh/model viewerを求め。と、簡単に、FBXファイルをglTF(glb)に変換ツールを求め。

C++

2024.06.06

M1 Macで、OpenGL GLUTを使ってコンパイルする

C

2024.04.27

Rust - Actix Web mongo ユーザー登録 JWT認証

Rust

2024.03.24

Rust - Actix Web JWT 認証認可 APIの作成

Rust

2024.02.25

Rust - Actix Web × JSON 静的ファイルをAPIで返却

Rust

2024.01.19

Rust - Actix Web × MongoDB環境をサクッと起動

Rust

2024.01.18

5分で学ぶ RustでWave Function Collapse (波動関数崩壊アルゴリズム)

Rust

2024.01.15

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、主に、ゲーム制作中心に港区六本木界隈で活動中。

FOLLOW US