2011年7月25日月曜日

とりあえずゲーム作り(3)

さて作りかけのゲームですが、いろいろ直すべきところはあります。ただ今回は、もう少しゲーム内容について改善していきます。

現時点では、静止した宇宙の中をAndroid君が移動するだけのゲームになっています。これではちょっと寂しいので、障害物のほうにも動きをつけてあげましょう。

今まで障害物は Rect クラスの配列で扱ってきましたが、拡張するためには専用クラスを作ってあげるのが良いでしょう。Rockクラスとして新しく定義します。とりあえずはMyViewの内部クラスにしときます。

public class Rock {
  Rect r;
  int w, h, dx, dy;
  public Rock(int sw, int sh) {
    w = 6 + rand.nextInt(7);
    h = 6 + rand.nextInt(7);
    int x = rand.nextInt(sw - w);
    int y = rand.nextInt(sh - h);
    r = new Rect(x, y, x + w, y + h);
    dx = rand.nextInt(5) - 2;
    dy = rand.nextInt(5) - 2;
  }
}

Rockオブジェクトは、従来のRect の他に幅(w)、高さ(h)、x方向の速さ(dx)、y方向の速さ(dy)を属性として持ちます。dx, dy が目新しいですね。

Rockのコンストラクタは、addRock関数にあった初期化部分をもってきています。移動速度dx,dyは -2~2 の範囲でランダムに設定されるようにしてみました。また小さな障害物が動くのはつらいので、縦横の最小サイズをそれぞれ+2しています。

ついでに障害物に関する処理をRockクラスに集めてしまいましょう。以下のようなメソッドを追加します。

void move(Canvas canvas){
  r.left = MyGame.check(r.left + dx, r.width(), canvas.getWidth());
  r.top = MyGame.check(r.top + dy, r.height(), canvas.getHeight());
  r.right = r.left + w;
  r.bottom = r.top + h;
}
Boolean collision(Rect d) {
  return d.contains(r);
}
void draw(Canvas canvas) {
  Paint paint = new Paint();
  paint.setColor(Color.RED);
  canvas.drawRect(r, paint);
}

move()は移動、collisionはAndroid君などとの衝突判定、draw()は画面表示の部分をもってきました。内部クラスということで、無駄はありますが気にしないでください。

check()は以下のような静的メソッドで、座標を補正して上下左右を繋げる役割です。将来的にはUtilだとかToolだとかいうクラスにまとめちゃう便利関数ですね。

static int check(int x, int w, int sw) {
  return x > sw ? -w : x < -w ? sw : x;
}

さて、全体をRockクラスを利用するように変更してみましょう。まず、addRock()メソッドは以下のように簡単になります。

void addRock(int sw, int sh){
  Rect safe = new Rect(d_r);
  safe.offset(d_dx * 4, d_dy * 4);
  Rock rock;
  do {
    rock = new Rock(sw, sh);
  } while(rock.collision(d_r) || rock.collision(safe));
  rocks.add(rock);
}

他にも、こまごまと変更がありますね。

List<Rect> rocks = new ArrayList<Rect>();

List<Rock> rocks = new ArrayList<Rock>();

for (Rect rock : rocks)
  canvas.drawRect(rock, paint);
for (Rock rock : rocks)
  rock.move(canvas);
  rock.draw(canvas);
}

for (Rect rock : rocks)
  if (d_r.contains(rock))
    gameover();
for (Rock rock : rocks)
  if (rock.collision(d_r))
    gameover();

さてこれで従来と同様になったはずです。それに加えて、さりげなく障害物を動かすmove()も加えてあります。

さて、実行してみましょう。障害物も動いていることで、なんか一気にゲームっぽい感じになってきました。


ま、例によって画面キャプチャだと判り辛いですけどね…

もう一点、修正しておきましょう。ゲームオーバーの画面から「戻る」操作をすると、ゲームが初期化されていないので妙なことになるのが、気になります。

