Androidネイティブ開発のすすめ

  • 160
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

1.はじめに

Unityアドベントカレンダー12日目の記事となります。
以前より、UnityでAndroid開発をどうやって始めたら良いの?や、
何が出来るのというお話をよく聞きます。
そこで、ハッピーバースデートゥミーな私UnityBearことリラゼクスがAndroidのネイティブ開発良いよ!
…といった内容を書いていきたいと思います。
初心者向けとは思っていますが、難しかったらごめんなさい(´・ω・`)

2.自己紹介

2010年iPhoneを買うか、Xperia X10を買うか悩んだ挙句、Xperia X10を買ったことにより
Android開発に興味を持ち、ネイティブ開発によるゲーム製作などをやってまいりました。
基本Android寄りな開発者ですが、必要に応じてiOS開発も時々やってます。

3.Unity使用歴について

えーと…、Unityワカラナイヨー。初心者ダヨー。[要出典]

4.ネイティブプラグインで出来ること

UnityとAndroidのネイティブでできることの違いは?という話をよく聞きます。

Unityでは3Dモデルや2D画像の描画や物理エンジンの計算などの機能に特化しています。
(ネイティブでも上記は実現可能ですが、Unityはより簡易的に機能を使えるよう提供していると考えてください)

当然Unityで提供されてる機能をわざわざネイティブで書くということはないので、
以下の様な用途でネイティブプラグインは使われています。
・OS組み込みダイアログを表示
・Webページをゲーム内に表示
・Push通知を利用
・課金機能を利用
・ゲームスコア、実績機能の利用

これらはAssetStoreでも提供されています。
その他ネイティブコードを使うメリットとして、通常のAndroidネイティブ開発向けに作られたライブラリを
利用することも可能な点が挙げられます。

5.ネイティブプラグイン開発入門編

5.1 前置き

Androidのプラグインは以下の作り方が存在します。

・Java言語で記述し、Unityのコードから呼び出し
・Unityのコード上のみからネイティブコードを呼び出し
・C言語で記述し、Unityのコードから呼び出し
・Java言語で記述し、C++言語を橋渡しにUnityのコードから呼び出し

この中では、一番上の「Java言語で記述し、Unityのコードから呼び出し」が最も利用されます。

コードの書き方としてよく参考になると挙げているのはWebViewの実装です。
(1)Activityを上書きする方法
https://github.com/keijiro/unity-webview-integration

(2)Activityを上書きしない方法
https://github.com/gree/unity-webview

上記2つは双方同じWebViewの実装ですが、実装方法が異なっています。
(※Unity4.3では仕様変更により上記のプラグインは動きません)
WebViewについては涙無くしては語れないことが多々あるんですが、
長いので割愛させていただきます。

ActivityとはAndroidで画面を構成する枠組みです。
その枠組みの中にボタンなどのパーツを配置してAndroidの1画面が完成します。
(※本当はウィンドウと言ってしまいたいんですが語弊がありそうなので枠組みと表現しています)
Activityを上書きする場合にはパッケージ名がプラグイン、Unityプロジェクト、
AndroidManifest(Androidアプリの設定ファイル)で一意でなければなりません。
(確かこれを守らないとアプリが実機で実行された瞬間クラッシュした気がします・・・)

一方でActivityを上書きしない場合は、大抵の用途には使用できますが、
Activity継承メンバを上書きしたい場合や、
メインスレッドで何か処理を実行させたいなどといった用途では使用できません。

それぞれの用途によりけりで、一概にどっちが良いとは言えませんが、
基本は後者の「Activityを上書きしない方法」で作るのがオススメです。

5.2 いざ実践(`・ω・´)

ただでさえ、内容が長くなるため、あらかじめAndroidSDKの導入とパスは通しており、
UnityからのAndroid出力が可能な前提で話を進めさせていただきます。

ネイティブコードのプラグインを作成するためにはEclipseが利用できます。
ここでは、AndroidSDK,ADTは導入済として説明を行います。

5.2.1 プラグイン用プロジェクトの作り方

(a) File -> New -> Other -> Android Application Projectを選択します。
(b) Application名、プロジェクト名、パッケージ名を入力します。
下部でSDKのバージョンを指定します。
(SDKのバージョンは使用者の用途で変わりますが、とりあえずAPI10なら色々都合がいいです)
1208ss5.png

