原神(Genshin Impact)をDualSenceでプレイする

概要

原神はDualSenceを無線(Bluetooth)接続した場合に限りDualSenceを使用してプレイできると書かれた記事が見受けられます。 しかし、PC環境によって原神がコントローラーを認識したり、しなかったりといった状況がある様です。 認識されない場合の対処方法にはいくつかの方法がありますが、本記事ではSteamのBig Pictureモードを使用する方法を説明します。

尚、この記事の内容は動作を保証するものではありません。試す場合には自己責任でお願いします。 また、本記事の方法でも動作しない場合、当方では対応しかねます。

(2021-10-06追記:Ver.2.2でDualSenceに正式対応するとのことなので、こちらの方法は不要かもしれません。)

目次

確認環境

  • Windows 10 Pro (21H1)
  • Steam ビルド Sep 16 2021
  • 原神(Genshin Impact) 2.1.0

参考情報

解説

事前にBluetoothのペアリングは済ませてある前提で話を進めます。ペアリングが済んでいない場合には、他の記事を検索すると説明している記事が見つかると思います。以下の画像の様に、「Wireless Controller」が「ペアリング済み」になっている必要があります。

Steamを管理者として実行する

Steamを起動する際には「管理者として実行」を選んで起動する必要があります。

ライブラリに原神を登録する

ライブラリメニューから、「非Steamゲームを追加」を選択して Genshin Impact を追加します。

「参照」ボタンを押下して、exeファイルを選択するウィンドウを開きます。

C:\Program Files\Genshin Impact\Genshin Impact Game フォルダにある GenshinImpact.exe を選択します。

そして、「選択したプログラムを追加」ボタンを押下します。

以上で原神アプリがライブラリに追加されます。

Big Picture モードに切替え、コントローラを設定する

ゲームを起動する際には Big Picture モードから起動します。

まずは、Big Picture モードに切り替えます。

歯車アイコンから設定メニューを開き、コントローラー設定を開きます。

PlayStation 設定サポート」を有効にします。 ついでに「Big Picture 終了時に電源を切る」を有効にすると、Big Picture を終了した際にコントローラーの電源が切れます。

以上で設定は完了です。

ゲームを起動する

ゲームを起動する際には、Big Picture モードのライブラリメニューから GENSHINIMPACT を選択します。

ゲームが開始されるとコントローラーでプレイできる思います。

(管理者として実行されていれば Big Picture モードでなくてもプレイできるみたいです。)

Pythonのプログラムでピボットテーブルを使う

概要

PythonからExcelのピボットテーブルを使いたいが、難しいプログラムを記述したくない場合の方法です。 ピボットテーブルの設定はExcelで行い、Pythonのプログラムではピボットテーブルの値の範囲のみを変更します。

目次

確認環境

参考情報

解説

テンプレートとなるExcelファイルを作成する

まずピボットテーブルを含んだExcelファイルを用意します。 今回は疑似個人情報データ生成サービスを使って生成したExcelファイルに対してピボットテーブルを作成します。 ピボットテーブルの作り方は検索して調べてください。

作成したExcelファイルは以下の通りになっているとします。

  • ピボットテーブルは pivot シートに含まれている
  • データは dummy_data シートに含まれている
  • Excelファイルの名前はPivotTemplate.xlsx

Pythonでピボットテーブルの値の範囲を設定する

Pythonのプログラムで以下の2つを合わせます。

  • PivotTemplate.xlsxに含まれているピボットテーブル
  • 別途作成したデータシート

合わせるためのプログラムは以下のとおりです。

import openpyxl

# データシートを作成する
new_wb = openpyxl.Workbook()
ws = new_wb.create_sheet('data')

# ... wsにデータを追加する ...


def add_pivot(cache_id, data_sheet, destination_book):
    template_wb = openpyxl.load_workbook('PivotTemplate.xlsx')

    pivot_sheet = template_wb['pivot']  # ピボットテーブルが含まれたシート
    pivot = pivot_sheet._pivots[0]
    pivot.name = data_sheet.title + '_pivot'  # ピボットテーブルに名前を付ける
    pivot.cacheId = cache_id  # IDが重複するとエラーになる
    pivot.cache.refreshOnLoad = True  # 読み込み時にピボットテーブルを更新
    source = pivot.cache.cacheSource.worksheetSource
    print(source)

    print(data_sheet.dimensions)
    print(data_sheet.title)
    source.ref = data_sheet.dimensions
    source.sheet = data_sheet.title

    pivot_sheet = destination_book.create_sheet(pivot.name)
    pivot_sheet.add_pivot(pivot)
    template_wb.close()


add_pivot(1000, ws, new_wb)
new_wb.save('PivotSample.xlsx')

まず、はじめにdataという名前のシートをnew_bookに追加して、 このdataシート(ws)にデータを用意しています。

次に、add_pivot関数でピボットテーブルを含むシートを追加しています。 cache_idはピボットテーブルのIDです。 他のピボットテーブルと重複しない値にします。 ここでは重複しないであろう値として適当に1000にしています。 data_sheetはデータを含むシートです。 ここでは最初の段階で作成したdataシート(ws)を指定しています。 destination_bookはピボットテーブルを含むシートを追加する先のブックです。 ここではnew_wbを指定しています。

add_pivot関数で行っている内容を説明します。 まず、テンプレートとなるPivotTemplate.xlsxファイルを開き、ピボットテーブルが含まれるシートからピボットテーブルの情報を取得します。 そして、このピボットテーブルの情報(pivot)を変更しています。 nameは設定しなくて構いませんが、ここではシート名に基づいて名付けています。 cacheIdは他のピボットテーブルと重複しない値を設定する必要があります。 refreshOnLoadTrueに設定しておくとファイルを開いたときにピボットテーブルが更新されます。 次に、ピボットテーブルが参照するデータの範囲を変更します。 refにデータの範囲、sheetにシート名を設定することで行えます。 data_sheetのデータ範囲(data_sheet.dimensions)とシート名(data_sheet.title)を設定しています。 最後に、新規シートにピボットテーブルを追加します。