まずはMyViewのコンストラクタなどにある初期化部分を抜き出して、init()メソッドに集めます。

void init() {
  Display disp = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  d_x = (disp.getWidth() - droid.getWidth()) / 2;
  d_y = (disp.getHeight() - droid.getHeight()) / 2 - 20;
  d_dx = d_dy = 0;
  rocks.clear();
  score = 0;
  startTime = System.currentTimeMillis();
}

そしてこのinit()を、gameover()メソッドの最後でも呼び出します。startActivity(intent);でゲームオーバー画面を表示した後、戻ってきたタイミングで初期化するわけですね。

ちょっと遊んでみたところ、ちと簡単すぎる気が。最初から障害物が1個表示されるようにして、かつ5秒に1個づつ増えるように変更しておきます。

if (rocks.size() < (now - startTime) / 30000)

if (rocks.size() <= (now - startTime) / 5000)

さーて、これでひと段落。今日はここまでにして、次回はパッケージなどまともに整理しましょうかねー

現時点のソースは こんな感じ になっています。

2011年7月24日日曜日

とりあえずゲーム作り(2)

涼しくて過ごしやすい日曜日、いいですね。今日も頑張ろう!

さて、Android君が動くようになったMyGame、今回はゲームっぽくしていきましょう。得点の概念が必要でしょうし、障害を配置してゲームオーバーの危険性があるといいですね。

最初はシンプルに、動かない障害物を表示してみましょう。宇宙空間に浮かんでいる岩やデブリのイメージです。Android君が衝突したらそれでゲームオーバー。時間とともに増えていく仕組みも欲しいですね。

とりあえず今回の準備として、MyViewクラスに以下のメンバを追加しておきます。

Rect d_r = new Rect();
List<Rect> rocks = new ArrayList<Rect>();
long startTime = System.currentTimeMillis();
long score;
Random rand = new Random();

衝突判定に必要なのは、Android君の実体(当たり判定)の定義です。表示している画像の大きさをそのまま使ってもいいのですが、通常はそれより小さめに設定します。

以下のコードをonDraw()のdrawBitmap()直後に追加します。描画するときに、その当たり判定も設定してあげる、という感じです。単に画像サイズより4ドットづつ小さい領域にしているのは、いつもどおりの手抜きでありんす。

d_r.set(d_x+4, d_y+4, d_x+droid.getWidth()-8, d_y+droid.getHeight()-8);

これを使って、障害物との当たり判定のロジックも作成しましょう。処理は単純で、Android君の実体(d_r)が、障害物のどれかと接触したら、つまり領域が重なったらゲームオーバーです。

onDraw()のunlockCanvasAndPost()の後ぐらいに以下のコードを追加します。

for (Rect rock : rocks)
  if (rock.contains(d_r))
    gameover();

あ、ゲームオーバー処理がまだなかった。Activityクラスに仮のものを実装しておきましょう。現時点では、単に元メニューにもどるだけ。そのうちゲームオーバー用のActivity作成します。

public void gameover() {
  Intent intent = new Intent(MyGame.this, Main.class);
  startActivity(intent);
}

さあ実行してみましょう… あれ?前と一緒だ。てか、障害物を追加する部分がまだできていない、ですね。ははは。


さて、障害物を追加しますかー

まずは障害物を表示するロジックを加えます。これはonDraw()のdrawBitmap()の前がいいと思います。後だとAndroid君より手前に表示されてしまいますので。

またもや手抜きで、赤い四角にしておきます。以下のような感じ。

Paint paint = new Paint();
paint.setColor(Color.RED);
for (Rect rock : rocks)
  canvas.drawRect(rock, paint);

で、とりあえず、30秒ごとに障害物が追加されていくというルールにしてみましょう。onDraw()のunlockCanvasAndPost()の直前ぐらいに、以下のロジックを追加してみます。

