Open7
Flame でゲーム作ってみる
flutter create
で初期化して flutter pub add flame
で入れる。
TextComponent
で文字を描く。
タイトル画面とゲーム画面を分けてみたくなったので Router を導入する。
late final RouterComponent router;
void onLoad() {
add(
router = RouterComponent(
initialRoute: 'title',
routes: {
'title': Route(TitleScreen.new),
},
),
);
}
FlameGame
の延長みたいな遷移先を作りたい場合は HasGameReference
で作ると良いみたい。
class TitleScreen extends Component with HasGameReference<MyGame> {
static final _titleTextPaint = TextPaint(
style: const TextStyle(
fontFamily: 'Press Start 2P',
fontSize: 48,
color: Colors.white,
),
);
Vector2 get size => game.size;
void onLoad() {
super.onLoad();
final scale = size.scaleFromStandard();
add(
TextComponent(
text: 'Hello, Flame',
anchor: Anchor.center,
textRenderer: _titleTextPaint,
position: Vector2(size.x / 2, size.y / 3),
scale: scale,
),
);
}
}
final standardScreenSize = Vector2(960, 720);
extension on Vector2 {
Vector2 scaleFromStandard() =>
Vector2(x / standardScreenSize.x, y / standardScreenSize.y);
}
Scale を計算して画面サイズが変わっても大丈夫なようにしている。
Google Font からそれっぽいフォント探して埋め込んでみた。
画面サイズを固定したい。 Linux マシンで作成しているのでとりあえず Linux と Web で。
Linux は set_resizable
を呼び出す。
diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc
index bd13a93..17906ba 100644
--- a/linux/runner/my_application.cc
+++ b/linux/runner/my_application.cc
@@ -27,7 +27,7 @@ static void my_application_activate(GApplication* application) {
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
- gboolean use_header_bar = TRUE;
+ gboolean use_header_bar = FALSE;
#ifdef GDK_WINDOWING_X11
GdkScreen* screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
@@ -47,7 +47,8 @@ static void my_application_activate(GApplication* application) {
gtk_window_set_title(window, "flame_platformer");
}
- gtk_window_set_default_size(window, 1280, 720);
+ gtk_window_set_default_size(window, 960, 720);
+ gtk_window_set_resizable(window, FALSE);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
Web はホストする要素を作ってサイズを固定する。
diff --git a/web/index.html b/web/index.html
index 347c6dd..b08bdfa 100644
--- a/web/index.html
+++ b/web/index.html
@@ -33,6 +33,14 @@
<link rel="manifest" href="manifest.json">
</head>
<body>
+ <div style="width: 960px; margin: 0 auto;">
+ <header>
+ <h1>My Game</h1>
+ </header>
+ <main>
+ <div id="flutter_host" style="width: 960px; height: 720px;"></div>
+ </main>
+ </div>
<script src="flutter_bootstrap.js" async></script>
</body>
flutter_bootstrap.js
を作成してホストする Element を渡すようにする。
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
hostElement: document.getElementById('flutter_host'),
}
});
タイトルメニューを作る。
extends FlameGame with HasKeyboardHandlerComponents
で Mixin を追加してキーボードイベントを受けられるようにしておく。
enum _MenuItem {
gameStart('Game Start'),
exit('Exit'),
;
final String text;
const _MenuItem(this.text);
}
static final _itemTextPaint = TextPaint(
style: const TextStyle(
fontFamily: 'Press Start 2P',
fontSize: 16,
color: Colors.white,
),
);
static final _selectedItemTextPaint = TextPaint(
style: const TextStyle(
fontFamily: 'Press Start 2P',
fontSize: 16,
color: Colors.red,
),
);
late final List<TextComponent> _menuItems;
var _selected = _MenuItem.gameStart;
var _overlay = false;
void onLoad() {
// 略
addAll(
_menuItems = _MenuItem.values
.mapIndexed((index, item) => TextComponent(
text: item.text,
anchor: Anchor.center,
textRenderer: _itemTextPaint,
position: Vector2(size.x / 2, size.y * 2 / 3 + index * 32),
scale: scale,
))
.toList(),
);
_onUpdateIndex();
add(
KeyboardListenerComponent(
keyDown: {
LogicalKeyboardKey.arrowUp: (pressed) {
if (_overlay) return false;
_updateIndex((current) => current - 1);
return true;
},
LogicalKeyboardKey.arrowDown: (pressed) {
if (_overlay) return false;
_updateIndex((current) => current + 1);
return true;
},
),
);
}
void _onUpdateIndex() {
for (final (index, item) in _menuItems.indexed) {
item.textRenderer =
index == _selected.index ? _selectedItemTextPaint : _itemTextPaint;
}
}
void _updateIndex(int Function(int current) fn) {
final nextIndex = fn(_selected.index) % _MenuItem.values.length;
_selected = _MenuItem.values[nextIndex];
_onUpdateIndex();
}
}
google_fonts
を見てて LicenseRegistry
を使う方法が書いてあったのでそれに習ってライセンスを表示したい。
void main() async {
LicenseRegistry.addLicense(() async* {
yield LicenseEntryWithLineBreaks(
['google_fonts'],
await rootBundle.loadString('assets/fonts/OFL.txt'),
);
});
runApp(GameWidget(
game: MyGame(),
overlayBuilderMap: {
'licenses': (context, game) => LicenseOverlay(),
},
));
}
LicenseOverlay
は普通の Widget で LicenseRegistry.licenses.toList()
を FutueBuilder
で表示するだけのもの。
メニュー項目をついかしてライセンスの Overlay を表示させる。表示中は他の操作が効かないようにする。
LogicalKeyboardKey.space: (pressed) {
switch (_selected) {
case _MenuItem.licenses:
_overlay = true;
game.overlays.add('licenses');
break;
case _MenuItem.exit:
SystemNavigator.pop();
break;
}
return true;
},
LogicalKeyboardKey.escape: (pressed) {
game.overlays.clear();
_overlay = false;
return true;
},