2010年12月27日月曜日

OS標準のクリッピングを呼び出す方法

Androidで壁紙なんかを設定しようとすると、サイズが大きい場合にクリッピングのインテントへ画像を飛ばしてくれる。
似たようなものを自力で作ってみたんだけど、もとからあるならそれを使わせてもらいたいもんです。

というわけで、「OS標準のクリッピングを呼び出す方法」を調べてみましたが、中々有益な情報に出会わない。
ちゅーか、画像のセレクタからクリッピングへの流れを組むのは

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true");
startActivityForResult(intent, 0);

てなのが出てくるんだけど。

違うんだよ。 今既にこっちでもっているBMPをクリッピングして返して欲しいんだよ!

ってなると、急激に情報がなくなっていきます。 少なくとも日本語圏ではまともなのがない(探し方が悪いだけって話もある)

なので、実際にクリッピング画面をカメラなり壁紙設定から実行して、DDMSのログを見てどのIntentが呼び出されているのかを調べてみました。

すると、
INFO/ActivityManager(1381): Displayed activity com.android.camera/.CropImage: 863 ms (total 863 ms)

まぁ、863msは置いといて。
com.android.cameraパッケージのCropImageが呼び出されているようです。

ちゅーことは。
Intent intent = new Intent();
intent.setClassName("com.android.camera", "com.android.camera.CropImage");

でウッドボール!!


と、思ったら大間違い。 OS2.1以上だと起動しない!!!

なんと、このクリッピングのインテントはOSによって違う模様。 な・なんだってー!

僕はIS01で開発してたので、エミュレータでOS2.1にしてみると

INFO/ActivityManager(72): Displayed activity com.android.gallery/com.android.camera.CropImage: 1289 ms (total 1472 ms)

うお。 パッケージ名が "com.android.gallery"になってる・・・。

つーか、パッケージが"com.android.gallery"でクラス名が"com.android.camera.CropImage"ってどういことなんだ。

ともかく、
Intent intent = new Intent();
intent.setClassName("com.android.gallery", "com.android.camera.CropImage");

にしてあげたら、OS2.1以上では起動しました。

・・・えぇ、1.6で起動しなくなりましたよ。当たり前ですけど。

なーぬーーーー。

ちゅーわけで、もう一手間。

android.os.Build でSDKのVerがわかるようなので、

final int sdkInt = Integer.parseInt(Build.VERSION.SDK);

で、sdkのVerを取得。 このsdkIntがAPIレベルになるようで。 すると、1.6は4。 2.2なら8です。

それら踏まえて
Intent intent = new Intent();
  final int sdkInt = Integer.parseInt(Build.VERSION.SDK);
  if(sdkInt <= 4){ //OS1.6
   intent.setClassName("com.android.camera", "com.android.camera.CropImage");
  }else{ //OS2.1
   intent.setClassName("com.android.gallery", "com.android.camera.CropImage");
  }  
という感じ。 ふーはー。 とと、本題を忘れていた。 これらインテントにこっちからBMPを渡すんでした。 これもいろいろあるみたいなんですが、Intentで渡せるデータ容量の関係もあって、一度テンポラリとして保存をして、その保存ファイルのUriを渡すものみたいです。 なので僕のソースコードはこんな感じ。
intent.setData(Uri.parse("file://" + url));
  intent.putExtra("crop", "true");
  intent.putExtra("outputX", dw);
  intent.putExtra("outputY", dh);
  intent.putExtra("aspectX", dw);
  intent.putExtra("aspectY", dh);
  intent.putExtra("scale", true);
  intent.putExtra("noFaceDetection", true);
  intent.putExtra("return-data", "true");
  intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + url));
  startActivityForResult(intent, INTENT_CALL_CROP);
urlが渡すBMPを保存したパスだと思いねぇ。 その際に、Intent.EXTRA_STREAMで指定した場所にクリッピングしたデータを書きこんでくれるみたいなので、僕は渡した画像ファイルのパスをそのまま指定してます。 ところで、
intent.putExtra("return-data", "true");
ってー指定があってね。 これによってリザルトが戻ってくるんだかなんだか・・・。 でも、ウェブではどう調べても
intent.putExtra("return-data", true);
って書いてあるんだよね。 本当かね。 ウチの環境ではここでtrueぶち込んでもリザルト返ってこないんだけど。

