トップ » 技術記事 » Android で再開する Java プログラミング(3) - モグラ叩き

Android で再開する Java プログラミング(3) - モグラ叩き

タグ: Android Java

Google の携帯端末プラットフォーム Android をきっかけに、Java プログラミングを再開しようという連載です。Java の文法を一通り知っている人を対象にして、Android 上で動く Java アプリを作っていきます。Android のSDKには、出来の良いエミュレータが用意されていますので、日本国内で Android 端末が発売されていない現状でも、作ったアプリをエミューレータ上で動かして楽しむことができます。来るべき、Android に備えて Java の腕を磨いておきましょう。

連載で作るもの(予定)
  • モグラ叩き(今回作成)
  • 迷路ゲーム
  • チャット
  • SQLiteを利用したメモ帳
対象とする読者
  • Android で自作ツールを作ってみたい人
  • Java で何か作ってみたい人
  • Java の文法を一通り知っている人
今回作るもの

前回まで、Android でプロジェクトを作成したり、グラフィックで絵を描くところまで紹介しました。今回は、実際に遊べるゲームを作ってみようと思います。一番簡単なゲームということで、モグラ叩きを作ってみます。

  • モグラ叩き

画面へのタッチ

さて、はじめにモグラ叩きを作る上でかかせない、画面へタッチするイベントを扱う方法を紹介します。(とは言っても、イベントを処理するメソッドを1つ増やすだけなのです。)ここで作るのは、画面をタッチすると、そこに円を描画するというものです。

画面を触ると丸が描画されます
画面を触ると丸が描画されます

※ちなみに、今回から、プロジェクトをゼロから作るところまでは解説しません。プロジェクトの始め方は、前回、前々回のものを参考にしてください。

TestTouchEvent など適当な名前でプロジェクトを作成したら、メインファイルである TestTouchEvent.java を開きます。そして、これを以下のように書き換えて実行します。

package com.example.Android.TestTouch;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

/** メインクラスの定義 */
public class TestTouch extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /** 描画クラスを設定 */
        TestTouchView view = new TestTouchView(getApplication());
        setContentView(view);
    }
}
/** 描画クラスの定義 */
class TestTouchView extends View {
    private ArrayList<Point> points;
    public TestTouchView(Context c) {
        super(c);
        setFocusable(true);
        points = new ArrayList<Point>();
    }
    /** タッチイベントを処理 */
    public boolean onTouchEvent(MotionEvent event) {
        Point p = new Point();
        p.x = (int)event.getX();
        p.y = (int)event.getY();
        points.add(p);
        this.invalidate();
        return true;
    }
    /** 記録されたポイントを描画する */
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL);
        for (int i = 0; i < points.size(); i++) {
            Point p = points.get(i);
            canvas.drawCircle(p.x, p.y, 20, paint);
        }
    }
}

このプログラムの仕組みは、画面にタッチした座標をArrayListに保存しておいて、描画のタイミングで記憶しておいた座標に円を連続で描くという簡単なものとなっています。

エミュレータ上では、画面をマウスでクリックするとタッチイベントが発生するようになっています。

画面をタッチしたことを検出するには、View クラス(を派生させて作った TestTouchView)に、onTouchEvent() というメソッドを追加するだけです。引数として得られる MotionEvent のインスタンスからタッチされた座標を取得することができます。

    /** タッチされた時のイベントを処理するメソッド */
    public boolean onTouchEvent(MotionEvent event) {
        Point p = new Point();
        p.x = (int)event.getX();
        p.y = (int)event.getY();
        points.add(p);
        return true;
    }

定期的な描画

前回、定期的に画面を描画するのに、Java の Timer クラスを使いました。しかし、Android の android.os.Handler クラスにもタイマーが用意されています。Android のサンプル(Snake)を見ると、Handler を利用したタイマーを使っていました。そこで、今回、改めて、画面を定期的に描画するサンプルを作ってみます。

ここでは、Mogura というプロジェクトを作って、画面の描画を再描画する部分だけ作ってみました。以下は、更新した回数を画面に表示するだけのサンプルです。