long now = System.currentTimeMillis();
if (rocks.size() < (now - startTime) / 30000)
  addRock(canvas.getWidth(), canvas.getHeight());

MyViewのメソッドaddRock()が未定義ですね。コードは以下です。

void addRock(int sw, int sh){
  int w = 4 + rand.nextInt(7);
  int h = 4 + rand.nextInt(7);
  int x = rand.nextInt(sw - w);
  int y = rand.nextInt(sh - h);
  Rect rock = new Rect(x, y, x + w, y + h);
  rocks.add(rock);
}

このメソッドを使って、画面内のランダムな位置に幅4~10ドット、高さ4~10ドットの障害物(赤い四角)を追加します。

さて、これで障害物(赤い四角)を避けながら宇宙を漂えるようになりました。ちょっとゲームっぽくなりましたよねw


何回かゲームとしてプレイしてみると、不条理な状況があることに気がつきます。いきなり Android君の内部に障害物が発生して、そのままゲームオーバーになることがあるのです。

障害物を発生させるaddRock()を修正しましょう。

void addRock(int sw, int sh){
  Rect safe = new Rect(d_r);
  safe.offset(d_dx * 4, d_dy * 4);
  Rect rock;
  do {
    int w = 4 + rand.nextInt(7);
    int h = 4 + rand.nextInt(7);
    int x = rand.nextInt(sw - w);
    int y = rand.nextInt(sh - h);
    rock = new Rect(x, y, x + w, y + h);
  } while(d_r.contains(rock) || safe.contains(rock));
  rocks.add(rock);
}

追加したコードは、障害物を生成した位置がAndroid君の領域内であれば、生成しなおすというベタなロジックになっています。

Ract safeは4フレーム(0.4秒)後にAndroid君が占める領域です。これもあわせてチェックすることにより、移動するすぐ先に障害物が発生することを防いでいます。

ただこのロジックだと、上下左右の画面の繋がりまでは考慮していません。端のほうは危険だよ、というゲーム性なのです、という言い訳で誤魔化しておきましょう。

さてこれで、一応はプレイできるものになりました。まだ面白くはないですが… よりゲームっぽくするために、次は得点に関する機能を実装してみましょう。

基本的には、得点は時間経過で増えていくとします。長くプレイすることで、高得点が狙えるわけですね。ただし動いていないときは点が増えないことにしましょう。そうでないと、最初の位置で動かないことが攻略法になってしまいますからね。

加えて、動作速度が速いほうが点が上がりやすい、としたほうが面白いでしょう。より危険なプレイが評価される、ゲームデザインの王道でありますな。

ではonDraw()の後に以下のようなコードを追加してみましょう。

score += Math.abs(d_dx) + Math.abs(d_dy);
paint.setColor(Color.WHITE);
paint.setTextSize(24);
String s = "0000000000"+score;
canvas.drawText(s.substring(s.length() - 10), 0, paint.getTextSize(), paint);

得点は単純に移動距離にしてみました。高速移動のときのボーナスが大きすぎる気もしますが、後でゲームバランスを調整するときに見直しましょう。スコアも単純に、左上に表示するだけにしています。

ついでに現在の速度も表示しておきましょう。先ほどのコードの後に以下を追加します。

paint.setColor(Color.GRAY);
s = "x:" + d_dx + " y:" + d_dy;
canvas.drawText(s, 0, paint.getTextSize() * 2, paint);

さて実行してみましょう。よりゲームっぽくなった気がしますね。


さてプレイしてみると、ゲームオーバーした時に得点がわからないですね。そろそろ専用画面を追加してみましょう。

まずはgameover()メソッドを以下のように書き換えます。

public void gameover() {
  Intent intent = new Intent(MyGame.this, MyGameOver.class);
  intent.putExtra("SCORE", score);
  startActivity(intent);
}

で、MyGameOverクラスを新規作成しましょう。とりあえずは得点を表示するだけのシンプルなAvtivityです。

