戌印-INUJIRUSHI- (Androidあれこれ)

Androidのプログラミングをメインにしてます。記事に貼られたソースコードはダブルクリックすることで行番号をはずしてコピーすることができます。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
07

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Unity に MMD のモデルデータを取り込んで Android に表示

ゲームエンジンとして人気の Unity を使って、MMD (MikuMikuDance) の3Dモデルを Android で表示してみました。

UnityforMMD.png


■ Unity
Unity 公式サイト(日本語)の Download より Unity をダウンロードしてください。
http://japan.unity3d.com/

2014/02/09時点の最新は Unity 4.3.4

Unity Pro は有料ですが、無料版の無印 Unity でも十分な機能を有しています。
(年商10万ドル以上の企業は無料版の利用不可)

ダウンロードした UnitySetup-4.3.4.exe ファイルを実行してインストール。

プログラミングは JavaScript と C# をメインに使います。
標準エディタとしてインストールされている MonoDevelop ver.4.0.1 は日本語入力できないので要注意。
半角/全角キー押したらMonoDevelopを再起動しないと入力できなくなりました… (Windows 7)

2014/02/09時点の最新 MonoDevelop 4.2.2 (Xamarin Studio) であれば日本語対応しています。
ただし、JavaScriptのコードアシストは利かないようです (C#は利く)

エディタの変更は [Edit] - [Preferences...] の [External Tools] - [External Script Editor] で行います。
※今回はプログラミングは行いません


■ MMD for Unity
Unity 上に表示させる3Dモデルとして PMD (or PMX) ファイル (MMDのモデルデータファイル) を利用します。
PMD ファイルはそのままでは Unity に取り込むことはできないので、MMD for Unity プラグインを使って取り込む必要があります。

MMDを動かす会より MMD for Unity をダウンロードします。
http://sourceforge.jp/projects/mmd-for-unity/

2014/02/09時点の最新は MMD for Unity 2.1b

ダウンロードした mmd-for-unity 2.1b.zip を解凍して、解凍したフォルダを Unity の [Assets] タブにそのままドラッグ&ドロップで突っ込んでください。
[Assets] にフォルダが入ると上のメニューに [MMD for Unity] が追加されます。


■ PMDファイル
次に取り込むための PMD ファイルを用意します。
ここでは無料配布されている初音ミクのモデルを利用します。
【MMD】ねんどミクさんが得意気です【モデル配布あり】
http://www.nicovideo.jp/watch/sm22400052
※配布されているモデルファイルの利用規約は必ず守ってください

ダウンロードした ねんどミクさんver1_30.zip を解凍して、解凍したフォルダを Unity の [Assets] タブにそのままドラッグ&ドロップで突っ込んでください。

次に [MMD for Unity] - [PMDLoader] を選択。
[PMD File] の右端の小さな丸をクリックして『ねんどミクさんver1_30.pmx』を指定します。
ファイル選択時は、[Assets] の全てのファイルが表示されるので検索バーに『ねんど』と入力して絞込みを行うと見つけやすいです。
(日本語入力できないので正確には入力ではなくコピペ…)

ファイルを選択したら [Convert] ボタン押下でモデルデータが取り込まれます。
UnityPMDLoader.png

取り込まれたモデルデータは X:0 Y:0 Z:0 に配置されます。


■ 表示のための微調整
モデルデータを取り込んだだけでは味気ないので少しだけ調整を行います。

・カメラの位置調整
ねんどミクは小さいため、カメラを近づけて表示させます。
[Hierarchy] タブから [Main Camera] を選択。
[Inspector] タブの [Transform] - [Position] の X:0 Y:1 Z:-2 に変更。

・明かりの追加
初期時は明かりがなく暗く表示されるため明かりを追加します。
[GameObject] - [Create Other] - [Directional Light] で太陽光を追加。
今回は位置はよほど離れていない限り調整不要です。

・モデルデータの調整
[Hierarchy] タブから [ねんどミクさんver1_30] を選択。
[Inspector] タブの [MMDEngine (Script)] からモデルの状態を変更できますが今回はそのままとします。

これで調整は終了です。
あとは[再生]ボタン(中央上の▲ボタン)押下で実行されます。

実行すると一番最初の画像のように表示されます。


■ Android へビルド (APK ファイル出力)
※Android へのビルドは 2013/05/21 より無料となりました。

まずはビルドするための Android SDK を指定する必要があります。
[Edit] - [Preferences...] の [External Tools] - [Android SDK Location] にインストールしているSDKフォルダを指定します。
UnityPreferences.png

準備が整ったらビルドを行います。
[File] - [Build Settings] で [Android] を選択し、[Switch Platform] ボタンを押下してファイルをコンバートします。

次に Android アプリの設定を行うため [Player Settings..] ボタンを押下して右の [Inspector] タブに [PlayerSettings] を表示させます。

最低限必要な設定は [PlayerSettings] - [Settings for Android] の以下の2つ。
・[Other Settings] - [Bundle Identifier] へのパッケージ名の入力。
・[Publishing Settings] - [Keystore] のキーストア指定。
 キーストアがなければ [Create new Keystore] をチェック。
UnityBuildForAndroid.png

これで後は [Build]ボタンを押下して apk ファイルを作成するだけです。
PCとAndroidを接続している場合は [Build And Run] でビルドと同時にAndroid端末にインストールされます。

apkファイルをインストールして初音ミクが表示されれば成功です。お疲れ様でした。
UnityForAndroid.png
HTC J ISW13HT にて表示


■ 参考サイト
Unity 公式(日本語)
Vocaloid Promotion Video Project
UnityでMMDを動かす会
【MMD】ねんどミクさんが得意気です【モデル配布あり】
GMOメディア エンジニアブログ - 無償化されたUnityのスマートフォン書き出し機能でお手軽にAndroidゲームを作る方法まとめ

複数の拡張ストレージのアプリ固有ディレクトリ取得 : getExternalCacheDirs()

Android 4.4 (API19 / KitKat) より Context#getExternalFilesDirs() が追加されました。
このメソッドは、拡張ストレージ(内部ストレージ&外部ストレージ)のアプリ固有のディレクトリを File[] で返します。

Ex. Android 4.4.2 Emulator
/storage/sdcard/Android/data/[package name]/files

Android 2.2 (API8 / Froyo) で追加された Context#getExternalFilesDir(String type) (最後にsがない方)では拡張ストレージが内部と外部に分かれていた場合、内部ストレージのディレクトリしか返しませんでした。
外部ストレージのディレクトリを取得するAPIがなかったため、待ちに待ったAPIと言えます。

SupportLibrary v4 でも r19 より ContextCompat#getExternalFilesDirs(Context context, String type) が追加されています。
Android 4.4 と同様の機能を有しますが、残念なことに複数拡張ストレージに対応しているのは Android 4.4 以上です。
Android 4.4 未満の端末では内部ストレージしか返ってきません。

実際に HTC J ISW13HT (Android 4.0.4) で試してみました。
StringBuffer sb = new StringBuffer();
File[] files = ContextCompat.getExternalFilesDirs(this, null);
for (File file : files) {
sb.append(file.getAbsolutePath());
sb.append("\n");
}
Log.d("Storage", sb.toString());
結果:
/mnt/sdcard/Android/data/jp.inujirushi.android.sample/files

結果に書いたように内部ストレージのみでHTC端末の外部ストレージ /mnt/sdcard/ext_sd は返ってきませんでした。結局、外部ストレージのディレクトリを取得するには独自実装しかないようです。

----------------------------------------
外部ストレージのディレクトリ取得方法例
1.【過去記事】外部ストレージのパスを取得する(Android 2.2~?)
2.我が征くは肥沃な荒野 - ストレージの扱いが混沌の極みの件


■ Android 4.4 (API 19 / KitKat) で追加された拡張ストレージに関する API
Context#getExternalFilesDirs(String type)
 拡張ストレージ内のアプリ固有のディレクトリを返します。

Context#getExternalCacheDirs()
 拡張ストレージ内のアプリ固有のキャッシュディレクトリを返します。

Context#getObbDirs()
 拡張ストレージ内のOBBファイルディレクトリを返します。

Environment#getStorageState(File path)
 指定したパスのストレージの状態を返します。
 拡張ストレージ以外のパスが指定された場合 MEDIA_UNKNOWN を返します。


■ 参考
TechBooster C85 Android MagicBook P.37~39

指定時間に処理を実行する : AlarmManager

Android では指定された時間、または、一定時間毎に処理を実行するために AlarmManager が用意されています。

例えば、10秒毎にサービスを実行するには以下のように記述します。
// 実行するサービスを指定する
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
new Intent(context, SampleService.class),
PendingIntent.FLAG_UPDATE_CURRENT);

