2013年6月29日土曜日

[Java]repaint()を使わずに自前で描画処理をしよう

どうも僕です.
今回はゲームとかで描画処理する時のタイミングを自分で決められるようにするためにrepaint()を使わずに描画する方法をメモっとこうと思います.

repaint()は再描画の要求であって実際に再描画するかどうか判断するのはJVMが決めます. JVMの判断ではスピードが要求されるゲームではきついので任意のタイミングで描画できるように自前で作る必要があります.

//スレッド使ってる前提で話し始める
public void run(){
 //ゲームループ
 while(true){
  //ゲームの処理を書く
  //例えばキャラの移動とか...

  //こっから本題
  render();
  paintScreen();

  //休憩
  try{
   Thread.sleep(10);
  }catch(Exception e){
   e.printStackTrace();
  }
 }
}

サクッとサンプル書くとこんなかんじです.
休憩とかは実際は1ループが毎回同じ時間で終わるようにするために時間を測って休憩に割り振るのが良いと思うのですが今回は適当にします.

render()とpaintScreen()が自作メソッドになります.
render()はSwingコンポーネントで勝手にやっちゃうのであまり見なくなったダブルバッファリングってやつを使います.

やり方は,まずバッファ領域を作ります.今回はBImageって名前にしときます.こいつは初めてrender()を呼び出した時に生成されます.次にBImageに書き込むのに必要なGraphicsオブジェクトを作ります.今回はBGraphicsって名前にしときます.こいつも呼び出されたタイミングで生成されます.


//ダブルバッファリング
    private void render() {
        if (BImage == null) {
            BImage = createImage(WIDTH, HEIGHT);
            if (BImage == null) {
                System.out.println("イメージ取得ミス");
                return;
            } else {
                BGraphics = BImage.getGraphics();
            }
        }

        // バッファをクリアする
        BGraphics.setColor(Color.WHITE);
        BGraphics.fillRect(0, 0, WIDTH, HEIGHT);

        ball.draw(BGraphics);
    }

上で作られたバッファイメージを描画する処理がpaintScreen()になります.

//バッファを画面に描画
    private void paintScreen() {
        try {
            Graphics g = getGraphics();
            if ((g != null) && (BImage != null)) {
                g.drawImage(BImage, 0, 0, null);
            }
            Toolkit.getDefaultToolkit().sync();
            if (g != null) {
                g.dispose();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

ballって何だよとか言われたくないので動く形にまとめておきます.

Panelクラス
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;

import javax.swing.JPanel;

public class Panel extends JPanel implements Runnable {
    public static final int WIDTH = 640;
    public static final int HEIGHT = 480;

    private Ball ball;

    private Graphics BGraphics;
    private Image BImage = null;

    private Thread gameLoop;

    public Panel() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));

        ball = new Ball(10, 10, 10, 10);

        //ゲームループ開始
        gameLoop = new Thread(this);
        gameLoop.start();
    }

    //ゲームループ
    public void run() {
        while (true) {
            update();
            render();
            paintScreen();

            //休憩
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //描画情報の更新一覧
    private void update() {
        ball.move();
    }

    //ダブルバッファリング
    private void render() {
        if (BImage == null) {
            BImage = createImage(WIDTH, HEIGHT);
            if (BImage == null) {
                System.out.println("イメージ取得ミス");
                return;
            } else {
                BGraphics = BImage.getGraphics();
            }
        }

        // バッファをクリアする
        BGraphics.setColor(Color.WHITE);
        BGraphics.fillRect(0, 0, WIDTH, HEIGHT);

        ball.draw(BGraphics);
    }

    //バッファを画面に描画
    private void paintScreen() {
        try {
            Graphics g = getGraphics();
            if ((g != null) && (BImage != null)) {
                g.drawImage(BImage, 0, 0, null);
            }
            Toolkit.getDefaultToolkit().sync();
            if (g != null) {
                g.dispose();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ボールクラス
import java.awt.Color;
import java.awt.Graphics;

public class Ball {
    private static final int SIZE = 32;
    private int x, y;
    private int vx, vy;

    public Ball(int x, int y, int vx, int vy) {
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
    }

    public void move() {
        x += vx;
        y += vy;

        if (x < 0) {
            x = 0;
            vx = -vx;
        }

        if (x > Main.WIDTH - SIZE) {
            x = Main.WIDTH - SIZE;
            vx = -vx;
        }

        if (y < 0) {
            y = 0;
            vy = -vy;
        }

        if (y > Main.HEIGHT - SIZE) {
            y = Main.HEIGHT - SIZE;
            vy = -vy;
        }
    }

    public void draw(Graphics g) {
        g.setColor(Color.RED);
        g.fillOval(x, y, SIZE, SIZE);
    }
}


実行用テストクラス
import javax.swing.JFrame;

public class Test extends JFrame {
    public Test() {
        setTitle("動作チェック");

        Panel panel = new Panel();
        getContentPane().add(panel);

        pack();
    }

    public static void main(String[] args) {
        Test frame = new Test();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}


こんな感じです.適当に書きすぎてクラスにまとめろよってかんじですが,これで任意のタイミングで描画することができるよになります. もっといい方法があれば僕に教えてください. 僕から以上.

0 件のコメント:

コメントを投稿