public class MyGameOver extends Activity {
  protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setTitle("My Game - Game Over");
    TextView view = new TextView(this);
    Bundle extras = getIntent().getExtras();
    Long score = extras.getLong("SCORE");
    view.setText("SCORE: " + score);
    setContentView(view);
  }
}

表示はこんな感じ。


これで本当に最低限ですが、ゲームの体裁が整いました。ただしやっつけの部分が多いので、次からは少しずつまともにしていきましょう。

現時点のソースコードは こんな感じ

2011年7月23日土曜日

とりあえずゲーム作り(1)

Android開発の勉強を始めて一週間、そろそろ自分の好きなアプリを作る時期でしょう。僕だとやはりゲーム製作になりそうです。

無謀?たしかに。でも雰囲気はつかめましたし、こういったものは試行錯誤が大事なのかな、と。趣味でやっているだけなので、時間はありますし。駄目だったらいったん止め、勉強し直せばいいだけの話です。それに無理すると、自分の弱点も見えてきます。

ネタは幾つかありますが、とりあえずは簡単なアクションゲームを作ってみます。わりと前から、タッチ操作ならではの操作のゲームネタを考えていたので。

というわけで、まずは表示部分を作成。SurfaceViewを拡張して、タッチした場所にAndroid君が表示されるようにします。

class MyView extends SurfaceView {
 Bitmap droid;
 int d_x, d_y;
 public MyView(Context context) {
  super(context);
  droid = BitmapFactory.decodeResource(context.getResources(),
  R.drawable.droid);
 }
 void doDraw() {
  Canvas canvas = getHolder().lockCanvas();
  if (canvas != null) {
   canvas.drawColor(Color.BLACK);
   canvas.drawBitmap(droid, d_x, d_y, null);
   getHolder().unlockCanvasAndPost(canvas);
  }
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  d_x = (int) event.getX();
  d_y = (int) event.getY();
  doDraw();
  return true;
 }
}

onTouchEvent()でタッチされた位置を覚えておき、onDraw()ではその位置にAndroid君の画像をdrawBitmap()しているだけ、です。まだゲームには遠いですねぇ。

で、それを呼び出すメインのクラスをActivityのサブクラスとして作成。変数の共用のため、MyViewクラスの定義はこのActivityクラスの中に移動してください。

public class MyGame extends Activity {
 MyView view;
 @Override
 protected void onCreate(final Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setTitle("My Game");
  view = new MyView(this);
  setContentView(view);
 }
}

実行して、タッチした場所にAndroid君が移動するのを確認します。


とりあえず問題ナシ。

さて、次は動きをつけてみましょう。SurfaceViewにRunnableを実装して次のコードを追加します。

int d_dx = 1, d_dy = 1;
public void run() {
 while (true) {
  d_x += d_dx;
  d_y += d_dy;
  doDraw();
  try {
   Thread.sleep(100);
  } catch (InterruptedException e) {}
 }
}

このrun()の中身は100m秒ごと、つまり1秒に10回呼び出されます。呼び出されるたびにRobot君の座標を右にd_dxドットぶん、下にd_dyドットぶん移動させています。d_dx, d_dy は1ですから、結果として1秒間で右に10ドット、下に10ドットずつRobot君が移動するようになります。

run()でdoDraw()を実行するようになったので、onTouchEventのほうは削除してしまいます。そして以下のようなスレッドの初期化処理をMyViewのコントラクタに追加してあげます。

 mainThread = new Thread(this);
 mainThread.start();

mainThreadメンバの定義がまだでしたね。停止処理も含めてActivityクラスに以下のコードを追加します。

 Thread mainThread;
 @Override
 protected void onDestroy() {
  super.onDestroy();
  if (mainThread != null && mainThread.isAlive()) {
   mainThread.interrupt();
  }
 }