(c) Create custom launcher icon、Create activityのチェックを外します。
1208ss6.png

(d) 作成したパッケージからsrcを右クリックし、New -> Packageを選択し、
先ほど入力したパッケージ名を入力します。
(パッケージ名を忘れてしまった場合はAndroidManifest.xmlを開いてみると書いてあります)
(e) 作成したパッケージを右クリックし、New -> Classを選択します。
クラス名を入力し、クラスを作成します。
(f) 作成したクラスにコードを記述していきます。

5.2.2 プラグインjarの作り方

(a) コードを記述し終えたらプロジェクトを右クリックし、Exportを選択します。
1206ss2.png

(b) Javaカテゴリ以下のJAR fileを選択します。
1208ss3.png

(c) 左の項目のプロジェクト名の横の三角を押し、プルダウンメニューを開き、
src以外のチェックを外します。
(d) 右の項目のチェックを全て外します。
(e) 保存先を選択し、Finishを押します。
1208ss4.png

5.2.3 Unityプラグインの追加

(a) プロジェクトを右クリックし、プロパティを開きます。
(b) 左項目からJava Build Pathを選択し、
右項目でLibrariesタブが開かれてるのを確認し、Add External Jars...ボタンを押します。
1208ss7.png

(c) Unityで用意されてるJarファイルを選択します。
例えば、デフォルトのインストール先になっていれば以下の場所を選択します。
C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androidplayer\bin\classes.jar

5.2.4 Unity上での作業

(a) Assets以下にPluginsフォルダを作成します。
(b) Plugins以下にAndroidフォルダを作成します。
(c) UnityのC#スクリプトをPlugins以下に、jarライブラリ、ManifestをAndroidフォルダに
それぞれ配置します。

5.2.5 コードについて

今回は簡単な文字の受け渡しを行うプラグインを例に取り、説明を行っていきます。
まずはJavaコードからです。

Hoge.java
package com.relboxes.unity.hoge;

import com.unity3d.player.UnityPlayer;

import android.util.Log;

public class Hoge{
    // (1) Unity側から呼び出されて何か処理を行う関数
    public void FuncA(final String str)
    {
        Log.d("Unity Native", str);
    }

    // (2) Unity側に値を返す関数
    // C#とJavaでバイト数が同じ型同士ならやりとり可能。
    // ただし、C#のstring型とJavaのString型など同じ型でも名前が違うものもあるので注意。
    // C#のint, long, bool(Javaだとboolean型)が使用可能。
    public String FuncB(final String str)
    {
        return "Back " + str;
    }

    // (3) Unity側のスクリプトのonCallBack関数を呼び出す関数
    // スクリプトがAttachされたGameObjectの名前を渡し、スクリプト内指定の
    // 関数を呼び出すことが可能。呼び出し先の関数がvoid型の場合は
    // UnityPlayer.UnitySendMessage(gameObjName, "onCallBack", ""); のように記述。
    // 3番目の引数は必ず記述しないとコンパイルエラーになった気がする…。
    public void FuncC(final String gameObjName, final String str)
    {
        UnityPlayer.UnitySendMessage(gameObjName, "onCallBack", str);
    }
}

次にUnity側のC#コードです。
以下のコードをPluginsフォルダに入れておきます。

HogeScript.cs
using UnityEngine;
using System.Collections.Generic;

public class HogeScript: MonoBehaviour {
static AndroidJavaObject    m_plugin = null;
static GameObject           m_instance;

    public void Awake () {
        // gameObject変数はstaticでないのでstatic関数から呼ぶことが出来ない.
        // そのためstaticの変数にあらかじめコピーしておく.
        m_instance = gameObject;
#if UNITY_ANDROID && !UNITY_EDITOR
        // プラグイン名をパッケージ名+クラス名で指定する。
        m_plugin = new AndroidJavaObject( "com.relboxes.unity.hoge.Hoge" );
#endif
    }