教えてエライヒト。 それでは、また。ノシシ

2010年12月20日月曜日

ブレークポイントを置いてステップ実行すると読めるのに、通常実行するとBitmapがnullる!

今テストとして、HTTP通信で画像を取ってきて表示する。という至極簡単なものを作っているんですがー。

「ブレークポイントを置いてステップ実行すると読めるのに、通常実行するとBitmapがnullる!」

という現象発生。


調べても同じような事象がみつからず。 しかも常に起こるわけではなくて、開こうとしている画像ファイルのサイズに依存しているような・・・。そうでもないような・・・。

UIスレッド上で動かすからいけないのか? と思って、別スレッド作ってみたものの結果は同じ。

同じどころか、UIスレッド外からImageView操作しようとして怒られたので、Handlerを使ってpostしなきゃいけない始末。

まだInputSreamの準備が出来てないとか、そういうのだろうかー。

AsyncTaskに変えてみようかな・・・。
多分関係ないな・・・。

2010年12月9日木曜日

Bitmap.copyメソッドがPNGの色情報を欠落させる件

突然ですが、アプリをマーケットに登録しました。
Asimoc」という可逆モザイクアプリです。 厳密に言えばモザイクでは無いんですが、パスワードをかけてモザイクをかけれる。と、いうソフトで。

特段難しいことはしてないんですが、「そろそろリリースできそう!」って時に起きた現象が

「Xperiaだと、BitmapをARGB_8888のフォーマット指定してcopyしても、下位ビットの色情報を欠損してくれる」
です。

いや、いろいろ調べていったら、Xperiaが悪いんじゃなくて、AndroidOS2.1ぐらいからのバグ? だか、仕様?だか。

今までは、BMPにピクセル単位で読み込んだり、書き込んだりするために、一回コピーをして、編集可能BMPを取得していたわけです。

Bitmap tempBmp = bitmap.copy(Config.ARGB_8888, true);

 これで、OS1.6までは行けたんだけど、なんかOS2.1からこれだとダメで。

しょうがないので


Bitmap tempBmp = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(),Config.ARGB_8888);
Canvas canvas = new Canvas(tempBmp);
canvas.drawBitmap(bitmap,0,0,null);

と、言う感じに、先に空でBitmapを作って、そこに描画。 と。

なんでこんな事をしなきゃいけなくなったのか・・・。

ううむーん。

2010年12月2日木曜日

シークバーに最小値の概念が無い

今つくっているアプリが大分整ってきたので、設定画面もつくりはじめました。

そんな特殊なことは無いのでpreferenceActivityを継承したアクティビティ作って、xmlで記述したレイアウト(?)を読み込むだけ。

これだけで保存も読み込みも自動でされるんかー。 便利ですなぁ。


しかし、またもや罠。
eclipseでパーツ追加しようと思うとーーー。

CheckBoxPreference
EditTextPreference
ListPreference
RingtonePreference

むむむー。当然あるであろうと思っていた「シークバー」が無い。
するってーと、何かと自作しなきゃなのかー!
って、さらに調べてたら、なんかもう誰かさんがつくっていたっぽい。
http://android.hlidskialf.com/blog/code/android-seekbar-preference

まぁ、素敵。
ということで、使ってみたんだけども。

(ここでやっとタイトル)なんと、シークバーには最小値の概念が無い。びっくり。
maxがあるのにminがないとは!!これいかに!

と、いうわけで、結局、拾ってきたシークバーはそのままでは使えなかったので、ちょっとだけ変更しました。

具体的には、最小値もセット出来るようにして、最大値は max - min としてあげる。
4~32のシークバーが欲しければ、maxは32ではなくて、32-4の28として、シークバー内部の値としては0~28。
でも、画面の表示や、取得時の数値は+minをして4~32に戻す・・・と。

こんな小細工をみんなしてるもんなんでしょうかーー。


2010年11月29日月曜日

BitmapFactoryのdecodeStreamでoutofMemory

そろそろ画像修正ツールが出来上がってきたので、学生のエクスペリア(2.1)を使ってテストテストーー。

すると、ガツガツ落ちる。 いやいや。 落ちること落ちること。

