読者です 読者をやめる 読者になる 読者になる

プログラムの素

スマートフォンアプリ開発に携わっているペーペープログラマのブログです

【Unity5】Xcode出力時にプリプロセッサマクロを登録する方法

Unity5からUnityEditor.iOS.Xcodeという名前空間が追加された模様です。

これはUnite Japan 2015でユニティ・ジャパンの伊藤さんの講演を受けた際に知ったのですが、どうもまだドキュメントがないらしいです。

UnityEditor.iOS.XcodeはUnityからiOSのプラットフォームを出力する際に、Xcodeのプロジェクトに対して、C#スクリプトから色々と操作が可能になっています。

とりあえず題名の通り、Xcode出力時にプリプロセッサマクロを登録することができたのでその方法を紹介したいと思います。

確認した環境はUnity5.0.1、Xcode6.1.1となっています。

using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

public static class PostProcessBuildScript {

    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget buildTarget, string path) {

        if (buildTarget == BuildTarget.iOS) {

            string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";

            PBXProject pbxProj = new PBXProject();
            pbxProj.ReadFromString(System.IO.File.ReadAllText(projPath));

            string target = pbxProj.TargetGuidByName("Unity-iPhone");
            string debugConfig = pbxProj.BuildConfigByName(target, "Debug");
            string releaseConfig = pbxProj.BuildConfigByName(target, "Release");
            pbxProj.AddBuildProperty(target, "GCC_PREPROCESSOR_DEFINITIONS", "PLATFORM_IOS=1");
            pbxProj.AddBuildPropertyForConfig(debugConfig, "GCC_PREPROCESSOR_DEFINITIONS", "DEBUG=1");
            pbxProj.AddBuildPropertyForConfig(releaseConfig, "GCC_PREPROCESSOR_DEFINITIONS", "RELEASE=1");

            System.IO.File.WriteAllText(projPath, pbxProj.WriteToString());
        }
    }
}

上記のスクリプトを/Assets/Editor/内に入れておき、出力されたXcodeを確認すると以下のようになりました。

f:id:famme_fatale:20150505194714p:plain

【iOS】UIWebViewのreloadメソッドのタイムアウトについて

iOSでUIWebViewベースのアプリを開発している時に原因不明の不具合が発生しました。

クライアントから「なんかたまに画面上のアイコンタップした時にイベントが起こらないんだけどどーなってんの!?」と。

確認してみると、確かにたまーに不具合が発生してしまいました。。。
UIWebViewで表示するHTMLとかJavascriptとかは他社さんが開発しているので、どーせWeb側の不具合でしょ、と思っていて原因を調査してみたら実はそうでもなさそうでした。。。

原因となりそうな項目は以下の2点。

前者についてはまだ未検証なのですが、reloadメソッドって引数ないですしタイムアウトも何も設定できないですよね?
不具合が発生したとき、ステータスバーのインジケーターがずっと回っていたのでWebViewをリロードしている最中だった可能性があります。(アプリ側でWebViewのローディング中はステータスバーのインジケーターを回すように実装しているので)
ちなみにそのとき、UIWebViewのreloadメソッドを呼び出してから10分くらい待ってもロードが完了しませんでした。

後者については以下のサイトを参考にしました。

iOS 6のSafariではAjaxの動作が異常–すでにデベロッパたちは周知 - TechCrunch

上記のサイトから引用すると

ブラウザとサーバのあいだに複数の接続を開けずに、一度に一つの接続しか開けない。だから、最初のリクエストが完全に、あるいはタイムアウトで終わるまで、次のたとえば画像のリクエストは待たされる。

ということらしいです。

以上を踏まえた上で、確かにアイコンをタップした時に発生するイベントはWebアプリ側で通信を行っている可能性が高いです。

WebViewのロード中にアイコンをタップしてWeb側で通信処理を行っているけど、Web側の通信処理が行われずイベントが発生しなかった、と推測しました。

対応策としては、UIWebViewのreloadメソッドを呼び出さず、現在のURLでloadRequestする方法(これならタイムアウトの設定も可能)がスマートかなと思いますが、まずはreloadメソッドがホントにタイムアウトにならないか検証する必要がありますね。。。