    // NativeコードのFuncA 関数を呼び出す.
    // Native側のコードが引数を持たない関数なら、
    // m_plugin.Call("FuncA"); のように引数を省略すればOK。
    public static void CallFuncA(string str)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if (m_plugin != null){
            m_plugin.Call("FuncA", str);
        }
#endif
    }


    // NativeコードのFuncB 関数を呼び出す.
    public static string CallFuncB(string str)
    {
        string modoriValue = null;
#if UNITY_ANDROID && !UNITY_EDITOR
        if (m_plugin != null){
            modoriValue = m_plugin.Call<string>("FuncB", str);
        }
#endif
        return modoriValue;
    }


    // NativeコードのFuncC 関数を呼び出す.
    public static void CallFuncC(string str)
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if (m_plugin != null){
            m_plugin.Call("FuncC", str);
        }
#endif
    }


    // ネイティブコードから呼ばれる関数
    // publicでかつ、非static関数でないと呼ばれない.
    public void onCallBack(string str)
    {
        Debug.Log("Call From Native. (" + str + ")");
    }

}

6.ネイティブのデバッグについて

ネイティブでの実行内容をコンソール出力したいという場面は多々発生します。
Androidでは公式に提供されているLogCatというアプリを利用することが可能です。
LogCatを利用するときは、その他の機能も組み込まれたddmsを使うと便利です。
ddmsは以下のディレクトリに存在します。
(AndroidSDKインストールパス)\android-sdk\tools\ddms.bat

・使い方
端末をUSBデバッグ接続後、(A)で端末を選択すると(B)にログが表示されます。
(C)のテキストボックスでログから特定項目を抽出できるのでUnityと入力するのがオススメです。
(D)から更に細かい絞り込みが出来ます。
1208ss1.png

(分かる人は分かるかもしれない余談)
この方法ではUnityアプリ関連しか取れないため、何か関連のサービスなどを取りたい場合は
cygwin導入が前提ですが、コマンドから"adb shell ps | grep (パッケージ名)"
と入力し、プロセスIDで抽出すると便利かもしれないですが、実行の度に変わってしまうので、
本当に確認が必要な場面にしか使えないのが玉にキズですね…。

7.ネイティブプラグイン開発応用編

…はい。書いてる当人も疲れてきたので急ぎ足で進行していきます。
さて、ここからは実際にネイティブで何が出来るのかというを実例を交えつつ
説明していきます。

7.1 何作ろう?

今回はTTS(Text to Speech)を例に取りたいと思います。
TTSとは、音声読み上げのことであり、Androidでは早い時期から導入されている機能でした。
(iOSでは7から利用できるそうなのでちょっと検証してみたいかなと思ったりしてます…)
このTTSですが、標準のものでは日本語読み上げは出来ず、英語読み上げとなっています。
そこでKDDIが無料で提供しているN2を使いたいと思います。
N2はGooglePlayの以下のURLからダウンロード可能です。
https://play.google.com/store/apps/details?id=jp.kddilabs.n2tts
インストールの注意点ですが、GooglePlayから落とすだけではダメで、
一回起動してダイアログの許可ボタンを押すのと、
端末設定から言語と入力→テキスト読み上げ→規定エンジンをN2に変更しておく必要があります。
(この設定は端末により、場所が異なる可能性があります)

7.2 早速使ってみる

まずは、ネイティブ側のコードからです。

TTSManager.java
package com.relboxes.unity.tts;

import java.util.Locale;

import com.unity3d.player.UnityPlayer;

import android.app.Activity;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;

public class TTSManager implements OnInitListener{
    private TextToSpeech tts;

    public void Init()
    {
        final Activity a = UnityPlayer.currentActivity;
        tts = new TextToSpeech(a.getApplicationContext(), this);
        tts.setLanguage(Locale.JAPANESE);
    }

    public void onDestroy()
    {
        if (tts != null){
            tts.shutdown();
        }
    }


    @Override
    public void onInit(int status) {
        if (status == TextToSpeech.SUCCESS){
            // TTS Setup Done.
        } else{
            // TTS Not Setup.
        }
    }

    public void Speak(String message)
    {
        if (tts.isSpeaking() == false){
            tts.speak(message, TextToSpeech.QUEUE_FLUSH, null);
        }
    }

    public void ForceStop()
    {
        tts.stop();
    }

    public void SetPitch(float pitch)
    {
        tts.setPitch(pitch);
    }


    public void SetPitchRate(float pitch)
    {
        tts.setSpeechRate(pitch);
    }

