学生向けプログラミング入門

学生向けにプログラミングを解説。Java、C++、Ruby、PHP、データベース

Javaプログラミング入門その27 アニメーションを描画するプログラム

<<前  [TOP]  次>>


基本的には複数のイメージを生成して、順次表示させることでアニメーションとなります。
アニメーションを行う場合、「repaint()」で画面描画を制御する必要があります。


以下のサンプルプログラムを作成してください。


AwtAnimationTest1.java 直

import java.awt.*;

public class AwtAnimationTest1 extends Canvas 
			implements Runnable {

	private final int MAX_IMAGE = 7;
        private final int WIDTH = 500;
        private final int HIGHT = 400;
        private Image[] image = new Image[MAX_IMAGE];
        private MediaTracker tracker;

	//ダブルバッファリング(オフスクリーン・バッファ用)変数
	private Graphics offg;
	private Image offImage;
	private int x = 0;			//画像の座標
	private int y = 100;                
	private int imageNum = 0;          //利用する画像
	private int imageCount = 1;         //画像の枚数カウント制御
	private int moveCount = 20;        //移動距離カウント制御

	/**コンストラクタ*/
	public AwtAnimationTest1() {
		setSize(WIDTH, HIGHT);
		 setBackground(Color.black);

                Toolkit tk = Toolkit.getDefaultToolkit();

                tracker = new MediaTracker(this);

		//キャラクターイメージの作成
		 for(int i=0; i<MAX_IMAGE; i++) {
                        image[i] = tk.getImage("char" + i + ".gif");
                        tracker.addImage(image[i], i);
                }

		//全イメージのダウンロードが終わるまで待つ
                try {
                        tracker.waitForAll();
                }
                catch (InterruptedException e) {
                        System.err.println("tracker error");
                }
        }

	/**メインの処理(スレッド処理)*/
	public void run() {
		while (true) {	//永久に繰り返し
			try {
				Thread.sleep(100);		//100ミリ秒(0.1秒)スリープ
			}
			catch (InterruptedException ex) {
				System.err.println(ex);
			}

			repaint();
		}
	}


	/**画像描写が必要な場合の処理*/
	public void update (Graphics g) {

		//キャラクター、障害物の消去(全画面消去)
		//オフスクリーン・バッファへの描き込み。実際にはまだ見えない
		offg.setColor(Color.black);
		offg.fillRect(0, 0, WIDTH, HIGHT);

		//口を開けるアニメか閉じるアニメかを設定
		if (imageNum >= (MAX_IMAGE-1)) {
			imageCount = -1;
		}
		else if (imageNum <= 0) {
			imageCount = 1;
		}
		imageNum+=imageCount;

		//イメージの移動
		if (x+image[0].getWidth(this) >= WIDTH) {
			moveCount = -10;
		}
		else if (x <= 0 ) {
			moveCount = 10;
		}
		x+=moveCount;

		//オフスクリーン・バッファへの描き込み。実際にはまだ見えない。
		offg.drawImage(image[imageNum], x, y, this);

		//フォントの設定
		 Font font = new Font("Serif", Font.ITALIC, 48);
                offg.setFont(font);
		offg.setColor(Color.yellow);
                offg.drawString("サンプル画像表示", 10, 200);

		//オフスクリーン・バッファに描かれたものを画面に表示
		g.drawImage(offImage, 0, 0, this);
	}

	/**キャンバスで描写の必要のあるときに呼ばれるメソッド*/
	public void paint(Graphics g) {

		//オフスクリーン・バッファの領域がない場合は作成
		if (offg == null) {

			//キャンバスと同じ大きさの仮想画面を生成
			offImage = createImage(getSize().width, getSize().height);

			//offimageから仮想画面描写用のグラフィックスコンテキストを取得
			offg = offImage.getGraphics();
		}

		g.drawImage(image[imageNum], x, y, this);

		//フォントの設定
		Font font = new Font("Serif", Font.ITALIC, 48);
		g.setFont(font);
		g.setColor(Color.yellow);
		g.drawString("サンプル画像表示", 10, 200);
	}


	/**main()*/
	public static void main( String[] args) {

		AwtAnimationTest1 canvas = new AwtAnimationTest1();
		Frame frame = new Frame("Animation1");
		frame.add(canvas);
		frame.pack();
		frame.setVisible(true);

		//スレッドを生成して、ゲーム開始
		new Thread(canvas).start();
	}
}



