2012年12月27日木曜日

Circular dependencies cannot exist in RelativeLayout


最近のADTではレイアウトの変更が簡単になったので、RelativeLayoutの属性で苦しんでいる僕にとっては、LinearLayoutでとりあえず組んで、RelativeLayoutに変更っていう手順がすごく楽なのですが、ときどき次のようなエラーが発生してアプリが起動しなくなることがあります。

12-27 15:34:47.104: E/AndroidRuntime(7123): java.lang.IllegalStateException: Circular dependencies cannot exist in RelativeLayout

このエラーはRelativeLayout内でIDを参照しあっている場合に発生します。
なので、そういう箇所をさがして修正してあげると治りますよ。

    <RatingBar
        android:id="@+id/ratingBar1"
        style="?android:attr/ratingBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/view1"
        android:layout_below="@+id/text4" />

    <View
        android:id="@+id/view1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/ratingBar1"
        android:layout_toRightOf="@+id/ratingBar1"
        android:layout_weight="1" />

こういうの

2012年12月14日金曜日

Android2.3と4.1のActivityの挙動の違い

Android2.3と4.1でActivityの挙動が少し異なるようなので調べてみました。
ActivityにSurfaceViewが一つあるだけのアプリを作成し、onCreate、onStart、onResume、onPause、onStop、onDestroy、onSaveInstanceState、onRestoreInstanceState、surfaceCreated、surfaceChanged、surfaceDestroyed それとSurfaceViewのコンストラクタにログ出力を入れて呼ばれるタイミングを調査しました。
使用した端末
・HTC EVO (Android 2.3.4)
・Galaxy Nexus (Android 4.1.1)

1.アプリ起動後、戻るボタンで終了
2.3 4.1
onCreate onCreate
SurfaceView Constructor SurfaceView Constructor
onStart onStart
onResume onResume
surfaceCreated surfaceCreated
surfaceChanged surfaceChanged
戻る押下 戻る押下
onPause onPause
surfaceDestroyed surfaceDestroyed
onStop onStop
onDestroy onDestroy


これは2.3と4.1で違いはありません。

2.アプリ起動後、電源ボタンでスリープしてから復帰
2.3 4.1
onCreate onCreate
SurfaceView Constructor SurfaceView Constructor
onStart onStart
onResume onResume
surfaceCreated surfaceCreated
surfaceChanged surfaceChanged
電源ボタンでスリープ 電源ボタンでスリープ
onSaveInstanceState onPause
onPause onSaveInstanceState

onStop
スリープ解除 スリープ解除
onResume onRestart

onStart

onResume

スリープ時に違いが二つ有りました。
・onSaveInstanceStateとonPauseの呼び出し順が逆になっている。
・2.3はポーズ状態に遷移しているが、4.1ではストップ状態に遷移している。
アプリによっては影響がありそうなので、注意が必要です。

3.アプリ起動後、ホームボタンで一時停止後、復帰
2.3 4.1
onCreate onCreate
SurfaceView Constructor SurfaceView Constructor
onStart onStart
onResume onResume
surfaceCreated surfaceCreated
surfaceChanged surfaceChanged
ホームボタン押下 ホームボタン押下
onSaveInstanceState onPause
onPause surfaceDestroyed
surfaceDestroyed onSaveInstanceState
onStop onStop
復帰 復帰
onRestart onRestart
onStart onStart
onResume onResume
surfaceCreated surfaceCreated
surfaceChanged surfaceChanged

onSaveInstanceStateとonPauseの呼び出し順が逆になっているのはスリープ時と同じですが、4.1ではonSaveInstanceStateはさらにSurfaceDestroyedより後に呼ばれています。