以上を行ってファイルを保存すれば、ピボットテーブルを含んだファイルが作成されます。

Unity Android Plugin の手間を軽減する管理方法(改)

概要

Unity Android Plugin の解説記事では JAR, AAR ファイルを手動コピーする例が多く見受けられます。 手動コピーでは依存ライブラリのバージョンを管理しづらくなるため、出来る限り手動コピーは避けたいです。 この記事では Gradle の Local リポジトリを経由して Android Plugin を Unity に公開する方法を説明します。

目次

確認環境

  • macOS 10.15.7
  • Android
    • Android Studio 4.1.2
    • minSdkVersion 19
    • buildToolsVersion 30.0.3
    • Gradle 6.5
  • Unity 2019.4.18f1
  • Rider 2020.3.2

参考情報

解説

Android Plugin (AAR) をビルドする

Android Library モジュールを作成し、モジュールの build.gradle を以下の様に記述します。 この例ではモジュール名を sample にしています。

plugins {
    id 'com.android.library'
    id 'kotlin-android'
    id 'maven-publish'
}

ext {
    groupId = "com.example"
    versionName = "1.0"
    unityVersion = "2019.4.18f1"
    unityJar = "/Applications/Unity/Hub/Editor/$unityVersion/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar"
}

group = groupId
version = versionName

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    compileOnly files(unityJar)
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

// see: https://developer.android.com/studio/build/maven-publish-plugin?hl=ja
afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                from components.release
            }
            debug(MavenPublication) {
                from components.debug
                artifactId = "${project.name}-debug"
            }
        }
    }
}

ポイントは以下の2点です。

  • Maven Publish Plugin を使用する
  • Unity の classes.jar をコピーせずに直接指定する

1つめのポイントとして、 Maven Publish Plugin を使用するために pluginsid 'maven-publish' を追加しています。 また、afterEvaluate 内に publishing の設定を記述しています。 この例の設定では、ビルドバリアント(debug, release)毎に AAR ファイルを生成します。 具体的には、sample-1.0.aar (release) ファイルと sample-debug-1.0.aar (debug) ファイルが生成されます。

2つめのポイントとして、 compileOnly files(unityJar) によって Unity の classes.jar を参照しています。 Windows の場合は unityJar のパスを適切なパスに変更してください。 Unity のバージョンを変える場合には unityVersion を変更します。 implementation ではなく compileOnly にしている理由は、 Unity から AAR ファイルを参照する際に Unity の classes.jar が重複して参照されることを防ぐためです。 implementation にすると Unity でビルドする際にエラーになります。

ビルド設定は以上です。

ビルドを実行する前に、今回は以下のサンプルプログラムを作成します。

package com.example.unity.sample

import com.unity3d.player.UnityPlayer

class KotlinObject {

    fun getActivityName(): String = UnityPlayer.currentActivity.localClassName

}

Android Plugin をビルドし、Local リポジトリに保存する

以下のコマンドを Terminal で実行するとビルドが行われ、その結果が Local リポジトリに保存されます。

project % ./gradlew publishToMavenLocal

Gradle のデフォルトでは、Local リポジトリ~/.m2/repository になっています。 この repository ディレクトリに以下のファイルといくつかの関連ファイルが作成されます。

  • com/example/sample/1.0/sample-1.0.aar
  • com/example/sample-debug/1.0/sample-debug-1.0.aar

以上で Android Plugin の作成と公開は終了です。

アプリに Android Plugin への依存を追加する

Unity で Project Settings > Player > Android > Publishing Settings > Build と辿っていくと以下の設定が表示されます。

f:id:nosix:20210225172356p:plain

画像の様に以下の2箇所にチェックを付けます。

  • Custom Main Gradle Template
  • Custom Base Gradle Template

チェックを付けることで Assets/Plugins/Android ディレクトリに以下の2つのファイルが作成されるはずです。 (作成されない場合は Build を実行してみると作成されると思います。)

  • mainTemplate.gradle
  • baseProjectTemplate.gradle

この2つのファイルを編集します。

// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN

apply plugin: 'com.android.library'
**APPLY_PLUGINS**

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.example:sample:1.0'
**DEPS**}

