このサイトは、只今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

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

LLaMAモデル GGMLフォーマット(llama.cpp)をRustフレームワーク Leptosを用いて M1MacMiniでサクッと動かす。

Rust

2024.01.11

2024年 狙っているモバイルノートPC

tool

2024.01.07

MacOS XcodeにSDL2を追加

tool

2023.12.26

php 7.4にアップデート

PHP

2023.12.24

5分で覚える Flutter Flameで作る Wave Function Collapse - 波動関数崩壊アルゴリズム

AI・Bot・algorithm

2023.12.20

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

Flutter

2023.07.23

Flutterで作る ChatGPT Prompt Manager

Flutter

2023.07.12

【M1 Mac】Python ScrapyがImportErrorで大ハマリ。lxmlなど環境作成し対応した件。

Python

2023.05.24

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

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

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

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

FOLLOW US