    public boolean GetIsSpeeking()
    {
        if (tts != null){
            return tts.isSpeaking();
        }

        return false;
    }
}

続いてUnity側のPlugins以下に配置するC#コードです。

TTSManager.cs
using UnityEngine;
using System.Collections.Generic;

public class TTSManager : MonoBehaviour {
    static AndroidJavaObject    m_tts = null;
    static Queue<string>        m_mesQueue = null;

    void Awake()
    {
#if UNITY_ANDROID && !UNITY_EDITOR        
        m_tts = new AndroidJavaObject( "com.relboxes.unity.tts.TTSManager" );
        m_tts.Call("Init");
        m_mesQueue = new Queue<string>();
#endif
    }


    void Start()
    {
        var pitch = PlayerPrefs.GetFloat("VoicePitch");
        Debug.Log("Pitch:" + pitch);
        if (pitch != 0){
            TTSManager.ChangePitch( pitch * 3.0f );
        }

        var pitchRate = PlayerPrefs.GetFloat("VoicePitchRate");
        Debug.Log("PitchRate:" + pitchRate);
        if (pitchRate != 0){
            TTSManager.ChangePitchRate( pitchRate * 3.0f );
        }
    }


    void Update()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if ((m_mesQueue != null) && (m_mesQueue.Count > 0) && (isSpeech == false)){
            if (m_tts != null){
                var message = m_mesQueue.Dequeue();
                m_tts.Call("Speak", message);
            }
        }
#endif
    }


    public static bool isSpeech{
        get{
#if UNITY_ANDROID && !UNITY_EDITOR
            if (m_tts != null){
                return m_tts.Call<bool>("GetIsSpeeking");
            }
#endif    
            return false;
        }
    }


    public static void ChangePitch(float pitch)
    {
#if UNITY_ANDROID && !UNITY_EDITOR        
        if (m_tts != null){
            m_tts.Call("SetPitch", pitch);
        }
#endif
    }


    public static void ChangePitchRate(float pitch)
    {
#if UNITY_ANDROID && !UNITY_EDITOR        
        if (m_tts != null){
            m_tts.Call("SetPitchRate", pitch);
        }
#endif
    }


    public static void StopSpeech()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if (m_mesQueue != null){
                m_mesQueue.Clear();
        }
        if (m_tts != null){
            m_tts.Call("ForceStop");
        }
#endif
    }


    public static void Speech(string message)
    {    
        Debug.Log("Call Speech");
#if UNITY_ANDROID && !UNITY_EDITOR
        if (m_mesQueue != null){
            m_mesQueue.Enqueue(message);
        }
#endif
    }    


    void OnApplicationPause(bool pauseStatus) {
        if (pauseStatus){
            if (m_mesQueue != null){
                m_mesQueue.Clear();
            }
            StopSpeech();
        }
    }
}

更に、テスト用のC#コードです。

TTSTestScript.cs
using UnityEngine;
using System.Collections;

public class TTSTestScript : MonoBehaviour {

    void Start () {
    }


    void Update () {
    }


    void OnGUI()
    {
        if (GUI.Button(new Rect(0, 0, 100, 100), "Push Me")){
            TTSManager.Speech("進捗どうですか");
        }
    }
}

使い方としては、適当な空のGameObjectに上記のTTSManagerとTTSTestScriptの
2つをコンポーネントとして追加し、Android 端末で実行できたらボタンを押すだけです。

ほら、かんたんでしょ(棒

実はこのクラスはマスコットアプリ用に作ったのですが、
最近UnityとAndroidで音声認識の話も出来ると聞いたので、
これで会話アプリが実現できますね!
夢が広がります!!!!11
…ちなみにUnity4.3でも動作は確認しております。

8.まとめ

ここまでかなり駆け足ですが、Unityネイティブ開発について書いてきました。
もしかすると間違っていたり、情報が古い可能性がありますので、
その際はご指摘頂けますと助かります。
正直、開発時の注意点などまだまだ書きたいことは山ほどあるのですが、
現時点での長文からお察しください…。
(どこかで書けたり話せる機会があれば良いなとポツリ。)

この記事が今後ネイティブコードを書く人の助けになれば幸いです。

この投稿は Unity Advent Calendar 201312日目の記事です。