2D/2.5Dゲームエンジン Playgroundのセットアップ

KLab株式会社さんが、9/25付で2Dゲーム(および2.5Dゲーム)用のゲームエンジンであるPlaygroundをオープンソースで公開されました(KLabさんによるプレスリリース )。

さっそく動作を確認してみようとされた方も多いと思いますが、Android版はハードルの高さに挫折された方も多いのではないでしょうか。弊社でも苦労しました。
まずドキュメントが英語です。この時点ですでに振り落とされる方も多いでしょう。そして、9/26時点でのドキュメントにはいくつか不備があります。

弊社では試行錯誤の結果、MacOS Xとその上で動作させているWindows7 VMを組み合わせて、Playgroundチュートリアルとして公開されているサンプルのビルドおよびAndroid上での動作確認まで行うことができました。その備忘録として、セットアップ手順をまとめておこうと思います。おそらく、数日中にPlayground側のドキュメントも改善され、この備忘録も不要になっていくだろうと思いますが。

(9/28追記: すでにかなりの修正がPlaygroundOSSで行われているので、ハマりポイントはこの記事執筆時点よりも減っていってます。)

Playgroundの取得

まずはgithubで公開されているPlaygroundのリポジトリからソースを取得します。ついでに作業用ブランチを作成します。

% git clone https://github.com/KLab/PlaygroundOSS.git
% cd PlaygroundOSS
% git checkout -b working

masterブランチ上で作業していて衝突(conflict)が発生すると面倒だったりする場合がありますので、ここでは念のためローカルブランチを作って作業しています。

Playgroudに含まれるもの

取得してきたものがそれぞれどういう役割を持っているか知っていた方がこれからどういう作業をしているのか理解しやすくなるので、少し説明します。

CSharpVersion
C#での開発に関連したファイルが入っています(調査中)。
Doc
ドキュメント類が入ってます。基本的に英語ドキュメントなので読むのに苦労します。また、9/26現在ではまだAndrodでのビルド方法などのドキュメントに不備がありますので、気をつけてください。
Engine
Playgroundのゲームエンジンそのものです。この下にある各プロジェクト用のひな形ファイルと、後述するassetsを組み合わせることで実行できるアプリをビルドすることができます。
SampleProject
紛らわしいですが、これはサンプルアプリが入っているディレクトリではありません。後述するエンジンのビルド時に用いるファイルが格納されています。エンジン本体をビルドする際には、このディレクトリ名を指定しなければなりません。
Tools
Windows用のassets作成ツールTobogganが入ってます。Tutorial以下に入っているサンプルをビルドするためには、このツールを使ってassetsを作成しないといけません。どうやらWindowsを用いなくても、monoでassetsの発行はできるらしいです(調査中)。
Tutorial
Luaを用いたサンプルプログラムがまとめられたzipファイルが入っています。本記事ではこちらのサンプルのビルドと実行までを確認します。

AndroidでPlayground を試すのための事前準備

開発環境インストール

Android SDKのインストール、Elicpseのインストール、EclipseへのADTのインストール、Android NDKのインストールを行ってください。
それぞれのインストールと設定の手順については本記事では省略します。各種のAndroid開発入門サイトや書籍を参考にしてください。

環境変数の設定

  • 環境変数 ANDROID_NDK_ROOT にNDKの場所を設定してください。.profileや.bash_profileなどに以下のような行を追加すると良いでしょう。

    export ANDROID_NDK_ROOT=$HOME/android-ndk-r9

  • 同様に、環境変数 ANDROID_SDK_ROOT にSDKの場所を設定してください。

    export ANDROID_SDK_ROOT=$HOME/adt-bundle-mac-x86_64-20130917/

  • 環境変数PATHにNDKとSDKを追加してください。

    PATH=$PATH:$ANDROID_NDK_ROOT:$ANDROID_SDK_ROOT/platform-tools

Playgroundエンジンのビルド

githubから取得した Playgroundは $HOME/PlaygroundOSS にあるという前提で説明します。

以下のコマンドを実行します(chmod は初回のみ必要です)。

