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

フニゲの開発日記

Electronとか...

Androidアプリの作成

原稿


2013/12/07校正

ADT (Android Development Kit) の環境設定

Androidのアプリを作る時まず最初にするべきなのは、Android 2.2以上の携帯かタブレットを入手してUSBで接続することです。エミュレータも一応ありますが、とにかく遅くてやる気を根こそぎ奪われてしまうので、使うべきではありません。
 端末の準備ができたら、開発者用のページからADTをダウンロードします。

http://developer.android.com/sdk/index.html

 あとNDK (Native Development Kit) もダウンロードしないといけません。NDKというのは、C++を使ってAndroidのアプリを開発をするためのキットです。NDKのバージョンは本書執筆時点ではr9bが最新です。

http://developer.android.com/tools/sdk/ndk/index.html

 本書では、~/Developというフォルダを作って、解凍したファイルを置くことにしました。

$ mkdir ~/Develop
$ cd ~/Develop
$ cp ~/Downloads/adt-bundle-mac-x86_64 . 
$ cp ~/Downloads/android-ndk-r9b . 

 次に~/Develop/adt-bundle-mac-x86_64/eclipse/にインストールされたEclipseをダブルクリックして、環境設定の続きです。 

f:id:funige:20131207140632p:plain

 もしここでADTのタイトル画面が表示されずに「javaが無い」とか何とか警告が出る場合は、javaのインストールが必要です。
 オラクルのサイト (oracle.com/­technetwork/­java/­javase/­downloads/­index.html) から、Java SE7のJDK (jdk-7u45-windows-x64.exe) をインストールします。SE7で問題が出る場合は、「Previous Releases」から一つ前のSE6 (jdk-6u45-windows-x64.exe) をインストールしてもいいと思います。


 Eclipseの起動に成功したら、次は環境設定です。Android SDKのバージョンはデフォルトで4.3とか4.4が入っているはずですが、Cocos2d-xのために古い2.2 (API 8)をインストールします。できるだけ古いバージョンを使ったほうが、より多くの端末で遊べるゲームになりますからね。
 Eclipseのメニューから「Window -> Android SDK Manager」でSDKマネージャを起動して、「Android 2.2 (API 8)」をチェックしてInstallボタンを押します。Google USB DriverとIntel x86 Emulator Accelerator (HAXM) もチェックしておいたほうがいいのかな。


 SDKの次はNDKの設定です。Macだと「ADT -> 環境設定」、Windowsだと「Window -> Preferences」でEclipseの設定画面が開けます。このへんの統一が取れていないのは何でかよくわかりませんが、設定画面はこんな感じです。

f:id:funige:20131207140652p:plain

 「Android -> NDK」タブでさっきインストールしたNDKの場所を入力します。筆者の環境では「/Users/funige」がホームディレクトリなので、入力するのは上の写真に示したようなパスになります。WindowsCygwin使ってる人なら「/cygwin/­home/­funige」がホームディレクトリになるでしょう。

 あと忘れがちなのですが、NDK_ROOTという環境変数を設定しておきます。Cocos2d-xの場合、ビルドの前にbuild_native.shというスクリプトが自動実行されるのですが、NDK_ROOTが無いとここでエラーが出て動きません。
 環境変数とか自信がないのでちゃんと説明できないのですが、Macならホームディレクトリにある.profileというファイルを開いて

export NDK_ROOT=/Users/funige/Develop/android-ndk-r9b

 とか書けばいいみたいです。Windowsではコンピュータの「システムのプロパティ → システムの詳細設定 → 環境変数」みたいな所で設定できます(Windowsなのでパスの区切りに "¥" を使って "C:¥..." と書きたくなると思いますけど、NDK_ROOTはUNIX式に "c:/..." で書かないとだめです)。
 もちろん、右辺には実際にNDKをインストールしたパスを書いてくださいね。


 次はCocos2d-xのライブラリ、libcocos2dxをビルドします。「File -> Import」で「Android -> Existing Android Code Into Workspace」を選んで、Root Directoryの欄にはcocos2d-xをインストールしたディレクトリの下にある「cocos2dx/platform/android」を入力します。
 Eclipseは自動的にコンパイルをはじめるので、いつ終わったのかわかりにくいのですが、終わったとき左のペインの「libcocos2d-x」のところに、黄色のちっちゃい「!」マークが出ていれば問題ありません。エラーの時は赤いバッテンが出ますからね。

