41
27

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 5 years have passed since last update.

Flutter #2Advent Calendar 2018

Day 2

Flutterの低レイヤー周り - Flutterでゲーム制作はできそうか調査してみた

Last updated at Posted at 2018-12-01

Flutter低レイヤープログラミング

Flutter公式のExamplesフォルダにはひときわ異質なフォルダがあります。

Examples of Flutter's layered architecture
https://github.com/flutter/flutter/tree/master/examples/layers

上記フォルダのraw/hello_world.dartソースコードでは通常のWidgetを組み合わせた作り方ではなくいわゆる低レイヤー処理を使って描画を行っています。
dart:ui.windows のフレーム毎に呼ばれる処理を直接呼び出し、
描画もWidgetを使用せずCanvasクラスで描画しています。

hello_world.dart
import 'dart:ui' as ui;

void beginFrame(Duration timeStamp) {
  : //処理とか描画とか
}

void main() {

  // The engine calls onBeginFrame whenever it wants us to produce a frame.
  ui.window.onBeginFrame = beginFrame;

  // Here we kick off the whole process by asking the engine to schedule a new
  // frame. The engine will eventually call onBeginFrame when it is time for us
  // to actually produce the frame.
  ui.window.scheduleFrame();
}

最近、スマホ界隈でも低レイヤー処理が必要となる場合はめったにないように思います。
ちなみに携帯電話のアプリ本体に10KB制限があった2001年頃、docomoのレポートでは
言語や時代は違いますが低レイヤー処理(低レベルAPI)について以下のように述べられています。

高機能iモード携帯機特集 携帯機組み込みJavaの実用化 (p18)
https://www.nttdocomo.co.jp/binary/pdf/corporate/technology/rd/technical_journal/bn/vol9_1/vol9_1_016jp.pdf

コンポーネントを用いた場合、アプリケーション作成者の自由度は低くなるが、少ないコードで高機能なアプリケーションを作成することが可能となる.
低レベルAPIを用いると、(中略)特にゲームのようなアプリケーションを作成する場合に有効である. したがって、アプリケーション作成者の自由度は高いが, その代わりにアプリケーション作成者がアプリケーションの挙動すべてを管理しなければならない.

というわけで通常のWidgetのみでなく低レイヤーも使って、Flutterでゲーム制作できそうか調べました。

Flutterでゲーム制作はどこまでいけそうか

Flutterの仕様として2D専用のため2Dゲームに限られます。
以下について調べました。

  • フレーム毎の処理
  • 入力受付処理
  • 描画処理
  • 衝突判定

フレーム毎の処理

フレーム毎の処理では以下で実現できます。

  • 短いアニメーションのみであれば標準のAnimationControllerを使用する
  • フレーム毎に処理が必要なWidgetのみTickerを使用する
  • 低レイヤー: ui.windowonBeginFrameに処理を記述する

また、フレーム自体に関しては、

Flutter aims to provide 60 frames per second (fps) performance, or 120 fps performance on devices capable of 120Hz updates.

パフォーマンスのページでは60fpsを目指していると書いてあり、実際にサンプルアプリは60fps付近で動作していますが、
fpsの変更機能の提供は見つかりませんでした。

アニメーションライブラリは標準でTweenあり、Easingありで充実しています。

入力受付処理

Flutter自体がモバイル向けなのでタップ入力メインです。以下の方法で可能です。

  • Widgetであれば GestureDetectorでタップからドラッグまで、ほとんどの処理が取得可。

  • ゲーム内オブジェクトをRenderBoxにするとhandleEventから入力種別・座標の取得可。

touch_input.dart
class RenderDots extends RenderBox {
:
  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
   :
  }
:

https://docs.flutter.io/flutter/gestures/PointerEvent-class.html
https://docs.flutter.io/flutter/rendering/BoxHitTestEntry-class.html

  • 低レイヤー: ui.windowonPointerDataPacketで入力データパケットを取得可。

描画処理

描画処理に関してはImageクラスにAPIが少なく、Canvasクラスを使用しようしないときついようです。
https://docs.flutter.io/flutter/dart-ui/Image-class.html

Canvas自体は以下のようにWidgetからでもWidget -> RenderBox -> Canvas で使用可能です。

.dart
class GameObjectWidget extends SingleChildRenderObjectWidget {
  @override
  RenderObject createRenderObject(BuildContext context){
    return new GameObject();
  }
}

class GameObject extends RenderBox {

  @override
  bool get sizedByParent => true;

  @override
  void performResize() {
    size = constraints.biggest;
  }

  @override
  bool hitTestSelf(Offset position) => true;

  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
  }

  @override
  void paint(PaintingContext context, Offset offset) {
  }
}

また、低レイヤーではui.window.render(scene)を呼び出すことで描画可能です。

描画に関するの詳細は次章に記載します。

衝突判定

標準ではありません。
自作か外部ライブラリを使用する必要があります。

googleからDart版のBox2Dを提供しています。
https://github.com/google/box2d.dart

Flutterの画像描画はなにができるか

個人的に一番壁になりそうだったのが描画周りだったので、
描画周りについて何ができるか、使ってみて使いやすさはどんな感じか、さらに調べました。
ゲーム作成では画像の使用頻度が多いので、今回は画像描画に絞りました。