% cd $HOME/PlaygroundOSS/Engine/porting/Android/GameEngine-android
% chmod +x ./build.py
% ./build.py --rebuild --project SampleProject

引数に指定するプロジェクト名は、SampleProjectでなければ成功しません。他のプロジェクト名を使いたい場合は、前述したSampleProjectディレクトリを複製して別の名前に変更し、さらには中のファイルもいろいろいじらないといけないと思われます。

ここではとりあえずサンプルのビルドと実行を目指していますので、上記のカスタマイズ方法については追求しないこととします。

環境設定が正しく行われていれば、$HOME/PlaygroundOSS/Engine/porting/Android/GameEngine-android/libs/ 以下の x86ディレクトリとarmeabiディレクトリの下に libGame.so ができるはずです。

Tobogganを用いて assets を発行する

ゲームエンジンはできましたが、それだけではゲームは動きません。ゲームの動作を記述したプログラムとデータを組み込む必要があります。
動作確認できるサンプルがTutorialディレクトリに用意されていますので、それで試しましょう。

先に述べたTobogganアプリを用いて、ゲームエンジンが利用できる形にassetsを発行する必要がありますので、ここからの作業はWindows上で行います。

Toboggan のインストールと実行

  • Tools/Toboggan-Tools.zip を展開してください。その中にできる Toboggan ディレクトリを適当な場所に置いてください。
  • Toboggan/KLBToolHost.exe を実行してください。

assetsの発行(publish)

  • Tutorial/Samples.zip をどこかに展開してください。たくさんのディレクトリができてしまいますので、どこか展開用のディレクトリを作成して、そこに展開するようにした方が良いかと思います。 9/28追記: 最新のリポジトリではすでに展開済みになっていますので、この手順は不要です。
  • 実行しているTobogganから、01.SimpleItemを開きます。 (メニューの File -> Open)
  • メニューの Tools -> Setup で開かれるダイアログに “Publish Targets” という項目がありますので、androidにチェックを入れてください。
  • メニューの File -> Publish All で発行処理を行ってください。
  • 01.SimpleItem/.publish/android ディレクトリに発行されたデータがありますので、このディレクトリをまるごとMacOS X環境にコピーしてください。

発行されたassetsをまとめる

ここから再び、作業環境はMacOS Xに戻ります。

前項で発行されたassetsは、AppAssets.zip というファイルにまとめなければなりません。Windowsでもこの作業は可能かとは思いますが、念のため、Playgroundの手順に従って、zip コマンドを用いて行いましょう。以下のコマンドを実行して下さい。