アプリを実行してみましょう。Android君が左上に現れ、右下に少しづつ動き続けるアプリになります。ほおっておくと画面から出て消えてしまいますね。どこかタッチすると、その位置に出現します。


どうも画面キャプチャだと動きが伝わらない気がしますが。そこはなんとか、感じ取ってくださいw

さて、ここまでは順調ですね。そろそろ今回のゲームのキモとなる部分を実装しましょう。

今回のゲームですが、Android君が無重力状態で漂うような感じで動きます。で、タッチ操作でAndroid君の動きに干渉できることにしましょう。

タッチした瞬間、Android君からタッチした地点までベクトル(矢印)が発生したと想定して、そのベクトルがAndroid君に影響を与える、としてみます。遠くをタッチするほど大きなベクトルが生成され、Android君に大きな影響がでます。

うーん固い、別な説明の仕方も考えましょう。Android君の下をタッチすると、Android君が引っ張られて下に動き出します。Android君のすぐ下をタッチした場合はゆっくり動き出します。離れたところをタッチした場合には、そのぶん強く引っ張られますので、はやく動き出します。ってな感じでしょうか。

プログラム的には簡単です。すぐ下をタッチした場合にはd_dyに1を加え、少し離れた下をタッチした場合には2を加え、だいぶ離れた下をタッチした場合には3を加えるという感じでコントロールします。

Android君の位置とタッチ位置の差をみて、距離に応じて動作速度を加減してあげればうまくいくはずです。運動方程式やらベクトル演算やら使えばよりリアルになると思いますが、現時点ではそれっぽい擬似的な簡易計算で実装しておきます。

さて、コードを修正していきましょう。

まず最初にAndroid君が静止しているよう d_dx, d_dy の初期値を0に変更します。また最初に画面中央に居て欲しいので、x, y の初期値を計算で求めます。以下のロジックをMyViewコントラクタに追加しましょう。droidビットマップを読み込んだ直後ぐらいが良いとおもいます。

Display disp = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
d_x = (disp.getWidth() - droid.getWidth()) / 2;
d_y = (disp.getHeight() - droid.getHeight()) / 2 - 20;

これでアプリ起動直後、Android君は画面中央に静止した状態になりますね。


え?d_yの計算が怪しい?鋭いですね… はい、手抜きしてます。

dispで得られるのは画面サイズであって、Viewの表示領域のサイズではない。上にあるシステムの表示領域とかアプリケーション名の欄まで含まれています。それを調整するため20を引いている訳ですが、まあこれ、環境に依存した手抜き処理ってやつです。後でなんとかしましょう。

さて、今度はタッチ処理を追加します。onTouchEventを以下のように書き換えてしまいます。

public boolean onTouchEvent(MotionEvent event) {
  int x = d_x + droid.getWidth() / 2;
  int y = d_y + droid.getHeight() / 2;
  d_dx += (event.getX() - x) / 50;
  d_dy += (event.getY() - y) / 50;
  return true;
}

最初に計算している x, y はAndroid君の中心です。そしてタッチされた位置と中心を比べ、50ドットごとに速度 d_dx, d_dy を増減しています。

ついでにAndroid君が画面から出てしまった場合の処理を考えましょう。

とりあえず今回は、上下左右が繋がった画面ということで、左右と上下をループさせましょう。doDraw()の最初に以下のロジックを追加します。

 if (d_x > canvas.getWidth())
   d_x = - droid.getWidth();
 else if (d_x < - droid.getWidth())
   d_x = canvas.getWidth();
 if (d_y > canvas.getHeight())
   d_y = - droid.getHeight();
 else if (d_y < - droid.getHeight())
   d_y = canvas.getHeight();

さて実行してみましょう。慣性をもって動くAndroid君をタッチで操作するのは、なかなか楽しかったりします。


え?画面キャプチャだと動きがわからない?一つ前の画面と一緒に見える? 動画をご用意できれば良かったんですが… すみませんが、ここも想像力で補ってください…