原因はタイトル通りBitmapFactoryのdecodeStreamでoutofMemory
うぇー。 IS01だと起きないのになぁ。 あれか。カメラの解像度が高すぎるからか。

てことで、調べたら、エクスペリアの最大の解像度は8Mピクセル!! サイズにすると3264×2448 !!!?

なんとまぁ。 それをARGB_8888で取ったらー。 大体容量32M。そりゃ落ちるわ。

対応としては、BitmapFactory.Optionsを上手く指定すればいいらしい。
家帰ったらやってみよう。

2010年11月26日金曜日

IS01

僕が持っているAndroid端末はIS01だけなんだけども。

まぁ、OSがアップデートされないのはよしとしよう。 実機チェックとしてはVerは高いより低いほうがなにかと便利だしね。

ただ、プロセスの暴走だけはなんとかしてくれんかなー。

最近のアップデートから、uimdっちゅープロセスも気付くと暴走していることが多い。

熱中して開発してたらPCからのUSB充電じゃ間に合わなくて、電源落ちたりするし。 やめとくれ。

電子書籍が最近話題だけども

最近、近くの技術書の揃いがグンバツの本屋さんが潰れちゃって途方に暮れてたんだけど、電子書籍なんか流行ろうもんなら本屋はますます潰れるよね。

しかしながら、いまいち電子書籍のウリがわかんねぃ。

エコですか。 紙使わないからエコですか。 んじゃぁ、エコポイントとかくれればいいのに。
そもそも紙使わないから安く売れるとか?
広告がズバズバカットインするから無料とか?
まぁ、値段で勝負ですよね。普通に考えれば。

あと「検索」か。

夜中とかに
「あれ、この台詞なんだっけ!!? どの漫画の誰の台詞だっけ!!!?」
って眠れぬ夜にはいいかもしれない。(最近では『犬みたいに同じところでぐるぐる回ってろ』みたいな台詞がどこ出展だったか思い出せなくて苦労した。 BLACK LAGOONだった。あふん。)

しかし。
冒頭にもどるけど、本屋が生き残る道はあるんだろうか。

ちょっと考えるとーーー。
本屋のウリって、「実物感」であったり、店員さんの趣味丸出しの「品揃え」だったり、技術書や小説で言うと「立ち読み」だったりだと思う。
すると、それをそのまま電子書籍に当てはめると、本屋はwifiのフリースポットみたいな状態になって、本を揃える(試し読みをする権利を買う)。
ユーザーは本屋に行くと、電子書籍が試し読み出来る。
んで、そこで購入すると、本屋にマージンが入る。 ・・・・とか?

駄目だ。本屋で立ち読み(試し読み)して、家帰ってから購入されちゃったらアウトだ。
ってーいうか、それって正に今の「本屋で立ち読みして、良さそうだったらAmazonで注文」の状況だし。

難しいね。

個人的には本屋さんには頑張っていただきたい。

華氏451度みたいになってもらっても困る。

2010年11月25日木曜日

ProgressDialogを上手く使う

昨日、「あの保存とかするときにくるくる回ってる砂時計替わりみたいなの出したい」的な事を書いたんですが、ProgressDialogなんですね。 ふむん。

で。
これの使い道っていうと、重い処理を別スレッドで動かして、その間に表示。
処理が終了したら閉じる。 ってなる。

なので、重い処理の開始前にshow()して、処理が終わったらdismiss()を呼ぶ。

でも、その「重い処理」ってーのは、別スレッドなので、別スレッドからshow()すると、RuntimeExceptionだしてくれるんだよね。コイツ。(Toastでも同じ現象発生)

んで、Handlerを使えとかなんとか言われるわけなんだけど。
なんとかクラスでまとめられないかなーと思って、作ってみました。

package mosaic.dividebyzero.net;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Handler;

class MyProgressDialog implements Runnable {
 private String title, message;
 private static ProgressDialog progressDialog;
 private static Handler handle = new Handler();

 public static void init(Context context) {
  if (progressDialog == null) progressDialog = new ProgressDialog(context);
 }

 public static void createDialog(String title, String message) {
  if (progressDialog == null)
   throw new RuntimeException("MyProgressDialog is not initialize\nplease call init Method.");
  handle.post(new MyProgressDialog(title, message));
 }