// ... 以下省略
// ... 以上省略
    repositories {**ARTIFACTORYREPOSITORY**
        google()
        jcenter()
        mavenLocal()
        flatDir {
            dirs "${project(':unityLibrary').projectDir}/libs"
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

mainTemplate.gradle には implementation 'com.example:sample:1.0' を追加しています。 これは groupId:artifactId:version の形式になっており、groupId と version は Android Plugin の build.gradle で指定した値になっています。 artifactId はモジュール名が使われており、今回は sample (release) か sample-debug (debug) です。

baseProjectTemplate.gradle には mavenLocal()repositories に追加しています。 ファイル内に repositories が2箇所あるので注意して下さい。 buildscript 内の repositories はビルドスクリプト(Gradle)が使用するライブラリのリポジトリを指定しています。 buildscript ではない方の repositories は Android アプリをビルドする際に使用するライブラリのリポジトリ指定です。 ここに Local リポジトリを参照する指定 mavenLocal() を追加します。

アプリをビルドする

今回は以下の Script を作成して動作確認をしました。

using UnityEngine;
using UnityEngine.UI;

public class AndroidExecutor : MonoBehaviour
{
    [SerializeField] private Text result;

    void Start()
    {
        using (var o = new AndroidJavaObject("com.example.unity.sample.KotlinObject"))
        {
            result.text = o.Call<string>("getActivityName");
        }
    }
}

Androidバイスを PC に接続するか Android Emulator を起動した上で、 Build Settings で Android Platform に Switch して、 Run Device にデバイスか Emulator を指定します。 File メニューの Build And Run を選択すると APK ファイルのファイル名を訊ねられないので、 Build Settings の Build And Run は使わずに閉じて File メニューから実行しています。

実行するとText に以下の様に表示されます。

f:id:nosix:20210225175148p:plain

Sharing

概要

NRSDK (Nreal Light の Software Developer Kit) の Observer View を理解するために、 サンプルとして提供されている Sharing (Scene) のソースコードを解読してみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。

目次

確認環境

  • NRSDK 1.2.1

解説

Sharing (Scene) の Hierarchy とそこで使われている Scripts を網羅して確認しました。

Sharing 機能は NetWorkSession (Prefab) を Hierarchy に追加して使用します。 NetWorkSession (Prefab) には SharingManager (Component) が設定されており、SharingManager のプロパティでは Player と ObjectPool を設定します。 Player, ObjectPool のいずれにも NetworkBehaviour (Component) を設定した Prefab を設定します。

ObjectPool には共有データを定義した Prefab を登録します。 共有データを NetObject と呼ぶとすると、他ユーザーから受け取る NetObject と自分から送信する NetObject を登録します。 Player には自分が生成する NetObject を定義した Prefab を登録します。

ObjectPool には複数の Prefab が登録されます。 登録する際には NetworkBehaviour を継承したクラスを作成し、作成したクラスを Component にもつ Prefab を作成します。 ObjectPool は NetworkBehaviour を継承したクラスのクラス名を Key、Prefab を Value とする辞書を保持します。 Key は NetObjectInfo.Key として使われます。

Player には単一の Prefab が登録されます。 ObjectPool に登録した Prefab のいずれか 1 つを登録します。 ObjectPool に複数の Prefab を登録するケースは理解できませんでした。 SharingManager は NetWorkSession のシングルトンインスタンスを使用しているため、SharingManager を複数使用してはいけないのではないかと思います。 そのため、Player に登録される Prefab は 1 つだけです。

NetworkBehaviour を継承したクラスの例として TestNetBehaviour が用意されています。 TestNetBehaviour では、共有データを管理します。 共有データはシリアライズして送受信します。 SynObject を継承させたクラスを独自に定義すれば任意のデータを共有できます。 標準では以下の SynObject を継承したクラスが用意されています。

  • SynTransform
  • SynInt
  • SynVector2
  • SynVector3
  • SynQuaternion

また、NetworkBehaviour.IsOwner は自身のセッションで作成した NetObject である場合には true になります。 つまり、Player に指定して生成されたインスタンスでは IsOwner が true になります。

Hierarchy

  • Sharing

    • Main Camera
      • [C] Camera
      • [C] Audio Listener
    • Directional Light
      • [C] Light
    • NetWorkSession (Prefab)
  • NetWorkSession (Prefab) (in Scripts/Sharing/Prefabs)

  • Player (Prefab) (in Demos/Sharing/Prefabs)

Scripts

  • SharingManager (in Scripts/Sharing/Scripts)

    • MonoBehaviour を継承している
    • Inspector の設定項目
      • Player : NetworkBehaviour (Demos/Sharing/Prefabs/Player)
      • Object Pool : NetWorkObjectPool (Scripts/Sharing/Prefabs/NetWorkObjectPool)
    • OnEnable
    • Start
      • Wraper.Initialize
      • ObjectPool.Init
      • NetObjectManager.Init
      • NetWorkSession.SearchLocalServer
        • LocalServerSearcher.Search
          • IP 255.255.255.255 port 1989 UDP に RequestForServerIP を送信する
          • IP:port を受信する
          • LocalServerSearcher.m_LocalServer に IPEndPoint(IP, port) を設定する
    • NetWorkSession.OnGetServerIP
      • EasyClient.ConnectAsync(EndPoint)
    • NetWorkSession.OnClientConnected
      • MsgGroup.LoginEvent.Request(GUID)
      • SharingManager.OnNetConnected
    • OnNetConnected
      • Player が設定されている場合は CreateNetObjRequest(NetworkBehaviour) を呼び出す
    • CreateNetObjRequest
      • MsgGroup.CreateNetObjectEvent.Request(NetworkBehaviour.GetType().Name)
      • OnCreateNetObjectResp
        • NetObjectManager.Create
          • NetWorkObjectPool から NetObjectInfo.Key で NetworkBehaviour を検索する
          • NetworkBehaviour が見つかったら NetworkBehaviour を Instantiate して、Initialize を呼び出す
            • NetObjectInfo.Owner が NetWorkSession.GUID と一致したら、NetworkBehaviour.IsOwner を true にする
          • Instantiate で生成された NetworkBehaviour の GameObject の name を NetObjectInfo.Key に設定する
        • SynContextRequest
          • MsgGroup.SynContextEvent.Request();
    • SynContextRequest
      • MsgGroup.SynContextEvent.Request()
      • OnSynContextResp
        • NetObjectManager.SynObjects
          • 受け取った NetObjectInfo 毎に以下を行う
            • NetObjectInfo.Identify 毎に NetObjectManager.Create を呼び出して NetworkBehaviour インスタンスを生成する
              • NetObjectInfo.Identify が NetworkBehaviour インスタンスの識別子になる
              • 既にインスタンスが生成されている場合は NetObjectManager.Create を呼び出さない
    • SynDataRequest
      • MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Others)
      • OnSynDataResp
        • NetMsgType が SynValue なら NetworkBehaviour.DeserializeData
        • NetMsgType が Commond なら NetworkBehaviour.ReplyCommond
  • MsgGroup

    • SynContextEvent
    • CreateRoomEvent
    • CreateNetObjectEvent
    • DestroyNetObjectEvent
    • GetRoomStateEvent
    • JoinRoomEvent
    • LeaveRoomEvent
    • LoginEvent
    • DownLoadCommondListEvent
    • UpLoadCommondListEvent
    • SynDataEvent
  • TestNetBehaviour (in Demos/Sharing/Scripts)

    • NetworkBehaviour を継承している
    • Inspector の設定項目
      • SynTransform
      • SynInt
      • SynVector2
      • SynVector3
      • SynQuaternion
    • Update
      • IsOwner が true ならば SynObject を更新する
      • Space キー押下時、MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Everyone))
  • NetworkBehaviour (in Scripts/Sharing/Scripts/RunTime)

    • Updater コルーチン
      • MsgGroup.SynDataEvent.Request(bytes, Data.RequestType.Everyone))
  • NetWorkObjectPool (in Scripts/Sharing/Prefabs)

    • ScriptableObject を継承している
    • Inspector の設定項目
      • NetObjects : List
    • NetworkBehaviour (のサブクラス) の Type Name をKey、NetworkBehaviour (のサブクラス) を Component に持つ GameObject を Value とした Dictionary を持つ