このエントリも長くなってきたので、とりあえず動いたということで、いったん終了して(2)に続きます。たぶん。

この時点でのソースファイルは コチラ

現時点のMyGameは SurfaceViewのデモ と同じパッケージに配置してしまっています。面倒だったので。ゲームとして成立しそうならば、ちゃんとしたパッケージを考えますw

ListView を理解する

いろいろ便利そうなViewといえば android.widget.ListView かな?と仕組みを調べてみました。

まず参考になるのが リストビューをカスタマイズする のページ。ArrayAdapterを拡張するあたりで、ListViewの仕組みがおおまかに把握できます。とりあえずこれ理解するだけで、ListViewは使えちゃう感じ。

で、上記のページでconvertViewの使いまわしが気になった場合、更に詳細な説明がある Y.A.M.の雑記帳サイト の - Virtualization and adapters - ページが非常に素晴らしい!

ここで更に注目なのがFastのケース。findViewById()だと遅いので、Holder用意してあげると速くなりますぜ、というコツが説明されています。Holder用にオブジェクトをわざわざ生成するのは重くなりそうに感じますが、ListViewの場合にはViewの使いまわしが非常に多いので効果が出やすい、ということだと思います。

Google I/O の「The world of ListView」 からのまとめだそうですが、残りのネタも非常に参考になります。感謝。

余談ですが LayoutInflater の inflate() でlayoutリソースからViewを直接生成することができるんですね。これもなかなか興味深いです。

というわけで、試しに作った適当なListViewデモです。ただ、ネタを判ってもらえる年代が非常に限られるような…orz


よくある画像+テキストなListViewのサンプルに対し、テキストを2つに増やしました、それらはLinearLayoutで縦に並べてみました、という安直な拡張しただけデス。


名前を表示するTextViewのほうには、以下のようにベタに属性を指定しています。

android:textSize="24sp"
android:background="#cccccc"
android:textColor="#000000"

セレクターについては [Android]ListViewについて ページが詳しいです。また気がつきにくい落とし穴として Android selector の item タグの color の注意点 ページの情報は助かりました。

というわけで、試してみましょう。既存の 9-patch png をもとに、色変換で怪しげな緑のタイルを作成してみました。

list_selector_background_green.9.png

で、drawable/list_selector_background.xml ファイルを新規作成し、以下の内容を記載します。ウィザードでちゃんと指定してあげれば、<item>タグ以外は自動生成されているハズ。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true" android:drawable="@drawable/list_selector_background_green" />
</selector>

え、短すぎるって?いいんです、変えたい部分だけ指定してあげれば十分ですよ。後はレイアウト設定で、対象のListViewに以下の属性を追加してやります。

android:listSelector="@drawable/list_selector_background"

これで完了。実行してみると、選択したアイテムの背景が怪しい緑色にかわっています。


ついでなので、文字色も変えてみましょう。

まずはセレクターの設定です。先ほどと同様にdrawable/list_selector_color.xml ファイルを新規作成し、以下の<item>要素を追加します。

    <item android:state_selected="true" android:color="#FF5050" />
    <item android:state_selected="false" android:color="#FFFFFF" />

今回は state_selected="false" が必要です。いったんセレクターを指定した以上、指定しないと勝手に黒に設定されてしまう?ようです。で、対象のTextViewのレイアウト設定に

android:textColor="@drawable/list_selector_color"

の属性を追加するだけで、選択したリストアイテムの説明文が赤に変わりました。


レイアウト設定、これだけでひとつのプログラミング環境みたいなものかもしれません。xmlでビューの構造を定義して、色など細かいものは属性で指定する。そしてその属性はセレクターを使って、状況によって値を変化させることができる。

いやぁ、さすがは今時の開発環境。面白いですね~

2011年7月22日金曜日

SurfaceViewを試す (SurfaceView Example)

