
ブログの引越しに手間取ってしまいなかなか時間が取れませんでしたが、ようやく Google Android SDK をさわる時間ができました。
のんびりしてる間に、世間では Androidでスーパーマリオ(ファミコン/NES)が動いた。んだそうです(@_@;素晴らしい!ちなみに、この記事中の「やっぱ、実機がないと、何を作ってもツマンナイです。」というコメントには同感です。
まあ、そんな世の中の展開の早さにはめげずマイペースでやっていきます^^;
さて、今回は、キーパッドからの入力に応じて画面上のボールを動かしてみようと思います。
http://jp.youtube.com/watch?v=BYVFS_lUFUE に動画をアップロードしてあります。黄色の●が画面中を動き回っているだけですが…興味がある方はぜひどうぞ。
まずは、activityCreator を使って ball プロジェクトを作成しました。activityCreator による Android プロジェクト作成の方法は、「Eclipse 使わずに Google Android を勉強してみます。」 をご覧ください。
プロジェクトのディレクトリ構造は、以下の通りです。
以下、ソースコードの説明を書きます。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<rio1218.ball.BallView
id="@+id/ball"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</FrameLayout>
Google Android SDK では、GUI 部品のレイアウトを xml に記述するやり方が推奨されています。レイアウト用の xml ファイルは、projectName/res/layout/ 以下に格納します。
ここでは詳細には説明しませんが、↑の main.xml では android.widget.FrameLayout をルートスクリーンにまず配置し、その子要素として rio1218.ball.BallView を配置する様に指定しています。また、android:layout_width および android:layout_height というサイズ属性に対して "fill_parent" を指定しているため、どちらも親要素いっぱいに広がる様に配置されます。
package rio1218.ball;
import android.app.Activity;
import android.os.Bundle;
public class BallActivity extends Activity
{
/** Called with the activity is first created. */
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.main);
BallView ballView = (BallView)findViewById(R.id.ball);
ballView.start();
}
}
基本となる Activity クラスです。setContentView(R.layout.main); により、先ほど説明した projectName/res/layout/main.xml の内容で GUI を作成する様に指定しています。
R とは、Android SDK によってデフォルトの Activity クラスと同じ場所に自動生成されるクラスです。R クラスのおかげで、様々なリソース(res ディレクトリ以下の各要素含む)を参照する際に、ID 指定によって参照することができます。
その後、findViewById(id) によって BallView クラスのインスタンスを取得し、BallView#start() によってアニメーション表示を開始します。
package rio1218.ball;
import java.util.Map;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
public class BallView extends View implements Updatable
{
// Frames per second, and the time between frames.
private static final int FPS = 60;
private static final long TIME_TO_SLEEP = (long)(1000.0/FPS);
// Some initial parameters.
private static final int BALL_INIT_X = 160;
private static final int BALL_INIT_Y = 100;
// Handler for periodic update of screen.
private UpdateHandler updateHandler = new UpdateHandler(this);
private Ball ball = new Ball();
public BallView(Context context, AttributeSet attrs, Map inflateParams)
{
super(context, attrs, inflateParams);
setFocusable(true);
}
public void start()
{
init();
update();
}
private void init()
{
ball.setPosition(BALL_INIT_X, BALL_INIT_Y);
ball.setDirection(Ball.CENTER);
}
public void update()
{
ball.update();
invalidate();
updateHandler.sleep(TIME_TO_SLEEP);
}
@Override
public void onDraw(Canvas canvas)
{
canvas.drawColor(Color.DKGRAY);
ball.draw(canvas);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
ball.setDirection(Ball.UP);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
ball.setDirection(Ball.RIGHT);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
ball.setDirection(Ball.DOWN);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
ball.setDirection(Ball.LEFT);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
ball.setDirection(Ball.CENTER);
return true;
}
return super.onKeyDown(keyCode, event);
}
}
唯一の View クラスです。View クラスのインスタンスをコード内で生成する場合は
public BallView(Context context);
というコンストラクタのみで構いませんが、本サンプルの様にレイアウトxmlファイルから生成する場合は
public BallView(Context context, AttributeSet attrs, Map inflateParams);
という形式のコンストラクタが必要になります。筆者はこれにはまりました…
また、コンストラクタ内で setFocusable() を実行していることに注目してください。これを実行しないと、いくらキーパッドを押しても onKeyDown() メソッドが実行されません(フォーカスが無いのでキー入力を受け付けていない)。筆者はこれにもはまりました…
外部から start() メソッドを実行することでアニメーションを起動します。init() にて Ball インスタンスの座標と進行方向を初期化し、update() を実行します。update() がアニメーションに直接関係していて、Ball インスタンスの状態を更新した後に View#invalidate() を実行することで画面の再描画を指示します。この View#invalidate() によって onDraw() が実行されます。その後、後述の UpdateHandler#sleep() を実行します。これにより、TIME_TO_SLEEP ミリ秒(33ミリ秒くらい)以上後に再度 update() が実行されます。
追記。実際には33ミリ秒という短い間隔は、エミュレータ上では出ないと思います(実機でも出るか分からないですけど…)。実際どれくらい出てるか計ってみるのも面白いでしょう。指定した時間は最低経過してから、再度 update() が実行される、程度と思って下さい。また、一定時間で毎回実行されるとも限りません。Ball の移動距離を一定にするためには、経過時間を取得して適切な移動距離を計算するなどしないといけませんが、本サンプルではそこまではしません^^;
onDraw() では、引数に渡された Canvas クラスに対して描画することで画面表示を更新します。本サンプルでは、Dark Gray で塗りつぶした後に Ball#draw() によって Ball 自身を描画させます。
キー入力が発生すると、onKeyDown() が実行されます。ここでは、送られてきたキーコードを KeyEvent クラスの定数と比較して押されたキーを判定し、Ball の進行方向を変更しています。
package rio1218.ball;
public interface Updatable
{
void update();
}
後述の UpdateHandler クラスにて、更新対象として扱うべきクラスが実装するインタフェースです。本サンプルでそこまで分ける必要があるのか?とも思いますが、自分のやり方に組み込む方が理解が深まりやすいと考えているためこの様にしています。
package rio1218.ball;
import android.os.Handler;
import android.os.Message;
public class UpdateHandler extends Handler
{
private Updatable updatable;
public UpdateHandler(Updatable updatable)
{
this.updatable = updatable;
}
@Override
public void handleMessage(Message msg)
{
updatable.update();
}
public void sleep(long sleepTime)
{
removeMessages(0);
sendMessageDelayed(obtainMessage(0), sleepTime);
}
}
Android SDK に付属しているサンプルを見る限り、定期的なループは↑の様に android.os.Handler クラスを使うのが良い様です。
追記。Snake というサンプルゲームではこの手法がとられていますが、LunarLander というサンプルゲームでは全く別のやり方が使われていました。本サンプルでは、Handler を使う方法を採用するということで。
sleep() を実行すると、指定ミリ秒(実際には、指定ミリ秒は最低経過してから…ということになるはず)後に指定メッセージを自身に対して送信します。送信されたメッセージの処理タイミングになると、handleMessage() が起動されます。handleMessage() にて Updatable インタフェースを処理対象としている以外は、Handler の使い方は Android SDK で使われているやり方と同じです。
package rio1218.ball;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
/**
* Ball class represents single ball.
*/
public class Ball
{
// Screen related constants.
private static final int SCREEN_W = 320;
private static final int SCREEN_H = 200;
// Ball related constants.
private static final int RADIUS = 5;
private static final int VX = 3;
private static final int VY = 3;
// Ball direction.
public static final int UP = 1;
public static final int RIGHT = 2;
public static final int DOWN = 3;
public static final int LEFT = 4;
public static final int CENTER = 5;
// Ball related variables.
private int direction = CENTER;
private int x;
private int y;
public void setDirection(int direction)
{
this.direction = direction;
}
public void setPosition(int x, int y)
{
this.x = x;
this.y = y;
}
public void update()
{
switch (direction) {
case UP:
y -= VY;
if (y < -RADIUS) {
y = SCREEN_H + RADIUS;
}
break;
case RIGHT:
x += VX;
if (x > SCREEN_W - RADIUS) {
x = SCREEN_W - RADIUS;
direction = LEFT;
}
break;
case DOWN:
y += VY;
if (y > SCREEN_H + RADIUS) {
y = - RADIUS;
}
break;
case LEFT:
x -= VX;
if (x < RADIUS) {
x = RADIUS;
direction = RIGHT;
}
break;
}
}
private final Paint paint = new Paint();
public void draw(Canvas canvas)
{
paint.setColor(Color.YELLOW);
paint.setAntiAlias(true);
canvas.drawCircle(x, y, RADIUS, paint);
}
}
表示するボールを表すクラスです。Android SDK 特有のメソッドは draw(Canvas canvas) のみです。draw() メソッドでは、まず描画スタイルを格納する Paint クラスのインスタンスに対して
に設定した後、現在の中心位置に半径 5 の円を描画します。
その他は、update() を実行する度に、現在の進行方向に向かって 1 フレーム分の移動距離だけ座標を更新しています。画面の左右の壁にぶつかった場合は跳ね返り、上下の場合は反対側から出てくる様に位置を調整しています。
以上で説明終わり!次は、これを少しゲームっぽくしてみようかな…
FC2 Blog Ranking に参加してます。クリックよろしくお願いします!
<<[Google Android SDK]タイトルやゲームオーバのメッセージを表示します。Activity の状態遷移にも対応してみます。 | ホーム | B'z、Hollywood's RockWalk 殿堂入りの授賞式。英語のスピーチ何て言ってました?>>
Author:いちのせ りょう
1974年北海道生まれ、育ちは沖縄/宮崎、で現在は東京在住のSEです。社会人10年目、未だにばりばり作ってます!Rio's Laboratory もよろしく…
性別:男性
車:MAZDA DEMIO
好きな音楽:B'z、The Yellow Monkey、Bon Jovi
好きなゲーム:MGS、Bio Hazard、Zeldaとか
やりたい事:F1観に行きたい、沖縄の海に潜りたい、空を飛びたい