4.アプリ起動後、端末を回転
2.3 4.1
onCreate onCreate
SurfaceView Constructor SurfaceView Constructor
onStart onStart
onResume onResume
surfaceCreated surfaceCreated
surfaceChanged surfaceChanged
回転 回転
onSaveInstanceState onPause
onPause onSaveInstanceState
onStop onStop
onDestroy onDestroy
surfaceDestroyed surfaceDestroyed
onCreate onCreate
SurfaceView Constructor SurfaceView Constructor
onStart onStart
onRestoreInstanceState onRestoreInstanceState
onResume onResume
surfaceCreated surfaceCreated
surfaceChanged surfaceChanged

これもonSaveInstanceStateとonPauseの呼び出し順が逆になっています。SurfaceDestroyedがonDestroyより後に呼ばれていますが、動きとしては共通です。


まとめ
onSaveInstanceStateがonPauseの前に呼ばれていたのが、後に呼ばれるようになったようです。ポーズ後に状態を保存するほうが合理的な気はします。ただし、呼び出し順で挙動が異なるようなアプリでは注意が要ります。
また、2.3ではストップ状態ではSurfaceDestroyedはすでに呼ばれている、あるいはすぐ後で呼ばれる(回転時)はずですが、4.1ではストップ状態に遷移しても、SurfaceDestroyed が呼ばれ無いケースがあります(スリープ時)。この辺の動きにも注意が要りそうです。
なお、手元にAndroid4.0の端末がないので試していませんが、4.1と同様の挙動だろうと思われます。

2012年11月27日火曜日

Android OpenGL ES ワンポイントテクニック その1

OpenGLでテクスチャを貼ったポリゴンを表示した場合、デフォルトでは、表も裏も表示されます。回転させた場合など、裏面が表示されているときは裏返しの画像になるわけですね。
裏面を表示させたくない、あるいは裏面だけを表示したい場合、カリングという機能を使います。

  // カリングを有効化、表か裏しか描画されなくなる
  gl.glEnable(GL10.GL_CULL_FACE);

  // 両面描画するよう戻すときは、カリングを無効化します。
  gl.glDisable(GL10.GL_CULL_FACE);


描画しない面を指定するときは、glCullFaceを使います。
  // 表面のみ描画                
  gl.glCullFace(GL10.GL_BACK);

  // 裏面のみ描画
  gl.glCullFace(GL10.GL_FRONT);

glCullFaceのパラメータは「描画しない面」であることに注意してください。

応用例として、くるくる回るトランプを考えます。
テクスチャとして、トランプの表と裏、それぞれの画像を用意しておきましょう。

onSurfaceChangedなどで、カリングを有効化しておきます。

  // カリングを有効化、表か裏しか描画されなくなる
  gl.glEnable(GL10.GL_CULL_FACE);

onDrawFrameでトランプを描画するとき、表と裏それぞれの画像を同じ座標、同じ大きさで描画します。その時に、glCullFaceでどちらの面を表示するか指定します。

  // 表面のみ描画                
  gl.glCullFace(GL10.GL_BACK);
  トランプの表画像を描画する

  // 裏面のみ描画
  gl.glCullFace(GL10.GL_FRONT);
  トランプの裏画像を描画する

これで、トランプをくるくる回転させると、表が見えているときは表の画像、裏が見えているときは裏の画像が表示されるようになります。



2012年11月20日火曜日

MacでAndroidの標準ブラウザをビルドする方法

Androidのソースには、Android標準のブラウザ等のソースも含まれています。

ここでは、MacでAndroidの標準ブラウザをビルドする方法について解説します。

まずは、Android OSのビルドを行う必要があります。

MacでAndroid OSのビルドを行う場合、大文字小文字を区別するパーティションを用意して、そこにAndroidのソースを取得する必要があります。
こちらのサイトなどを参考に、Android OS のビルドを行って下さい。
http://techbooster.org/android/hacks/4920/

ただし、2012/11/20現在のところ、repoコマンドのダウンロードやソースコードのアドレスが変わっているようなので、それぞれ以下のようにして下さい。

・repoのダウンロード
curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo

