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

Android で再開する Java プログラミング(4) - モグラ叩き2 はてなブックマーク数 このエントリーをブックマークに追加

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

今回作るもの

前回まで、Android でプロジェクトを作成したり、グラフィックで絵を描いたり、簡単なゲームを作るところまで紹介しました。今回は、グラフィックスの扱い方をもう少し考慮して、見た目を改善してみましょう。

  • モグラ叩き(改良版)

画像ファイル(ビットマップ)の表示

今回のメインは、画像ファイルを表示する部分です。そこで、前回のモグラ叩きに組み込む前に画像を表示するだけのプログラムを作ってみます。

まずは、以下のような、画像ファイル「monster.png」を用意して、これを表示させてみます。

http://aoikujira.com/demo/hakkaku/rc/20090215YI5ed-monster.png

はじめに、Eclipse 上で新規プロジェクトを作成します。ここでは、BitmapTest というプロジェクトを作りました。

新規プロジェクトの作成
新規プロジェクトの作成

そして、プロジェクトの res/drawable というフォルダに、表示させたい画像「monster.png」をドラッグ&ドロップします。

http://aoikujira.com/demo/hakkaku/rc/200902157k2MTy-copy_bitmap.png

すると、自動的にリソースが定義されているクラスファイル R.java の内容に画像ファイルのIDが追記されるようになっています。

// R.java より抜粋
    public static final class drawable {
        public static final int icon=0x7f020000;
        public static final int monster=0x7f020001; //←ここが勝手に追記された
    }

そして、メインファイルの「BitmapTest.java」を以下のように書き換えます。

package com.example.android.BitmapTest;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;

/** メインクラスの定義 */
public class BitmapTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /** 描画クラスを設定 */
        TestView view = new TestView(getApplication());
        setContentView(view);
    }
}
/** 描画クラスの定義 */
class TestView extends View {
    private Bitmap bmp;
    public TestView(Context c) {
        super(c);
        setFocusable(true);
        Resources r = getResources();
        bmp = BitmapFactory.decodeResource(r, R.drawable.monster);
    }
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(bmp, 0, 0, null);
    }
}

これを実行すると、以下のように表示されます。

http://aoikujira.com/demo/hakkaku/rc/200902152Gr8i6-BitmapTest-run.png

ここでもう少し詳しくプログラムを見てみましょう。画像ファイル(ビットマップ)を表示するためには、BitmapFactory を利用します。そして、Canvas.drawBitmap() を利用して画像を表示できます。

// リソースからビットマップを取り出す
Resources r = getResources();
Bitmap bmp = BitmapFactory.decodeResource(r, R.drawable.monster);
..
// キャンバスに表示
protected void onDraw(Canvas canvas) {
    ..
    canvas.drawBitmap(bmp, 0, 0, null);
}

Android SDKでは PNG 画像のほか、JPEG や GIF も扱うことができます。(PNGが推奨されています。)

ゲームに組み込む

それでは、前回、作ったモグラ叩きに画像を組み込んでみます。モグラ叩きということで、先ほどより、少し小さくし、モグラを叩いたことが分かるように、モグラが痛いという顔をしている絵も作ってみました。

http://aoikujira.com/demo/hakkaku/rc/200902150WaRIt-game-mogura.png

ここで用意したのは、以下の2枚の画像です。

monster.png
monster.png
monster2.png
monster2.png

組み込むと言っても、前回の四角を描画していた部分を画像に取り替えただけです。変更した部分は、View のコンストラクタで、画像リソースを読み込んでおいて、onDraw() メソッドでモグラ画像を描画するようにしました。

また、モグラを叩いた後、しばらく叩かれたモグラの画像を表示するようにしています。そのために、モグラを表す Monster クラスに、anim プロパティを導入し、叩かれたらこれを加算するようにして、表示する画像を変更しています。

package com.example.android.mogura2;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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 Mogura2 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;
    private Bitmap bmpMogura;
    private Bitmap bmpMoguraHit;
    public MoguraView(Context c) {
        super(c);
        setFocusable(true);
        Resources r = c.getResources();
        bmpMogura    = BitmapFactory.decodeResource(r, R.drawable.monster);
        bmpMoguraHit = BitmapFactory.decodeResource(r, R.drawable.monster2);

        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);
        int i = 0;
        while (i < monsters.size()) {
            Monster p = monsters.get(i);
            if (p.anime > 0) {
                p.anime++;
                if (p.anime > 3) {
                    monsters.remove(i);
                    continue;
                }
                canvas.drawBitmap(bmpMoguraHit, p.x, p.y, null);
            }
            else {
                p.move();
                canvas.drawBitmap(bmpMogura, p.x, p.y, null);
            }
            i++;
        }
        // 新しいモグラを作るかどうか
        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)) {
                p.anime++;
                score++;
            }
            i++;
        }
        return true;
    }
}
/** モグラクラスの定義 */
class Monster {
    public int x;
    public int y;
    public int w = 76;
    public int h = 100;
    protected int speed;
    protected int dirX;
    protected int dirY;
    protected MoguraView view;
    protected int anime = 0;
    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);
    }
    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);
    }
}
まとめ

今回は画像の表示方法について見てみました。いくつかのポイント(タッチイベントと画像の表示、そして、画面の定期的な書き換え方法など)を覚えれば、大抵のお手軽ゲームは作ることができそうです。今回のモグラ叩きもルールが簡単ということもありますが、200行以下でできています。そういう意味では、改めて、Android は週末プログラミングにもってこいなのではないかと思いました。

Series Navigation«モグラ叩きSDK1.5でのインストールから実行までを10ステップで学ぶ»

このサイトについて

八角研究所
株式会社八角研究所のWEBサイトですよー。 いろんなものを創り出すことのできる環境をコツコツ構築中。 いったい、いつになったらできるのか。 この技術情報サイトもそのための活動の一環のつもり。

執筆者紹介

クジラ飛行机

クジラ飛行机

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

TrackBack URL :

public class Mogura2 extends Activity {
の構文でエラーが出ます。
「public 型 Mogura はそれ独自のファイル内に定義されなければなりません」
と言うエラーになります。どのような意味でしょうか。
突然の問合せの失礼をお許しください。

おや?!エラーから類推すると、ソースファイルを記述ファイルが違うようですね。
Mogura2.java というファイルにソースを記述していますか?

お忙しいところ早速のご指導感謝します。
ファームウエアバージョンを2.1から1.5に変えてみたところ動作確認出来ました。ありがとうございました。
2.1の場合は前記の構文はどのように記述するのでしょうか。
少し疑問が残りました。

HT-03A実機(Android 2.1)を入手しましたので試したところ
動作確認できました。開発のプラットホーム1.5でも実機2.1でスカスカ動作します。PCからHT-03Aへのインストールも簡単ですね。
有難うございました。