@ITの連載記事 SurfaceViewならAndroidで高速描画ゲームが作れる のサンプルを試しながらSurfaceViewに関して学んでみたり。

途中で気がついたんですが、連載のサンプルはAndroid2.0(API level5)ベースみたいですね。Android1.6(API level4)で試してたので、そのままでは動かないところがありました。

android:theme="@android:style/Theme.Wallpaper"

ってListViewの背景をホーム画面の背景にあわせる設定が利かないとか。仕方ないので適当な画像を用意して設定しました。

listView.setBackgroundResource(R.drawable.rgm79c);


また1.6で抽象クラスだったのが2.0で実装されたのか、@Overrideが軒並みエラーになっていたので削除してみたり。設定の違いかもしれませんが。

この状態でViewを透過させる Transparent SurfaceView などのサンプルは問題なく動作しました。なかなか面白いですね。


また Offscreen はゲームなどに必須のダブルバッファリングに関するサンプルで、非常に参考になりました。ただこのサンプル、

Canvas canvas = holder.lockCanvas();
/* ここに osb の更新処理 */
canvas.drawBitmap(osb, 0, 0, null);
holder.unlockCanvasAndPost(canvas);

ってなっていて、lockCanvas()が名前どおりのロックする関数だったら、ダブルバッファリングの意味が薄い気がw

/* ここに osb の更新処理 */
Canvas canvas = holder.lockCanvas();
canvas.drawBitmap(osb, 0, 0, null);
holder.unlockCanvasAndPost(canvas);

と修正しておきました。これが正しい… ですよね?たぶん…

もっとも解説には「Androidではフレームワークが内部でダブルバッファリングを行ってくれるため、オフスクリーンバッファを持たなくてもよくなりました」とありますので、このへんは気にしなくて良い部分かもしれません。

実際、ちょっと描画を重くしてフレームレートを表示させてみましたが、Offscreen/NoOffscreen サンプル共にあまり違いが感じられませんでした。


解説にもありますが、Offscreen手法はあくまで他の環境用のアプリとロジックなどを共用する場合にのみ使えば良いのかもしれませんね。

で、最後は入力系のイベント処理です。まず、タッチ入力の処理が笑っちゃうほどシンプルなので転載します。

public boolean onTouchEvent(MotionEvent event) {
  left = event.getX();
  top = event.getY();
  doDraw();
  return true;
}
void doDraw() {
  Canvas canvas = getHolder().lockCanvas();
  canvas.drawBitmap(bitmap, left, top, null);
  getHolder().unlockCanvasAndPost(canvas);
}

セットアップなど必要ですが、基本これだけで↓が動くのはなかなかです。


そして定番の、カーソルでオブジェクトを動かす GameSample ですね。自身でRunnableを実装して

public void run() {
  while (true) { doDraw(); }
}

し続けてonKeyイベントの結果を反映させ続けるという、漢気あふれる作りに目を奪われますが、カーソルでロボット君を自在に動かすのは楽しいものです。


とりあえずSurfaceViewに関して、最低限は使えるようになった気がします。

2011年7月21日木曜日

初心者的 FAQ

記事にするほどでもない小ネタの集まり。

Log.d()したメッセージがコンソールに出ない
LogCatって専用ビューがあるのでEclipseメニューから追加

AVDエミュレーターに位置情報を設定する方法
Emulator Controlって専用ビューがあるのでメニューから追加

実行時に*.out.xmlファイルが自動作成されてエラーになる
xmlファイル以外を選択して実行する

コードに問題なさそうなのにActivityNotFoundException
AndroidManifestのApplication NodesにActivity登録があるか確認

ItemizedOverlayをMapViewに追加するとNullPointerException
コンストラクタ内でpopulate();を呼んでおく

ItemizedOverlayでアイコンが表示されない
DrawableにsetBounds()を忘れていないか確認

onKeyイベントが取れない
setFocusable(true);requestFocus();してフォーカスを得る

2011年7月18日月曜日

