連載
» 2011年07月04日 00時00分 公開

UnityでAndroidの機能を拡張する2つの手法とはUnityで楽々スマホ用3Dアプリ開発入門(2)(2/3 ページ)

[佐藤大介,グリー株式会社 メディア開発本部]

Javaクラス呼び出し用C#スクリプト

 次は、Unity側の作業です。

Unity側でのファイルの準備

 まず、[Project]ウィンドウにおいて、先ほど追加したHelloWorldJava.jarとlibjni.soが「/Plugins/Android」フォルダに表示されていることを確認してください(図5)。

 次に、Javaクラスを呼び出すためのC#用ヘルパークラスのファイル「JavaVM.cs」「JNI.cs」を「Plugins」フォルダに登録します(図6)。

図5 Unityの[Project]ウィンドウ 図5 Unityの[Project]ウィンドウ
図6 C#用ヘルパークラスのファイルを「Plugins」フォルダに 図6 C#用ヘルパークラスのファイルを「Plugins」フォルダに

 以上でJavaクラスを呼び出すための準備は完了です。後は、クラスを呼び出して関数を実行するスクリプトを記述します。

C#スクリプトを書いて実行

 C#スクリプトファイルとして「CallJavaCode.cs」をルートフォルダに作成し、エディタで開きます。

CallJavaCode.cs
using UnityEngine;
using System .Collections;
using System .Runtime.InteropServices;
using System ;
 
public class CallJavaCode : MonoBehaviour {
    private IntPtr HelloWorldJavaClass;
    private int ptr_getStaticHello;
    private int ptr_getHello;
    private string announceString;
 
    void Start (){
        // attach our thread to the java vm; obviously the main thread is already attached but this is good practice //……[1]
        JavaVM.AttachCurrentThread();
 
        // create reference of HelloWorldJava class //……[2]
        IntPtr cls_HelloWorldJavaClass = JNI.FindClass("com/example/java/helloworld/HelloWorldJava");
        int mid_HelloWorldJavaClass = JNI.GetMethodID(cls_HelloWorldJavaClass, "", "()V");
        IntPtr obj_HelloWorldJavaClass = JNI.NewObject(cls_HelloWorldJavaClass, mid_HelloWorldJavaClass);
 
        HelloWorldJavaClass = JNI.NewGlobalRef(obj_HelloWorldJavaClass);
 
        // create references of HelloWorldJava class methods//……[3]
        ptr_getStaticHello = JNI.GetStaticMethodID(cls_HelloWorldJavaClass, "getStaticHello", "()Ljava/lang/String;");
        ptr_getHello = JNI.GetMethodID(cls_HelloWorldJavaClass, "getHello", "()Ljava/lang/String;");
        announceString = "Sample Started";
    }
 
    //getStaticHello() //……[4]
    private String getStaticHello(){
        IntPtr str_GetStaticHello = JNI.CallStaticObjectMethod(HelloWorldJavaClass, ptr_getStaticHello);
        Debug.Log ("str_GetStaticHello = " + str_GetStaticHello);
        IntPtr stringPtr = JNI.GetStringUTFChars(str_GetStaticHello, 0);
        Debug.Log ("stringPtr = " + stringPtr);
        String staticHello = Marshal.PtrToStringAnsi(stringPtr);
        JNI.ReleaseStringUTFChars(str_GetStaticHello, stringPtr);
        Debug.Log ("return value is = " + staticHello);
        return staticHello;
    }
 
    //getHello()//……[5]
    private string getHello(){
        IntPtr str_GetHello = JNI.CallObjectMethod(HelloWorldJavaClass, ptr_getHello);
        Debug.Log ("str_GetHello = " + str_GetHello);
        IntPtr stringPtr = JNI.GetStringUTFChars(str_GetHello, 0);
        Debug.Log ("stringPtr = " + stringPtr);
        String hello = Marshal.PtrToStringAnsi(stringPtr);
        JNI.ReleaseStringUTFChars(str_GetHello, stringPtr);
        Debug.Log ("return value is = " + hello);
        return hello;
    }
 
    void OnGUI (){
        resetButton();
 
        if(makeButton("getStaticHello")){
            string message = "Message From HelloWorldJava(getStaticHello()): " + getStaticHello(); //……[6]
            announceString = message;
            _log(message);
        }
        if(makeButton("getHello")){
            string message = "Message From HelloWorldJava(getHello()): " + getHello(); //……[7]
            announceString = message;
            _log(message);
        }
        showMessage(announceString);
    }
 
    int buttonX = 0;
    int buttonY = 0;
 
    void resetButton(){
        buttonX = 0;
        buttonY = 0;
    }
 
    void showMessage(string message){
        int labelW = 400;
        int labelH = 50;
        GUI.Label(new Rect(10,80,labelW,labelH), message);
    }
 
