4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FlutterでRPGゲームを作る【Part 2/5 - プレイヤー】

Last updated at Posted at 2022-10-24

Banner.gif

はじめに

開発環境

OS:       macOS 12.6
Flutter:  3.3.5
Bonfire:  2.10.10
Tiled:    1.9.2
Xcode:    14.0.1
テスト環境: Chrome, iOS Simulator

今回のパートの大まかな流れ

  1. プレイヤー用アニメーションスプライトの作成
  2. プレイヤークラスの作成
  3. マップ用タイルに当たり判定を設定 (Tiled利用)
  4. 奥行きを表現する

1. プレイヤー用アニメーションスプライトの作成

1.1 アセットの準備

  • こちらから画像をダウンロード
    sNCkWs.png
    Download NowからNo thanks, just take me to the downloadsを選択で無料ダウンロードできます
  • ダウンロードした画像をsimple_bonfire/assets/images/charactorsに配置
    (simple_bonfireはFlutterプロジェクト名です)
  • pubspec.yamlに追加したアセットのディレクトリを記載
pubspec.yaml
flutter:
  uses-material-design: true
  assets:
    - assets/images/
    - assets/images/maps/
    - assets/images/charactors/ # ←追加

1.2 追加したアセットからSpriteSheetを作成

  • ゲームで使用できるSpriteSheet生成するためのクラスを実装
    • libディレクトリ下にplayer/player_sprite.dartを作成します
    • 今回は複数のキャラクターが1枚になったアセットを利用します
    • 画像内のそれぞれのコマは、左から右の順でコマ送りになっています
simple_bonfire/lib/player/player_sprite.dart
import 'package:bonfire/bonfire.dart';

class PlayerSpriteSheet {
  static late SpriteSheet all;

  // ゲーム起動時に実行するメソッド
  static Future<void> load() async {
    // 追加したアセットのパスからSpriteSheetを作成
    all = await _create('charactors/franukas_charactors_16x24.png');
  }

  // 画像からSpriteSheetを生成する処理本体
  static Future<SpriteSheet> _create(String path) async {
    // ファイルパスから画像を取得して
    final image = await Flame.images.load(path);
    // 1枚ずつ分割する (横40x縦8)
    return SpriteSheet.fromColumnsAndRows(image: image, columns: 40, rows: 8);
  }
}

  • 起動後にSpriteSheetを作成するようmain()に追加
simple_bonfire/lib/main.dart
void main() async {
  // ...省略...

  // アセットからSpriteSheetを生成
  await PlayerSpriteSheet.load(); // ←追加
  runApp(const MyApp());
}

2. プレイヤークラスの作成

  • 作成したSpriteSheetを実際に表示して動かすプレイヤークラスを作成します

2.1 プレイヤークラスの作成

  • lib/playerディレクトリ下にplayer_beared_dude.dartを作成します
  • プレイヤーはBonfireSimplePlayerクラスを継承します
    • ObjectCollision Mixinを継承し当たり判定を持たせます
  • ここではコンストラクタ内で以下の4つを設定します
    • idle状態とrun状態のアニメーション
    • ゲーム内での表示サイズ
    • run状態時の移動スピード
    • 当たり判定
  • idlerunにはそれぞれ上下左右があるため、合計8種類分のアニメを設定します