更新した回数を画面に表示するサンプル
更新した回数を画面に表示するサンプル
package com.example.Android.Mogura;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
/** メインクラス */
public class Mogura extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MoguraView v = new MoguraView(getApplication());
        setContentView(v);
    }
}
/** 画面再描画の仕組みを提供するクラス */
class RedrawHandler extends Handler {
    private View view;
    private int delayTime;
    private int frameRate;
    public RedrawHandler(View view, int frameRate) {
        this.view = view;
        this.frameRate = frameRate;
    }
    public void start() {
        this.delayTime = 1000 / frameRate;
        this.sendMessageDelayed(obtainMessage(0), delayTime);
    }
    public void stop() {
        delayTime = 0;
    }
    @Override
    public void handleMessage(Message msg) {
        view.invalidate();
        if (delayTime == 0) return; // stop
        sendMessageDelayed(obtainMessage(0), delayTime);
    }
}
/** 描画用のView */
class MoguraView extends View {
    public int dispX = 480;
    public int dispY = 640;
    public long drawCount = 0;
    public MoguraView(Context c) {
        super(c);
        setFocusable(true);
        RedrawHandler handler = new RedrawHandler(this, 8);
        handler.start();
    }
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        String msg = "Frame" + drawCount++;
        canvas.drawText(msg, 2, 30, paint);
    }
}

定期的に描画が行われるように、Handler から派生させた RedrawHanlder というクラスを作成し、ここでタイマーを利用して定期的に View.invalidate() メソッドを呼び出すようにしています。

ここで利用しているのは、Handler.sendMessageDelayed() メソッドです。引数に指定した時間(単位はミリ秒)の後、handleMessage() メソッドが呼ばれるという仕組みになっています。

/** 画面再描画の仕組みを提供するクラス */
class RedrawHandler extends Handler {
    private View view;
    private int delayTime;
    private int frameRate;
    public RedrawHandler(View view, int frameRate) {
        this.view = view;
        this.frameRate = frameRate;
    }
    public void start() {
        this.delayTime = 1000 / frameRate;
        this.sendMessageDelayed(obtainMessage(0), delayTime);
    }
    public void stop() {
        delayTime = 0;
    }
    @Override
    public void handleMessage(Message msg) {
        view.invalidate();
        if (delayTime == 0) return; // stop
        sendMessageDelayed(obtainMessage(0), delayTime);
    }
}

モグラ叩き

それでは、簡単なモグラ叩きを完成させます。画面が定期的に更新される仕組みさえ分かってしまえば、後は、ゲームのロジックに集中できます。今回は、グラフィックスを重視しないので、モグラは赤い長方形として描画することにしました。

赤い長方形にタッチすると、モグラを叩いたことになり、スコア(Score)が加算されます。また、モグラは次々と何匹も現れることにしました。一般的なモグラ叩きでは、モグラは表示されたら、動かずじっとしていますが、このモグラ叩きでは、モグラが好き勝手に動くようにしてみました。パラパラと赤い長方形のモグラが動き回って、画面は賑やかです。

モグラ叩き
モグラ叩き

以下が、モグラ叩きのソースコードです。

