はじめに
- Flutterでマルチプラットフォーム対応 (Web, モバイル, PC) のRPGゲームを作成します
- ソースコードはこちら (アセットなど一部差し替えています)
- この記事は5つのPartに分かれていて、今回はそのPart 3です。
- Part 1:マップとUIの実装
- Part 2:プレイヤーと当たり判定の実装
- Part 3:マップ移動の実装
- Part 4:アイテムと取得イベントの実装
- Part 5:NPCと会話の実装
- Bonfireという2Dゲームに特化したフレームワーク(Flutterパッケージ)を利用しています
- マップ制作にはTiledという無料のGUIツールを利用しています
Tiledは必須ではありませんが、使い方も簡単で制作作業がとても捗ります - アセットはitch.ioで購入できるものを利用しています
- トップの画像は利用しているアセットのサンプル画像です
開発環境
OS: macOS 12.6
Flutter: 3.3.5
Bonfire: 2.10.10
Tiled: 1.9.2
Xcode: 14.0.1
テスト環境: Chrome, iOS Simulator
今回のパートの大まかな流れ
- マップを作成しセンサーを配置する
- マップ移動のメソッドを作成する
- マップ移動を実装する
1. マップを作成しセンサーを配置する
1.1 2つ目のマップを作成する
- マップ移動を実装する前に、まず2つ目のマップを作成します。
マップWidgetはマップ生成用のjsonファイルを元に作られます。
GUIツールTiledを使ったマップ生成用jsonファイルの作成方法はPart 1やPart 2でも簡単に解説していますので、そちらをご覧ください。
今回もこのアセットで画像の様なマップを用意し、名前はsimple_bonfire/assets/maps/halloween_map_02.json
としました。
無料アセットを利用されたいかたはこちらなどがおすすめです
- 今回は柵と木や家を重ねて描画したり大きな木を隣接して配置するために、レイヤーをたくさん重ねています。
1.2 センサーを配置する
マップの移動はセンサーオブジェクトとプレイヤーの接触をトリガーにします。
-
オブジェクトレイヤーの追加
センサーを配置するために、マップにオブジェクトレイヤーを追加します。
レイヤー
>新規
>オブジェクトレイヤー
の順で選択すると、画像の様にタイルレイヤーとは異なるアイコンのレイヤーが右のカラムに追加されます。
-
センサーを配置
- 追加されたオブジェクトレイヤーをリストから選択した状態で、メニューの
四角形を追加
を選択する。 - マップの出口付近をドラッグしてセンサーを配置する。
- 追加されたオブジェクトレイヤーをリストから選択した状態で、メニューの
-
センサーのプロパティの編集
-
生成されたjsonファイルの
layers
の配列に"type": "objectgroup"
の要素が追加されているのを確認してください。
{
"compressionlevel": -1,
"height": 16,
"infinite": false,
"layers": [
{"tilelayerが": "ここに並びます"},
{
"draworder": "topdown",
"id": 6,
"name": "Object Layer 1",
"objects": [
{
"class": "exitSensor",
"height": 8,
"id": 1,
"name": "bottomExitSensor",
"rotation": 0,
"visible": true,
"width": 64,
"x": 128,
"y": 216
}
],
"opacity": 1,
"type": "objectgroup",
"visible": true,
"x": 0,
"y": 0
}
],
"以下": "略"
}
1.3 マップWidgetを作成する
ほぼPart 1で作成したhalloween_map_01.dart
のコピペなので、ここには変更箇所のみ記載します。
- 読み込むjsonのパス
- プレイヤーの初期位置を調整し、初期方向を上に
-
cameraConfig()
にzoom: 1.25
を追加
import 'package:flutter/material.dart';
import 'package:bonfire/bonfire.dart';
import 'package:flutter/services.dart';
import 'package:simple_bonfire/player/player_bearded_dude.dart';
import 'package:simple_bonfire/player/player_sprite.dart';
class HalloweenMap02 extends StatefulWidget {
const HalloweenMap02({Key? key}) : super(key: key);
@override
State<HalloweenMap02> createState() => _HalloweenMap01State();
}
class _HalloweenMap01State extends State<HalloweenMap02> {
final tileSize = 48.0; // タイルのサイズ定義
@override
Widget build(BuildContext context) {
// 画面
return BonfireWidget(
// showCollisionArea: true,
// マップ用jsonファイル読み込み
map: WorldMapByTiled(
'maps/halloween_map_02.json',
forceTileSize: Vector2(tileSize, tileSize),
),
// プレイヤーキャラクター
player: PlayerBeardedDude(
Vector2(tileSize * 9.5, tileSize * 12),
spriteSheet: PlayerSpriteSheet.all,
initDirection: Direction.up,
),
// カメラ設定
cameraConfig: CameraConfig(
zoom: 1.25, // 小さめのマップなので少し拡大してみる
moveOnlyMapArea: true,
sizeMovementWindow: Vector2.zero(),
smoothCameraEnabled: true,
smoothCameraSpeed: 10,
),
// 入力インターフェースの設定
joystick: Joystick(
// 画面上のジョイスティック追加
directional: JoystickDirectional(
color: Colors.white,
),
actions: [
// 画面上のアクションボタン追加
JoystickAction(
color: Colors.white,
actionId: 1,
margin: const EdgeInsets.all(65),
),
],
// キーボード用入力の設定
keyboardConfig: KeyboardConfig(
keyboardDirectionalType: KeyboardDirectionalType.wasdAndArrows,
acceptedKeys: [LogicalKeyboardKey.space],
),
),
// ロード中の画面の設定
progress: Container(
width: double.maxFinite,
height: double.maxFinite,
color: Colors.black,
),
);
}
}
1.4 もう一つのマップを調整する
Part 1で作ったマップのプレイヤーの初期位置を入り口の近くに変更します。(今回はセンサーのすぐ上あたりに変更)
// ...省略...
class _HalloweenMap01State extends State<HalloweenMap01> {
final tileSize = 48.0;
@override
Widget build(BuildContext context) {
// 画面
return BonfireWidget(
// ...省略...
// プレイヤーキャラクター
player: PlayerBeardedDude(
Vector2(tileSize * 9.5, tileSize * 12.5), // 初期位置変更
spriteSheet: PlayerSpriteSheet.all,
initDirection: Direction.up, // 初期向き変更
),
// ...省略...
);
}
}
2. マップ移動のメソッドを作成する
Flutterでよく使われるNavigator.push()
やpop()
による画面遷移はゲームのマップ移動には不向きです。
pushAndRemoveUntil()
を使い、より適した、そしてゲームらしいエフェクトの画面遷移を実装します。
2.1 遷移メソッドの作成
コードはBonfire公式サンプルのひとつ、MultiBiomeGameから拝借しました。
import 'package:flutter/material.dart';
extension BuildContextExtensions on BuildContext {
Future goTo(Widget page) {
return Navigator.pushAndRemoveUntil(
this,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return Container(
color: Colors.black,
child: FadeTransition(
opacity: animation,
child: child,
),
);
},
),
(Route<dynamic> route) => false,
);
}
}
2.2 マップ移動センサーデコレーションを作成
Bonfireでは、プレイヤーとマップ以外のゲーム内の要素は正式にはDecorationと呼びます。
ここでは遷移を実行するDecoration
を作成します。
プレイヤーと接触をトリガーにメソッドを実行できるSensor
を継承しています。
引数でposition
, size
と、移動先のマップWidgetを受け取ります。
import 'package:flutter/material.dart';
import 'package:bonfire/bonfire.dart';
import 'package:simple_bonfire/utilities/extentions.dart';
class ExitMapSensor extends GameDecoration with Sensor {
ExitMapSensor({required Vector2 position, required Vector2 size, required this.nextMap})
: super(position: position, size: size);
// 移動先のマップ
Widget nextMap;
// 連続実行防止用
bool hasContact = false;
@override
void onContact(component) {
if (!hasContact && component is Player) {
hasContact = true;
context.goTo(nextMap);
}
}
}
3. マップ移動を実装する
作成したExitMapSensor
デコレーションをオブジェクトとしてゲーム内に生成します。
3.1 マップにセンサーを追加
-
BonfireWidget
内のmap: WorldMapByTiled
にobjectsBuilder
を追加 -
objectsBuilder
にExitMapSensor
デコレーションを渡す
オブジェクト群はmap型で渡します。keyはTiledで作成した (jsonファイルに記載されている) オブジェクトの名前を、valueに作成したExitMapSensor
を渡します。
Tiledで設定した (jsonファイルに記載されている) オブジェクトの座標やサイズなどの値はproperties
の各プロパティから参照できます。
import 'package:simple_bonfire/utilities/exit_map_sensor.dart';
// ...省略...
class _HalloweenMap02State extends State<HalloweenMap02> {
final tileSize = 48.0; // タイルのサイズ定義
@override
Widget build(BuildContext context) {
// 画面
return BonfireWidget(
showCollisionArea: true, // センサー確認するためtrueにする
// マップ用jsonファイル読み込み
map: WorldMapByTiled(
'maps/halloween_map_02.json',
forceTileSize: Vector2(tileSize, tileSize),
objectsBuilder: {
// デコレーションを追加
'bottomExitSensor': (properties) => ExitMapSensor(
position: properties.position, // jsonから取得
size: properties.size, // jsonから取得
nextMap: const HalloweenMap01(), // 移動先のマップ
),
},
),
// ...中略...
);
}
}
3.2 ゲーム内で確認
BonfireWidget
内にshowCollisionArea: true
を追加してオブジェクトを可視化したら、ゲーム画面を確認します。
想定通りの位置にセンサーがあり、接触で実際にマップ移動することを確かめてください。
正しく実装できているのが確認できたら、移動先のマップにも同様にセンサーを実装しましょう。
同じ作業ですので詳しい説明は省きます。
import 'package:simple_bonfire/utilities/exit_map_sensor.dart';
// ...省略...
class _HalloweenMap01State extends State<HalloweenMap01> {
final tileSize = 48.0; // タイルのサイズ定義
@override
Widget build(BuildContext context) {
// 画面
return BonfireWidget(
showCollisionArea: true, // センサー確認するためtrueにする
// マップ用jsonファイル読み込み
map: WorldMapByTiled(
'maps/halloween_map_01.json',
forceTileSize: Vector2(tileSize, tileSize),
objectsBuilder: {
// デコレーションを追加
'bottomExitSensor': (properties) => ExitMapSensor(
position: properties.position, // jsonから取得
size: properties.size, // jsonから取得
nextMap: const HalloweenMap02(), // 移動先のマップ
),
},
),
// ...中略...
);
}
}
おわりに
今回はセンサーをマップ移動に利用しました。
次回Part 4では、落ちてるアイテムを拾ったりスイッチを踏むなどのイベントを引き続きセンサーを使って実装していきます。
いいねとコメント、質問などお待ちしております!