ãã£ã¨æ°ã«ãªã£ã¦ãFlutterã®ã²ã¼ã ã¨ã³ã¸ã³ãFlameã
ã¨ãããããããã¥ã¡ã³ããèªã¿ã¤ã¤ã
ããããæ´çããã¨ãã®åå¿é²(*´Ïï½*)
Flameã¨ã¯ | Getting Started
Flutterç¨ã®ã²ã¼ã ã¨ã³ã¸ã³ã
ã·ã³ãã«ã§å¹æçãªã²ã¼ã ã«ã¼ãã¨ã²ã¼ã ã«å¿
è¦ãªæ©è½ãæä¾ã
å
¥åãã¹ãã©ã¤ããã¢ãã¡ã¼ã·ã§ã³ãè¡çªå¤å®ãªã©ã
Flame Component System(FCS)ã¨å¼ã°ããã³ã³ãã¼ãã³ãã·ã¹ãã ãããã
ã¾ããBridge Packagesã¨å¼ã°ãããä»ã®ã©ã¤ãã©ãªã¨ã®é£æºããã±ã¼ã¸ãæä¾ã
AudioPlayers / Lottie / Riverpodãªã©ãªã©
ãã¥ã¼ããªã¢ã«ããµã³ãã«ãªã©ãç¨æããã¦ããã
Flameã«ã¯ãããã¯ã¼ã¯æ©è½ã¯ãªãã®ã§ãè¤æ°äººã®ãªã³ã©ã¤ã³ã²ã¼ã çã®å ´åã¯ã
以ä¸ã®ããã±ã¼ã¸ã®å©ç¨ãããããããã¦ããã
- Nakama: Nakama is an open-source server designed to power modern games and apps.
- Firebase: Provides dozens of services that can be used to write simpler multiplayer experiences.
- Supabase: A cheaper alternative to Firebase, based on Postgres.
ãã©ã«ãæ§æ | File Structure
ããã©ã«ãã®ãã©ã«ãæ§æã¯ããããªæããæå¾ ã
. âââ assets âââ audio â âââ explosion.mp3 âââ images â âââ enemy.png â âââ player.png âââ tiles âââ level.tmx
æ¢å®ã®å ´æã«é ç½®ããã¦ããã¨ã以ä¸ã®æãã§ãã¡ã¤ã«ãèªã¿è¾¼ããã
void main() { FlameAudio.play('explosion.mp3'); Flame.images.load('player.png'); Flame.images.load('enemy.png'); TiledComponent.load('level.tmx', tileSize); }
ãã¡ãããpubspec.yaml
ã«ãè¨å®ã¯å¿
è¦ã
flutter: assets: - assets/audio/explosion.mp3 - assets/images/player.png - assets/images/enemy.png - assets/tiles/level.tmx
audio
ã¯ã以ä¸ã®2ã¤ã«åãã¦ãOK
music
... BGMãªã©ã®é³æ¥½sfx
... å¹æé³
. âââ assets ââââ music â âââ bgm.mp3 ââââ sfx âââ button.mp3
ç»å ´äººç©
主è¦ãªç»å ´äººç©ã¯ãããªæã
- Gameã¤ã³ã¹ã¿ã³ã¹
- Flameã§ã¤ããã²ã¼ã ã®èµ·ç¹ãã¨ã³ã¸ã³é¨åã£ã½ã
- ä½ã¬ãã«ã®game APIã¸ã®ã¢ã¯ã»ã¹ãæä¾ããAbstractã¯ã©ã¹
- Gameã¤ã³ã¹ã¿ã³ã¹ã«æç»ãããã³ã³ãã¼ãã³ãã追å ããããã
- é常ã¯å®å ¨ãªå®è£ ã®FlameGameãå©ç¨ãã
- FlameGame â Flame
- Game Widget
- Gameã¤ã³ã¹ã¿ã³ã¹ãæç»ããFlutter Widget
- Game Widget â Flame
- Component
- Flame Component System(FCS)ã§æ±ããã³ã³ããã³ãã®Abstractã¯ã©ã¹
- ç¨æããã¦ããUI/Sprite/Effect/Timerãªã©ã®è¦ªã¯ã©ã¹
- ã²ã¼ã ãæ§æããè¦ç´ ãé¨å/ãã¼ãã®æå³ã ã¨ãã£ãããã
- Components â Flame
- World
- æç»ããå ¨ã³ã³ãã¼ãã³ãã®ã«ã¼ãã³ã³ãã¼ãã³ã
- Camera component â Flame
ã»ãã«ãRouter/Collision/Effect/Cameraãªã©ãããããããã©ã
ã¾ãã¯ãã®ããããåºæ¬ã
é層çã«ã¯ãããªæãã
Flutter Widget Tree â Game Widget â Gameã¤ã³ã¹ã¿ã³ã¹ â Worldã³ã³ãã¼ãã³ã â åComponent
Game Widget
Gameã¤ã³ã¹ã¿ã³ã¹ãFlutterã®Widget treeã«æç»ããããã®ã¯ã©ã¹ã
ãããªå½¢ã§å©ç¨ããæããrunApp()
ç´ä¸ã§ãªãã¦ãOKã
void main() { runApp( GameWidget(game: MyGame()), ); }
GameWidget
ã¯StatefulWidget
ãç¶æ¿ã
class GameWidget<T extends Game> extends StatefulWidget
Game
ã®æç»ã ãã§ãªãã以ä¸ãæä¾ãã¦ããã
- loadingBuilder to display something while the game is loading;
- errorBuilder which will be shows if the game throws an error;
- backgroundBuilder to draw some decoration behind the game;
- overlayBuilderMap to draw one or more widgets on top of the game.
GameWidget
ã¯ãµã¤ãºãè¶
ãã¦æç»ãããã®ã§ã¯ã¿åºããã¨ããããããã
å¿
è¦ã«å¿ãã¦ãcameraã調æ´ããããFlutterã®ClipRect
ã使ãã¨è¯ãã
ã³ã³ã¹ãã©ã¯ã¿ã¯2ã¤ãããContainer
é
ä¸ãªã©ã
ä»ã®Widgetã®ä¸ã«æç»ããå ´åã¯ãGameWidget.controlled
ã使ãã¨ãããããã
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: GameWidget.controlled( gameFactory: MyGame.new, ), ); } }
Gameã¤ã³ã¹ã¿ã³ã¹ / Game Loop
GameLoop
ã¯ã²ã¼ã ã«ã¼ãã®æ¦å¿µãã·ã³ãã«ã«æ½è±¡åããã¢ã¸ã¥ã¼ã«ã
render
ã¡ã½ããã§ãç¾å¨ã®ç¶æ ãæç»ããããã«ãã£ã³ãã¹ãåå¾update
ã¡ã½ããã§ãååã®æ´æ°ããçµéããæéãåãåã£ã¦ã次ã®ç¶æ ã«å¤æ´
ããã§ããç¶æ
ã¨ããã®ã¯ã
ã¹ã³ã¢ã§ãã£ããããã£ã©ã®ä½ç½®ã ã£ããããªã©ãªã©
Component
ã«ç¨æããã¦ããupdate()
ãªã©ã®é¢æ°ããªã¼ãã¼ã©ã¤ãã§ãã
Game
ã¯è¿½å ãããComponent
ã®update()
ã®å¼ã³åºããç¹°ãè¿ãã
update()
ã«ãå¼ã°ãããã³ã«å³ã¸1移åããã
ã®ãããªå¦çãæ¸ãã°ãèªåã§ç§»åããã³ã³ãã¼ãã³ããã§ããã
update()
ãå«ããã©ã¤ããµã¤ã¯ã«ã¯ãããªæãã
onLoad()
ã§ãã¹ãã©ã¤ããç»åã®èªã¿è¾¼ã¿ããããªã£ããã
/// A component that renders the crate sprite, with a 16 x 16 size. class MyCrate extends SpriteComponent { MyCrate() : super(size: Vector2.all(16)); @override Future<void> onLoad() async { sprite = await Sprite.load('crate.png'); } }
onRemove()
ã§ãåã³ã³ãã¼ãã³ãããã£ãã·ã¥ã®åé¤ãããã«ã¤ããã
@override void onRemove() { // Optional based on your game needs. removeAll(children); processLifecycleEvents(); Flame.images.clearCache(); Flame.assets.clearCache(); // Any other code that you want to run when the game is removed. }
SingleGameInstance mixin
Game
ã¤ã³ã¹ã¿ã³ã¹ã¯ããã¤ãä½ããããåºæ¬ã¯åä¸ã
SingleGameInstance
mixinã使ãã¨ã
ç¹å®ã®ç¶æ³ã§ããã©ã¼ãã³ã¹ãä¸ãããããã
class MyGame extends FlameGame with SingleGameInstance { // ... }
Pause/Resuming/Stepping game execution
ä¸æåæ¢ãåéãªã©ã®æ©è½ãæä¾ããã¦ããã
æ¹æ³ã¯2ã¤ãã
pauseEngine()
ãresumeEngine()
ã¡ã½ããã使ãpaused
å±æ§ãå¤æ´ãã
ä¸æåæ¢ããã¨ãGameLoopãåæ¢ãã¦ãupdate()
ãªã©ãå¼ã°ããªããªãã
ã¾ããéçºåãã®æ©è½ã¨ãã¦ãä¸æåæ¢ä¸ã«stepEngine()
ãå¼ã¶ã¨ã
1ãã¬ã¼ã é²ãããã¨ãã§ããã
Components
å
¨ã³ã³ãã¼ãã³ããç¶æ¿ããComponent
ã
Flame Component System(FCS)ã®ãã¼ã¹ã
UIãç»åãEffectãå
¨é¨ããã®Component
ãç¶æ¿ãã¦ãã¦ã
Component
ããã©ã¤ããµã¤ã¯ã«ã®ã¡ã½ãããå¼ã°ããæãã
Component
ã«ã¯è¦ªåé¢ä¿ããããåã³ã³ãã¼ãã³ãã追å ãããã§ããã
void main() { final component1 = Component(children: [Component(), Component()]); final component2 = Component(); component2.add(Component()); component2.addAll([Component(), Component()]); }
Game
ã¤ã³ã¹ã¿ã³ã¹ã¨åæ§ã«ã©ã¤ããµã¤ã¯ã«ãããã
ãããããç¶æ¿ãã¦ãåã³ã³ãã¼ãã³ããå®è£
ãã¦ããã
Priority(z-index)
ã³ã³ãã¼ãã³ãã«ã¯åªå
度ãã¤ãããã¨ãã§ãã
ã©ããä¸ã«æç»ãããã決ãããã¨ãã§ããã
priority
ã®å¤ã大ããé ã«ä¸ã«æç»ãããã
class MyGame extends FlameGame { @override void onLoad() { final myComponent = PositionComponent(priority: 5); add(myComponent); } } class MyComponent extends PositionComponent with TapCallbacks { MyComponent() : super(priority: 1); @override void onTapDown(TapDownEvent event) { priority = 2; } }
Visibility of components
ãã£ã¨ãããã®ã¯add
ãremove
ã§ã³ã³ãã¼ãã³ãèªä½ãåé¤ãããã¨ã
ãã ä¸æçãªé表示ã®å ´åã¯ãHasVisibility
mixinãã¤ããã£ã½ãã
/// Example that implements HasVisibility class MyComponent extends PositionComponent with HasVisibility {} /// Usage of the isVisible property final myComponent = MyComponent(); add(myComponent); myComponent.isVisible = false;
Effect
ããããªã¨ãã§ã¯ããç¨æããã¦ãã¦ã
ã¨ãã§ã¯ããé©ç¨ãããã³ã³ãã¼ãã³ãã«è¿½å ããã°OK
final effect = MoveEffect.by( Vector2(30, 30), EffectController(duration: 1.0), ); final component1 = MyComponent(); component1.add(effect);
Collision
è¡çªå¤å®(Collision Detection)ã¯ã
HasCollisionDetection
mixinãCollisionCallbacks
mixinã使ã£ã¦å®è£
ããã
Camera / World
横ã¹ã¯ãã¼ã«ã²ã¼ã ã®ããã«ããã¬ã¤ã¤ã¼ã移åããã¨èæ¯ãåãæ¿ãããããªå ´åã
ã¹ãã¼ã¸å
¨ä½(=æç»ãããã¹ã¦ã®è¦ç´ )ãWorld
ã§ã
ãã¬ã¤ã¤ã¼ãä¸å¿ã«è¦ãã¦ããç¯å²ãCamera
ãªæãã
Router
Flutterã®Navigatorã®ãããªã«ã¼ãã£ã³ã°æ©è½ããã¤Component
ã®ãµãã¯ã©ã¹ã
Route
ã®åå被ãã®ãããhide
ãå¿
è¦ã
import 'package:flutter/material.dart' hide Route; class MyGame extends FlameGame { late final RouterComponent router; @override void onLoad() { add( router = RouterComponent( routes: { 'home': Route(HomePage.new), 'level-selector': Route(LevelSelectorPage.new), 'settings': Route(SettingsPage.new, transparent: true), 'pause': PauseRoute(), 'confirm-dialog': OverlayRoute.existing(), }, initialRoute: 'home', ), ); } } class PauseRoute extends Route { ... }
é常ã®ç»é¢ã¯Route
ãç¶æ¿ãã¦å®è£
ããå½¢ã
ã»ãã«ããOverlayRoute
ã¨ValueRoute
ãããã
OverlayRoute
- ä¸æåæ¢ä¸ã®ãã¤ã¢ãã°ãªã©ãä»ã®ç»é¢ã«éãã¦è¡¨ç¤ºãããç»é¢
ValueRoute
- çµæç»é¢ãªã©ãã¹ã³ã¢ãªã©ã®å¤ãåãåã£ã¦è¡¨ç¤ºãããç»é¢
Tap/Drag/Gesture Eventãªã©
ããããmixinãç¨æããã¦ãã¦ã
ããã使ããã¨ã§åã³ã¼ã«ããã¯ããªã¼ãã¼ã©ã¤ãã§ããã
class MyComponent extends PositionComponent with TapCallbacks { MyComponent() : super(size: Vector2(80, 60)); @override void onTapUp(TapUpEvent event) { // Do something in response to a tap event } }
Overlays
ä¸æåæ¢ç»é¢ãã¡ãã¥ã¼ãªã©ç»é¢ã®ä¸ã«éããUIã®ããã®æ©è½
ãããªæãã§ãoverlayBuilderMap
ã«keyã¨builderãè¨å®ããã¨ã
// On the widget declaration final game = MyGame(); Widget build(BuildContext context) { return GameWidget( game: game, overlayBuilderMap: { 'PauseMenu': (BuildContext context, MyGame game) { return Text('A pause menu'); }, }, ); }
ãããªæãã§ãkeyã使ã£ã¦ãoverlays.add
ãªã©ã§è¡¨ç¤ºããããã¨ãã§ããã
// Inside your game: final pauseOverlayIdentifier = 'PauseMenu'; // Marks 'PauseMenu' to be rendered. overlays.add(pauseOverlayIdentifier); // Marks 'PauseMenu' to not be rendered. overlays.remove(pauseOverlayIdentifier);
Bridge Packages
ã»ãã®ã©ã¤ãã©ãªãæ©æ¸¡ãããããã®ããã±ã¼ã¸ç¾¤ã
ã²ã¼ã é¢é£ã®ãã®ãããã¤ãç¨æããã¦ããã£ã½ãã
- é³å£°/Audio
- ç¶æ 管ç ...
- ã¢ãã¡ã¼ã·ã§ã³
- flame_lottie(Lottie) Adobe After Effects ã¢ãã¡ã¼ã·ã§ã³
- flame_rive(Rive) ã¤ã³ã¿ã©ã¯ãã£ãã¢ãã¡ã¼ã·ã§ã³
- flame_spine(Spine) ã²ã¼ã ç¨2Dã¢ãã¡ã¼ã·ã§ã³
- 2Dã¿ã¤ã«ããã
- ãã¯ã¹ãã£ã¢ãã©ã¹ ... è¤æ°ã®ç»åãä¸ã¤ã®ç»åã«ã¾ã¨ãããã®
- ç©çæ¼ç®(Box2D)
- ãã®ä»
- flame_isolate ... éãå¦çãå¥ã¹ã¬ããã§å®è¡
- flame_network_assets ... ãããã¯ã¼ã¯ä¸ã®ã¢ã»ããã®åå¾
- flame_svg ... SVGç»åã®æç»
- flame_bloc ... Blocã§ã®ç¶æ 管ç
- flame_oxygen ... FCSã§ã¯ãªãã軽éã³ã³ãã¼ãã³ãã·ã¹ãã (Oxygen)ã¸ã®ç½®ãæã
- jenny ... Yarn Spinnerã使ã£ãä¼è©±æ/ã·ããªãªã®è¡¨ç¤ºãªã©
flame_riverpod
flame_riverpodã¯æ°ã«ãªãã®ã§ãããå°ãã
Riverpodã¯å¤æ´ãããã¨ãWidgetãåæç»ãã¦ããããã
flameã§ã¯Widgetã§ã¯ãªãComponent
ãå©ç¨ãã¦ããã
ãã®ãããComponent
ã§ãå¤æ´ã«å¿ãã¦å¼ã³åºãããããã«ããã
WidgetãMixinãæä¾ããã
RiverpodAwareGameWidget
...GameWidget
ã®ä»£ããã«ä½¿ãRiverpodGameMixin
...FlameGame
ã«mixinããRiverpodComponentMixin
...Component
ã«mixinãã
/// An excerpt from the Example. Check it out! class RefExampleGame extends FlameGame with RiverpodGameMixin { @override Future<void> onLoad() async { await super.onLoad(); add(TextComponent(text: 'Flame')); add(RiverpodAwareTextComponent()); } } class RiverpodAwareTextComponent extends PositionComponent with RiverpodComponentMixin { late TextComponent textComponent; int currentValue = 0; @override void onMount() { // buildé¢æ°ã«å¦çã追å addToGameWidgetBuild(() { ref.listen(countingStreamProvider, (p0, p1) { if (p1.hasValue) { currentValue = p1.value!; textComponent.text = '$currentValue'; } }); }); // super.onMount()ã®åã«addToGameWidgetBuildãå¼ã¶ super.onMount(); add(textComponent = TextComponent(position: position + Vector2(0, 27))); } }
Casual Games Toolkit
Flutterå
¬å¼ããã¥ã¡ã³ãã«ããã«ã¸ã¥ã¢ã«ã²ã¼ã ãã¤ããããã«ä¾¿å©ãª
ããã±ã¼ã¸ãã¾ã¨ããCasual Games Toolkitã¨ãããã¼ã¸ããã
FlameãBridge Packagesã§ç´¹ä»ããã¦ããã®ãè¼ã£ã¦ãããã
app_reviewãªã©ãä»ã®ãããã®ã§ã
ç®ãéãã¦ããã¨ããæãæ°ãããã
以ä¸ï¼ï¼ æã£ã以ä¸ã«ããããã§ããããããããã(*´Ïï½*)