・repoの初期化(ジンジャーブレッドのソースを取得する場合)
repo init -u https://android.googlesource.com/platform/manifest -b android-2.3.7_r1

これらのアドレスは、また変更になるかもしれません。

OSのビルドが終わったら、ブラウザのビルドを行います。
ブラウザはEclipseのプロジェクトになっていないため、ターミナルからビルドします。


cd packages/apps/Browser/
として、ブラウザのフォルダに移動します。
ブラウザのフォルダで、
mm
を実行すると、ブラウザがビルドされます。





2012年11月14日水曜日

MacにAndroidのCocos2d-xの開発環境を構築する方法、その2

前回、Cocos2d-xの開発環境をMacにインストールし、実行するところまで解説しました。
しかし、C++のソースをテキストエディタで編集し、ターミナルからビルド、Eclipseで実行するという、手間のかかる環境でした。
今回は、C++のソースの編集とビルドもEclipse上で行うための設定を解説します。
前提条件として、EclipseにCDTがインストールされている必要がありますが、最新のADTをインストールするときにNDK Pluginsをインストールしていれば、CDTもインストールされます。NDK Pluginsをインストールされていない方は、ADTの新規インストールを行ってNDK Pluginsをインストールしておいて下さい。


・C/C++ Projectsに変換する。
前回作成した、Cocostestプロジェクトを元に説明します。
まずは、プロジェクトをC/C++ Projectsに変換します。

プロジェクトを右クリックして、「New」→「Others」と選択して「Select a wizard」ダイアログを表示表示します。

「C/C++」ツリーの「Convert to a C/C++ Project(Adds C/C++ Nature)」を選択し「Next」ボタンを押下します。

「Convert to C or C++」のエリアでラジオボタン「C++ Project」を選択します。

「Project type:」のエリアで「Makefile project」を選んで「-- Other Toolchain --」を選択します。
※環境によっては「Android GCC」などという選択肢が現れますが、これを選ぶとえらい目にあうので気をつけましょう。

「Finish」ボタンを押下します。

・Eclipseからビルド出来るよう設定をする。
次にビルダの設定をします。
プロジェクトを右クリックして、プロパティを選択します。

「C/C++ Build」を選択します。
「Use default build command」のチェックを外します。
「Build command」に以下のように入力します。
bash ${workspace_loc:/Cocostest}/build_native.sh NDK_DEBUG=1 V=1
※ Cocostestのところはプロジェクトに合わせて変更して下さい。


「OK」ボタンを押下します。

一度、プロジェクトをビルドしてみましょう。
「please define NDK_ROOT」と表示された場合、環境変数の設定が必要です。
Macの場合、plist で NDK_ROOTにNDKのパスを設定する必要があります。

「~/.MacOSX/environment.plist 」を編集しますが、Xcodeがインストルされていれば、environment.plist をダブルクリックすると、Property List Editor が起動します。
使い方は、こちらのサイトが参考になります。
http://www.ki.nu/software/macosx/property-list-editor.html

NDKのパスはフルパスで入力する必要があります。
environment.plist を変更したら、変更を反映させるにはMacを再起動する必要があります。Macを再起動しましょう。

これで、Eclipseからビルドが出来るようになったはずです。

・EclipseでC++のソースを編集できるようにする

次に、C++ ソースの編集を出来るようにします。

プロジェクトを右クリックして、プロパティを表示します。
「C/C++ General」から「Paths and Symbols」を選択します。

「Source Location」タブを選んで、「Link Folder...」ボタンを押下します。

「Folder name:」に「Classes」と入力します。
「Link to folder in the file system」にチェックを入れて「Browse」ボタンを押下します。
プロジェクトフォルダ直下の「Classes」フォルダを選択して、OKボタンを押下します。

これで、EclipseからC++ のソースを開けるようになります。試しにEclipseからClassesフォルダのHelloWorldScene.cpp をダブルクリックしてソースをエディタで開いてみてください。エラーが沢山表示されると思います。