simple_bonfire/lib/player/player_beared_dude.dart
import 'package:bonfire/bonfire.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class PlayerBeardedDude extends SimplePlayer with ObjectCollision {
  PlayerBeardedDude(
    position, {
    required this.spriteSheet,
    Direction initDirection = Direction.down,
  }) : super(
          // SpriteSheetからアニメーションを設定
          animation: SimpleDirectionAnimation(
            idleDown: spriteSheet.createAnimation(row: 0, stepTime: 0.4, from: 1, to: 3).asFuture(),
            idleLeft: spriteSheet.createAnimation(row: 1, stepTime: 0.4, from: 1, to: 3).asFuture(),
            idleRight:
                spriteSheet.createAnimation(row: 2, stepTime: 0.4, from: 1, to: 3).asFuture(),
            idleUp: spriteSheet.createAnimation(row: 3, stepTime: 0.4, from: 1, to: 3).asFuture(),
            runDown: spriteSheet.createAnimation(row: 0, stepTime: 0.1, from: 4, to: 8).asFuture(),
            runLeft: spriteSheet.createAnimation(row: 1, stepTime: 0.1, from: 4, to: 8).asFuture(),
            runRight: spriteSheet.createAnimation(row: 2, stepTime: 0.1, from: 4, to: 8).asFuture(),
            runUp: spriteSheet.createAnimation(row: 3, stepTime: 0.1, from: 4, to: 8).asFuture(),
          ),
          // 画面上の表示サイズ
          size: Vector2(16, 24) * 3,
          // 移動速度
          speed: 80 * 3,
          position: position,
          initDirection: initDirection,
        ) {
    // 当たり判定の設定
    setupCollision(
      CollisionConfig(
        collisions: [
          CollisionArea.rectangle(
            // 首から足下までの矩形を指定
            size: Vector2(size.x * 12 / 16, size.y * 7 / 24),
            align: Vector2(size.x * 2 / 16, size.y * 16 / 24),
          ),
        ],
      ),
    );
  }
  final SpriteSheet spriteSheet;
}
  • createAnimation()メソッドについて
    アセットからコマを切り出して、コマ送りアニメーションを生成します
    引数の数値の設定方法にクセがあるので要注意です
    • row:スプライトシートの上から何行目かを指定
      Screen Shot 2022-10-24 at 0.18.50.png
    • from:アニメの開始コマが左から何番目かを指定
    • to:アニメの終了コマが左から何番目かを1始まりの番号 (要注意!) で指定
      Screen Shot 2022-10-24 at 0.18.50 2.png
    • stepTime:1コマあたりの表示時間、小さいほどコマ送りが速くなります

2.2 マップに配置する

  • Part 1で作成したマップにプレイヤーを追加
    • 初期位置と初期方向initDirectionを適当に決める
    • spriteSheetは先に作成したPlayerSpriteSheetからallを取得
  • カメラの追従設定を好みで追加
    • moveOnlyMapAreatrueでタイルの無い部分へのカメラ移動を制限
    • sizeMovementWindow: プレイヤーを収めるカメラ内の範囲
      (広くするほどカメラの追従が甘くなる)
    • smoothCameraEnabledtrueでカメラの動きが滑らかになる
    • smoothCameraSpeedsmoothCameraEnabled: true時の、カメラの軽快さ
      (小さくするとカメラが緩やかに動き始め、緩やかに止まる)
simple_bonfire/lib/maps/halloween_map_01.dart
// ...省略...

class _HalloweenMap01State extends State<HalloweenMap01> {
  final tileSize = 48.0; // タイルのサイズ定義

  @override
  Widget build(BuildContext context) {
    // 画面
    return BonfireWidget(
      showCollisionArea: true, // 当たり判定の可視化
      // マップ用jsonファイル読み込み
      map: WorldMapByTiled(
        'maps/halloween_map_01.json',
        forceTileSize: Vector2(tileSize, tileSize),
      ),

      // ---------- ここから追加 ----------
      // プレイヤーキャラクター
      player: PlayerBeardedDude(
        Vector2(tileSize * 7.5, tileSize * 4),
        spriteSheet: PlayerSpriteSheet.all,
        initDirection: Direction.down,
      ),
      // カメラ設定
      cameraConfig: CameraConfig(
        moveOnlyMapArea: true,
        sizeMovementWindow: Vector2.zero(),
        smoothCameraEnabled: true,
        smoothCameraSpeed: 10,
      ),
      // ---------- ここまで追加 ----------

      // ...以下略...
    );
  }
}

  • BonfireWidget内にshowCollisionArea: true,を追加して当たり判定を設定
    showCollisionAreaでより当たり判定を可視化することができます
    この時点でジョイスティックやWASDキーでキャラクターが操作できる様になっているので、アニメーションや移動の速度、カメラの追従が想定通りに設定できているかチェックしてください
    Screen Shot 2022-10-24 at 0.40.06.png

3. マップ用タイルに当たり判定を設定

  • プレイヤー以外のオブジェクトに当たり判定を設定します
  • Part 1で作成したタイルセットのjsonファイルをTiledを利用して編集します

3.1 柵にシンプルな当たり判定を追加

  • Open file or Projectを選択、Part 1で作成したsimple_bonfire.tiled-projectを開く
    左のカラムにTiledプロジェクトのディレクトリと、Part 1で作成したjsonファイルが表示されます

Screen Shot 2022-10-23 at 23.03.36.png

  • 左のカラムからtileset.jsonを選択して、Part 1で追加したアセットがタイル状に表示されるのを確認
    ここでは上部の柵に当たり判定をつけるため、タイル一覧最上段のID 17 (左から18番目) のタイルを選択します