% cd .publish/android
% zip -0 -r <どこか適当なディレクトリ>/AppAssets.zip ./*

zip の最初のオプションは-O (オー)ではなく -0 (ゼロ)です気をつけてください。これは非圧縮でzipファイルを作成するためのオプションです。

また、再帰的にサブディレクトリもアーカイブしておかないと、参照するデータがAppAssets.zipに含まれないという事故も起こってしまうので、-r オプションもつけましょう。 

Eclipseへのプロジェクトのインポート

Eclipse に Playground 用の Androidプロジェクトを作成しなければなりませんが、これはすでに用意されているコードをEclipseにインポートすればOKです。英語版Eclipseのメニューで説明しますので、日本語化されている場合は適宜読み替えてください。

  • File -> Import を選択し、開いたダイアログで Existing Android Code into Workspace を選びNextボタンで次に進みます。
  • Browsボタンを押して、$HOME/PlaygroundOSS/Engine/Android/GameEngine-android を選択します。
  • Copy projects into workspace にチェックを入れ、Finishボタンを押します。

AppAssets.zip のEclipseプロジェクトへの組み込み

インポートしたElicpseプロジェクトは、Playgroundのゲームエンジンが入っているだけで、ゲームプログラム部分はまだ入っていません。それは AppAssets.zip としてまとめたものになりますので、このファイルをプロジェクトの assets フォルダに入れてください。

また、プロジェクトのassetsフォルダに version というファイルを新規作成し、その中に 1 とだけ記載してください。Playgroundのドキュメントに記載されている通り、 echo コマンドを用いて version ファイルを作成しても構いません。

AppAssets.zip を別のファイルに入れ替える際には、versionファイルの中身も書き換えましょう(たとえば数字をインクリメントしましょう)。AppAssets.zip 入れ替え前と違う数になっていればOKです。

プロジェクトのビルド

ここまででPlaygroundドキュメントに記載されているビルドできるプロジェクトの準備ができました。ではビルドしてみましょう。たいていの方はここで失敗します。

さて、何が悪いのでしょうか?実はインポートしたEclipseプロジェクトがあなたの環境にあっていないのです。

ということで、プロジェクトを選択し、右クリックしてコンテキストメニューからPropertiesを選んで設定画面を開いてください。以下の設定項目をご自身の環境に合わせて修正してください。

  • C/C++ の下のEnvironmentを選び、右側に表示された環境変数の一覧のうち、ANDROID_NDK_ROOT, NDKROOT, NDK_ROOTをご自身の環境に合わせて修正します。
  • C/C++ を選び、右側に表示された Build Command の項目を、${ANDROID_NDK_ROOT}/ndk-build -j に修正します。

プロジェクトの実行

ビルドが成功したら実行してみましょう! PlaygroundゲームエンジンはOpenGLを用いているので、Androidではエミュレータではなく実機を使った方が良いでしょう。

最後に

ビルド手順についてまとめたドキュメントの不備については、すでにPlaygroundのオープンソースプロジェクトの方に伝達していますので、Github上のドキュメントも今後改善されていくでしょう(9/28追記:改善作業が着々と進んでいます)。

オープンソースの2Dゲームエンジンとしては、cocos2d-x が有名です。また製品では、Unity も今秋の4.3から2Dゲーム開発を支援する機能を提供していくことが発表されています。Playground はそれらと競合するという位置づけです。

プレスリリースによると、Playgroundゲームエンジンは、音の遅延が小さく、リズムアクションゲームなどに適しているなどの強みがあるとのことです。

Playgroundについては、すでにハッカソンの企画も動いています。弊社もハッカソンの参加や、開発情報の共有などを通じて貢献できればと考えております。

Android: NotificationListenerServiceとAccessibilityServiceの違い

Android 4.3から導入された NotificationListenerService で出来る機能は、AccessibilityServiceでも実現できる場合があります。

新しい NotificationListenerService を使わなくてもできるのであれば、昔ながらのやり方をすれば良いと考えられるかもしれませんが、新しいOSバージョンでは NotificationListenerService を利用し、どうしても古いバージョンでも対応する必要がある場合は AccessibilityService を併用することをおすすめします。

NotificationListenerService の利用を推奨する大きな理由は以下の通りです。

  • 通知管理のほとんどをシステム側に任せられ、またできることが増える
  • セキュリティメニューによって有効・無効が管理されるため、ユーザに通知管理をダウンロードしたアプリに任せることのセキュリティリスクを意識させやすい

業務用アプリであれば、対象端末をAndorid 4.3以上に限定して開発することで、工数を大幅に削減することも可能となるでしょう。システムを設計する際に、ソフトウェアの開発工数とAndroid 4.3端末の導入にかかる費用を合算して検討することで、お客様にとって最適なシステムを提案できるでしょう。 ((新型Nexus7など、Android 4.3を指定した端末の導入は現実的な選択肢になってきていると、弊社では考えております。))

Google Playで広いユーザに対して提供するアプリを開発しなければならない場合に、通知管理機能を開発しなければならない場合は、引き続きAccessibilityService も利用しての開発を頑張りましょう。

通知管理について

AccessibilityService を利用して、裏技的に通知を管理しようとする場合、通知が届く度にその情報をそのアプリ内で管理する情報として保持し、アプリが起動される度にそのアプリの通知情報を削除するといったことをアプリ内で行う必要があります。それをちゃんと設計・実装するには大きな工数が必要となります。

また残念ながら、管理のためにたくさんのコードを書いたとしても、通知バーに表示されている情報とアプリが管理している情報を正確に一致させることはできません。

NotificationListenerService を利用する場合、以下のことができるので、アプリ側で行う処理を非常にシンプルにすることが可能となりますし、アプリ内の情報を通知バーの情報と一致させることは容易です。

  • getActiveNotifications() で有効なすべての通知を取得する
  • cancelAllNotification() や cancelNotification() で通知を削除する
  • onNotificationPosted() で新しい通知の到着をトリガとして処理を実行する
  • onNotificationRemoved() で通知の削除をトリガとして処理を実行する

通知の即時実行と、実行後の自動削除を行うサンプルコード

以下のコードサンプルは、通知が届いたらすぐに通知をタップしたのと等価な処理を実行するものです。その時、通知が削除可能かどうか、タップした時に自動削除することを要求しているか確認して、どちらもtrueなら通知を削除する処理を呼び出しています。

@Override
public void onNotificationPosted(StatusBarNotification sbn) {
    Notification notification = sbn.getNotification();
    Intent intent = new Intent();
    PendingIntent pendingIntent = notification.contentIntent;
    try {
        pendingIntent.send(this, 0, intent);

        int id = sbn.getId();
        String tag = sbn.getTag();
        String packageName = sbn.getPackageName();
        int flags = sbn.getNotification().flags;

        // 通知がキャンセル可能、かつユーザによる通知タップ時に
        // 消える設定の場合に通知を消す
        if (sbn.isClearable() &&
            flags & Notification.FLAG_AUTO_CANCEL) {
            cancelNotification(packageName, id, tag);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Android 4.3: NotificationListenerServiceマニアックスとAndroidセキュリティ観測

Android 4.3では、NotificationListnerService という、アプリが、他のアプリも含めて通知バーに追加された通知の情報を取得することが出来るという機能が追加されています。

このNotificationListnerServiceとリフレクションを用いたサンプルアプリをgithub上に公開しております

本記事では、NotificationListenerServiceとはどういうものか、基本的な使い方と踏み込んだ使い方、そしてNotificationListenerServiceを含めたAndroidのセキュリティについての考え方の方針転換の可能性についてを書きたいと思います。

NotificationListenerServiceとは?(基本的な使い方)

4.2以前のAndroidでは、自分のアプリの通知を通知バーに登録することは出来ました。4.3では、他のアプリが通知を行ったことを知り、それをトリガにして処理を実行することが出来るようになりました。 ((裏ワザ的なAccessibilityService使用によって、同様のことは出来たりはしますが))

ユーザは以下のように NotificationListnerService の派生クラスを作り、

public class MyListenerService extends NotificationListenerService {

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        /* 通知が追加された時に呼ばれる */
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        /* 通知が通知バーから削除された時に呼ばれる */
    }
}