検証したら結果はこの記事に記載したいと思います。

【Unity5】UnityスクリプトをDLLファイル化する方法

別にUnity5に限った話じゃないですが、Unityで作成したプロジェクトを外部の人に渡す必要がありその時にスクリプトを見せたくなかったので隠蔽する方法を検索しました。

どうやらUnityのスクリプトはDLL化できるみたいでしたのでその方法をメモ。

参考にさせていただいたサイトはこちらです。

Unityで、自作Assetをdllファイルにコンパイルする - Qiita

公式のサイト(日本語)はこちら。

Unity - マニュアル: Unity Project での Mono DLL 使用

Unityのスクリプトコンパイルするために、専用のコンパイラが提供されています。

Macの場合だと

/Applications/Unity/Unity.app/Contents/Frameworks/Mono/bin/smcs

Windowsの場合だと

C:¥Program Files¥Unity¥Editor¥Data¥Mono¥bin¥smcs.bat

にあるシェルスクリプト、バッチファイルでコンパイルを行う事ができます。

記述例は以下のような感じです。

/Applications/Unity/Unity.app/Contents/Frameworks/Mono/bin/smcs -r:/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll -r:/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEditor.dll -target:library -out:hoge.dll *.cs 
C:¥Program Files¥Unity¥Editor¥Data¥Mono¥bin¥smcs.bat -r:"C:¥Program Files¥Unity¥Editor¥Data¥Managed¥UnityEngine.dll" -r:"C:¥Program Files¥Unity¥Editor¥Data¥Managed¥UnityEditor.dll" -target:library -out:hoge.dll *.cs

引数やオプションについては以下の意味があります。

  • -r
    • ビルドに含めるライブラリのパス
      上記の例ではUnityEngine.dllとUnityEditor.dllを設定しています
  • -target
    • ビルドの種別
      libraryと指定するとDLLビルドを行う
  • -output
    • 出力されるファイル名
  • ソースファイル
    • 最後にコンパイルするソースファイルを指定する
      上記の例ではカレントディレクトリにあるC#ソースファイルを全てコンパイル

オプションについては

smcs -help

で見る事ができるので、出力された説明を参考にしてみてください。

【iOS】【Android】動画をOpenGL ESで描画する方法

iOSAndroidで動画をOpenGL ESで描画したかったのでその方法を調査しました。

iOSの場合

iOSiOS Dev Centerにこれを実現するためのサンプルコードが提供されていました。

Real-time Video Processing Using AVPlayerItemVideoOutput

ミソはAVPlayerItemVideoOutputというクラスです。

このクラスを使用すれば任意の時間のCVPixelBufferRefというバッファデータが取得できます。
このバッファデータが任意の時間における動画の画像情報となります。

このCVPixelBufferRefのオブジェクトから、CVOpenGLESTextureCacheCreateTextureFromImage()関数でCVOpenGLESTextureRefオブジェクトを取得し、これをCVOpenGLESTextureGetTarget()関数の引数に指定する事によって、OpenGLにおけるテクスチャIDが取得できます。

取得したテクスチャIDでテクスチャをバインドさせる事により、動画から取得した画像データが使用できます。

ちなみに、サンプルコードではAVPlayerItemVideoOutputからYUV420形式で画像データを取得しており、フラグメントシェーダーでは2枚のテクスチャ(Y情報とUV情報のテクスチャ)からRGBへと変換して描画していました。

ちなみに余談ですが、OpenGL ESでなくMetalでも動画を描画する事が可能みたいです。

MetalVideoCapture

Androidの場合

AndroidではGitHubにアップされていた以下のサンプルコードを参考にさせていただきました。

crossle/MediaPlayerSurface · GitHub

また実装してから気づいたのですが、以下のサイトのページがすごいまとまっていました。

SurfaceTextureでMediaPlayerやカメラ映像をOpenGLテクスチャとして使う場合の注意点 - eaglesakuraの技術ブログ

AndroidではMediaPlayerSurfaceTextureの組み合わせで実現が可能でした。

このSurfaceTextureというクラスですが、OpenGL ESのテクスチャとして動画のデータを取得できるみたいです。