package com.example.Andoroid.Mogura;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.View;
/** メインクラス */
public class Mogura extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MoguraView v = new MoguraView(getApplication());
        setContentView(v);
    }
}
/** 画面再描画の仕組みを提供するクラス */
class RedrawHandler extends Handler {
    private View view;
    private int delayTime;
    private int frameRate;
    public RedrawHandler(View view, int frameRate) {
        this.view = view;
        this.frameRate = frameRate;
    }
    public void start() {
        this.delayTime = 1000 / frameRate;
        this.sendMessageDelayed(obtainMessage(0), delayTime);
    }
    public void stop() {
        delayTime = 0;
    }
    @Override
    public void handleMessage(Message msg) {
        view.invalidate();
        if (delayTime == 0) return; // stop
        sendMessageDelayed(obtainMessage(0), delayTime);
    }
}
/** 画面描画用のViewクラス */
class MoguraView extends View {
    public int dispX = 480;
    public int dispY = 640;
    private int frame = 0;
    private int score = 0;
    private int monsterSpeed = 4;
    private ArrayList<Monster> monsters;
    public MoguraView(Context c) {
        super(c);
        setFocusable(true);
        monsters = new ArrayList<Monster>();
        RedrawHandler handler = new RedrawHandler(this, 8);
        handler.start();
    }
    /** 画面サイズが変更されたときに呼び出されるメソッド */
    protected void onSizeChanged(int w, int h, int oldw, int oldh){
        dispX = w;
        dispY = h;
    }
    protected void onDraw(Canvas canvas) {
        // 画面を白で初期化
        canvas.drawColor(Color.WHITE);
        // モグラ(Monster)の描画
        Paint mpaint = new Paint();
        mpaint.setColor(Color.RED);
        mpaint.setStyle(Paint.Style.FILL);
        for (int i = 0; i < monsters.size(); i++) {
            Monster p = monsters.get(i);
            p.move();
            canvas.drawRect(p.getRect(), mpaint);
        }
        // 新しいモグラを作るかどうか
        if (frame % 20 == 0) {
            if (monsters.size() < 50) {
                monsters.add(new Monster(this, monsterSpeed));
                monsterSpeed += 2;
            }
        }
        // Message の描画
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        String msg = "Score:" + score +
            "/Mogura:" + (monsters.size()) +
            "/Frame:" + (frame++);
        canvas.drawText(msg, 2, 30, paint);
    }
    /** タッチイベントの処理 */
    public boolean onTouchEvent(MotionEvent event) {
        int tx = (int)event.getX();
        int ty = (int)event.getY();
        // モグラにあたったかどうか判定
        int i = 0;
        while (i < monsters.size()) {
            Monster p = monsters.get(i);
            if (p.hitTest(tx, ty)) {
                monsters.remove(i);
                score++;
                continue;
            }
            i++;
        }
        return true;
    }
}
/** モグラクラスの定義 */
class Monster {
    public int x;
    public int y;
    public int w;
    public int h;
    protected int speed;
    protected int dirX;
    protected int dirY;
    protected MoguraView view;
    public Monster(MoguraView view, int speed) {
        this.view = view;
        this.speed = speed;
        dirX = (int)Math.round(Math.random() * 3 - 1);
        dirY = (int)Math.round(Math.random() * 3 - 1);
        x = (int)Math.floor(Math.random() * view.dispX);
        y = (int)Math.floor(Math.random() * view.dispY);
        w = (int)(20 + Math.random() * 20);
        h = (int)(20 + Math.random() * 20);
    }
    public void move() {
        x += speed * dirX; // 単純に移動
        y += speed * dirY;
        x = Math.min(view.dispX, Math.max(0, x));
        y = Math.min(view.dispY, Math.max(0, y));
        if (Math.random() > 0.7) { // 動く向きを変換する
            dirX = (int)Math.round(Math.random() * 3 - 1);
            dirY = (int)Math.round(Math.random() * 3 - 1);
        }
    }
    /** 当たり判定 */
    public boolean hitTest(int tx, int ty) {
        return (x <= tx && tx < (x+w) && y <= ty && ty < (ty + h));
    }
    public Rect getRect() {
        return new Rect(x, y, x+w, y+h);
    }
}

グラフィックスを省略したとは言え、少し長くなってしまいました。モグラ叩きのロジックで肝心なのは、モグラをどのように管理するのかと、画面にタッチした時に、モグラに当たったかどうかの判定を行う部分です。

モグラ自体は、Monster というクラスです。現在いる座標やモグラの大きさを管理しています。モグラの移動処理を行うのが、move() メソッドです。単純にモグラの速度(speed)に従って動かしています。

そして、モグラに当たったかどうか判定する部分(hitTest()メソッド)では、タッチされた座標がモグラに当たっているかどうかを比較します。

View クラスでは、複数のモグラを管理する monsters という ArrayList を用意して、画面の再描画のタイミング(onDraw()メソッド)で、各モグラの移動処理と描画を行っています。

まとめ

以上、今回は、グラフィックスを極力シンプルにしたモグラ叩きを作ってみました。個人的には、画面の再描画を「どう実装すると美しいか」にこだわったので試行錯誤してしまいましたが、それ以外は、すんなり作ることができました。次回は、今回省略したグラフィックスにもう少し凝ってみたいと思います。お楽しみに。

Series Navigation«図形の描画モグラ叩き2»

執筆者紹介

クジラ飛行机

クジラ飛行机

くじらはんど(http://kujirahand/)にて、日本語プログラミング言語「なでしこ」(IPA未踏ユース採択)、テキスト音楽「サクラ」(OSPオンラインソフト大賞入賞)など多くのオンラインソフトを開発。著書に「Flexプロフェッショナルガイド」「なでしこ公式バイブル」、「一週間でマスターするActionScript3.0」など。

TrackBack URL :