    bool makeButton(string label){
        int buttonW = 150;
        int buttonH = 50;
        int buttonsInRow = 2;
        bool b = GUI.Button(new Rect(10+buttonX*(buttonW+5), 160+buttonY*(buttonH+5), buttonW, buttonH), label);
        buttonX++;
 
        if(buttonX==buttonsInRow){
            buttonX = 0;
            buttonY++;
        }
        return b;
    }
    void _log(string logstring){
        Debug.Log (logstring);
    }
}

 [1]では、JavaVMスレッドをアタッチし、JNIアクセスができる状態にしています。

 [2]では、HelloWorldJavaクラスを探し、新しいオブジェクトとして生成します。JNI.FindClass()に今回利用するJavaクラス「com.example.java.helloworld.HelloWorldJava」を指定してアクセス先クラスを指定しています。

 [3]では、[2]で取得したクラスオブジェクトから参照したい関数(「getStaticHello()」「getHello()」)へのポインタを引き出します。静的メソッドに対しては、「GetStaticMethodID()」関数、インスタンスメソッドに対しては「GetMethodID()」関数で取得できます。

 [4]は、「getStaticHello()」関数の呼び出しと、取得した文字列データをUnityで扱えるString型への変換を行う内部関数です。[5]では、[4]と同じ処理を「getHello()」関数に対して行っています。

 [6]では、ボタンをクリックした際に「getStaticHello()」で取得した文字列を出力します。

図7 「getStaticHello()」で取得した文字列を出力 図7 「getStaticHello()」で取得した文字列を出力

 [7]では、ボタンをクリックした際に「getHello()」で取得した文字列を出力します。

図8 「getHello()」で取得した文字列を出力 図8 「getHello()」で取得した文字列を出力

 以上で、Javaで書いたクラスへUnityからアクセスできました。

 さらに、ネイティブ部分をカスタマイズしていきたい場合は、Unityを実行しているActivity自体のカスタマイズも可能です。

 まず、その方法を説明する前に、UnityがAndroid上でどのように動作しているかを簡単に説明します。

UnityはAndroid上で、どのように動作しているのか

 Unityによってビルドされたアプリは、開発者が開発するUnityプログラム自体と、そのプログラムを実行させる「UnityPlayer」と呼ばれる実行プログラムから構成されています。UnityPlayer自体が前回簡単に紹介したように複数のOSに向けて提供されており、結果Unityアプリケーションが複数のOS上で動作します

 UnityがAndroid用にアプリをビルドする際、下記のようにプログラムが構成されます。

図9 Android用のUnityアプリの構成 図9 Android用のUnityアプリの構成

 APK内に「UnityPlayerActivity」というActivityを継承したクラスが1つ組み込まれ、その中でUnityPlayerクラスがロードされ、その上で開発者が作成するUnityコンテンツが再生されます。

 つまり、Android Frameworkから見ると、通常のAndroidアプリのように、「UnityPlayerActivity」という名前のActivityが実行され、Android Frameworkとの間でライフサイクルマネジメントが行われています。

UnityPlayerActivityクラスをカスタマイズ

 Unityでは、このUnityPlayerActivityクラスのソースコードを公開しており、開発者がActivityにカスタマイズを加えることが可能です。UnityPlayerActivityのソースコード自体は下記のパスで確認できます。

/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player/UnityPlayerActivity.java

カスタマイズでできる、あんなこと、こんなこと

 このUnityPlayerActivityクラスを継承したクラスを実装、あるいは同等のクラスを実装し、差し替えてビルドすることで例えば、以下のように振る舞いをいろいろカスタマイズ可能になります。

  • Activity自体にイベントハンドラを追加
  • Activityの構成を変更
  • 他のActivityからIntentを受け取る
  • 他のActivityの呼び出しし、終了時のコールバックの受け取り
  • 「Activityを追加したい」などの要求があり、ActivityクラスやAndroidManifest.xmlを改変

UnityでAndroidのActivityを拡張するには

 Unityでは、デフォルトで追加・ロードされるUnityPlayerActivityの差し替えやAndroidManifest.xmlの変更を行える方法を提供しています。

 次ページではEclipseとUnityを用いた場合の手順について解説します。Unityのサイトでは「Unity - Integrating Unity with Eclipse」で紹介されています。

 手順は大まかに下記の通りです(下記リストはインデックスになっています)

  1. UnityPlayerActivityライブラリプロジェクトの作成
  2. 新規Androidプロジェクトの生成
  3. UnityPlayerActivityライブラリプロジェクトをインクルードしたAndroidプロジェクトの生成
  4. 生成したAndroidプロジェクト上のメインActivityでUnityPlayerActivityを継承
  5. (必要な場合)AndroidManifest.xmlの変更
  6. Eclipse側でビルド

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。