エラーを解消していきましょう。

まずは、インクルードパスを設定します。

プロジェクトを右クリックして、プロパティを表示します。
「C/C++ General」から「Paths and Symbols」を選択します。

「Includes」タブを選んで、「Languages」から「GNU C++」を選択します。
「Add...」ボタンを押下すると、パスの設定ダイアログが開くので「File System...」から、以下の二つを追加します。

~/dev/cocos2d-2.0-x-2.0.4/cocos2dx/include
~/dev/android-ndk/platforms/android-14/arch-arm/usr/include
※「~/dev」は、インストールされている環境に合わせて変更してください。

まだ、沢山エラーが出るので、エラーを無視するように設定します。
プロジェクトを右クリックして、プロパティを表示します。
「C/C++ General」から「Code Analysis」を選択します。

ラジオボタンの「Use project settings」を選択します。
「Problems」のリストから「Syntax and Semantic Errors」のチェックを外します。

これで、Eclipseからソースの編集とビルドを実行出来るようになりました。
便利ですね。

2012年11月9日金曜日

EclipseのContentAssistが使えなくなったとき( No Default Proposals)

Eclipseを使っていてある日突然コード補完(ContentAssist)が使えなくなった人いませんか?
僕もちょっと嵌ってしばらくContentsAssistなしの生活を強いられて苦しい思いをした口です。


にっくきこの画面、再インストールしたのに!と思う人もいるかもしれません、そして、それをそのへんの人に八つ当たりしちゃうとたまったもんじゃないですね。

こういうときは次の設定を試しててみてください。
Eclipseの設定からJava→Editor→Content Assist→Advanced
を選択し、Java Proposalsにチェックが入っているか確認してください。


多分、チェックが外れてる・・・



Cocos2d-xのHello Worldを改造して15パズルっぽい何かを作る(前編)


前回、Cocos2d-xの開発環境をインストールし、Androidで Hello World を表示することが出来ました。
今回は、そのHello World を改造して、15パズルのような何かを作って行きます。
私自身はCocos2dは初めてで、なにが出来るのかまったく知りません。調べつつ実装しつつ覚えていきたいと思います。

15パズルについて簡単に説明しておくと、正方形の画像を縦横4分割して16のピースに分割し、その内1枚のピースを抜いて残った15枚をスライドさせながら元絵を完成させるパズルです。

15パズル

・16分割の画像を作成
まずは、画像を16分割することを考えます。画像はとりあえずHello Worldに使用されているCocos2d-xのマスコット(?)画像を使用します。
AndroidのJavaプログラムであれば、Bitmapに読み込んだ画像をCanvasのdrawBitmapで切りだしていけば良いですが、Cocos2d-xではどうすれば良いでしょうか。
とりあえず、Hello Worldのソースファイルからの画像読み込み箇所を見てみます。

Classes/HelloWorldScene.cpp から
    // add "HelloWorld" splash screen"
    CCSprite* pSprite = CCSprite::create("HelloWorld.png");

どうやら、画像をスプライトとして読み込んでいるようです。
スプライトはゲームプログラムではおなじみですね。
http://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%97%E3%83%A9%E3%82%A4%E3%83%88_(%E6%98%A0%E5%83%8F%E6%8A%80%E8%A1%93)

これをどうやって16分割するか、CCSpriteに画像を分割するようなメソッドがあれば良いのですが。
まずは、スプライトを生成しているCCSprite::create のリファレンスを見てみます。
http://www.cocos2d-x.org/reference/native-cpp/d4/de7/classcocos2d_1_1_c_c_sprite.html#a4410a015719defb1aa4ca40c8762d7ad



画像のファイル名と矩形を指定して切り出す CCSprite::create メソッドがありました。

static CCSprite* create( const char *  pszFileName, const CCRect &rect )

これが使えそうです。