f:id:funige:20131207140719p:plain

プロジェクトをEclipseでビルドする

 そろそろ何のために何をしているのかわからなくなってきたと思うんですけど、ここまでが共通の環境設定。ここからようやくSampleプロジェクトのビルドです。

 libcocos2dxをビルドしたときと同じ要領で「File -> Import」->「Android -> Existing Android Code Into Workspace」へ進んで、cocos2d-xをインストールしたディレクトリの下に6章で作った「projects/­Sample/­proj.android」を入力します。

 ここまで正しく設定していれば、「Run -> Run as -> Android Application」で実行できるはずです。最初のコンパイルは時間かかりますので、その間にお茶でもどうぞ……。

 あれれ。動かないですか。最初はなかなか動かないものですよ。
 うまくいかないときは「Project -> Properties」を開いてプロジェクトの設定を確認してください。「Android」タブでさっきインストールしたAndroid 2.2が選択されているでしょうか。下のLibraryの欄では正しくlibcocos2dxが選択されてるか(緑のチェックマークが表示されているか)確認してください。

f:id:funige:20131207140740p:plain

 あと、何でか不明なのですが「Builders」タブにInvalid External Tool Builderというのが表示されることが多いです。もしあれば、選択してRemoveボタンを押して削除しちゃってください。

f:id:funige:20131207140756p:plain

3階から目薬

 ゲームが動いたと言っても、実際にアプリとしてリリースするためには、広告を入れたり、世界ランキングを実装したり、Twitterでツイートするボタンを追加したりしなければなりません。
 いや絶対必要ってことは無いんですけど。これをきちんと書くのは大変なんです。ご存じのように、Androidjavaで動いています。java。その上でNDKを使ってC++のレイヤーを作り、さらにその上にjavaScriptのゲームが乗っかっているのですから、3階の窓から目薬を差すようなコードになります。
 まじめにJNIとかJSAPIとか勉強してると幾らページがあっても足りません。目薬数滴のためにエレベーターを作るようなものです。

 本書では、簡単に使えるハックを紹介します。

Androidネイティブのメソッドを呼び出す

 Sampleプロジェクトのproj.androidフォルダを見てみましょう。このアプリの本体は、Sample/­proj.androidの下にあるsrc/­com/­funige/­Sample/­Sample.javaです。このファイルに、以下のようにshowDialogメソッドを追加します。

...
package com.funige.Sample;

import org.cocos2dx.lib.Cocos2dxActivity;
import android.os.Bundle;
import android.util.Log; // この行も追加

