2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スマホ向けブラウザゲームをFlutterでネイティブアプリ化してみた

Last updated at Posted at 2023-12-10

はじめに

こんにちは。medibaでモバイルアプリエンジニアをしている@m-itakura-medibaです。
本記事は mediba Advent Calender 2023 の11日目の記事です。

私はモバイルアプリ開発以外では、前職以前にスマホ向けブラウザゲームの開発を行っていたことがあります。
せっかくブラウザ向けに作っていたゲームをネイティブアプリ化できないかということで、今回Flutterを使ってネイティブアプリ化する方法を調べて試してみました。

どうやってネイティブアプリ化するか?

「ブラウザゲームをネイティブアプリ化する」というのは要するにWebViewでゲームを表示するということですが、大まかに分けて以下の2種類の手段があると思います。

  • どこかのサーバーにホスティングして、ネイティブアプリ内のWebViewで表示する
  • ネイティブアプリ内にローカルサーバーを立てて、WebViewで表示する

今回は後者の「ネイティブアプリ内にローカルサーバーを立てて、WebViewで表示する」でやってみたいと思います。
またAndroidエミュレータ/ iOSシミュレータで実行するまでを今回のゴールとします。

環境

Flutter 3.13.8

手順

その1:スマホ向けブラウザゲームを用意

まずスマートフォンのブラウザで動作するゲームを用意してください。
今回は以下で公開されているサンプルゲームを利用してみることにしたいと思います。

上記のサンプルゲームはPCブラウザ向けの作りになっているため、キャンバスのサイズを変更したり、タップでジャンプするようにしたりと、スマートフォンブラウザで動作させやすいように変更した上で利用することにしました。

なお、ゲームのファイルは以下のようなものになります。

ゲームエンジン Phaser について
このサンプルゲームは Phaser CE というゲームエンジンが使われています。Phaserは主に2Dゲーム向けの軽量なゲームエンジンです。日本ではあんまりですが、海外では人気のあるゲームエンジンです。CE(v2系)とv3があり、v3の方が新しいバージョン(CEとv3に互換性はありません)ですが、私はCEのほうが使い慣れているため今回もCEを使用しています。

その2:Flutterプロジェクトを作成

以下のコマンドを実行してFlutterプロジェクトを作成します。

flutter create sample_game

その3:必要なパッケージを追加

必要なパッケージを追加します。
といっても1つだけです!

flutter_inappwebviewというパッケージを以下のコマンドでインストールします。

flutter pub add flutter_inappwebview

なお執筆時点では5.8.0がインストールされます。

flutter_inappwebviewは多機能なWebViewプラグインで、ローカルサーバーを起動する機能も搭載されています。

その4:ブラウザゲームのコード一式をFlutterプロジェクト内に配置

Flutterプロジェクトにassetsというフォルダを作成し、ブラウザゲームのファイル一式を格納します。以下のようになります。

assetsフォルダに格納したブラウザゲームのファイルをFlutterで読み込めるように、pubspec.yamlassetsを記載します。

pubspec.yaml
flutter:
  uses-material-design: true

  # 以下を追記
  assets:
    - assets/
    - assets/images/

assets/を指定しただけだとその直下のimagesフォルダが読み込まれないため、assets/images/も追加することがポイントです。

なお、フォルダ階層が深くて全てを記載するのが大変な場合にはasset_fillというパッケージを使うと良いかもしれません。(私は使ったことがないため、使ってみた方はぜひ教えてください!)

その5:ゲームを再生

以下2点を実装し、Flutter製ネイティブアプリ上でゲームを再生させます。

  • ローカルサーバーを起動
  • WebViewを追加し、ゲームをロード

2点ともflutter_inappwebviewパッケージに含まれる機能で実装可能です。
基本的には以下のドキュメントを参考にして実装すれば大丈夫です。

ローカルサーバーを起動

flutter_inappwebviewが持つ In-App Localhost Server という機能を利用します。
InAppLocalhostServerのインスタンスを生成し、

main.dart
final InAppLocalhostServer localhostServer = InAppLocalhostServer();

main関数で起動させます。

main.dart
// ローカルのサーバーを起動する。
await localhostServer.start();

なおインスタンス生成時にポート番号を指定できますが、省略している場合は8080が使用されます。

WebViewを追加し、ゲームをロード

あとはゲームをロードするだけです。
flutter_inappwebviewが提供するInAppWebViewというWidgetを利用します。

以下のようにInAppWebViewでゲームが配置されているURLを指定してロードします。

main.dart
InAppWebView(
  initialUrlRequest: URLRequest(
    url: Uri.parse('http://localhost:8080/assets/index.html'),
  ),
),

全体のコード

全体のコード例は以下のようになります。

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

final InAppLocalhostServer localhostServer = InAppLocalhostServer();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // ローカルのサーバーを起動する。
  await localhostServer.start();

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: InAppWebView(
          initialUrlRequest: URLRequest(
            url: Uri.parse('http://localhost:8080/assets/index.html'),
          ),
        ),
      ),
    );
  }
}

その6:各OS向けの対応

Android

Androidエミュレータで実行してみたところ、以下のようにERR_CLEARTEXT_NOT_PERMITTEDエラーが発生しました。

Android 9(API レベル 28)からHTTPプロトコル通信が許可されなくなったことが原因のようでした。
そのため、HTTP通信を許可するためにAndroidManifest.xmlusesCleartextTrafficを追加しました。

AndroidManifest.xml
    <application
        android:label="sample_game"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:usesCleartextTraffic="true"><!-- こちらを追加 -->

iOS

iOSシミュレータでは特に問題なく動作させることができました。

完成!

これで完成です!
Androidエミュレータで実行した様子は以下のような感じになります。

Appendix: FlutterとJavaScriptの通信

InAppWebViewはJavaScriptと通信することも、その逆を行うこともできます。

例えばゲーム終了時にインタースティシャル広告を表示させたい場合、以下のようなJavaScriptハンドラーを登録します。(ここではハンドラーの名前をendGameとしています。)

InAppWebView(
  initialUrlRequest: URLRequest(
    url: Uri.parse('http://localhost:8080/assets/index.html'),
  ),
  onWebViewCreated: (controller) {
    // ゲーム終了
    controller.addJavaScriptHandler(
      handlerName: "endGame",
      callback: (args) async {
        // インタースティシャル広告の表示処理を書く。
      },
    );
  },
)

実際にゲームが終了した時、ゲームのJavaScriptから以下のようにハンドラーを呼び出します。

window.flutter_inappwebview.callHandler('endGame', ...args);

まとめ

いかがだったでしょうか。
簡単にブラウザゲームをネイティブアプリ化できる、と感じていただけた方もいらっしゃるのではないでしょうか。
今回はエミュレータでの起動まででしたが、実際にAndroid / iOSアプリとしてストアにリリースするとなると、他の対応が必要になるかもしれませんが、その点はご了承ください。

最後に

現在medibaでは、メンバーを大募集しています。
募集・応募ページ
medibaってどんな会社だろう? と興味を持っていただいた方は、
カジュアル面談もやっておりますので、お気軽にお申込み頂ければと思います。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?