 public static void close() {
  if (progressDialog != null) progressDialog.dismiss();
 }

 public MyProgressDialog(String title, String message) {
  this.title = title;
  this.message = message;
 }

 @Override
 public void run() {
  // TODO 自動生成されたメソッド・スタブ
  progressDialog.setTitle(title);
  progressDialog.setMessage(message);
  progressDialog.setIndeterminate(false);
  progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
  progressDialog.setCancelable(true);
  progressDialog.show();
 }
}

肝は、createDialogより前にinitを呼ぶということ。
さらに言えば、initはviewのスレッドから呼ぶ必要があるっぽいです(てか、無きゃinitとcreateに分ける必要が無い)

正直、ここらへんは行き当たりばったりというか、やってみたら出来た。というか。
理論に基づいた作りじゃないのでなにか間違ってるかもですが。とりあえず、今のところ問題無しです。

SDに保存した画像がすぐに認識しない件

SDカードに画像を保存した直後は、何故かギャラリー系のアプリから見ることが出来ないんですよね。
一回PCへマウントして、アンマウントしたり、OS自体を再起動したりすれば見れるんですけども。 なんでしょうか。これは。

でも、touch2pixelとかはちゃんと保存した後もすぐ見れるんですよね。これがまた。
なにか特別な保存方法があるんだろうかー。

って、調べてみたら。
ContentResolverってので登録しなきゃいけないんですね。 サムネイル絡みかと思ってたら、こんなのがあるとは・・・。

一応コード
// 保存先を取得
  String status = Environment.getExternalStorageState();
  File fout = null;
  if (!status.equals(Environment.MEDIA_MOUNTED)) {
   // SDカードがマウントされてないので、システムの使える場所に保存
   fout = getContext().getFilesDir();
  } else {
   // SDカードがマウントされているので、勝手にフォルダ作成
   fout = new File("/sdcard/ciasom/");
   fout.mkdirs();
  }
  File fname = new File(fout ,"/ciasom" + getDate() + ".png");
  FileOutputStream fos = new FileOutputStream(fname);
  bitmap.compress(CompressFormat.PNG, 100, fos);
  fos.flush();
  fos.close();
  //メディアに登録作業が必要らしい。
  ContentValues values = new ContentValues();
  ContentResolver contentResolver = getContext().getContentResolver();
  values.put(Images.Media.MIME_TYPE, "image/png");
  values.put(Images.Media.DATE_MODIFIED,System.currentTimeMillis()/1000);
  values.put(Images.Media.SIZE, fname.length());
  values.put(Images.Media.TITLE, fname.getName());
  values.put(Images.Media.DATA, fname.getPath());
  contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
これは今作っている可逆モザイクアプリのソースそのままぶっこぬいてきました。

これまたはまったのが、Images.Media.DATE_MODIFIED には更新時間を入れるんだけど、ちょっとぐぐると
「System.currentTimeMillis()」が平気で指定されてるところ。

でも、これで保存すると、2037年とかになっちゃうんですよな。

ここで指定するのは1970年1月1日からの~ってとこまでは一緒なんだけど、「秒数」を入れるらしい。
んで、System.currentTimeMillis()は1970年起点の「ミリ秒」なので、時間が狂う。と。

ちゅーわけで、/1000が要るわけです。 ふむん。

完成が近づいてきたぜー

2010年11月24日水曜日

カメラをintentで外部起動して、指定した場所に保存してもらう

最近はゲームはさておき、自分がほしいな。 っていうツールを作るのでいっぱいいっぱいです。 kszです。

今は可逆モザイクアプリを作ろうと思っているんだけども、当然元の画像はギャラリーから持ってきたり、カメラから取得しなきゃなので、調べつつつくっていたんですが、ハマりまくりました。

まず、カメラから戻ってきた(onActivityResultの引数の)Intent の data には縮小した画像が入ってしまうということ。
これはIntentに受け渡せるデータ量の上限が決まっているかららしい。 まずこれで1ハマリ。

ではでは、EXTRA_OUTPUT で、urlを指定して、指定した場所に保存をしてもらおう。と。
具体的には
putExtra(MediaStore.EXTRA_OUTPUT, [ここに保存してほしい場所をurlで]);
ふむ。