NRPhotoCapture, NRVideoCapture を読み解いてみる

概要

RGBCamera-CaptureRGBCamera-Record を理解すると NRPhotoCapture クラスと NRVideoCapture クラスが主要クラスだということが分かります。 NRPhotoCapture, NRVideoCapture の両クラスの動作を理解することで RGBCamera への理解が深まると考え、これらのソースコードを解読してみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。

目次

確認環境

  • NRSDK 1.2.1

解説

拾い読み

例のごとく、NRPhotoCapture を拾い読みすると、以下が要点になるのかなと思います。

  • NRPhotoCapture
    • CreateAsync
      • NRPhotoCapture インスタンスを生成する
      • FrameCaptureContext インスタンスを生成する
        • FrameCaptureContext 生成時に AbstractFrameProvider を渡す
          • Unity Editor では EditorFrameProvider
          • それ以外では RGBCameraFrameProvider
    • StartPhotoModeAsync
      • FrameCaptureContext.StartCaptureMode(CameraParameters)
        • CameraParameters の camMode は PhotoMode に設定される
        • CaptureBehaviourBase インスタンスを生成する
          • camMode が PhotoMode の場合は NRCaptureBehaviour (Record/Prefabs/NRCaptureBehaviour)
        • IEncoder インスタンスを生成する
          • camMode が PhotoMode の場合は ImageEncoder
          • Config(CameraParameters) を行う
        • FrameBlender インスタンスを生成する
          • CaptureBehaviourBase.CaptureCamera, IEncoder, CameraParameters で初期化する
        • FrameProvider.OnUpdate に CaptureBehaviourBase.OnFrame を登録する
      • FrameCaptureContext.StartCapture()
        • IEncoder.Start()
        • FrameProvider.Play()
    • TakePhotoAsync
      • NRCaptureBehaviour.DoAsyn
        • CaptureTask インスタンスを生成して、CameraParameters を設定する
        • SystemInfo.supportsAsyncGPUReadback が true の場合、ImageEncoder.Commit(CaptureTask)
          • 非同期実行で画像データが取得されて CaptureTask.OnReceive が呼ばれる
        • false の場合、ImageEncoder.Encode(CaptureTask)
          • RenderTexture を使用して Texture2D から画像データが取得されて CaptureTask.OnReceive が呼ばれる
        • OnReceive では PhotoCaptureFrame インスタンスが生成される
    • StopPhotoModeAsync
      • FrameCaptureContext.StopCaptureMode()
        • FrameCaptureContext.Release() を呼び出す
    • Dispose
      • FrameCaptureContext.Release()
        • FrameProvider.OnUpdate から CaptureBehaviourBase.OnFrame を削除する
        • FrameProvider.Release()
        • FrameBlender.Dispose()
        • IEncoder.Release()
        • Destroy(CaptureBehaviourBase)
      • FrameCaptureContext を破棄する
  • NRCaptureBehaviour
    • ImageEncoder の Commit, Encode により画像データを取得する
    • OnFrame では
      • NRFrame.GetHeadPoseByTime に成功した場合に RGBCameraRig の localPosition, localRotation を更新する
      • FrameBlender.OnFrame を呼び出す
  • ImageEncoder
    • 画像データを取得する
  • FrameBlender
    • OnFrame で BlendMode (RGBOnly/VirtualOnly/Blend/WidescreenBlend) に応じて Blit を行い画像データを作る
      • 作った画像データは ImageEncoder に渡される
  • RGBCameraFrameProvider
    • NRRGBCamTexture を使う
      • Play, Pause, Stop で NRRGBCamTexture の Play, Pause, Stop を呼び出す
      • NRRGBCamTexture.OnUpdate に AbstractFrameProvider.OnUpdate を登録する
  • EditorFrameProvider
    • AbstractFrameProvider.OnUpdate に毎フレーム RGBTextureFrame を渡す
      • RGBTextureFrame.texture に Texture2D (Record/Textures/captureDefault) を設定する

同様に、NRVideoCapture を拾い読みすると、以下が要点になるのかなと思います。

  • NRVideoCapture
    • CreateAsync
    • StartVideoModeAsync
      • FrameCaptureContext.StartCaptureMode(CameraParameters)
        • CameraParameters の camMode は VideoMode に設定される
        • CameraParameters の hologramOpacity は 1 に設定される
        • 他は NRPhotoCapture と同様だが、camMode が異なるため以下が異なる
          • NRCaptureBehaviour は NRRecordBehaviour (Record/Prefabs/NRRecorderBehaviour) になる
          • IEncoder は VideoEncoder になる
    • StartRecordingAsync
      • IsRecording が true の場合は UnknownError とする (Unknown なんだ...)
      • NRRecordBehaviour.SetOutPutPath を行う
      • FrameCaptureContext.StartCapture()
        • NRPhotoCapture と同様
      • IsRecording を true にする
    • StopRecordingAsync
      • IsRecording が false の場合は UnknownError とする (Unknown なんだ...)
      • FrameCaptureContext.StopCapture()
        • IEncoder.Stop()
        • FrameProvider.Stop()
      • IsRecording を false にする
    • StopVideoModeAsync
      • NRPhotoCapture と同様
    • Dispose
      • NRPhotoCapture と同様

要点の整理

NRPhotoCapture, NRVideoCapture の基本的な使い方は似ています。以下の 5 つに分類して理解します。

  1. CreateAsync
  2. Start[Photo/Video]ModeAsync
  3. TakePhotoAsync or [Start/Stop]RecordingAsync
  4. Stop[Photo/Video]ModeAsync
  5. Dispose

CreateAsync

引数は showHolograms と callback の 2 つです。 showHolograms は使用されていません。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから CreateAsync が終了します。