Screen Shot 2022-10-23 at 23.06.09.png

  • メニューからタイルセット > タイルの当たり判定エディターを選択
  • 選択したタイルが拡大表示されたら四角形を追加を選び、画像の様に一つ四角形を追加する
    右側の画面で視覚を追加した後、真ん中のプロパティから値を変更することもできます

Screen Shot 2022-10-23 at 23.07.04.png

  • 保存をするとfranuka_halloween_tileset.jsontiles内の配列にid: 17の要素が追加されている
franuka_halloween_tileset.json
{
    "columns": 32,
    "image": "franuka_halloween_tileset.png",
    "imageheight": 512,
    "imagewidth": 512,
    "margin": 0,
    "name": "franuka_halloween_tileset",
    "spacing": 0,
    "tilecount": 1024,
    "tiledversion": "1.9.2",
    "tileheight": 16,
    "tiles": [
        {
            "id": 17,
            "objectgroup": {
                "draworder": "index",
                "id": 2,
                "name": "",
                "objects": [
                    {
                        "class": "",
                        "height": 12,
                        "id": 4,
                        "name": "",
                        "rotation": 0,
                        "visible": true,
                        "width": 16,
                        "x": 0,
                        "y": 1
                    }
                ],
                "opacity": 1,
                "type": "objectgroup",
                "visible": true,
                "x": 0,
                "y": 0
            }
        }
    ],
    "tilewidth": 16,
    "type": "tileset",
    "version": "1.9"
}
  • ゲーム画面内で想定通りの当たり判定が設定されているか確認
    キャラクターを操作すると、柵より向こうに移動できなくなっているはずです
    Screen Shot 2022-10-23 at 23.24.35.png

3.2 複雑な形状の当たり判定の追加

  • 同様に他のオブジェクトにも当たり判定を設定
    四角形以外の図形や複数の図形を追加することもできますが、画像の例では、柵の角に二つの四角形を追加しています
    Screen Shot 2022-10-23 at 23.30.56.png

  • ゲーム画面で想定通りの当たり判定が追加されたのを確認する
    Screen Shot 2022-10-23 at 23.32.18.png

4. 奥行きを表現する

  • 木などの高さのあるオブジェクトは、当たり判定だけではうまく表現できません
  • オブジェクトの後ろにキャラクターが回り込める様にして、高さと奥行きを表現します
  • 画像は回り込みを実装する前なので、キャラクターが木の上に描画されてしまっています
    Screen Shot 2022-10-23 at 23.33.02.png
  • 木の根元に当たり判定をつける
    柵と同じ要領で、木の根元のタイルに当たり判定をつけます。
    今回は左右のタイルに分かれているので、両方に適当な形を追加します。
    Screen Shot 2022-10-23 at 23.35.14.png
  • 木の上部にabove属性を付与
    木の上部は高さがありキャラクターと衝突しない想定のため、当たり判定はつけません
    その代わりに、プロパティ内のClassaboveを入力して、キャラクターより手前に描画される様にします
    Screen Shot 2022-10-23 at 23.38.29.png
  • 保存後franuka_halloween_tileset.json内のtilesclass: aboveのタイルが登録されているのを確認する
franuka_halloween_tileset.json
{
    "columns": 32,
    "image": "franuka_halloween_tileset.png",

    "長いので": "中略",

    "tiles": [
        {"長いので": "省略"}

        {
            "class": "above"
            "id": 205,
        }

        {"長いので": "省略"}
    ],
    "tilewidth": 16,
    "type": "tileset",
    "version": "1.9"
}
  • ゲーム画面で当たり判定と、回り込みが再現できているか確認
    画像の様に、木のまわりでの衝突と描画が上手く再現できたら完成です。
    Bonfireにおけるタイルのタイプは他にもdynamicAboveがあります。
    詳しくはこちらをご覧ください
    Screen Shot 2022-10-23 at 23.37.37.png
    Screen Shot 2022-10-23 at 23.38.04.png

おわりに

マップとキャラクターの作成ができました。
Bonfire (と裏で動いているFlameエンジン) のおかげで、とてもシンプルな手順でここまでゲーム開発ができます。
次回のパート以降ではマップの移動、アイテムやNPCなどのRPGゲームらしい要素を実装します。
いいね、質問やコメントお待ちしております!

Part 3はこちら

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?