Google Map を表示 (MapsDemo)

最初はSDKにあったMapsDemoサンプルで遊んでみました。地図/位置情報/GPSを使うAndroidアプリを作るには など参考に、MapView Activity に MyLocationOverlayを追加して門前仲町駅のあたりを表示しています。


AVDエミュレーターに位置を指定する方法がわからず苦労しましたが、Eclipseの「ウィンドウ(W)→Viewの表示(V)」から"Emulator Control"ビューを表示させ、その中のLocation Controlsを使って指定できるようです。


Location値を得るにはGoogle Mapで目的の場所を表示した後、以下のBookmarkletを実行すると得られます。

javascript:void(prompt('',gApplication.getMap().getCenter()));

その後、setBuiltInZoomControls(true) で拡大縮小メニューを追加してみたり、お気に入りのお店(焼肉ドラゴン、魚三酒場)の位置を ItemizedOverlay でアイコン表示してみました。


Android2.1+Google APIs の時点では、ItemizedOverlay にはちょいと癖がありますね。抽象クラスなので継承して createItem() size() をoverrideしないといけないんですが、これが謎。

内部的に扱うのは List<OverlayItem> なので、とりあえずはソレ実装しておいてくれれば親切じゃない?とか思います。過保護すぎますかね?

なお事前にデータを用意している場合、継承したクラスのコンストラクタ内などでpopulate(); をコールしないと、初期化が不十分なのかエラーになります。

アイテムごとにアイコンを変えるのは、変更したい OverlayItem に setMarker() でアイコンを指定してあげればOK。これは簡単でした。


ただ「アイコンの横に店の名前を表示する」ことが難しい。OverlayItem のコンストラクタでTitleを指定しているのに、これを実際に表示する設定がわからない。

ググってみたら drawを書き換える力技のページ がヒットするところをみると、現時点では標準的な手段が提供されていない感じですかね?困ったものです。

ただアイコン+表示テキストを動的に生成して、それを個々にアイコンとして指定することで擬似的に実現できそうですね。面倒そうなので、実際に必要になったらチャレンジするとしますw

また ItemizedOverlay に OnTap() という関数をみつけたので、Override してみました。マーカーをタップすることで、その店の詳細情報を表示するダイアログを表示します。なんか、アプリっぽい感じになりましたね~


さて、ちょっと話を変えます。

ソースを眺めてますと、最初のメニュー画面(MapsDemo)もなかなか面白い作りになっていることに気がつきます。


このリストは以下のようなロジックで動的に検索されるよう組まれています。

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_SAMPLE_CODE);

PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);

アクションがMAINで、カテゴリがSAMPLE_CODEを受け入れるActivityをパッケージ内から動的に検索して追加しています。条件に合う設定のActivityを追加するだけで、既存コードは変更無しでリストに追加できますね。

Android の Intent の仕組みをうまく利用しているサンプルと言えましょう。

同じくMapsDemo Activityなんですが、以下のような事前定義された値を使っていますね。ListView用の最もシンプルな設定(テキストの一覧)の模様。

android.R.layout.simple_list_item_1
android.R.id.text1

APIサイト に詳細が無くて弱っていたのですが、visible trueさんのサイトソースが転載 されていて理解できました。また他の定義値に関して public static void mainサイト の simple_list_item_2とtwo_line_list_itemの表示の違い エントリが参考になりました。

というわけで適当なコメントを追加して simple_list_item_2 に変更してみた結果がこちらw


Android開発に関する情報(初歩編)

基本的な理解のためざっと読んだサイトのリスト。


慣れてきたら以下の情報が役立ちます。


随時追加していきます。先人たちに感謝。

Blog はじめました

Android アプリ開発の勉強を始めたので、ログ用にBlogを始めてみました。

とりあえず実機は Tablet PC Ainol Novo8 を入手しました。8インチ1280x768スクリーンのAndroid2.2な中華Padです。