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