CreateAsync は NRPhotoCapture, NRVideoCapture インスタンスを生成します。 この時、FrameCaptureContext インスタンスを生成して設定します。 更に、FrameProvider (AbstractFrameProvider のサブクラス) インスタンスを生成して FrameCaptureContext インスタンスに設定します。 つまり、下記の階層構造になります。

  • NR[Photo/Video]Capture
    • FrameCaptureContext
      • FrameProvider

FrameProvider は実行環境により異なります。 Unity Editor では EditorFrameProvider、それ以外では RGBCameraFrameProvider が使われます。

Start[Photo/Video]ModeAsync

引数には CameraParameters, callback が渡されます。 Video では AudioState が追加で渡されます。 CameraParameters は諸々の場面で使用されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから Start[Photo/Video]ModeAsync が終了します。 AudioState は使用されていません。

Start[Photo/Video]ModeAsync では PhotoMode/VideoMode で FrameCaptureContext を初期化します。 FrameCaptureContext.StartCaptureMode(CameraParameters) を呼び出し、以下に続くいくつかの処理を行います。

CameraParameters の値をいくつか上書きします。 Photo/Video に応じて、CameraParameters.camMode の値を PhotoMode/VideoMode で上書きします。 Video の時のみ、CameraParameters.hologramOpacity を 1 に上書きします。

Behaviour (CaptureBehaviourBase のサブクラス) インスタンス、Encoder (IEncoder を実装) インスタンス、FrameBlender インスタンスを生成します。 Behaviour と Encoder は Photo/Video により生成するインスタンスが異なります。

  • Photo
    • NRCaptureBehaviour (Record/Prefabs/NRCaptureBehaviour)
    • ImageEncoder
  • Video
    • NRRecordBehaviour (Record/Prefabs/NRRecorderBehaviour)
    • VideoEncoder

FrameProvider.OnUpdate に CaptureBehaviourBase.OnFrame を登録します。 FrameProvider.OnUpdate が呼び出されたときに Behaviour の OnFrame が呼び出される様になります。

終了時点では、階層構造は下記に更新されています。

  • NR[Photo/Video]Capture
    • FrameCaptureContext
      • FrameProvider
      • Behaviour
      • Encoder
      • FrameBlender

Photo の時のみ、FrameCaptureContext.StartCaptureMode() に続いて FrameCaptureContext.StartCapture() を呼び出します。 FrameCaptureContext.StartCapture の説明は StartRecordingAsync を参照してください。

Stop[Photo/Video]ModeAsync

引数には callback が渡されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから Stop[Photo/Video]ModeAsync が終了します。

Stop[Photo/Video]ModeAsync では FrameCaptureContext を無効化します。 FrameCaptureContext.StopCaptureMode() を呼び出すことで行われます。

StopCaptureMode では FrameCaptureContext.Release() を呼び出します。 Release では以下を無効化します。

  • FrameProvider
  • Behaviour
  • Encoder
  • FrameBlender

また、FrameProvider.OnUpdate から CaptureBehaviourBase.OnFrame を削除します。

(Photo の時は FrameCaptureContext.StopCapture() を呼び出した方が良いと思うけどやっていない。)

Dispose

引数はありません。他とは違い Async が付かないため明確に同期呼び出しでの使用を想定していると思われます。

Dispose では FrameCaptureContext.Release() を呼び出した後に、FrameCaptureContext インスタンスを破棄します。 Release の処理内容は Stop[Photo/Video]ModeAsync を参照してください。

撮影/録画

StartRecordingAsync

引数には filename と callback が渡されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから StartRecordingAsync が終了します。 録画中に呼び出したり、処理中に例外が発生すると callback に UnknownError が渡されます。

StartRecordingAsync では FrameCaptureContext.StartCapture() を呼び出して、録画を開始します。 StartCapture では、Encoder を開始(Start)し、FrameProvider を再生状態に(Play)します。

StartCapture の呼び出し前には NRRecordBehaviour に filename を設定します。 NRRecordBehaviour では設定された filename を VideoEncoder.EncodeConfig に設定します。

StopRecordingAsync

引数には callback が渡されます。 callback は処理が終了するときに呼び出されます。 非同期処理ではありません。 callback が呼び出されてから StopRecordingAsync が終了します。 未録画時に呼び出したり、処理中に例外が発生すると callback に UnknownError が渡されます。

StopRecordingAsync では FrameCaptureContext.StopCapture() を呼び出して、録画を停止します。 StopCapture では、Encoder を停止(Stop)し、FrameProvider を停止状態に(Stop)します。

TakePhotoAsync

引数のパターンが 2 種類あります。 filename と PhotoCaptureFileOutputFormat を指定するパターンと、指定しないパターンです。 いずれの場合も callback が渡されます。 filename を指定するパターンでは callback が使用されません。 filename を指定しないパターンでは画像が取得できたときに callback が呼ばれます。 SystemInfo.supportsAsyncGPUReadback が true の場合は非同期処理、false の場合は同期処理になります。

TakePhotoAsync は引数のパターンにより処理方法が異なります。

filename を指定するパターンでは、NRCaptureBehaviour.Do を呼び出します。 Do では ImageEncoder.Encode(width, height, captureFormat) を呼び出します。 Encode により画像データが取得し、ファイルに書き出します。

filename を指定しないパターンでは、NRCaptureBehaviour.DoAsyn を呼び出します。 DoAsyn では CaptureTask インスタンスを生成し、CaptureTask に応じた画像データ取得の処理を行います。 CaptureTask には OnReceive 関数を設定して、画像データの取得が完了した場合の処理を指定します。 OnReceive 関数は画像データが得られたときに PhotoCaptureFrame インスタンスを生成し、callback を呼び出します。 PhotoCaptureFrame は callback の引数として渡されます。

CaptureTask は SystemInfo.supportsAsyncGPUReadback の値により処理方法が異なります。

supportsAsyncGPUReadback が true の場合、CaptureTask をキューに積んで非同期に処理を行います。 CaptureTask は Update のタイミングで処理されます。 UnityEngine.Rendering.AsyncGPUReadbackRequest を使用して画像データを取得します。 画像データ (Texture2D) が取得されると OnReceive を呼び出します。 呼び出す際に CaptureTask.CaptureFormat に従って JPG/PNGエンコードしたデータが渡されます。

