質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.64%

きれいに描画する方法

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 1,034

nakamura-

score 46

こちらで前々回と前回でSurfaceViewやプログラムの改善点など教えていただいたのですが、疑問に思ったことがあるので質問させていただきます。
前々回リンク内容
前回リンク内容

下のプログラムではダブルバッファの機能?を使ってきれいに描かれてる絵だけを描画していると思うのですが、
例えば、先に0.005秒ごとに座標の変化した桜の絵を1枚ずつ全て作り、その後でそれを順番に描画したら、さらにきれいになるのではないでしょうか?
絵を描いている途中を見てしまうので、ちらつきやカクカクが起こるのであれば先に描画する絵を全て用意してからそれを描画するという考えです。

桜が落ちるプログラム

コード
public class MainActivity extends Activity{

    int sakuraCount = 100;        //桜の枚数
    Sakura sakura[] = new Sakura[sakuraCount];
    CanvasView cView;

    /**
     * メインメソッド
     */
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        cView = new CanvasView(this);
        setContentView(cView);

        sakuraCreate();
    }

    /**
     * Sakuraインスタンス化メソッド
     */
    public void sakuraCreate(){
        Random rnd = new Random();

        for(int i = 0; i < sakuraCount; i++){
            sakura[i] =
                new Sakura(    (Math.random() * 800) - 100,
                                -(Math.random() * 5000), rnd.nextInt(11) + 5);
        }
        /*このクラスのsakuraCountとSakuraがCanvasViewクラスのものと同値であると示す*/
        cView.setSakuraCountAndSakura(sakuraCount, sakura);
    }
}
コード
public class CanvasView extends SurfaceView implements SurfaceHolder.Callback, Runnable{

    int sakuraCount;
    Sakura sakura[] = new Sakura[sakuraCount];
    SurfaceHolder sHolder;
    Thread thread;

    /**
     * CanvasViewクラスのコンストラクタ
     */
    public CanvasView(Context context){
        super(context);

        sHolder = getHolder();
        sHolder.addCallback(this);
    }

    /**
     * sakuraCountとsakura[]の同値を示すメソッド
     */
    public void setSakuraCountAndSakura(int sakuraCount, Sakura sakura[]){
        this.sakuraCount = sakuraCount;
        this.sakura =sakura;
    }

     /**
      * SurfaceViewが生成されたときに呼ばれるメソッド
      */
     @Override
     public void surfaceCreated(SurfaceHolder sHolder){
         thread = new Thread(this);
     }

    /**
     * SurfaceViewの状態が変更されたときと
     *                 生成されたときも呼ばれるメソッド
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder,int format, int width, int height){
        thread.start();
    }

    /**
     * SurfaceViewが破棄されるときに呼ばれるメソッド
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder sHolder){
        thread = null;
    }

    /**
     * run(描画)メソッド
     */
    public void run(){

        //Sakura管理スレッド ここから
        Thread sakuraManageThread = new Thread(new Runnable(){
            public void run(){

                while(true){
                    try{
                        Thread.sleep(5);
                    }
                    catch(InterruptedException e){
                    }

                    for(int i = 0; i < sakuraCount; i++){
                        sakura[i].updata();
                    }
                }
            }
        });
        sakuraManageThread.start();
        //ここまで

        while (true){
            Canvas canvas = sHolder.lockCanvas();

            if(canvas != null){
                canvas.drawColor(Color.WHITE);

                Path path = new Path();
                for(int i = 0; i < sakuraCount; i++){
                    path.moveTo(sakura[i].Screen_vData[0][0], sakura[i].Screen_vData[0][1]);
                    path.quadTo(sakura[i].Screen_cData[0][0], sakura[i].Screen_cData[0][1], sakura[i].Screen_vData[1][0], sakura[i].Screen_vData[1][1]);
                    path.quadTo(sakura[i].Screen_cData[1][0], sakura[i].Screen_cData[1][1], sakura[i].Screen_vData[0][0], sakura[i].Screen_vData[0][1]);
                    canvas.drawPath(path, sakura[i].paint);
                }
            }
            sHolder.unlockCanvasAndPost(canvas);
        }
    }
}
コード
public class Sakura{

    int Vertex_data[][] = {{  1,  1, 0},        //点P0
                          { -1, -1, 0}    };    //点P1
                                                //頂点データ
    float Screen_vData[][] = new float[2][3];    //表示頂点データ

    int Control_data[][] = {    {-1,  1, 0},    //点P0から点P1
                                { 1, -1, 0}    };    //点P1から点P0
                                                //曲線描くときのコントロールデータ
    float Screen_cData[][] = new float[2][3];    //表示コントロールデータ

    double pointX;                //表示位置のx座標
    double pointY;                //表示位置のy座標
    int scale;                    //描画倍数
    double distanceY;            //移動距離
    double phi;                //x軸周りの回転角
    double theta;                //y軸周りの回転角

    Paint paint = new Paint();    //Paintクラスをインスタンスごとに管理
    Random rnd = new Random();