このプログラムを実行するには画像ファイルが必要です。
char0.gif〜char6.gifをプログラムを保存するのと同じフォルダ(ディレクトリ)に保存してください。
char0.gif 直
char1.gif 直
char2.gif 直
char3.gif 直
char4.gif 直
char5.gif 直
char6.gif 直


アニメーションさせる場合、「止まって見える時間」を作る必要があります。
今回は、「スレッド」と呼ばれる機能を利用します。
スレッドはjava.lang.Threadクラスで定義されており、ひとつのプログラムの中で並列した処理を行える機能のことを言います。
なお、スレッドにはいろいろな機能がありますので、全部一気に覚えようとはせず、出てきた機能を順番に一つずつ覚えていってください。


スレッドを行うには二通りの方法があります。
まずはRunnableインターフェイスを実装する方法を示します。
Runnableインターフェイスを実装すると、run()メソッドを実装しますので、runメソッドの処理を記述します。
下の例では、実行を一時停止するThreadクラスのsleep()メソッドを呼び出しています。
スレッドは「Thread(test).start()」のように、スレッドを実装したクラスを引数としてオブジェクトを生成し、start()メソッドで、run()メソッドの処理を実行します。

public class Test implements Runnable {

    private String str;
    private int count;

    /** コンストラクタ */
    public Test(String str, int count) {
        this.atr = str;
        this.count = count;
    }

    /** メインの処理(スレッド処理) */
    public void run() {
        while (count > 0) {    //指定回数繰り返し
            try {
                Thread.sleep(100);    //100ミリ秒(0.1秒)スリープ
                System.out.println(str);
                count--;
            }
            catch (InterruptedException ex) {
                System.err.println(ex);
            }
        }
    }

    /** main() */
    public static void main(String[] args) {
        Test test = new Test("テスト", 10);
        new Tread(test).start();    //スレッドを生成してスタート
    }
 }



インターフェイスは、コンストラクタを持たず、フィールドとメソッドを持っています。
インターフェイスに記述されているフィールドやメソッドを利用するには、インターフェイスを実装しなければなりません。
実装するには、implementsの後に利用するインターフェイス名を記述します。
このように、インターフェイスを実装したクラスのオブジェクトを作成するには、インターフェイスのメソッドをすべて定義するという作業が必要になります。
上のプログラムのように、Runnableインターフェイスを実装したら、Runnableインターフェイスが持つ、run()メソッドは必ず記述しなければなりません。
記述しなければコンパイル時にエラーとなります。


ここで、少しextends(継承)とimplements(実装)の違いについて説明しておきます。
クラスの継承とは、スーパークラスの機能を引き継ぎ、それに機能を追加して拡張することであり、インターフェイスの実装とは、インターフェイスで定義されたメソッドの処理内容を記述することです。
さらにインターフェイスにだけ許されていることもあります。
それは、1つのクラスが複数のインターフェイスを継承して実装する多重継承が許可されているということです。
クラスの多重継承が煩雑になるという理由から、C++で許可されていたクラスの多重継承が、Javaでは禁止されています。


ただし、定数とメソッドのプロトタイプの定義だけなら同時に複数継承しても煩雑にはならないだろうという理由から、Javaではインターフェイスの多重継承が許可されています。
多重継承ではなく、多重実装と呼ぶべきかもしれません。


インスタンスを 1 つのスレッドで実行するすべてのクラスでは、Runnable インタフェースを実装する必要があります。
このクラスは、引数のないメソッド run を定義しなければなりません。


このインタフェースは、アクティブな間にコードを実行したいオブジェクトが使う、共通のプロトコルを提供するために設計されています。


たとえば、Runnable は Thread クラスによって実装されます。
アクティブであるということは、スレッドが開始されて、まだ終了していない状態を意味します。