さっそく、先のHelloWorld.png読み込み処理を、矩形を切りだして16のスプライトを生成する処理に書き換えます。

// add "HelloWorld" splash screen"
CCSprite* pSprites[16]; // 16個分のスプライトの配列を用意する
const int PIECE_SIZE = 64; // ピースのサイズをとりあえず16ピクセルとする
int ix=0;
for (int y=0;y<4; y++) {
for (int x=0;x<4; x++) {
CCRect rect(float(x*PIECE_SIZE), // X座標
float(y*PIECE_SIZE), // Y座標
float(PIECE_SIZE), // 幅
float(PIECE_SIZE)); // 高さ
pSprites[ix] = CCSprite::create("HelloWorld.png", rect);
ix++;
}
}

pSprites はCCSpriteのポインタの16個の配列です。配列はその場でスタック上にメモリを割り当てられますので、Javaのようにnew をする必要はありません。
CCRect のメンバーは x, y, width, height となっています。AndroidのJavaで用意されているRect は left, top, right, bottom なので違いに注意する必要があります。
http://www.cocos2d-x.org/reference/native-cpp/da/d4d/classcocos2d_1_1_c_c_rect.html#aa270b18ac7ce93be45f31a8bee71eeec

これで16個のスプライトを用意出来ました。


・スプライトを画面に表示
生成したスプライトを画面に表示してみましょう。表示の方法は元のHello Worldソースを参考にします。


Classes/HelloWorldScene.cpp から
    // position the sprite on the center of the screen
    pSprite->setPosition( ccp(size.width/2, size.height/2) );

    // add the sprite as a child to this layer
    this->addChild(pSprite, 0);

スプライトにsetPosition で表示座標を設定しaddChild でレイヤに追加しているようです。
ここで、Cocos2dにおける座標の扱いについて知っておく必要があります。
先ほど、CCSprite::create で画像から矩形を切り出しましたが、その時に指定した座標はAndroidプログラマーにはおなじみの左上が原点の座標でした。
ところが、Cocos2d-xの表示座標は左下が原点になっています。つまりY座標が逆転しています。これは、Cocos2dのベースとなっているOpenGLの仕様に合わせたものと思われます。
先程の、スプライト生成処理に、表示座標計算と座標設定処理を加えて書き換えます。

// add "HelloWorld" splash screen"
CCSprite* pSprites[16]; // 16個分のスプライトの配列を用意する
const int PIECE_SIZE = 64; // ピースのサイズをとりあえず16ピクセルとする
const int GAP = 2; // ピース間の隙間
int ix = 0;
int posy,posx; // 表示位置座標要変数
for (int y=0;y<4; y++) {
posy = size.height/2 - ((PIECE_SIZE+GAP) *(y-2)); // Y座標算出

for (int x=0;x<4; x++) {
posx = size.width/2 + ((PIECE_SIZE+GAP) *(x-2)); // X座標算出

CCRect rect(float(x*PIECE_SIZE), // X座標
float(y*PIECE_SIZE), // Y座標
float(PIECE_SIZE), // 幅
float(PIECE_SIZE)); // 高さ
pSprites[ix] = CCSprite::create("HelloWorld.png", rect);

// position the sprite on the center of the screen
pSprites[ix]->setPosition( ccp(posx,posy) );

// add the sprite as a child to this layer
this->addChild(pSprites[ix], 0);
ix++;
}
}

ビルドして実行してみます。


15パズルっぽい表示が出来ました。
元絵が15パズル向きではないですが、別の絵に差し替えれば問題はないでしょう。
あとは、1ピース抜いてタッチ操作でピースをスライドさせられるようにすれば、15パズルっぽい何かの完成です。
実装方法は、今から調べます。

今回のポイント:
・Cocos2dはスプライトを使っている
・スプライトは画像から矩形を切り出して生成できる
・Cocos2dの表示座標系は左下が原点