supportsAsyncGPUReadback が false の場合、ImageEncoder.Encode(width, height, captureFormat) を呼び出します。 width, height, captureFormat は CaptureTask から取得します。 Encode により画像データが取得されたら OnReceive を呼び出します。 呼び出す際に Encode で得たデータが渡されます。

ImageEncoder.Encode は filename を指定するパターンと filename を指定せずに supportsAsyncGPUReadback が false の場合に呼び出されます。 Encode では FrameBlender.OnFrame で生成された RenderTexture から得られた画像データ (Texture2D) を CaptureTask.CaptureFormat に従って JPG/PNGエンコードします。 FrameBlender.OnFrame については次に説明します。

FrameBlender

FrameBlender.OnFrame では BlendMode (RGBOnly/VirtualOnly/Blend/WidescreenBlend) に応じて Texture を Blit します。 OnFrame は引数に RGBTextureFrame を受け取ります。 Blit した結果の RenderTexture は Encoder に設定(Commit)されます。

FrameBlender.OnFrame は CaptureBehaviourBase.OnFrame から呼ばれます。 CaptureBehaviourBase.OnFrame は FrameProvider.OnUpdate に登録されるため、FrameProvider.OnUpdate が呼び出されたときに FrameBlender.OnFrame が呼ばれることになります。

FrameProvider.OnUpdate は引数に RGBTextureFrame を受け取っており、これが FrameBlender.OnFrame の引数になります。

RGBCameraFrameProvider

Unity Editor 以外で動かした場合に使われる FrameProvider です。 NRRGBCamTexture を使って RGBTextureFrame を取得します。

FrameCaptureContext の操作に応じて以下の様に NRRGBCamTexture の操作が使われます。

FrameCaptureContext RGBCameraFrameProvider NRRGBCamTexture
StartCapture Play Play
StopCapture Stop Pause
Release Release Stop

NRRGBCamTexture では NRRgbCamera.GetRGBFrame を呼び出して RGBTextureFrame を取得しています。 この RGBTextureFrame が FrameProvider.OnUpdate を呼ぶときに渡されます。

FrameProvider.OnUpdate は NRKernalUpdater.OnUpdate が呼ばれて NRRGBCamTexture.OnUpdate が呼ばれたときに呼ばれます。 OnUpdate, OnFrame の呼び出し関係を整理すると下記の様になります。

NRKernalUpdater.OnUpdate
  NRRGBCamTexture.OnUpdate
    RGBCameraFrameProvider.OnUpdate
      Behaviour.OnFrame
        FrameBlender.OnFrame
          Encoder.Commit

EditorFrameProvider

Unity Editor で動かした場合に使われる FrameProvider です。 RGBTextureFrame インスタンスを生成し、インスタンスに Texture2D (Record/Textures/captureDefault) を設定します。

コルーチンで FrameProvider.OnUpdate を呼んでいます。その際に生成した RGBTextureFrame を渡します。 但し、isPlay が true のときのみ呼ばれます。isPlay は FrameCaptureContext の操作に応じて以下の様に設定されます。

FrameCaptureContext EditorFrameProvider isPlay
StartCapture Play true
StopCapture Stop false
Release Release false

NRFrame.GetTrackables を読み解いてみる

概要

ImageTracking の仕組みを理解するために NRFrame.GetTrackables を読み解いてみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。

目次

確認環境

  • NRSDK 1.2.1

解説

拾い読み

NRFrame.GetTrackables から続く処理を拾い読みしました。以下が要点かと思います。

  • NRFrame.GetTrackables
    • NRTrackableManager.GetTrackables
      • UpdateTrackables を呼び出す
      • m_AllTrackables を m_NewTrackables と m_OldTrackables に振り分ける
    • NRTrackableManager.UpdateTrackables
      • trackablelist_handle = NativeTrackable.CreateTrackableList()
      • NativeTracking.UpdateTrackables(trackablelist_handle, trackable_type)
        • trackable_type : TrackableType (BASE/PLANE/IMAGE)
      • count = NativeTrackable.GetSize(trackablelist_handle)
      • count 回繰り返す
        • trackable_handle = NativeTrackable.AcquireItem(trackablelist_handle, index)
        • Create(trackable_handle, m_NativeInterface) を呼び出す
      • NativeTrackable.DestroyTrackableList
    • NRTrackableManager.Create
      • trackableType = NativeTrackable.GetTrackableType(trackable_handle)
      • trackableType が TRACKABLE_IMAGE の場合に NRTrackableImage インスタンスを生成する
      • m_TrackableTypeDict, m_AllTrackables に NRTrackableImage インスタンスを登録する

ここまで読んで、TrackingImageDatabase (ScriptableObject) は何に使われているのか疑問に思いました。 TrackingImageDatabase が関連している処理を探して読んでいくと、NRSessionBehaviour から始まる処理で使用されていることがわかりました。 一連の処理では以下が要点になると思います。

  • NRSessionBehaviour
    • NRSessionManager.CreateSession
      • NRDevice.Init
      • NativeInterface.NativeTracking.Create
        • NativeApi.NRTrackingCreate(ref m_TrackingHandle)
        • NativeInterface.TrackingHandle = m_TrackingHandle
      • NativeAPI.NativeTracking.SetTrackingMode(TrackingMode)
      • DeployData
        • database = SessionConfig.TrackingImageDatabase
        • ZipUtility.UnzipFile(database.RawData, database.TrackingImageDataOutPutPath, NativeConstants.ZipKey)
          • TrackingImageDataOutPutPath は [Application.persistentDataPath]/TrackingImageData/
    • NRSessionManager.StartSession
      • SetAppSettings(SessionConfig.OptimizedRendering)
      • NativeAPI.NativeTracking.Start()
        • NativeApi.NRTrackingStart
      • NativeAPI.NativeHeadTracking.Create()
        • NativeApi.NRHeadTrackingCreate
    • NRSessionManager.SetConfiguration
      • NativeConfigration.UpdateConfig
        • NativeApi.NRConfigCreate(m_NativeInterface.TrackingHandle, ref m_ConfigHandle)
          • m_ConfigHandle が 0 の場合のみ行う
        • NRSessionConfig.ImageTrackingMode が DISABLE の場合
          • NativeTrackableImage.DestroyDataBase(m_DatabaseHandle)
            • m_DatabaseHandle が 0 以外の場合のみ行う
        • NRSessionConfig.ImageTrackingMode が ENABLE の場合
          • m_DatabaseHandle = NativeTrackableImage.CreateDataBase()
          • NativeTrackableImage.LoadDataBase(m_DatabaseHandle, TrackingImageDataPath)
            • TrackingImageDataPath : NRSessionConfig.TrackingImageDatabase.TrackingImageDataPath
              • [Application.persistentDataPath]/TrackingImageData/[DB_GUID] ディレクト
          • NativeApi.NRConfigSetTrackableImageDatabase(m_NativeInterface.TrackingHandle, m_ConfigHandle, m_DatabaseHandle)