逆を言えば、何か外部からグローバルのintent呼び出しを自分のアプリがうけた場合は、このEXTRA_OUTPUTが入っていた場合はそこに保存するようにしなきゃって事ですよな。

それはさておき。
では、それでonActivityResultで受け取ろうーって思ったんだけど、
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 String url = data.getStringExtra(MediaStore.EXTRA_OUTPUT);
//↑で受け取ったurlから画像ファイル読み込み処理をここから下に
}

ってやったら、ヌルポが飛ぶ。 何が!? って調べると、そもそもIntent data のdataがnull。
EXTRA_OUTPUTを指定しちゃうと、戻りのIntentがnullになる・・・。 んですか・・・?

腑に落ちねぇ・・・。

2010年11月17日水曜日

eclipseショートカット覚書

Ctrl + Shitf + B
でブレークポイント設置/解除

うーん。 F9がみに染み付いていて困る。 VisualStudioショートカットに全部変えてしまいたい・・・。

2010年11月15日月曜日

マイフェイバリットな壁紙

人は皆「マイフェイバリット壁紙」を持っている。

しかし、得てして「マイフェイバリット壁紙」は人に見られるとまずい場合が多い。 例えば、エロすぎたり。例えば、その人のキャラに合わないぐらい可愛かったり。

なので、誰かに見られる可能性がある時はまともな壁紙に変更し、家に帰ってプライベートスペースが確保できたらマイフェイバリットな壁紙に変更する。

でも、この作業が面倒くさい。 なぜなら、マイフェイバリット壁紙がその他の壁紙達に紛れ込んでしまうから。

そして僕は考えた。 「壁紙を即座に切り替えるアプリケーションは無いだろうか」

さがすと、結構ある。 けど、多機能過ぎる。 常駐してほしいわけじゃないし、ランダムで画像変える~とかもいらんのだ。

ただ単純に「起動したら、今の壁紙をまともな壁紙に変更して即終了。 その逆も可」ってのが欲しいんだ。

僕が捜した感じでは無い。

無い。なら。作れ。

現在の壁紙をゲームの背景にする

ContextWrapper.getWallpaper();
でDrawableが取れるので、それをー・・・って感じなんだけど、最終的に描画するためにBitmapが欲しい(OenGLでテクスチャを作るためにもBitmapが欲しい)ので、
  // 現在の壁紙を取得
  Bitmap wallpaper = ((BitmapDrawable) getWallpaper()).getBitmap();
てな感じ。
ActivityはContextWrapperを継承だか実装だかしてるぽいので、殆どの場合が直呼び出し。

しかし、BitmapDrawableでキャストするって・・・。 低いVerだと使えないってことかな。

2010年11月11日木曜日

Eclipseでandroid開発していると度々フリーズする件

EclipseにはVisualStudioでいうところのInterisenceがProposal(プロポーザル)とかいう名前であって、便利で良いことなんだけども、候補をだした瞬間度々止まるので非常にイラつく。

そのせいで、最近ではプロポーザル機能自体を消したりしてたんだけども、やっぱり復活してほしい。

んで、なんでそこで止まっちゃうのか。

多分なんだけど、本当はプロポーザルで候補表示と同時に、ソースに書いてあるjavadocを表示してくれるようになっていて。 でも、androidSDKはclassが詰まったjarファイルしかないので、ソースを探しに行ってる時間がでフリーズするんじゃないか。

と、いうのも逆説的で。
試しにandroidSDKのソースを関連付けたら、ピタっ!!っとプロポーザル時のフリーズがなくなったのできっとそうなんじゃないかなー。

このソースの関連付けですが、これはこれで結構罠がありました。
jarをコンパイルしなおすとか、ビルドパス構成でjarファイルをさらに追加してソースフォルダを指定するとか。 これまたぐぐるとたくさんでてくるんですが、どれも僕は失敗しました。(conversion to dalvik format failed with error 1 とか言われたり)

で、結局たどり着いた方法が
「android.jar と同じ階層に sources という名のフォルダを作って、その中にソースファイルをコピー」
でした。

簡単だし、eclipseの設定をいじるわけではないので、新規プロジェクトつくるたびに設定とかしなくてもいい!!! これでウッドボール!!!

androidでBGM再生