// 10秒毎にサービスの処理を実行する
AlarmManager am = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), 10 * 1000, pendingIntent);


■ AlarmManager の注意点
①AlarmManager の実行周期が長いと時間が遅れる(数時間で2~3分?)
②AlermManager は cancel しなくても以下の時に終了する
 ・デバイスの電源をオフにする(再起動する)
 ・アプリをアップデートする
 ・アプリをアンインストールする

この問題は BroadcastReceiver クラスを使い、再実行することで回避することが出来ます。

①の時間が遅れる問題は時間が自動設定されているときに発生するので、システム時間が変更された時に発生する broadcast を受け取るようにします。
<receiver
android:name=".SampleBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.DATE_CHANGED" />
</intent-filter>
</receiver>

②はデバイス起動時とアプリ更新時に発生する broadcast を受け取ってください。アンインストール時はアプリ自体がなくなるので指定する必要はありません。
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<receiver
android:name=".SampleBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data
android:path="jp.inujirushi.android.sample"
android:scheme="package" />
</intent-filter>
</receiver>
起動時に broadcast を受け取るには android.permission.RECEIVE_BOOT_COMPLETED 権限が必要です。
そして、アプリ更新時には <data android:scheme="package" /> が必要です。<data> タグに android:path を指定することで自身のアプリのみを対象にできる…らしいのですが android:path を指定しても他のアプリ更新時に broadcast が投げられてしまいました。BroadCast クラス側で path のチェックをする必要があるようです。
(HTC J ISW13HT で確認)