要点を整理する

NRFrame.GetTrackable

NRFrame.GetTrackable の API は以下の様になっており、引数 trackables には検出した結果を格納する List、引数 filter には結果を絞り込む条件を指定します。

void NRFrame.GetTrackable<T>(List<T> trackables, NRTrackableQueryFilter filter) where T : NRTrackable

NRTrackableQueryFilter は All と New の 2 種類です。 NRTrackable のサブクラスは NRTrackableImage と NRTrackablePlane があり、T にはいずれかを指定します。

NRFrame.GetTrackable では、NRTrackableManager.GetTrackables を呼び出しています。引数は NRFrame.GetTrackables と同じです。 NRTrackableManager.GetTrackables では以下を行います。

  • NRTrackableManager.UpdateTrackables を呼び出して AllTrackables (List<NRTrackable>) を更新する
  • AllTrackables に新しく追加された NRTrackable を NewTrackables (List<NRTrackable>) に格納する
  • filter が All であれば AllTrackables の NRTrackable を trackables に格納する
  • filter が New であれば NewTrackables の NRTrackable を trackables に格納する

NRTrackableManager.UpdateTrackables では、Native API を呼び出して AllTrackables を更新します。 UpdateTrackables の引数には TrackableType が渡されます。 TrackableType は型パラメータの T から決定されます。 T が NRTrackableImage の場合は TrackableType は TRACKABLE_IMAGE、T が NRTrackablePlane の場合は TrackableType は TRACKABLE_PLANE になります。

NRTrackableManager.UpdateTrackables は以下の疑似コードの様に Native API を呼び出します。

void UpdateTrackables(TrackableType trackable_type)
{
    UInt64 trackable_list_handle;
    int list_size;
    NRTrackableListCreate(session_handle, ref trackable_list_handle);
    NRTrackingUpdateTrackables(session_handle, trackable_type, trackable_list_handle);
    NRTrackableListGetSize(session_handle, trackable_list_handle, ref list_size);
    for (int index = 0; index < list_size; index++)
    {
        UInt64 trackable_handle;
        TrackableType out_trackable_type;
        NRTrackableListAcquireItem(session_handle, trackable_list_handle, index, ref trackable_handle);
        NRTrackableGetType(session_handle, trackable_handle, ref out_trackable_type);
        if (out_trackable_type == TRACKABLE_PLANE) AllTrackables.Add(NRTrackablePlane(trackable_handle));
        if (out_trackable_type == TRACKABLE_IMAGE) AllTrackables.Add(NRTrackableImage(trackable_handle));
    }
    NRTrackableListDestroy(session_handle, trackable_list_handle);
}

上記のコードにおいて、session_handle は NativeInterface.TrackingHandle です。 NativeInterface.TrackingHandle は NRSessionManager.CreateSession の過程で設定されます。 これについては後ほど説明します。

以上で、NRFrame.GetTrackable から始まる一連の処理は終了です。 Native API を使用して ImageTracking を行っていることが分かります。

ImageTracking に関連する初期化処理

ここまで TrackingImageDatabase (ScriptableObject) は全く登場しませんでした。 ImageTracking には不要なのでしょうか? これについては NRSessionManager の Awake, Start の処理を見ていくことで理解が深まります。

NRSessionManager の Awake, Start では NRSessionManager の CreateSession, StartSession, SetConfiguration を呼び出しています。 これらの過程では以下に示す ImageTracking に関連する初期化処理が行われています。

  • NativeInterface.TrackingHandle の初期化

TrackingHandle の値は Native API から取得されます。 疑似コードで示すと以下の様になります。

UInt64 tracking_handle;
NRTrackingCreate(ref tracking_handle);
NativeInterface.TrackingHandle = tracking_handle;
  • TrackingImageDatabase データの展開

TrackingImageDatabase データは SessionConfig.TrackingImageDatabase.RawData に byte[] として保持しています。 この RawData は zip アーカイブのデータになっており、unzip して [Application.persistentDataPath]/TrackingImageData/ に展開されます。

  • ImageTracking の有効化/無効化

