2011年12月27日火曜日

Android SDK r16にハマる

android sdk r16がリリースされましたので早速使ってみることにしました。違いは android 4.0.3対応のようです。いつものようにsdkをダウンロードして、次にsdk/tools/androidを実行して必要なファイルをダウンロードします。すべてダウンロードするには結構な時間が掛かりました。
ここまでは、問題ありません。ハマるのはこれからでした。
まず開発中のゲームをビルドしてみます。自分はantを使ってビルドしているので。

% ant clean
% ant debug とします。問題なくビルドできましたので、開発作業を再開します。
(% はマンドプロンプトです。csh系の場合は'%'、bashなどのsh系は'$'、rootシェルの場合は'#'と表記します。自分はcsh系が好きなので tcsh を使っています。因みに開発環境は Ubuntu 10.04)

ところが、プログラムを書き始めると「あれ?」、「何か間違えたかな?」... 5分ほど何が起きたのか
判りませんでしたが修正箇所が反映されていないようです。antの出力をよく見ると。

-dex:
      [dex] No new compiled code. No need to convert bytecode to dalvik format.
ソースコードを書き換えているのにdexファイルが作られていないようです。という事はapkファイルは?

-package:
[apkbuilder] No changes. No need to create apk.
やっぱりapkファイルも新しく作られていません。
一旦sdk 16を使うのを中止。r15に戻して作業を続ける事にしました。

さて、開発中のゲームが完成してリリース準備ができたので、放置していたr16でビルドできない問題を調べる事にします。
まず、ソースコードをダウンロードしておきます。リポジトリに android-4.0.3_r1 というのがあるのでこれを取ってきてビルドしておきます。

次にdexファイルを作るdxコマンドが動くかどうかチェックします。
% dx --dex --verbose --output bin/claases.dex bin/classes libs
問題ありません。ちゃんとclasses.dexができました。

次は ant関連を調べます。普段antを使っているのですが、いままでどのようにantでビルドされているのかちゃんと調べたことがなかったので調べる事から開始です。
sdkの下に tools/ant/build.xml というファイルがありました。これを見ると次のような記述があります。

<path id="android.antlibs">
    <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
    


これがandroidのビルドをする為のanttaskのjarファイルです。
androidのビルドをするためのanttask.jarがsdkに含まれているものとソースからビルドしたものが違うとソースを見ても意味がありませんから、同じかどうかチェックしておきます。
先ほどソースからビルドしたanttasks.jarと配布されているsdkのディレクトリのanttasks.jarのファイルサイズは同じなので同じ物のようです。

さらにbuild.xmlには、下記のような記述があります。
<taskdef name="dex"
        classname="com.android.ant.DexExecTask"
        classpathref="android.antlibs" />

名前からこのクラスがdexファイルを作るクラスのように見えます。先ほどダウンロードしておいたソースに DexExecTask.java ファイルがないか探します。
% find . -name DexExecTask.java
./sdk/anttasks/src/com/android/ant/DexExecTask.java
原始的な事をしましたけど、@checkelaさんがもっと便利なやり方を紹介しています。

DexExecTask.javaファイルに"No new compiled code. No need to convert bytecode to dalvik format."というメッセージを出している所がないかと探すと、ありますあります。すぐに見つかりました。(笑)
if (initDependencies(depFile, inputPaths) && dependenciesHaveChanged() == false) {
    System.out.println(
             "No new compiled code. No need to convert bytecode to dalvik format.");
    return;
}

ここを直せばよいような気がします。
この調子でソースを追っていくと、 dexファイルを作る為の依存関係にあるファイルとして libs/ ディレクトリの下にあるファイルと bin/classesディレクトリをチェックしているのが分かります。そしてファイルの先頭にドットがあるファイルを無視したいように書かれています。そのファイルを無視する判定メソッドがInputPath.javaにあります。
ここが間違っています。 trueとfalseを逆にして返してしまうという単純なミス。
これでソースファイルを変更したのにdexファイルが作られない現象が直りました。
以前載せていたのはリバースド・パッチになっていました、訂正してあります。恥ずかしい...