    /**
     * Sakuraクラスのコンストラクタ
     */
    public Sakura(double pointX, double pointY, int scale){
        this.pointX = pointX;
        this.pointY = pointY;
        this.scale = scale;

        phi = 0;
        theta = 0;

        paint.setColor(Color.MAGENTA );    //色設定
        paint.setStrokeWidth(1);            //線の太さ設定
        paint.setAntiAlias(true);            //滑らかに書く
        paint.setStyle(Paint.Style.FILL);    //塗りつぶし
    }

    /**
     * 位置更新メソッド
     */
    public void updata(){

        if(scale >= 10){
            pointX += (Math.random() * 0.5);
        }
        else{
            pointX -= (Math.random() * 0.5);
        }

        phi += Math.PI / (rnd.nextInt(101) + 30);    //角度変更処理
        theta += Math.PI / (rnd.nextInt(101) + 30);    //角度変更処理
        pointY += (Math.random() + 1);                //落下処理

        setRotPosition ();
    }

    /**
     * 回転後の座標を求めるメソッド
     */
    public void setRotPosition(){
        double Xsin = Math.sin(phi);
        double Xcos = Math.cos(phi);
        double Ysin = Math.sin(theta);
        double Ycos = Math.cos(theta);

        for(int i = 0; i < Vertex_data.length; i++){
            double rvx = Vertex_data[i][0] * Ycos +
                                Vertex_data[i][2] * Ysin;
            double rvy = Vertex_data[i][0] * Xsin * Ysin +
                                Vertex_data[i][1] * Xcos -
                                    Vertex_data[i][2] * Xcos * Ycos;
            double rvz = - Vertex_data[i][0] * Xcos * Ysin +
                                Vertex_data[i][1] * Xsin +
                                    Vertex_data[i][2] * Xcos * Ycos;

            Screen_vData[i][0] = (float)(pointX + (rvx * scale));
            Screen_vData[i][1] = (float)(pointY - (rvy * scale));
            Screen_vData[i][2] = (float)(rvz * scale);
        }

        for(int i = 0; i < Control_data.length; i++){
            double rcx = Control_data[i][0] * Ycos +
                                Control_data[i][2] * Ysin;
            double rcy = Control_data[i][0] * Xsin * Ysin +
                                Control_data[i][1] * Xcos -
                                    Control_data[i][2] * Xcos * Ycos;
            double rcz = - Control_data[i][0] * Xcos * Ysin +
                                Control_data[i][1] * Xsin +
                                    Control_data[i][2] * Xcos * Ycos;

            Screen_cData[i][0] = (float)(pointX + (rcx * scale));
            Screen_cData[i][1] = (float)(pointY - (rcy * scale));
            Screen_cData[i][2] = (float)(rcz * scale);
        }
    }
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

極論を言えばそのとおりです。そして、それは「プリレンダリングムービー」と呼ばれるもので、一般的に呼べば「動画」です。その瞬間ごとに計算して座標を出すのではなく、前もって全ての計算結果を描画してしまっているので、「プリ(事前)」「レンダリング(描画)」といいます。

しかし、プリレンダリングには弱点があります。圧倒的なメモリ不足です。
例えばAndroidのCanvasからBitmapを生成したとすると、480x800の解像度の場合、概算で480*800*4=1,536,000、つまり1.5Mくらいメモリを食います。1000枚作るだけでおそらく端末のメモリはパンクするでしょう。
それらを圧縮したものがmp4と呼ばれるものであったり、3gpと呼ばれるものだったりします。しかし、それらはリアルタイムに計算しているわけではないので、葉の落ちる速度や向きを傾きに合わせて変えるなどのカスタマイズは不可能になります。

結局その手段を取るか取らないかは、見た目のクオリティとインタラクティブ性のどちらを取るかによるかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/06/15 16:09

    回答ありがとうございます。
    プリレンダリングムービーという手法があるんですね。

    ではandroidで作ってもメモリ不足になってしまうんですね><

    プリレンダリングムービーでするならクオリティも高く速度や向きなど複雑な処理ができるが、圧縮したものは定期的な動きしか処理できないということでしょうか?

    キャンセル

  • 2016/06/15 16:20

    プリレンダリングムービー=録画した映像だと思ってください。そして、映像の容量を抑えるためのものがmp4や3gpといった動画フォーマットです。圧縮するしないは関係なく、プリレンダリングムービーは録画した時の動きしかできません。

    いまあなたのSakuraのプログラムは、プリレンダリングムービーとは対のリアルタイムレンダリングと呼ばれます。
    いまのプログラムを改造して、携帯の傾きに応じて落下速度や向きを変えるプログラムを書いたとして、それがその場で桜の動きに反映できるのは「プリレンダリングムービーではない」場合のみです。プリレンダリングムービーで行うのであれば、映像を見ている「今」の傾きではなく、「撮影した時」の傾きになります。説明がごちゃごちゃしてすいません。

    キャンセル

  • 2016/06/17 10:31

    録画したものだと後から手を加えれないってことですね。
    こちらこそ理解力不足ですみません。
    わかりやすい説明ありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.64%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る