SessionConfig.ImageTrackingMode が ENABLE の場合には ImageTracking を以下の疑似コードの処理で有効化します。 session_handle は NativeInterface.TrackingHandle で、trackable_image_database_directory は SessionConfig.TrackingImageDatabase.TrackingImageDataPath です。 TrackingImageDataPath は [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/ です。

UInt64 config_handle;
UInt64 trackable_image_database_handle;
NRConfigCreate(session_handle, ref config_handle);
NRTrackableImageDatabaseCreate(session_handle, ref trackable_image_database_handle);
NRTrackableImageDatabaseLoadDirectory(session_handle, trackable_image_database_handle, trackable_image_database_directory);
NRConfigSetTrackableImageDatabase(session_handle, config_handle, trackable_image_database_handle);

SessionConfig.ImageTrackingMode が DISABLE の場合には ImageTracking を以下の疑似コードの処理で無効化します。 session_handle は NativeInterface.TrackingHandle で、trackable_image_database_handle は有効化した時の値、trackable_image_database_handle は 0 です。

UInt64 config_handle;
NRConfigCreate(session_handle, ref config_handle);
NRTrackableImageDatabaseDestroy(session_handle, trackable_image_database_handle);
NRConfigSetTrackableImageDatabase(session_handle, config_handle, trackable_image_database_handle);

以上で、初期化処理は終了です。TrackingImageDatabase のデータは RawData に保持しており、unzip した上で Load しています。

TrackingImageDatabaseInspector を読み解いてみる

概要

TrackingImageDatabase の生成を理解するために TrackingImageDatabaseInspector を読み解いてみます。 個人的な読解結果であるため誤りが含まれている可能性があることをご了承ください。

目次

確認環境

  • NRSDK 1.2.1
  • macOS 10.15

解説

拾い読み

ざっくりと TrackingImageDatabaseInspector を拾い読みすると、以下が要点になるのかなと思います。

  • TrackingImageDatabaseInspector
    • NRTrackingImageDatabase のインスペクターを表示する際
      • UpdateDatabaseQuality を呼び出す
      • BuildImage を呼び出す
        • 出力先は [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.json
    • TrackingImageDatabasePreprocessBuild の OnPreprocessBuild が実行される際
      • BuildDataBase を呼び出す
        • BuildImage を呼び出す
          • 出力先は [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.json
        • m_UpdatedQualityScores (JsonData) を更新する
        • UpdateDatabaseQuality を呼び出す
        • 呼び出した次に NRTrackingImageDatabase の BuildIfNeeded を呼び出す
          • BuildIfNeeded では
            • [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.json を読み込み
            • [Application.persistentDataPath]/TrackingImageData/[DB_GUID]/markers.dat に書き込み
            • [Application.persistentDataPath]/TrackingImageData/[DB_GUID] を [Application.persistentDataPath]/TrackingImageData/[DB_GUID]_zipFile にアーカイブ
            • zip のバイナリを NRTrackingImageDatabase の m_RawData に保持する
    • UpdateDatabaseQuality
      • m_UpdatedQualityScores (JsonData) の内容を NRTrackingImageDatabaseEntry に設定する
        • NRTrackingImageDatabaseEntry の Quality, Width, Height を設定する
    • BuildImage
      • trackableImageTools_[os] -image_path="[imagePath]" -save_dir="[outPath]" -width="[imageWidth]" を ShellHelper.RunCommand で実行する
        • os : osx/win/linux
        • imagePath : Texture のパス
        • outPath : [Application.persistentDataPath]/TrackingImageData/[DB_GUID]
          • macOS では /Users/xxx/Library/Application Support/DefaultCompany/[Project]/TrackingImageData/[DB_GUID]/
        • imageWidth : NRTrackingImageDatabaseEntry の Width

要点の整理

拾い読んだ内容を整理すると、インスペクター表示やビルドを行う際に TrackingImageData ディレクトリに追跡対象画像の学習結果を保存していると思われます。 macOS では /Users/[Account]/Library/Application Support/DefaultCompany/[Project]/TrackingImageData/[DB_GUID]/ ディレクトリに保存されます。

TrackingImageData/[DB_GUID] ディレクトリの内容は以下の様になっています。

.
├── Data
│   ├── nreal_city.fset
│   ├── nreal_city.fset3
│   ├── nreal_city.iset
│   ├── nreal_snake.fset
│   ├── nreal_snake.fset3
│   ├── nreal_snake.iset
│   ├── nreal_trib.fset
│   ├── nreal_trib.fset3
│   └── nreal_trib.iset
├── markers.dat
└── markers.json

さらに、markers.json と makers.dat の内容は以下の様になっています。

{
   "is_use_world_cam" : 0,
   "list" : [ "nreal_city", "nreal_snake", "nreal_trib" ],
   "nreal_city" : {
      "filter" : 50,
      "info" : "20200422212420_30.72_1.79_2_2",
      "physical_height" : 396.87496948242188,
      "physical_width" : 396.87496948242188,
      "train_score" : 70.502113342285156,
      "type" : "NFT"
   },
   "nreal_snake" : {
      "filter" : 50,
      "info" : "20200422212420_30.72_1.79_2_2",
      "physical_height" : 396.87496948242188,
      "physical_width" : 396.87496948242188,
      "train_score" : 64.339988708496094,
      "type" : "NFT"
   },
   "nreal_trib" : {
      "filter" : 50,
      "info" : "20200422212420_30.72_1.79_2_2",
      "physical_height" : 396.87496948242188,
      "physical_width" : 396.87496948242188,
      "train_score" : 68.515426635742188,
      "type" : "NFT"
   },
   "pixel_format" : "MONO8",
   "track_thread_num" : 8
}
# Number of markers
3

./Data/nreal_city
NFT
FILTER 50
MARKER_WIDTH 396.874969482422
MARKER_HEIGHT 396.874969482422

./Data/nreal_snake
NFT
FILTER 50
MARKER_WIDTH 396.874969482422
MARKER_HEIGHT 396.874969482422

./Data/nreal_trib
NFT
FILTER 50
MARKER_WIDTH 396.874969482422
MARKER_HEIGHT 396.874969482422

そして、markers.json を生成するために以下のコマンドを実行しています。

trackableImageTools_[os] -image_path="[imagePath]" -save_dir="[outPath]" -width="[imageWidth]"
  • os : osx/win/linux
    • コード上は Linux も記述されているがコマンドは osx/win しか存在しない
  • imagePath : Texture のパス
  • outPath : TrackingImageData/[DB_GUID] ディレクトリのパス
  • imageWidth : 画像の幅 [m]

ビルド時には、NRTrackingImageDatabase の BuildIfNeeded にて markers.json を markers.dat に変換し、TrackingImageData/[DB_GUID] ディレクトリ zip ファイル ([DB_GUID]_zipFile) にアーカイブしています。

また、NRTrackingImageDatabase は ScriptableObject であり以下の情報を保持しています。

  • RawData : byte[]
    • zip ファイルのバイナリデータ
  • Images : List
    • Name : string
    • Width : float
    • Height : float
    • Quality : string
    • TextureGUID : string