public class Sample extends Cocos2dxActivity{

	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
	}

	@Override
    public void showDialog(final String pTitle, final String pMessage) {
        if (pTitle.equals("command")) {
            Log.i(pTitle, pMessage);

            if (pMessage.indexOf("showAd") >= 0) {
                showAd();

            } else if (pMessage.indexOf("hideAd") >= 0) {
                hideAd();
            }
        }
	}

    // showAdとhideAdはあとで実装します
    public static void showAd() {}
    public static void hideAd() {}

    static {
        System.loadLibrary("cocos2djs");
    }
}

 Cocos2d-xにはCCMessageBoxというあまり使わない関数があって、画面にアラートメッセージを表示できます。このメッセージは最終的にこのshowDialogに流れてきますから、このメソッドを乗っ取ればメッセージの受信に使うことができるわけです。上の例ではC++側から送った「hideAd」という文字列が、画面に表示される代わりにjavaのhideAdメソッドを実行するコマンドになっています。

 1階(java)と2階(C++)の間はこれで突破できましたが、まだ2階(C++)と3階(JavaScript)の間があります。Cocos2d-x jsbにもcc.MessageBoxという関数があるので使えそうな気がするのですが、マイナーな関数なのでちゃんと実装されていないんです。これが。
 しょうがないので、2階と3階の間は別の関数を使います。似たような関数がないか探したところ、どうもlocalStorageが使えそうです。localStorageは前に一度出てきましたね。文字列やハイスコアをセーブするための関数ですが、これを改造して、セーブする時にshowDialogに文字列を横流ししちゃいましょう。
 ここからはフレームワークの改造です。sys.localStorageで送った文字列をネイティブ側で受け取るのは、libcocos2dxの中にある~/cocos2d-x/cocos2dx/platform/android/java/src/org/cocos2dx/lib/Cocos2dxLocalStorage.javaのsetItemメソッドです。このsetItemメソッドに以下のように1行だけ追加して、libcocos2dxを再ビルドします。

    public static void setItem(String key, String value) {
        ((Cocos2dxActivity)Cocos2dxActivity.getContext()).showDialog(key, value); // この行を追加
    	try {
    		String sql = "replace into "+TABLE_NAME+"(key,value)values(?,?)";
    		mDatabase.execSQL(sql, new Object[] { key, value });
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }

 原則として、フレームワークを書き換えるのはあまりいいやり方ではないと思います。Cocos2d-xをバージョンアップするたびにやり直さないといけないですからね。しかしまあ、動けばいいのです。ネイティブのメソッドをちょっと呼び出したいとき、こういう方法もあるということは知っていて損はないと思います。


 では、ちゃんとメッセージが流れるか確認してみましょう。

//game.js

  onEnter:function () {
    // ゲームが始まったら広告を隠す
    sys.localStorage.setItem("command", "hideAd");
    ...
  },

  ...
  onGameover:function () {
    // ゲームが終わったら広告を表示
    sys.localStorage.setItem("command", "showAd");
    ...

 こんな感じですね。実機で実行すればshowDialogメソッドを通るときにログが表示されるはずです。広告を表示するコードも書いてみますか。

広告の表示

 無料アプリに欠かせないのが広告です。最近はアイコン型広告とかメディエーションとかどんどん進化していますが、バナー広告だけ理解できれば、あとはその応用で何とかなります。例としていつもお世話になっているアイモバイルさん (www.i-mobile.co.jp) のSDK1.5.0を使いましょう。
 SDK付属のドキュメントに従って進めていけばいいのですが、まず i-mobileSDK.jar をプロジェクトのlibsの下にドラッグアンドドロップします。

f:id:funige:20131207140825p:plain

 それからSample/­proj.androidの下にあるAndroid­Manifest.xmlを修正します。<application>...</application>の直後に以下の2行を追加します。

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 
<uses-permission android:name="android.permission.INTERNET" />

 あとは最初に編集したSample.javaに戻って、showAdとhideAdを実装するだけです。

...
package com.funige.Sample;

import org.cocos2dx.lib.Cocos2dxActivity;
import android.os.Bundle;

import android.util.Log;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import android.content.Context;
import jp.co.imobile.android.AdView;

public class Sample extends Cocos2dxActivity{
    private static AdView adView = null;
    private static Sample me = null;

    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        me = this;
        adView = AdView.create(me, [メディアID], [スポットID]);

        DisplayMetrics metrics = new DisplayMetrics();
        ((WindowManager)me
            .getSystemService(Context.WINDOW_SERVICE))
            .getDefaultDisplay().getMetrics(metrics);

        int height = (int)(50.0f * metrics.widthPixels / 320.0f);
        me.addContentView(adView, new RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.WRAP_CONTENT, height));
        adView.start();
	}

    ...
	
    public static void showAd() {
        me.runOnUiThread(new Runnable() {
            public void run() {
               adView.setVisibility(LinearLayout.VISIBLE);
            }
        });
   }

    public static void hideAd() {
        me.runOnUiThread(new Runnable() {
            public void run() {
                adView.setVisibility(RelativeLayout.INVISIBLE);
            }
        });
    }

 同じやり方で、インテントを使ってTwitterにツイートすることもできますし、ScoreCenter (scorecenter.jp) などを利用すれば世界ランキングを数行のコードで実装することもできるでしょう。

広告を非表示にする