応用プログラミング 第5回


授業の目的

 この授業では,ダブルバッファを利用したアニメーションの生成方法を修得します.


出席メール

出席メールで「授業中の課題の実施」を確認します.メールを出した時刻,PCの場所(アドレス)を確認します.

to:jhoshino@esys.tsukuba.ac.jp
subject: 応用プログラミング 第5回.氏名,学籍番号
本文: なし(書かなくて結構です)

 


理論

スレッドの作成

アニメーションやサウンドを利用したアプレットを書く場合には,複数の画像表示を同時に行ったり,画像と音を同時に提示するなど,複数の作業を同時に行うことが増えてきます.こうした要求に応えるため,Java言語には Thread(スレッド)と呼ばれるクラスが 用意されています.

   スレッドA  -------------------------> アニメーション1
   スレッドB  -------------------------> アニメーション2
   スレッドC  -------------------------> 音声1

スレッドの定義の仕方は,Threadクラスを継承する方法と,Runnableインターフェースを実装する方法があります.

class ThreadX extends Thread{
  public void run(){
    スレッドの実行部分を書く
  }
}

class ThreadDemo1 {
  public static void main(String args[]) {
   ThreadX tx = new ThreadX();
   tx.start(); //実行開始
  }
}

class RunnableY implements Runnable {
  public void run(){
    スレッドの実行部分を書く
  }
}

class ThreadDemo2 {
  public static void main(String args[]) {
   RunnableY ry = new RunnableY();
   Thread t = new Thread(ry);
   t.start(); //実行開始
  }
}

ダブルバッファを使うときには,ウィンドウ上への描画と並列して,バッファへの書き込みを行う必要があるため,

 

アプレットを使ったスレッドの作成

アプレットを使う場合は,Appletクラスを継承する必要があるため,Threadクラスは継承できません.
そのため,Runnableインターフェースを実装することで,スレッドを生成します.

  public class NoDoubleBuffer extends Applet implements Runnable{ }

次のような手順で,スレッドの生成や呼び出しを行います.

  1. init(){
    ブラウザがアプレットを読み込んだとき一番最初に呼ばれるメソッドで,初期設定を記述する。スレッドを開始する.
  2. run(){
    スレッドを実行するメソッド.paint()を呼び出す.
  3. paint(){
    実行する内容を記述する.run()メソッドから呼び出される.

ダブルバッファによるアニメーションの描画

これまでにグラフィックライブラリを使って四角や丸などの簡単な図形を描画しましたが,連続的に異なる位置に描画をする操作を繰り返すことで,アニメーションを描画することができます.ところが,グラフィクスを描くコマンドを実行するときにもある程度の時間はかかりますので,前回の描画を消してから,次の図形を書くまでに少し時間が空いてしまいます.すると何も表示されていない時間ができますので,画面がちらつくことになります.

このような画面のちらつきを無くすために,「ダブルバッファ」と呼ばれる手法が良く使われます.画像を表示するためのフレームバッファ(画像メモリ)2画面分用意し,1枚のフレームバッファの内容を表示している間に,もう1枚のフレームバッファに画像を書き込んでおきます.書き換えが終わった時点で二つの画面を高速に入れ替えることで,連続しているように見せます.書き換え中の画面が表示されないので,画面のちらつきを抑えることができます.

 

アプレットを使ったダブルバッファの作成

次のような手順でダブルバッファの描画を行っています.

public class DoubleBuffer extends Applet implements Runnable {
  //ダブルバッファ用の変数の定義
  public void init() {
    // スレッドを開始する
    1) バッファを作成する
  }
  public void run() {
     // 再描画を要求する
     //update()メソッドの呼び出しをする
  }
  public void update(Graphics g) {
    //paint()メソッドの呼び出しをする
  }
  public void paint(Graphics g) {
    2) バッファを描画する    
3) ウィンドウへ転写する
} }

バッファの描画と,ウィンドウの更新の部分だけ抜き出して説明します.

bufferg = buffer.getGraphics(); //グラフィック領域の取得
bufferg.fillRect(0, 0, d.width, d.height); //バッファを描画する
g.drawImage(buffer, 0, 0, this); //ウィンドウへ転写する

 

実習

スレッドの作成

次の例では, Threadクラスのサブクラスを使ってスレッドを作成します.ThreadXクラスはThreadを拡張してrun()メソッドをオーバーライトします.メソッド内の無限ループで2000ミリ秒待機してからHelloを表示する処理を繰り返します.try{}catch(){}という例外処理については,今のところ詳しい意味を知る必要はなく,このように書くものだと思って貰えればいいと思います.書かないとエラーが出ます.

