第 4 章 Java によるアニメーション表現 41
4.3 アニメーションプログラムの完成
実習
sample06.java
とsample07.java
の大きな違いは,implements
の後に
Runnable
という名前(正確にはインタフェースと呼びます)が追加されたことと,
Thread
クラスの変数の扱い,特にsleep()
メソッドの 周辺でした。しかし,せっかくのアニメーションもギクシャクとした動 きで何か不満に思えませんか?そこで,プログラム例を元に,より滑ら かな動きを表現するための定石について学んでいきます。4.3.1
ダブルバッファ滑らかな動きを妨げるひとつの要因は,
paint()
メソッドの呼ばれ方にあります。何も 手を打っていないと,このメソッドは,repaint()
から間接的に呼ばれるupdate()
内か ら,画面を背景色でクリアした後に呼ばれるようになっています。通常は,この動作で不 都合はないし,むしろ,いちいち画面を消すことを考えなくてすむので好ましい設計とも いえます。ところが,アニメーションのようにできるだけすばやく絵を書き換えようとす ると,この消去の動きも目に止まるようになるので,結果としてチラチラして見にくくな るわけです。sample05.java
で気付かれた人もいるかと思いますが,これを避けるためには,標準で用意されている
update()
メソッドの代わりに自前のメソッドを準備して消去動作を迂 回して直接paint()
を呼ぶようにします。このように,標準の機能を書き換えてしまうこ とをオーバーライトと呼んでいます。ただ,これだけだと前の画面が残ってしまうことになって,自前で消す方法を考えなく てはいけません。ひとつの手は,前の画面と新しい画面で異なる部分だけを描き直すとい うものです。これは,前の画面の状態をどこかで保持しておく必要もあり,なかなか面倒 です。そこで,前の画面の状態にかかわらず,できるだけ高速に画面を切替える手法とし てダブルバッファと呼ばれる手法がよく利用されます。
簡単に言えば,表示用の画面と(計算を伴うために時間のかかる描画のための)作業用 の画面を2つ用意し,作業用の画面での描画が終った段階で,表示用の画面に(個別に図 形を描画するよりも一般に高速な)メモリ転送を使って描くというものです。
4.3.2 sample07.java
の改変それでは,具体的にどのような改変をするか,以下に実際の例をあげてみました。元 のプログラムと比較するために,
sample07.java
を開いたら「名前を付けて保存」でsample07a.java
と変えてから異なる部分に注意して修正してみてください。ダブルバッファを使用したアニメーション(
sample07a.java
) 1 // 円軌道を運動するボールの軌跡4.3.
アニメーションプログラムの完成45
2 import java.applet.* ;3 import java.awt.* ; 4
5 public class sample07a extends Applet implements Runnable {
6 int t = 0 ; // 角度 (deg.)
7 double hankei = 150 ; // 軌道の半径 8 Thread th = null ;
9 Image backImg = null ; 10 Graphics backG = null ; 11
12 public void start() {
13 backImg = createImage(400,400) ; 14 backG = backImg.getGraphics() ; 15 paintBack() ;
16 repaint() ;
17 th = new Thread(this) ; 18 th.start() ;
19 } 20
21 public void run() { 22 while( th != null ) { 23 paintBack() ; 24 repaint() ; 25 t = (t+5)%360 ;
26 try {
27 th.sleep(10) ; // 10ms = 0.01秒停止
28 }
29 catch (InterruptedException e) {
30 }
31 }
32 } 33
34 public void paintBack() { 35 double a ;
36 int x, y ;
37 if( backG != null ) {
38 backG.setColor(Color.white) ; 39 backG.fillRect(10,10,380,380) ; 40 a = (double)t*Math.PI/180.0 ; 41 x = (int)( hankei * Math.cos(a) ) ; 42 y = (int)( hankei * Math.sin(a) ) ; 43 backG.setColor(Color.blue) ;
44 backG.fillOval( x+200-10, -y+200-10, 21, 21 ) ;
45 }
46 } 47
48 public void update(Graphics g) { 49 paint(g) ;
50 }
51 public void paint(Graphics g) {
52 if( backImg != null ) {
53 g.drawImage(backImg,0,0,this) ;
54 }
55 } 56 }
GUI部品による制御
余力のある方は,
GUI
部品を追加して,動きをとめたり再開するためのオプションを用 意してみましょう。boolean
型の変数(たとえばisRunning
)を共通変数として準備し,ボタンのイベント に対応して,リスナ内でisRunning
をtrue
またはfalse
を代入するようにしておきます。run()
にも手を加えて,角度を更新している部分をisRunning
がtrue
の時だけ実行す るように改造します。また,ボタンのラベルは,実行/停止の状態に応じて,
bt.setLabel("実行") ; bt.repaint() ;
などとすることで切替えが可能ですので,できるならボタンはひとつですますのがスマー トです。
これもできた方は,回転方向を変えるためのボタンを追加するなどいろいろ試してみま しょう。
ここでちょっと休憩