効果音はできたので、今度はBGM
MediaPlayerが使えるのだけど、曲と曲のつなぎにフェードインフェードアウトとか、すでにその曲を再生中の時には再生しないなどの管理を毎回やるのは面倒なので、やっぱりマネージャーっぽいものを作成


package dividebyzero.net;

import android.media.MediaPlayer;

public class BgmManager {
    static BgmManager instance = new BgmManager();
    private MediaPlayer player;
    private int currentPlayBgm;
    private int nextPlayBgm;
    private int vol, targetVol;
    enum PLAY_STAT{
        STOP,FADEIN,PLAYING,FADEOUT;
    }
    private PLAY_STAT stat;

    private BgmManager() {
        stat = PLAY_STAT.STOP;
        currentPlayBgm = 0;
        nextPlayBgm = 0;
        vol = 100;
        targetVol = 100;
    }

    public static BgmManager get() {
        return instance;
    }

    public static void release() {
        instance.stop();
        instance = null;
    }

    public void play(int id, boolean isFade) {
        if (id == currentPlayBgm) { // 現在再生しているので何もしない
            return;
        } else {
            nextPlayBgm = id;
            stat = PLAY_STAT.FADEOUT;
        }
    }

    public void update() {
        switch (stat) {
            case STOP:
                stop(); 
                if(nextPlayBgm != 0){
                    currentPlayBgm = nextPlayBgm;
                    nextPlayBgm = 0;
                    stat = PLAY_STAT.FADEIN; 
                }
                break;
            case FADEIN:
                if (player == null) {
                    player = MediaPlayer.create(GLView.get().getContext(), currentPlayBgm);
                    player.setLooping(true);
                    player.start();
                    vol = 0;
                    player.setVolume(vol/100.0f, vol/100.0f);
                }
                vol += 2;
                if (targetVol < vol) {
                    vol = targetVol;
                    stat = PLAY_STAT.PLAYING;
                }
                player.setVolume(vol/100.0f, vol/100.0f);
                break;
            case PLAYING:
                break;
            case FADEOUT:
                if (player != null) {
                    vol -= 2;
                    if (vol < 0) {
                        vol = 0;
                        stat = PLAY_STAT.STOP;
                    }
                    player.setVolume(vol/100.0f, vol/100.0f);
                } else {
                    stat = PLAY_STAT.STOP;
                }
                break;
        }
    }

    public void stop() { // 現在の曲を止める
        if (player != null) {
            player.stop();
            player.release();
            player = null;
        }
    }

    public void setVolume(int vol) {
        this.vol = vol;
    }

    public int getVolume() {
        return vol;
    }
}

前の効果音の時もそうだったんだけど、リソースのデータを読みたい時って、Contextが必要になってくるので、それをいかにしてマネージャークラスに渡すかってのが悩みどころ。

僕はもうOpenGLが使えるGLViewクラスをシングルトンぽくしているので、そっからgetContext()なんだけどーー。 普通はどうやるんですかね。

2010年11月8日月曜日

androidで効果音マネージャ

やっぱりゲームには効果音が必要なので、調べると、SoundPoolってのが良いらしい。
ところで、ゲーム作ったことがある人には常識だけど、同じフレーム内で同じ音声を鳴らしたって意味が無い。むしろ読み込みやらなんやらで負荷が増大してるはず。

なので、効果音を鳴らしたい時に、直で鳴らすのではなく、「鳴らしたい予約」をしておいて、あとで予約されているものを全て鳴らすべき。

さらに言えば、60fpsで動いてるとして、1フレーム目に鳴らした音を2フレーム目に鳴らしてもやっぱり無駄。 なので、前回再生フレームを覚えておいて、ある程度の間隔があった場合だけ再生するようにするのがよさそう(今回はまだここまでやってないけど)

と、いうわけで、こんな感じでSoundManagerを作ってみた。

package dividebyzero.net;

import java.util.HashMap;

import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;

public class SoundManager {
    // 個々のサウンド
    public SoundManager(int sndId) {
        this.sndId = sndId;
        this.isPlay = false;
    }

    private int sndId;
    private boolean isPlay;
    private float leftVol, rightVol;

    public void play(float leftVol, float rightVol) {
        this.leftVol = leftVol;
        this.rightVol = rightVol;
        this.isPlay = true;
    }