注意点としては、OpenGL ESのExtension機能が使用されている点です。
テクスチャターゲットにはGL_TEXTURE_2Dの代わりにGL_TEXTURE_EXTERNAL_OESを使用しなければなりません。

またフラグメントシェーダーでは先頭行に#extension GL_OES_EGL_image_external : requireを記述して、サンプラーのタイプをsamplerExternalOESで指定する必要があります。

詳細は上記のサンプルコードやサイトを参考にしてみてください。

AndroidAnnotationsについて

AnroidAnnotationsという便利なライブラリがあったので使い方等をメモ。

AndroidAnnotationsとはアノテーションを使用してコードのスリム化を行い見通しの良いコードを記載するためのライブラリです。

公式サイトはこちらです。

AndroidAnnotations

概要

AndroidAnnotationsではapt (Annotation Processing Tool)という仕組みを使用しています。

例えば@EActivityというアノテーションが付加されたActivityがあった場合、このActivityのクラス名の末尾にアンダースコアが付加されたサブクラスが生成されます。

例としては以下のような形になります。

package com.some.company;
@EActivity
public class MyActivity extends Activity {
  // ...
}

この場合、同じパッケージ名で別のソースフォルダ内にサブクラスが生成されます。

package com.some.company;
public final class MyActivity_ extends MyActivity {
  // ...
}

なので、AndroidManifest.xmlにはActivity名は以下のように記載します。

<activity android:name=".MyListActivity_" />

startActivityで呼び出す際は以下のように記載します。

startActivity(this, MyActivity_.class);

導入

AndroidStudioに導入する例を紹介したいと思います。

使用しているAndroidStudioのバージョンは1.1.0になります。

AndroidAnnotatiosを導入するために、build.gradleに記述していく必要があります。

参考はこちらです。

ProjectRoot/build.gradle

まずはプロジェクトディレクトリの直下にあるbuild.gradleに対して以下のように編集を行います。
以下はサンプルとなります。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:1.1.0'
        // replace with the current version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}

allprojects {
    repositories {
        mavenCentral()
    }
}

app/build.gradle

続いてappディレクトリ内にあるbuild.gradleのサンプルです。