AndroidManifest.xml にその派生クラスがNotificationListnerServiceの権限でのアクセスのみを受け付けることを宣言します。

    <service
        android:name=".MyListenerService"
        android:label="@string/app_name"
        android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
        <intent-filter>
            <action android:name="android.service.notification.NotificationListenerService" />
        </intent-filter>
    </service>

これによって、MyListenerSeriviceはBIND_NOTIFICATION_LISTENER_SERVICE のpermissionを持つものからの要求のみを受け付けるようになります。
BIND_NOTIFICATION_LISTNER_SERVICE は一般ユーザが使用することが出来ないprotectionLevelを持っているため、このサービスの機能を呼び出すことが出来るのはAndroid 4.3のシステムに組み込まれて適切な権限を持っているものからのみとなります。

前述のpermission宣言により、アプリ自身からもこのサービスを起動することは出来ませんが、[設定]->[セキュリティ]->[通知へのアクセス]の設定に従って自動的にシステムにより起動されます。以下はセキュリティ設定画面のキャプチャです。

security-setting

「通知へのアクセス」という項目は、実際にNotificationListnerServiceを用いたアプリを手順に従って端末にインストールするまではあらわれません。
最初、アプリをインストールしただけでは、そのアプリの使用許可は与えられていない状態です(下図参照)。

