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)

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

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

0 件のコメント:

コメントを投稿