    public void play(int vol) {
        play(vol, vol);
    }

    public void play() {
        play(100, 100);
    }

    // 管理スタティックゾーン
    private static HashMap<String, SoundManager> soundMap = new HashMap<String, SoundManager>();
    private static SoundPool sp;
    private static Context _context;

    public static SoundManager get(String name) {
        return soundMap.get(name);
    }

    public static void init(Context context) {
        _context = context;
    }

    // 予約されたものを全部再生
    public static void playAll() {
        for (SoundManager obj : soundMap.values()) {
            if (obj.isPlay) {
                sp.play(obj.sndId, obj.leftVol, obj.rightVol, 1,0, 1f);
                obj.isPlay = false;
            }
        }
    }

    public static SoundManager add(int id, String name) {
        SoundManager sm = soundMap.get(name);
        if (sm == null) {
            if (sp == null)
                sp = new SoundPool(10, AudioManager.STREAM_MUSIC, 100);
            sm = new SoundManager(sp.load(_context, id, 1));
            soundMap.put(name, sm);
        }
        return sm;
    }

    public static void release() {
        sp.release();
    }
}

使い方としては、効果音(出来ればogg)をres-rawフォルダに突っ込んで、


public void init(){
         SoundManager.add(R.raw.jump,"適当な名前");
}

public void update(){
        if(ボタンを押したら){

                SoundManager.get("適当な名前").play();
        }
//////////////////////////////////最後に
        SoundManager.playAll();
}

という感じ。
文字列がキーになっているのが、ちょっとオーバーヘッドありそうですが、気になる人はget()の戻り値がSoundManager型なので、確保しておいてplay()すりゃー良いと思います。

ちょっとSoundPoolのインスタンスの作る場所が気に食わないけど、初期化タイミングが難しいですよなぁ。
もうちょっと修正する予定。

これとは別に、BGMでループ再生とかにはMediaPlayerを使う必要がありそうです。
基本、ノートPCで作業しているので、作業場所が狭い。 ちょこちょことエディタウインドウの最大化を右上のアイコンクリックしてやってたんだけど、よくよく調べたらショートカットがあった。

ctrl + M でエディタ最大化⇔復元

ちょっとぐぐったら、Alt + '-' + x とかあったけど、こっちでよくない?
まぁ、バージョンの違いなのかもね。

2010年11月5日金曜日

傾きを取るために

今までは「方位センサー」で傾きを取っていたんだけども、もしかして世間様は「加速度センサー」で傾きを取ってらっしゃる? のか?

 確かにどっちでも取れるんだけど、「方位センサー」の方が戻ってくるのが360度の角度の範囲で帰ってくるから、楽とか思って方位なんだけどーーーーうーーん。

どっちがどういう利点があって、どういう欠点があるのかを言及したサイトが特に見つからない。
なんだろう。 

ともかく操作性がよくなる方を使おう。

2010年11月4日木曜日

UIを考える

ゲームに置いてユーザーインターフェースっていうのはかなりの割合を占めると思う。

んで、androidで問題になるのが、「キーボードが存在しない」ということ。

いや、しないんじゃなくて、「存在することが確定できない」という状態。多分日本で一番普及しているであろうエクスペリアはキーボード付いてないわけだし。

と、いうわけで、使える入力装置が非常に限られる。

確実にAndroid端末ならついてるのが 「タッチパネル」なんだけど。
ところが、マルチタッチは存在確定しない。 先に出てきたエクスペリアもマルチタッチ非対応。 IS01はなんかあるけど、OSレベルでは対応してないし。

すると、どうするんだ。 シューティングゲームすらまともに作れないじゃないか。
僕がちょっと見かけたものだと、弾はでっぱなしで、操作だけをタッチパネルのドラッグで行うというもの。

これは果たして「シューティング」といえるのか。 

んで、もう一個、ほぼ確実にあると言われているのが「加速度センサー」および「傾きセンサー」
(僕が知る限りはToshibaのdynabook AZに付いてない)

さっき例に出したシューティングだとして。
傾きでプレイヤーの上下左右移動を行い、タップでショット。

こ・・・これか・・・? これなのか・・・・?

とりあえず、作ってみよう・・・。