setting-notificationlistener01

チェックボックスをオンにすることでインストールされたアプリを有効化することが出来ますが、その時に非常に刺激的な警告文が表示されます。

setting-notificationservice02

非常にいろんなことが出来るので、信頼出来るアプリにしか許可を与えてはいけないという警告文です。この警告を受けた上でOKボタンを押すと、チェックボックスが有効になり、インストールしたアプリのNotificationListnerServiceを派生したサービスが起動します。逆にチェックボックスを外すと、サービスは停止します。

このようにして、システムから起動されたサービスは新しい通知が通知バーに登録される度にonNotificationPosted()が呼び出され、通知が削除される度にonNotificationRemoved()が呼び出されますので、それらをトリガとして処理を行うことが可能となります。

NotificationListnerServiceの踏み込んだ使い方

NotificationListenerService#onNotificationPosted()の引数として渡されるStatusBarNotificationからは、通知元のパッケージ名や通知が作成された時間、Linux UID、Notificationクラスのインスタンスなどを取得することが出来ます。しかしそれらの情報を用いてできることはそんなにたいしたことではありません。先ほど、機能を有効にする際にダイアログで警告されたようなことはほとんど出来ません。

しかしあれは単なる脅しではありません。Androidでは非公開のAPIもリフレクションを用いて呼び出すことができます。リフレクションによって先のダイアログにて警告されたような機能を実現することは可能です(本記事では述べません)。

以下のサンプルコードは、StatusBarNotificationのインスタンスからメールが到着した時にそのGmailを表示するためにPendingIntentを取得し、そのPendingIntentに紐付けられたIntentを送る方法を示します。

public class MyListenerService extends NotificationListenerService {
    private static final String TARGET_APP_PACKAGE = "com.google.android.gm"; // Gmail Application

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
    // return if the notification isn't from the target
    if (!TARGET_APP_PACKAGE.equals(sbn.getPackageName())) {
        return;
    }

    Notification notification = sbn.getNotification();

    Intent intent = new Intent();
    PendingIntent pendingIntent = notification.contentIntent;
    try {
        pendingIntent.send(this, 0, intent);
    } catch (PendingIntent.CanceledException e) {
        e.printStackTrace();
    }
}

実際に動作するサンプルコード全体は github で公開しておりますので、そちらをご参照ください。

NotificationListenerService のセキュリティ

従来のpermissionモデルであれば、ユーザがアプリをインストールした時点で、そのアプリが宣言していた機能をすべて使用させることになりますが、NotificationListenerServiceはその方法で通知バーの情報を保護しません。

通知バーにアクセスをする機能を持ったアプリをユーザがインストールし、なおかつ、ユーザが明示的に通知バーへのアクセスを許可するように設定を変更することを求めます。そして、その設定変更時には非常に強い警告を表示することで、ユーザが安易に設定を変更しないことを求めています。

このようにして、アプリのインストール後にユーザがアプリに対して機能の利用をON/OFFするというのは、弊社ブログでも紹介したAppOpsでも行われています。これは、Androidのセキュリティについての考え方が変化してきている兆候だろうと弊社では考えています。

騙してインストールさせて、電話帳のデータや端末固有情報(機体番号や電話番号)などを吸い出し、サーバに勝手に送信してしまうようなアプリが社会的な問題となりました。そのため、アプリ作成時に利用する権限を宣言し、インストール時に確認をさせるだけでなく、インストール後にも与えた権限を剥奪出来るようにすることや、重大な機能はユーザが明示的に許可するまでは使用できなくするようにGoogleはAndroidのセキュリティについての考え方を変化させつつあると判断しています。

また、通知バー広告を使ったアプリの排除など、Google Playを通じたアプリの配信でもセキュリティ強化は進んでいます。

特にGoogle Playを通じて配信する場合は、Androidでのセキュリティについての考え方の変化についてもしっかりと考慮し、誠実なアプリ開発を行っていくことが必要です。