2012年5月27日日曜日

SurfaceViewの留意点

SurfaceViewの留意点

SurfaceViewは、他のViewと違い、取扱いが特殊である。
SurfaceViewを用いたsample codeはLunarLanderである。このsample codeは本格的なゲームであるため、(私から見て)難しくて学習向きであるようには思えない。
このsample codeで気になるのは、背景を常に書き換えていることである。

        /**
         * Draws the ship, fuel/speed bars, and background to the provided
         * Canvas.
         */
        private void doDraw(Canvas canvas) {
            // Draw the background image. Operations on the Canvas accumulate
            // so this is like clearing the screen.
            canvas.drawBitmap(mBackgroundImage, 0, 0, null);
doDrawメソッド内の第一行目でBitmapを使って、画面全体を塗り潰している。そして、第二行目以降で、ゲームに登場するキャラクターを描画していくのである。「変化が無い領域をも再描画することは無駄ではないか」と思ってしまうが、これがSurfaceViewの流儀であるようだ。
Canvas and DrawablesのOn a SurfaceViewには次のように書かれてある。
Note: On each pass you retrieve the Canvas from the SurfaceHolder, the previous state of the Canvas will be retained. In order to properly animate your graphics, you must re-paint the entire surface. For example, you can clear the previous state of the Canvas by filling in a color with drawColor() or setting a background image with drawBitmap(). Otherwise, you will see traces of the drawings you previously performed.
滑らかなアニメーションを実現するために、画面全体を再描画しなければならないのである。動いているのが、主人公たった一人だけであってもだ。

画面内の個々の構成要素のサイズや位置を、アニメが発生する毎に計算するのは手間や時間がかかる。
LunarLanderの場合だと、黒色一色だけで塗り潰せば良いので問題は無い。
しかし、空や太陽や山や家や木等を重ね合わせる計算処理を、アニメが発生する毎に行うのは手間と時間がかかる。

そのような手間を、ある程度緩和するために、事前に、動きの無い背景の絵をBitmapに描いて、それを再描画の度に背景に表示するようにすれば良い。

Canvasをnewする時の引数にBitmapを設定した上で、このCanvasに描画していけば、その描画がBitmapに反映されるのである。

この技法を使ったsample codeは下記のとおりである。
このサンプルではグラデ―ションを作成しているが、この作成が仮に、手間がかかり時間がかかる処理だとする。そんなことをrunが実行される度に行うと、多くの計算処理を行うことになる。
このため、最初の描画の時に、Bitmap画像として大域変数に保管しておき、必要に応じて、このBitmap画像を表示すれば良い。

public class MySurfaceView extends SurfaceView
    implements
    Callback,
    Runnable
    {
    Thread t;
    Bitmap bSource, bBackground;
    int iY;
    
    public MySurfaceView(Context c){
        super(c);
        t = null;
        iY = 50;
        getHolder().addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceCreated(SurfaceHolder h) {
        Canvas c = h.lockCanvas();
        
        //背景用の、空っぽのBitmapを用意する・
        bBackground = Bitmap.createBitmap(c.getWidth(), c.getHeight(), Bitmap.Config.ARGB_8888);
        
        //Bitmapに書き込むCanvasを用意する。
        Canvas cBackground = new Canvas(bBackground);
        
        LinearGradient(c);//表示用の書き込み
        LinearGradient(cBackground);//背景用画像の作成
        
        //ドロイド君の画像を表示する。
        Resources r = getResources();
        bSource = BitmapFactory.decodeResource(r, R.drawable.ic_launcher);
        c.drawBitmap(bSource, 100, iY, null);
        
        h.unlockCanvasAndPost(c);
        t = new Thread(this);
        t.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
    
    @Override
    public void run() {
        while(t!=null){
            Canvas c = getHolder().lockCanvas();
            if(c!=null){
                //背景画像を表示する。
                c.drawBitmap(bBackground, 0.0f, 0.0f, null);
                iY++;
                if(iY>400) iY = 50;
                c.drawBitmap(bSource, 100, iY, null);
                getHolder().unlockCanvasAndPost(c);
            }
            try {
                Thread.sleep(10);
            }
            catch (InterruptedException e) {
                t = null;
            }
        }
    }
    
    private void LinearGradient(Canvas c){
        LinearGradient lg = new LinearGradient(
                c.getWidth()/2, 0.0f, c.getWidth()/2, c.getHeight(),
                Color.BLUE, Color.RED, Shader.TileMode.CLAMP);
        Paint p = new Paint();
        p.setShader(lg);
        c.drawPaint(p);
    }
}

0 件のコメント:

コメントを投稿