当たり判定高速化

ゲームと言えば当たり判定なので、ここを最大限スリムにしておくに越したことはないはず。

作りたいゲームにもよるけど、基本は矩形(四角と四角)で良いと思う。 ゲームによっては円と円にするけども。

まぁ、矩形で考えて、(x1,y1)-(x2,y2)の四角と(x3,y3)-(x4,y4)の四角で当たり判定するとして普通に作ると

boolean hitCheck(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){

    if(x4 < x1)return false;

    if(y4 < y1)return false;

    if(x3 > x2)return false;

    if(y3 > y2)return false;

    return true;

}

という感じ。
書いてて思ったけど、intを8個引数で渡すのってどうなんだ。 int(4Byte)*8=32Byte? うーん?
ゲームの座標で4Byteも必要ないからshortでいいのか。
もしくは

class Rect{
    public int x1,y1,x2,y2; 
}

ってクラスを作って、参照渡ししたほうがいいのか。

まぁ、それはさておき、高速化の基本はif文を除く事だと思うので、最適化をしまくっていくと

boolean hitCheck(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
    final boolean boolean_c[] = {false,true}; 
    return  boolean_c[((x1-x4) & (x3-x2) & (y1-y4) & (y3-y2)) >>> 31];
}

と、if文を削れるところまで行ける。
んじゃぁ、実際これが速いのかどうなのか。 実際にランダム座標で100,000回処理のタイムを測ってみると

 ~1回目~
最適化前:45,111ms
最適化後:45,246ms

~2回目~
最適化前:45,197ms
最適化後:45,944m


あるぇ~~~。
最適化したほうが遅い!!! うっはーー!!!

これは座標にもよるけど、全てのものが重なってるってことはほとんど無いはずなので、if文方式は途中であたってない事が確定してfalseが返って、あとの処理がスキップされることによる結果だと思う。

ええ、じゃぁ、if文方式でいいってことですか。

ちなみにint4つ渡すオーバーヘッドを減らすべく、クラスで参照渡し方式にしてみたら

参照渡し方式:44,800ms

あ、若干マシ・・・。
と、いうわけで、最終的に

     class HitRect{
        public int x1,y1,x2,y2;
    }

を使って、

   public static boolean hitCheck(HitRect a,HitRect b) {
        if (b.x2 < a.x1)return false;
        if (b.y2 < a.y1)return false;
        if (b.x1 > a.x2)return false;
        if (b.y1 > a.y2)return false;
        return true;
    }

が最速でFA???

まぁ、 100,000回実行なので、実際には目に分かるような差はないんだけども。

目指せ25ドル!

eclipseショートカット

これは使える! ってのを覚書
Ctrl + Shift + t
Ctrl + Alt + h
Alt + Shift + s
三つ押し系はちょっと簡単には覚えられないのでこういう所に書いとかなきゃねー。

2010年11月2日火曜日

Androidメモ

いろいろな基礎をすっ飛ばして、Eclipseで開発。OpenGLを使って画面をしてみた。

ただそれだけなのだけど、とてつもなく罠があったのでここに書いておく。

・Eclipseの日本語化プラグインを入れると、なぜかビューの追加が出来ないようになったり、キーバインドが消えたりする(ctrl+tabでソースの切り替えが出来ないと開発効率がだた下がる!)
・エミュレータの起動が遅すぎて、なんども再起動してしまった。 まさかあんなに(2分ぐらい?)待つとは。
・「エミュレータが1度起動した後は速い」とかよく見るけど、んなこたなぃ。そのために、IS01を買ってきてしまった(これは罠じゃないな)
・実機でもマニフェストにdebuggable="true"を入れると、FPSが2~3分の1になるイメージ。
・実機で1回停止させて、またすぐ実機の方でアプリを起動しても、まだPCがデバッグを掴んでいるような感じで遅いまま。 PCの方のデバッグのプロセス自体を落とす必要があるっぽい(じゃぁいつ勝手にプロセスは開放されるんだ・・・?)

まだまだあるけど、今日はこのへんで。

なんとか開発者登録の25ドルぐらいは回収したいなぁ。

開発者登録

まだなにも作ってないのに、Androidの開発者登録をさっきしてしまいました。

 背水の陣! ゲーム作るぜー!