【Flutter × Vuforia】flutter unity widgetを用いてiOS向けARアプリを作成する

# はじめに
UnityとVuforiaをFlutterに組み込み、iOS向けのARアプリを作成する方法をご紹介します。

本稿は[【Vuforia × Unity】URLから取得した画像をARマーカーとして使う](https://qiita.com/aosounairo_/items/9a4cb9efed81f5738245)で作成したUnityプロジェクトを使用しています。

https://qiita.com/aosounairo_/items/9a4cb9efed81f5738245

# 開発環境
```
macOS: 14.1.2
iOS: 17.3
Flutter: 3.19.0
Dart: 3.3.0
Unity: 2022.3.15f1
Vuforia: 10.21.3
VS Code: 1.84.2
XCode: 15.1 (15C65)
```
# 事前準備

- Unityプロジェクトの作成
https://qiita.com/aosounairo_/items/9a4cb9efed81f5738245

>今回使用するUnityプロジェクトは以上のものを使用します。Vuforiaの導入から構築までを記載しているため、ご一読ください。

- `fuw-XXXX.unitypackage`のダウンロード
https://github.com/juicycleff/flutter-unity-view-widget/tree/master/unitypackages

>UnityのプロジェクトをFlutterにエクスポートするパッケージです。
>最新のものをダウンロードしてください。

# Unity側の作業
flutter unity widgetを用いることで、表題の通りUnityをFlutterに組み込むことができます。
今回はARアプリ作成に必要な箇所だけを抽出して解説を行いますので、あらかじめご了承ください。

https://github.com/juicycleff/flutter-unity-view-widget?tab=readme-ov-file

### Unityプロジェクトのセットアップ
Flutterのプロジェクトフォルダーに「unity」と言う名前のフォルダーを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/da959d1e-8b35-3a33-d7be-1fd688463c1e.png)

フォルダー追加後、以前作成したUnityプロジェクトを「unity」フォルダー内にコピー(または移動)してください。

:::note warn
注意
Unityプロジェクトのパスが`unity/プロジェクト名`となっていることを確認してください。エクスポートに失敗する可能性があります。
:::

プロジェクトを開き、先ほどダウンロードしたパッケージをAssets > Import Package > Custom Packageよりインストールします。

### Unityプロジェクトをエクスポートする
「⇧⌘B」を押下し、Build Settingsを起動します。

`Add Open Scenes`を押し、現在のSceneをScene In Buildに追加します。
次に、Platform内の`iOS`を選択し、`Switch Platform`より変更してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/d44cbe29-aa41-d31e-7fa8-9d0f1b5c98ea.png)

最後に`Player Settings`を押し、`Camera Usage Description`の欄に「Used to read AR markers」と記述します。(カメラにアクセスする際は利用目的を明示する必要があるため。ちなみに文字列はなんでも良い。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/c7ed5ad2-60cb-bae7-0d7d-7fc9991761ce.png)

以上の設定が完了したら、Flutter > Export iOS (Debug)よりプロジェクトをエクスポートします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/fffa87ea-8c2c-76a9-4d14-49ed7e4360ef.png)


# Flutter側の作業

lib直下に`ar.dart`を作成します。
```
-lib
    -main.dart
    -ar.dart
```

そして、それぞれのファイルを以下のように書き換えます。

### main.dart

```main.dart
import 'package:flutter/material.dart';
import 'ar.dart';

void main() => runApp(const MyApp());

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Home(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Unity Flutter Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => const AR()),
                );
              },
              child: const Text('AR'),
            ),
          ],
        ),
      ),
    );
  }
}
```

### ar.dart

```ar.dart
import 'package:flutter/material.dart';
import 'package:flutter_unity_widget/flutter_unity_widget.dart';

class AR extends StatefulWidget {
  const AR({super.key});

  @override
  State<AR> createState() => _ARState();
}

class _ARState extends State<AR> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AR'),
      ),
      body: Card(
        margin: const EdgeInsets.all(8),
        clipBehavior: Clip.antiAlias,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20.0),
        ),
        child: UnityWidget(
          onUnityCreated: onUnityCreated,
          fullscreen: false,
        ),
      ),
    );
  }

  // Callback that connects the created controller to the unity controller
  void onUnityCreated(controller) {}
}

```

### AppDelegate.swiftの変更

`ios/Runner/AppDelegate.swift`を以下のように変更します。

```AppDelegate.swift
import UIKit
import Flutter
import flutter_unity_widget

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    InitUnityIntegrationWithOptions(argc: CommandLine.argc, argv: CommandLine.unsafeArgv, launchOptions)
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
```

### Info.plistの変更
`ios/Runner/Info.plist`に以下を追加します。

```Info.plist
<dict>
    <key>NSCameraUsageDescription</key>
    <string>Used to read AR markers</string>
</dict>
```

# アプリをビルドする
最後に、アプリをビルドするためにXCodeで設定を行えば完了です。

XCodeで`ios/Runner.xcworkspace`ファイルを開き、`Add Files to "Runner"`を押して`ios/UnityLibrary/Unity-Iphone.xcodeproj`を追加します。

:::note warn
拡張子に注意してください！
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/723a7751-0444-d1b4-f2c9-b129620fd5b2.png)

次に`Unity-iPhone/Data`フォルダーを選択し、Dataフォルダーの`Target Membership`をUnityFrameworkのみに変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/d6419eea-c6f7-1af9-67dc-540443c5209f.png)

`Runner`を選択し、General > Frameworks,Libraries and Embedded Contentの＋ボタンより`UnityFramework.framework`を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/61e32239-8ee4-c1fd-299a-6222e684fc77.png)

もう一度＋ボタンを押し、Add Other... > Add Files... を選択したら、UnityLibrary > Frameworks > com.ptc.vuforia.engine > Vuforia > Plugins > iOS 直下にある2つのフォルダーを選択し、`Open`を押してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/e8b194c0-3515-0125-c053-01375661fefb.png)

以下のようにframeworkが追加されればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/8745a202-268a-e3d8-14f8-6ab7e03d3a25.png)


:::note warn
最後に...
`Signing & Capabilities` でサインインすることを忘れずに！
:::

すべての設定が完了すれば、いよいよビルドが実行可能です。
ARマーカー上に、作成した3D Objectが表示されるはずです！

![IMG_8350.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3716019/31b8661b-171f-e871-f7c9-23932521bf75.png)

# おわりに
今回はUnityとVuforiaをFlutterに組み込み、iOS向けのARアプリを作成する方法をご紹介しました。従来、iOS向けのARアプリといえばARKitを用いることが一般的かと思いますが、せっかくクロスプラットフォーム開発を行えるFlutterを用いるのであればOSに依存しない方法でARを表示できないかと思い本稿を執筆しました。

UnityとFlutterを用いることでかなり拡張性の高いアプリが作成できることかと思いますが、一つ欠点を挙げるとすれば起動時にかなり重くなってしまうことでしょうか。アニメーションを追加するなどしてユーザビリティの向上を図りたいですね。