以上を踏まえ、10秒毎にログを出力する簡単なアプリを作ってみました。
AlermManager のON/OFFの管理は行っていません。アプリが1度でも起動したら常にONになります。

なお、Android 3.1 (API12 / Haneycomb) 以降、アプリが1度も実行されていない場合は STOP 状態となり broadcast も受け取りません。そのためインストール時に自動で開始させることはできません。なので「1度でも起動したら」という表記を用いています。

SampleService.java
package jp.inujirushi.android.sample;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import android.util.Log;

public class SampleService extends IntentService {

private static final String TAG = "SampleService";

public SampleService() {
super(TAG);
}

@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "time:" + SystemClock.elapsedRealtime());
}

/**
* サービスを処理する AlarmManager を開始する。
*
* @param context
*/
public static void startAlarm(Context context) {
// 実行するサービスを指定する
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
new Intent(context, SampleService.class),
PendingIntent.FLAG_UPDATE_CURRENT);

// 10秒毎にサービスの処理を実行する
AlarmManager am = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(), 10 * 1000, pendingIntent);
}
}

SampleActivity.java
package jp.inujirushi.android.sample;

import android.app.Activity;
import android.os.Bundle;

public class SampleActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);

// AlarmManager を開始する
SampleService.startAlarm(getApplicationContext());
}
}

SampleBroadcastReceiver.java
package jp.inujirushi.android.sample;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class SampleBroadcastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
// 他のアプリ更新時は対象外とする
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
if (!intent.getDataString().equals(
"package:" + context.getPackageName())) {
return;
}
}

// AlarmManager を開始する
SampleService.startAlarm(context);
}
}

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="jp.inujirushi.android.sample"
android:versionCode="1"
android:versionName="1.0" xmlns:android="http://schemas.android.com/apk/res/android">

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />

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

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".SampleActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver
android:name=".SampleBroadcastReceiver"
android:process=":remote" >
<intent-filter>
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.DATE_CHANGED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data
android:path="jp.inujirushi.android.sample"
android:scheme="package" />
</intent-filter>
</receiver>

<service android:name=".SampleService"></service>
</application>

</manifest>
<receiver> タグにある android:process=":remote" は別プロセスで起動する場合に指定します。必須ではありません。


■参考サイト
Y.A.M の 雑記帳 - AlarmManager
素人のアンドロイドアプリ開発日記 - AlarmManagerを使う場合の注意点
rokuta96のAndroidアプリ開発 - アラーム3
kino's blog - Android Service を自動的に再起動する方法
プロフィール

とむ・やむくん

Author:とむ・やむくん
管理人について

Windows 7 / 64bit
Eclipse 4.2 Juno (日本語パッチ済)

スポンサーサイト
最新トラックバック
検索フォーム
ブロとも申請フォーム
QRコード
QR
Twitter
2013/01/04 19:00 カウント開始

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。