InputPath.javaのパッチ。
diff -r -c source-4.0.3_r1/sdk/anttasks/src/com/android/ant/InputPath.java source/sdk/anttasks/src/com/android/ant/InputPath.java
*** source-4.0.3_r1/sdk/anttasks/src/com/android/ant/InputPath.java     2012-01-07 23:18:45.718828865 +0900
--- source/sdk/anttasks/src/com/android/ant/InputPath.java      2011-12-27 18:13:11.843712831 +0900
***************
*** 76,82 ****
       */
      public boolean ignores(File file) {
          // always ignore hidden files/folders.
!         return file.getName().startsWith(".") == false;
      }

      /**
--- 76,82 ----
       */
      public boolean ignores(File file) {
          // always ignore hidden files/folders.
!         return file.getName().startsWith(".");
      }

      /**

2011年12月6日火曜日

「androidで動くゲームプログラミング入門」 その3

「androidで動くゲームプログラミング入門」 その2の続きです。

[複数の解像度への対応]
Androidは色々な解像度の端末に対応できるように作られています。その機能を活かすには、Android.xmlのmanifestに以下のように書きます。

  <supports-screens
     android:smallScreens="true"
     android:normalScreens="true"
     android:largeScreens="true"
     android:xlargeScreens="true"
     android:anyDensity="true"/>

この説明はyanzmさんのblogに詳しく書いてあります。

ここでは画像データの対応について書きます。画像データはres/drawableの下に置くのですが、解像度と画面密度別に複数のデータを置けるようにできています。
画面サイズはsmall,normal,large,xlargeの4種類。
画面密度はldpi,mdpi,hdpi,xhdpiの4種類あります。このうちxlargeとxhdpiはandroid 2.3以降の対応になります。
携帯端末では480x320はmdpi、8??x480はhdpi、1280x720はxhdpiになるはずです。
drawable-hdpiに800x480向けの画像データを入れておくと読み込んだ時に、mdpi端末では縮小され、xhdpiでは拡大され、ちょっとボケますが拡大縮小などせずにそのまま使えます。
ここでちょっと落とし穴があって、拡大/縮小の具合は画面密度でされるのです。解像度ではありません。自分は勘違いしていて解像度に合わせて拡大/縮小してくれるのだと思っていました。だからタブレット端末だと画像を拡大してくれるのかと思っていたのです。とろこがタブレット端末は高解像度(1280x768ぐらい)ですが、画面密度は中程度なのです。ですからmdpi扱い、つまり縮小されてしまいました。画面が大きくなって画像が縮小されてしまって、小さい画像が表示されて困ってしまいました。解決方法はAndroidディベロッパーラボ東京の時に教えていただきました。
解決方法はタブレット端末の画像は drawable-xlarge-mdpi に入れるでした。1280x768用のデータを作りdrawable-xlarge-mdpiに置き無事解決。
言語毎にデータを分けることもできます。
データを置くディレクトリ名のルールは drawable-言語-解像度-画面密度 となります。
これはlayoutでも同様で layout-言語-解像度-画面密度となります。(試していません、間違っていたらごめんなさい)

例えば、現在売られている端末に合わせて画像データを作るとします。画像制作の手間を減らしたいのでなるべく種類は少なくします。解像度が低い端末向けには用意しません。androidによって縮小された画像を使うようにします。画像の縮小なら劣化はあまり目立ちません、ただしOut of memoryには注意です。目立つのは画像の拡大ですので大きな画像を用意します。
800x480(携帯)向け、1280x768(タブレット)向け、1280x720(携帯向け)。このうち1280x720と1280x768は同じデータで済めば楽だと思います。

ディレクトリ構成は以下のようになります。
drawable-hdpi  (800x480) 各言語共通
drawable-xhdpi (1280x720) 各言語共通
drawable-xlarge-mdpi (1280x768) 各言語共通
drawable-ja-hdpi  (800x480) 日本語用
drawable-ja-xhdpi (1280x720) 日本語用
drawable-ja-xlarge-mdpi (1280x768) 日本語用

例えばこのように動作します。800x480の端末で言語を日本語にすると drawable-ja-hdpi 以下のデータが読み込まれ、もしなければdrawable-hdpiのデータが読み込まれます。
言語設定が日本語以外ならdrawable-hdpi以下のデータが読み込まれます。
このように作るとandroidの「画面サイズが違っても表示される大きさは同じ」という考えと合いません。「高解像度の端末だと大きく表示される」になります。ゲームは絵が大きい方が見やすいですし、楽しいですから。
実際にこのように作ったのがこれです。よかったらダウンロードして遊んでみてください。タブレットでも画像がボケずに大きく綺麗になっています。

[まだ作っていないところ]
今回作ったのはサンプル的なもので、実装レベルもまだまだです。たとえばセーブ機能がありません。プログラムの状態が保存できないので端末の向きを変えるとゲームは最初からやり直しになります。端末を回転させると、ActivityのonDestory()が呼ばれ、onCreate()からやり直しだからです。そうならない為にはセーブしておかなければなりませんが、実装していません。セーブはonPause()で行うのが基本です。
ゲームの基本的な動きは作ったつもりですが、商品レベルにするにはまだまだ沢山作りこまなくてはなりません。UIをまともに作っていないのでちゃんと作る。見た目を格好良くする。ランキングシステムはどうする。広告はいれる? などなど。

コードの説明は androidに関連する部分だけにしてゲーム部分の説明はしませんでした。たぶん読んでも眠くなるだけですし。
質問、コメントや間違いの指摘を寄せていただいたら有難いです。

「androidで動くゲームプログラミング入門」 その1
「androidで動くゲームプログラミング入門」 その2
「androidで動くゲームプログラミング入門」 その3