apply plugin: 'com.android.application'
apply plugin: 'android-apt'
def AAVersion = 'XXX'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.myproject.package"
        minSdkVersion 14
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    // This is only needed if you project structure doesn't fit the one found here
    // http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Project-Structure
    sourceSets {
        main {
            // manifest.srcFile 'src/main/AndroidManifest.xml'
            // java.srcDirs = ['src/main/java', 'build/generated/source/apt/${variant.dirName}']
            // resources.srcDirs = ['src/main/resources']
            // res.srcDirs = ['src/main/res']
            // assets.srcDirs = ['src/main/assets']
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'

    apt "org.androidannotations:androidannotations:$AAVersion"
    compile "org.androidannotations:androidannotations-api:$AAVersion"
}

apt {
    arguments {
        androidManifestFile variant.outputs[0].processResources.manifestFile
        resourcePackageName 'com.myproject.package'

        // If you're using Android NBS flavors you should use the following line instead of hard-coded packageName
        // resourcePackageName android.defaultConfig.packageName

        // You can set optional annotation processing options here, like these commented options:
        // logLevel 'INFO'
        // logFile '/var/log/aa.log'
    }
}

利用可能なアノテーション

こちらに利用可能なアノテーションの一覧が記載されています。

結構な数のアノテーションが利用可能です。

さすがに全てを紹介するのは難しいので、上記のページから使用用途に合わせて調べてみてください。

Khronosの次期グラフィックスAPI「Vulkan」

GDCにおいて、OpenGLなどのAPIを策定しているKhronos Groupから新グラフィックスAPI「Vulkan(ヴァルカン)」が発表されたみたいです。

pc.watch.impress.co.jp

iOSでのMetalのようなローレベルのAPIだそうです。

OpenGL ESとかだとマルチスレッド対応するのはケッコーめんどい感じですが、MetalのようなAPIだとするとマルチスレッドでも動作させる事ができそうな気がします。

DirectX12にしろVulkanにしろ、グラフィックスAPIは時代に逆行してローレベル化されていってますね。。。

そっちの方がむしろプログラマが色々とカスタマイズすることができてよいのかもしれませんが、実装できるプログラマの数も少なくなっていきそうな気がします。

UnityやUnreal Engineがこの辺りをサポートすれば基本的にプログラマが触る事はないですが、個人的な意見でゲームエンジンに頼っていると結局プログラマとしてのスキルが身に付かないような気がします。

ちなみにモバイルではサポートされるかどうかが今のところ不明みたいですが、今後の動向に注目していきたいです。

UnityのPhysicsについて

UnityのPhysicsについて、RigidbodyだとかColliderだとかisKinematicだとかどう設定すればよいのか理解が浅かったので整理してみました。

Rigidbody

まずはRigidbodyについて。

  • オブジェクトに物理挙動を付加するコンポーネント
  • オブジェクトの挙動はRigidbodyにより制御されるため、基本的にはTransformプロパティを変更してはいけない(例外あるので後述)

Collider

続いてColliderについて。

  • 物理的な衝突を目的としたオブジェクトの形が定義されるコンポーネント
  • 目に見えず、オブジェクトのメッシュと同じ形状である必要はない
  • 主なものとして、以下の種類のColliderが存在
    • 3D
    • 2D
      • Box Collider 2D
      • Circle Collider 2D
      • Polygon Collider 2D
  • Mesh Collider同士は衝突しない
    • Mesh ColliderのConvexにチェックを入れると衝突する

Collider + Rigidbodyの組み合わせ

Static Collider

  • Colliderのみ持ち、Rigidbodyは持たないゲームオブジェクト
  • 常に同じ場所に留まり、動き回ることのないオブジェクトに対して使用する
  • ゲームプレイ中に移動、スケーリング等行うと、物理エンジンのパフォーマンスが著しく低下する

Rigidbody Collider

  • ColliderとisKinematicにチェックが入っていないRigidbodyを持ったゲームオブジェクト
  • 物理エンジンによってシミュレートされ、スクリプトから適用される衝突や力に反応する

Kinematic Rigidbody Collider

  • ColliderとisKinematicにチェックがあるRigidbodyを持つゲームオブジェクト
  • Transformコンポーネントの変更により、スクリプトからオブジェクトを動かすことが可能
  • ただし、衝突や力を加えて動かす事はできない
  • isKinematicプロパティを切り替えることにより、Rigidbody Colliderとして動作させることも可能

衝突時のスクリプトアクション

  • 衝突が発生した際には、ゲームオブジェクトにアタッチされているスクリプトに対して、特定の関数を呼び出す
    • OnCollisionEnter : 衝突検知時に呼び出される
    • OnCollisionStay : 衝突検知中に呼び出される
    • OnCollisionExit : 衝突検知終了時に呼び出される
  • 衝突する2つのColliderに対して、どちらか一方はisKinematicがオフでなければならないと関数は呼び出されない

呼び出される組み合わせは以下になります。

  Static Collider Rigidbody Collider Kinematic Rigidbody Collider
Static Collider    
Rigidbody Collider
Kinematic Rigidbody Collider    

Trigger

  • ColliderのisTriggerプロパティをONにする
  • すると衝突が発生した際に、Collider同士はすり抜けるようになる
  • ColliderがTriggerの空間に侵入した際、Triggerはアタッチされているゲームオブジェクトのスクリプトに対して、特定の関数を呼び出す
    • OnTriggerEnter : 接触検知時に呼び出される
    • OnTriggerStay : 接触検知中に呼び出される
    • OnTriggerExit : 接触検知終了時に呼び出される

呼び出される組み合わせは以下になります。

  Static Collider Rigidbody Collider Kinematic Rigidbody Collider Static Trigger Collider Rigidbody Trigger Collider Kinematic Rigidbody Trigger Collider
Static Collider        
Rigidbody Collider      
Kinematic Rigidbody Collider      
Static Trigger Collider    
Rigidbody Trigger Collider
Kinematic Rigidbody Trigger Collider

参考サイト

Unity - マニュアル: Rigidbodies

Unity - マニュアル: コライダー