さらに Runnable は、Thread をサブクラス化せずにクラスをアクティブにする手段を提供します。
Runnable を実装するクラスは、Thread のインスタンスを生成し、ターゲットとしてクラス自身を渡すことにより Thread をサブクラス化をしなくても実行できます。


Thread クラスのメソッドのうち、run() だけをオーバーライドして使用する場合は、Runnable インタフェースを使用します。
これは、クラスの基本的な動作を修正または拡張するのでない限り、そのクラスをサブクラス化することは好ましくないため、重要です。


次にThreadクラスの子クラスとしてスレッドを利用する場合の例を挙げておきます。
スレッドは「test.start()」のように、生成したThreadクラスのstart()メソッドで利用します。
先の例と同様に処理はrun()メソッドで行います。

public class Test2 implements Runnable {

    private String str;
    private int count;

    /** コンストラクタ */
    public Test2(String str, int count) {
        this.atr = str;
        this.count = count;
    }

    /** メインの処理(スレッド処理) */
    public void run() {
        while (count > 0) {    //指定回数繰り返し
            try {
                Thread.sleep(100);    //100ミリ秒(0.1秒)スリープ
                System.out.println(str);
                count--;
            }
            catch (InterruptedException ex) {
                System.err.println(ex);
            }
        }
    }

    /** main() */
    public static void main(String[] args) {
        Test2 test = new Test2("テスト", 10);
        test.start();    //スレッドを生成してスタート
    }
}



キャンバスに多数の図形やイメージを描画すると書き替えの際の「ちらつき」が気になる場合が多いです。
それを避けるためには、「ダブルバッファリング」という手法を使います。
ダブルバッファリングは、「オフスクリーン」と呼ばれる仮想的な画面に描画して、すべてを描画してから、表示可能なキャンバスに描画を行うというものです。
ただし、オフスクリーンでの描画が間に合わないと「コマ落ち」のような表示になる場合もあります。

Graphics offg;
 Image offImage;

 //オフスクリーン・バッファの領域がない場合は作成
if(offg == null) {
    //キャンバスと同じ大きさの仮想画面を生成
     offImage = createImage(geSize().width, getSize().height);

    //offImageから仮想画面描画用のグラフィックスコンテキストを取得
     offg = offImage.getGraphics();
 }

 //全画面消去
//オフスクリーン・バッファへの書き込み。(実際にはまだ見えない)
offg.setColor(Color.black);
 offg.fillRect(0, 0, WIDTH, HEIGHT);
 offg.drawString("サンプル画像表示", 10, 200);

 //オフスクリーン・バッファに書かれたものを画面に表示
//gは表示可能なGraphicsだとする
g.drawImage(offImage, 0, 0, this);

drawImageメソッドを利用する際に、 thisというものを使っています。
これは、このクラスのコンストラクタの情報を与えているものです。
少し難しいのですが、今はこのようにして使うものだと思って使用してください。

drawImage(指定するイメージ, int x, int y, イメージの情報);


public class AwtAnimationTest1 extends Canvas implements Runnable {

extends Canvasで、Canvasクラスのフィールド、メソッドをすべて継承しています。
これにより、コンストラクタ内ですべてを記述することなく、Canvasの機能を使うことが出来ます。
必要な部分のみ記述することで、その部分のみを書き換えることが出来ます。
さらにRunnableインターフェイスを実装して、Threadを使用しています。
なお、継承は一つのクラスに一つしか許されていませんが、実装は何個でもすることが出来ます。
今回は一つずつしかしていませんが。


プログラムを実行してみましょう。
大きさ(500,400)のフレームが表示され、中にキャンバスが表示されています。
キャンバスにはcahr0.gif〜char6.gifの画像が指定された位置にアニメーション表示されています。
一定の時間でキャラクターが左右に行ったり来たりします。
その下に指定したフォントで「サンプル画像の表示」と表示されます。
フレームの終了ボタンは使えませんので、Ctrlキー+Cキーで強制終了してください。








<<前  [TOP]  次>>