/*
  ThreadDemo1.java  スレッドの作成 
*/
class ThreadX extends Thread {
  
  public void run() {
    try {
      while(true) {
        Thread.sleep(2000);
        System.out.println("Hello");
      }
    }
    catch(InterruptedException ex) {  //例外処理
      ex.printStackTrace();
    }
  }
}

class ThreadDemo1 {

  public static void main(String args[]) {
    
    ThreadX tx = new ThreadX();
    tx.start();
  }
}

次の例では,Runnableインターフェイスを実装してスレッドを作成します.RunnableYクラスはRunnable
インターフェイスを実装します.run()メソッド内の処理は前述の例と同じです.
/*
  ThreadDemo2.java  Runnableインターフェイスの実装 
*/
class RunnableY implements Runnable {
 
  public void run() {
    try {
      while(true) {
        Thread.sleep(2000);	//2秒間待機する
		System.out.println("Hello");
      }
    }
    catch(InterruptedException ex) {
      ex.printStackTrace();
    }
  }
}

class ThreadDemo2 {

  public static void main(String args[]) {
    
    RunnableY ry = new RunnableY();  //オブジェクトの生成と呼び出し方の違いに注意
    Thread t = new Thread(ry);
    t.start();
  }
}

アプレットを使ったスレッドの作成

アプレットサイズはwidth = 300 height = 300で良いと思います.
/*
  NoDoubleBuffer.java  通常のアニメーション
*/
import java.applet.*;
import java.awt.*;

public class NoDoubleBuffer extends Applet implements Runnable {
  int x = 0;
  Thread t;

  public void init() {

    // スレッドを開始する
    t = new Thread(this);
    t.start();
  }

  public void run() {
    try {
      while(true) {

        // 再描画を要求する
        repaint();

        // ウィンドウを更新する前に休止する
        Thread.sleep(100);
      }
    }
    catch(Exception e) {
    }
  }

  public void paint(Graphics g) {

    // 塗りつぶした円を描画する
    Dimension d = getSize();
    g.fillOval(x, d.height/4, 50, 50);

    // x座標を増やす
    x += 5;
    if(x + 50 > d.width)
      x = 0;
  }
}

 

ダブルバッファの作成

以下のプログラムでは,フレームバッファを使わない場合と,使った場合の結果を比較します.repaint()メソッドから直接paint()を呼び出すと画面を一度消してしまいます.そのためupdate()メソッドから呼び出します.

/*
  DoubleBuffer.java  ダブルバッファリングのサンプルプログラム
*/
import java.applet.*;
import java.awt.*;

public class DoubleBuffer extends Applet implements Runnable {
  int x = 0;
  Thread t;
  Image buffer;  //ダブルバッファ用の変数の定義
  Graphics bufferg;


  public void init() {

    // スレッドを開始する
    t = new Thread(this);
    t.start();

    // バッファを作成する
    Dimension d = getSize();
    buffer = createImage(d.width, d.height); //バッファを作成します
  }


  public void run() {
    try {
      while(true) {

        // 再描画を要求する
        repaint();  //update()メソッドの呼び出しをする

        // ウィンドウを更新する前に休止する
        Thread.sleep(100);
      }
    }
    catch(Exception e) {
    }
  }


  public void update(Graphics g) {
    paint(g);
  }


  public void paint(Graphics g) {

    // バッファのグラフィックコンテキストを取得する
    if(bufferg == null)
      bufferg = buffer.getGraphics();


    // バッファを描画する
    Dimension d = getSize();
    bufferg.setColor(Color.white);
    bufferg.fillRect(0, 0, d.width, d.height);
    bufferg.setColor(Color.black);
    bufferg.fillOval(x, d.height/4, 50, 50);


    // ウィンドウを更新する
    g.drawImage(buffer, 0, 0, this);

    // x座標を増やす
    x += 5;
    if(x + 50 > d.width)
      x = 0;
  }
}
練習課題

ダブルバッファを利用して画面内でボールが弾むアニメーションを作成して下さい.ボールや画面の領域は適当に設定して下さい.

提出先のアドレスはjhoshino@esys.tsukuba.ac.jpです.Subject欄に「応用プログラミング 第5回課題,氏名,学籍番号」を書いて下さい.本文にソースコード,実行結果,考察を記載して下さい.

締め切りは次の授業の開始時まで.ソースコードはメール本文へのコピーあるいは添付どちらでも結構です.


参考資料

 クラス MediaTracker

 Graphics2Dクラス仕様

 Java2D(パッケージ java.awt.geom)仕様


/*
ThreadDemo3.java