第5回 「複雑なアニメーションの表示」
前回のコードの中で、若干説明不足のところがあったので、補足しておきたいと思います。ARView.java の中に
setCoordReticle(new Point(tar.x, tar.y));
int bombX = tar.tick * tar.x / 10;
int bombY = tar.tick * tar.y / 10;
というコードがありましたが、これは本来ならばターゲットクラスの変数 tar の属性値を getTick() や getX()、getY() などで取得するように書くべきところでした。C++ では、そのように書いてもコンパイラがインライン展開をしてくれるおかげで、効率はまったく変わらずに実行できますが、Android では同じクラス内のオブジェクトに対して setter や getter 関数を使った場合には効率がかなり落ちることがわかっています:
http://developer.android.com/guide/practices/design/performance.html#internal_get_set
もっとも、上記のコードはよそのクラスの変数の参照なので、やはり本来はお行儀良く getter 関数を使うべきところですが、Point 型や Rect 型などと同様に、本稿では直接属性値を参照、設定するスタイルで統一しているので注意してください。
さて、今回はアニメーションをもう少し複雑化して、ミサイルがターゲットに当たったら爆発するように直してみましょう。前回と同様にターゲットのライフサイクルを書いてみると次のようになります。
タイミング | 照準マーク | ミサイル | 爆発 | |
---|---|---|---|---|
画面タップ | ------ ターゲット生成 ------ | |||
ターゲット到達まで | ターゲット位置に固定 | 軌跡上を移動 | (非表示) | |
爆発アニメ終了まで | (非表示) | (非表示) | 爆発 | |
残像アニメ終了まで | (非表示) | (非表示) | フェードアウト | |
終了 | ------ ターゲット消滅 ------ |
この拡張に伴って変更する部分は AimnShoot.java と ARView.java だけですが、中でも AimnShoot.java については下記の数行を変えるだけです。
AimnShoot.java
public Target target1;
private void attackTarget(int x, int y)
{
target1 = new Target(this, x, y);
target1.startTimer();
mARView.postInvalidate();
}
private void showNextFrame()
{
target1.tick++;
mARView.postInvalidate();
}
1行目の target1 は、前回の private 宣言を public に変更しています。これによって target1 は ARView の中から直接参照できるようになります。
次の attackTarget() は mARView.postInvalidate() を呼ぶように変更しました。また showNextFrame() は、前回のように ARView に target1 を渡して mARView.showNextFrame() を実行する代わりに、やはり mARView.postInvalidate() を呼ぶようにしました。postInvalidate() については第2回の末尾に書きましたが、ARView に対して再描画を要求します。メイン側で target1 の内部状態を変更してから再描画を要求することにより、ARView 側では描画関数 onDraw() の中から target1 を参照して、すべての処理を onDraw() の中で行うようにしたわけです。この変更によって、構造が非常にすっきりしました。
ここで ARView に進む前に、画像を準備しておきましょう。
exp00.gif | exp01.gif | exp02.gif | exp03.gif | |
exp04.gif | exp05.gif | exp06.gif | exp07.gif |
私は画才がないので、この程度の絵でご容赦願います。逆に、ここはこのアプリの見せ場ですから、フレーム数もずっと増やして、このアニメーションを凝ったものにすると非常にアプリの魅力が増すはずです。
絵に自信がない人は、ムービーやアニメGIFを適当なツールで分解して画像を作成してもいいかもしれません。ただし、著作権には注意してください。また、「発色弾」 のようなアニメーション作成ツールを利用する方法もあります。ちなみに、発色弾については、Vector にあるのは古いので、作成者のサイト からダウンロードすることをお勧めします (ソフトウェア > ツール > 発色弾)。
さて、ARView の内容に入りましょう。
ARView.java
package com.artiscc.aimnshoot;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
public class ARView extends View
{
private static final String TAG = "ARView";
private static final int NUM_TRACKING_FRAMES = 10;
private static final int NUM_EXP_FRAMES = 8;
private static final int NUM_FADING_FRAMES = 4;
AimnShoot main = null;
Paint[] trans = new Paint[NUM_FADING_FRAMES];
public ARView(Context ctx)
{
super(ctx);
this.main = (AimnShoot)ctx;
prepareImages();
trans[0] = new Paint(Paint.ANTI_ALIAS_FLAG);
trans[0].setColor(0xCCffffff);
trans[1] = new Paint(Paint.ANTI_ALIAS_FLAG);
trans[1].setColor(0x99ffffff);
trans[2] = new Paint(Paint.ANTI_ALIAS_FLAG);
trans[2].setColor(0x66ffffff);
trans[3] = new Paint(Paint.ANTI_ALIAS_FLAG);
trans[3].setColor(0x33ffffff);
}
@Override
public void onDraw(Canvas canvas)
{
Target tar = main.target1;
if(tar == null){
return;
}
if(tar.tick < NUM_TRACKING_FRAMES){
if(bmpReticleOn != null){
canvas.drawBitmap(bmpReticleOn, tar.x - bmpReticleOn.getWidth()/2, tar.y - bmpReticleOn.getHeight()/2, null);
}
int launcherX = getWidth() / 2;
int launcherY = getHeight();
int bombX = launcherX + tar.tick * (tar.x - launcherX) / NUM_TRACKING_FRAMES;
int bombY = launcherY + tar.tick * (tar.y - launcherY) / NUM_TRACKING_FRAMES;
if(bmpBomb != null){
canvas.drawBitmap(bmpBomb, bombX - bmpBomb.getWidth()/2, bombY - bmpBomb.getHeight()/2, null);
}
}else if(tar.tick < NUM_TRACKING_FRAMES + NUM_EXP_FRAMES){
int tick = tar.tick - NUM_TRACKING_FRAMES;
if(bmpExp[tick] != null){
canvas.drawBitmap(bmpExp[tick], tar.x - bmpExp[tick].getWidth()/2, tar.y - bmpExp[tick].getHeight()/2, null);
}
}else if(tar.tick < NUM_TRACKING_FRAMES + NUM_EXP_FRAMES + NUM_FADING_FRAMES){
int tick = tar.tick - NUM_TRACKING_FRAMES - NUM_EXP_FRAMES;
int lastFrame = NUM_EXP_FRAMES - 1;
canvas.drawBitmap(bmpExp[lastFrame], tar.x - bmpExp[lastFrame].getWidth()/2, tar.y - bmpExp[lastFrame].getHeight()/2, trans[tick]);
}else{
tar.cancelTimer();
}
}
private Bitmap bmpBomb = null;
private Bitmap bmpReticleOn = null;
private Bitmap bmpReticleOff = null;
private int[] resExp = {R.drawable.exp00, R.drawable.exp01, R.drawable.exp02, R.drawable.exp03, R.drawable.exp04, R.drawable.exp05, R.drawable.exp06, R.drawable.exp07};
private Bitmap[] bmpExp = new Bitmap[NUM_EXP_FRAMES];
private void prepareImages()
{
Resources r = getResources();
bmpBomb = BitmapFactory.decodeResource(r, R.drawable.bomb);
bmpReticleOn = BitmapFactory.decodeResource(r, R.drawable.reticleon);
bmpReticleOff = BitmapFactory.decodeResource(r, R.drawable.reticleoff);
for(int i = 0; i < NUM_EXP_FRAMES; i++){
bmpExp[i] = BitmapFactory.decodeResource(r, resExp[i]);
}
}
}
ここでは、まず本講義冒頭に示したターゲットのライフサイクルに対応して、3 つの定数を次のように定義します (カッコ内は現在の値)。
- NUM_TRACKING_FRAMES
- ミサイル飛行中のフレーム数 (10コマ)
- NUM_EXP_FRAMES
- 爆発中のフレーム数 (8コマ)
- NUM_FADING_FRAMES
- フェードアウト中のフレーム数 (4コマ)
次に、透過度を表すペイント型の配列 trans を準備します。これは、フェードアウトの効果を出すのに用いるもので、コンストラクタ中の setColor() で、色のアルファ値を CC、99、66、33 (16進数) と設定することにより、画像の透過度が 20 %、40 %、60 %、80 % となるようにしています。
次の onDraw() が描画処理をすべて引き受けています。
まず、ターゲットクラスのポインタ tar を、メインの target1 を指すように設定します。もし tar が null ならば何もせずに戻ります。
次に tar の内部時計 (tick) の値を参照し、ミサイル飛行中か、爆発中か、フェードアウト中か、それ以外かを判定します。if の各条件の意味は明らかでしょう。
ミサイル飛行中の場合の処理は前回とほとんど同じです。ただし、ミサイルの発射地点は原点ではなく、画面の下辺中央 (ユーザの現在地のつもり) としてあります。
爆発中の処理は、アニメーションの 8 枚のフレームの中から、所定のフレームを表示させるだけです。
フェードアウト中の処理は、爆発のアニメーションの最後のフレームを継続して表示し、その画像の透過度を trans に従って徐々に変更しています。
それ以外の分岐に来るのは、上記のアニメーションのフレームがすべて終了した場合ですから、ここでは tar のタイマーを削除します。これにより、このターゲットに対しては onDraw() が呼ばれることはなくなります。
第1部 ARアプリ作成の基本
第1回: カメラ画面の表示画面上にカメラのプレビュー画像を表示し続けるだけのアプリで、ARの基本中の基本です。
第2回: AR画面の表示
カメラ画面の上にAR画面を載せ、カメラ画面上にいろいろな画像を重ね合わせます。
第3回: ジェスチャの認識
タップ、ダブルタップ、長押し、フリックなどの画面操作を認識し、対応する処理を行います。
第4回: アニメーションの表示
タイマーを用いてミサイルが目標に向かって飛んでいく単純なアニメーションを表示します。
第5回: 複雑なアニメーションの表示
ミサイルが当たった後の爆発や残像の表示など、複雑なアニメーションを表示します。
第6回: 複数の目標の扱い
同時に複数個の目標を設定し、それぞれをミサイルで撃破する枠組みを導入します。
第7回: ズーム機能の導入
ズームボタンを設置し、カメラ画面をズームして表示する機構を手作りで実装します。
第8回: ネイティブコードの利用
ズーム機構を高速化するため、C、C++ を使ったネイティブコードを導入します。