画像に関してのAPIは以下4つがあります。

  • drawImage(Image image, Offset p, Paint paint)
  • drawImageRect(Image image, Rect src, Rect dst, Paint paint)
  • drawImageNine(Image image, Rect center, Rect dst, Paint paint)
  • drawAtlas(Image atlas, List transforms, List rects, List colors, BlendMode blendMode, Rect cullRect, Paint paint)

上記APIについて、ひよこのドット画像で検証します。
0.png

drawImage: 一枚絵画像の描画

drawImageはシンプルに座標の位置に画像全体を表示ができます。
Unityとは異なり初期設定でエイリアスがないので
ドット絵のゲームの制作に良さそうです。
座標系は2Dらしく左上が(0.0, 0.0)です。
座標系はすべてdoubleで指定します。

Paintのカスタマイズについては後述します。

.dart
    canvas.drawImage(bg, Offset(0.0, 0.0), Paint());
    canvas.drawImage(image, Offset(20.0, 20.0), Paint());

drawImageRect: 画像の一部を描画

drawImageRectは画像の一部を切り抜き表示します。
パラパラアニメの表示を行うことができます。
LTWHはLeft-Top-Width-Heightの頭文字です。

.dart
    final src = Rect.fromLTWH(24.0 * 0, 0.0, 24.0, 24.0);
    final dst = Rect.fromLTWH(30.0, 189.0 - 24.0, 24.0, 24.0);

    canvas.drawImageRect(image, src, dst, Paint());

drawImageNine: 9分割画像を描画

drawImageNineはウィンドウ画像のような9分割画像を表示します。

.dart
    final src = Rect.fromLTWH(16.0, 16.0, 32.0, 32.0);
    final dst = Rect.fromLTWH(30.0, 30.0, 120.0, 64.0);
    canvas.drawImageNine(windowImage, src, dst, Paint());

drawAtlas: アトラス描画

drawAtlasはなぜかドキュメントに説明がありません。
シューティングゲームの弾などまとめて描画する場合に使用するようです。
第二引数と第三引数の配列数は同じでなければなりません。
Rotation, Scaleの指定が可能です。
translateX, translateYで別途座標ズレを指摘します3
また、Transform, BlendModeが出現しますがPaint, Canvasで後述します。

.dart
final src = Rect.fromLTWH(24.0 * 0, 0.0, 24.0, 24.0);
canvas.drawAtlas(
        image,
        [
          RSTransform.fromComponents(
              rotation: 0.0,
              scale: 1.0,
              anchorX: 0.0,
              anchorY: 0.0,
              translateX: 30.0,
              translateY: 30.0),
          RSTransform.fromComponents(
              rotation: 0.0,
              scale: 1.0,
              anchorX: 0.0,
              anchorY: 0.0,
              translateX: 30.0,
              translateY: 60.0)
        ],
        [
          src,
          src,
        ],
        [], //No need for colors
        BlendMode.src,
        null, //No need for cullRect
        Paint()
    );

Paintの機能: 透過, ブレンドモード

drawImage最後尾の引数Paintでは透過度やブレンドモードを設定できます。

.dart
    Paint paint = Paint()
      ..color = Color.fromARGB(128, 0, 0, 0)
      ..blendMode = BlendMode.hardLight;
    canvas.drawImage(image, Offset(20.0, 20.0), paint);

Canvasの機能: 反転, 変形, クリッピング

drawImage系には回転はありますが反転がありません。
このためCanvasに行列計算を行い反転します。
反転する箇所のCanvasをsave - restore でくくります。
行列計算のためtranslate -> rotate の処理順も重要であることに注意します。
またアンカーポイントがないので反転位置にも注意します。

また、Canvasには指定の領域にのみ描画するクリッピング機能があります。

.dart
    canvas.save();

    Matrix4 cc = Matrix4.identity()
      ..translate(60.0, 60.0)
      ..translate(24.0, 0.0)
      ..rotateY(180.0 * 3.14 / 180);

    canvas.transform(cc.storage);
    final dst = ui.Rect.fromLTWH(10.0, 0.0, 24.0, 24.0);
    canvas.drawImageRect(image, src, dst, Paint());
    
    canvas.restore();

    canvas.clipRect(dst);

まとめ/感想

  • Flutterは低レイヤープログラミング可能。ドキュメントもあり。
    • ゲームに向いていそうだが、描画関連が辛い。
    • フレーム処理、キー入力において各レイヤーで様々なアプローチが可能。
  • Flutterでゲーム制作はできそうか
    • アニメーションは充実しているが描画関連がつらそう。
    • 特に反転サポート・AnchorPointがないのがつらい。
    • 描画部分の拡張と衝突判定が自作か別ライブラリで必要そう。
    • ゲームオブジェクトをWidgetで持ってCanvasで描画するのが良さげ

Flutter #2 Advent Calendar

この記事はFlutter #2 Advent Calendar 2018の記事になりました。
あした、あさっては登録募集中で12/5は@D_R_1009さんの「Flutterのビルドコマンドを追ってみよう」です!

41
27
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
41
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?