Javaゲームプログラミング

シューティングゲームを作ってみようの連載7回目です。画面のちらつきを解消します。



弾の発射 その3

このページには、以下のサンプルを掲載しています。 下の項目をクリックをすると各サンプルにジャンプします。 ※2週間以内の新着記事はNewアイコン、更新記事はUpアイコンが表示されます。

前回は、自機を動かしながら弾を発射すると自機が止まってしまうという不具合の修正をしましたが、 まだ下のような不具合が残っています。
1.画面がちらついてしまう。
2.弾が画面のはしに行く前に消えているように見える。
まずは、画面のちらつきから修正したいと思います。これは、ゲームプログラミングではよくある現象で、画面描画 に画像処理が追いついていない場合などに発生します。前回のサンプルでは、10ミリ秒ごとにrepaintメソッドを呼び出し 画面の再描画を行っています。下は前回サンプルプログラムからrepaint呼び出しの部分を抜き出したものです。
■アプレットクラス (ShootGame.java)
  ...
   8. public class ShootGame extends JApplet implements KeyListener {
  ...
  ...
  20.   public void start(){
  21.     t = new Thread(){
  ...
  ...
  29.           try {
  30.             Thread.sleep(10);
  31.             repaint();
  ...
  ...
  ...
一方、画像処理では、paintメソッドの中で画面全体のクリアや自機の描画、弾の描画などを行っています。これらの処理を画面が再描画される間隔(ここでは、10ミリ秒) 以内で行わないと画像処理が完結しないまま(書きかけのまま)再描画が行われます。結果として画面がちらつくように見えてしまいます。
これを解消するには、ダブルバッファリングと言う手法を使います。ダブルバッファリングでは、実画面とは別にメモリ上に仮想の画面を用意します。描画処理は、 この仮想画面に対して行います。仮想画面上に全ての描画対象を書き終えた時点で(描画処理が完了した時点)仮想画面上のイメージを実画面に描画します。 この仮想画面から実画面への描画は、メモリーデータの転送だけですので処理時間的にはそれほどかかりません。 それでも画面のちらつきが100%解消するわけではないと思いますが、描画処理を行いながら直接実画面に描画データを書き込んでいくよりは大分良くなると思います。


それでは、前回のサンプルにダブルバッファリングの処理を加えてみます。
※赤字の部分が前回からの追加・変更点になります。
■アプレットクラス(ShootGame.java)
  1. import java.awt.Color;
  2. import java.awt.Graphics;
  3. import java.awt.Graphics2D;
  4. import java.awt.event.KeyEvent;
  5. import java.awt.event.KeyListener;
  6. import java.awt.image.BufferedImage;
  7. import javax.swing.JApplet;
  8. public class ShootGame extends JApplet implements KeyListener {
  9.   private static final long serialVersionUID = 1L;
  10.   public Gun gun = new Gun(87, 180);
  11.   public Bullet bullet;
  12.   public static Thread t;
  13.   BufferedImage bi;
  14.   Graphics2D offs;
  15.   int w, h;
  16.   public void init() {
  17.     addKeyListener(this);
  18.     w = getWidth();
  19.     h = getHeight();
  20.     bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
  21.     offs = (Graphics2D) bi.getGraphics();
  22.     offs.setBackground(Color.WHITE);
  23.   }
  24.   public void start(){
  25.     t = new Thread(){
  26.       public void run(){
  27.         Thread ct = Thread.currentThread();
  28.         while( t == ct ){
  29.           try {
  30.             Thread.sleep(10);
  31.             repaint();
  32.           } catch(Exception e) {
  33.             e.printStackTrace();  
  34.           }
  35.         }
  36.       }
  37.     };
  38.     t.start();
  39.   }
  40.   public void paint(Graphics g) {
  41.     //Graphics2D g2 = (Graphics2D)g; ←この行は削除
  42.     offs.clearRect(0,0,w,h);
  43.     gun.move();
  44.     gun.draw(offs);
  45.     if ( bullet != null ) {
  46.       if ( bullet.getBulletY() > -15 ) {
  47.         bullet.draw(offs);
  48.         bullet.setBulletY(bullet.getBulletY() - 5 );
  49.       } else {
  50.         bullet = null;
  51.       }
  52.     }
  53.     g.drawImage(bi, 0, 0, null);
  54.   }
  55.   public void update(Graphics g){
  56.     paint(g);
  57.   }
  58.   public void destroy() {
  59.     t = null;
  60.   }
  61.   public void keyPressed(KeyEvent e) {
  62.     if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
  63.       gun.setDirection(Gun.RIGHT_MOVE);
  64.     } else if (e.getKeyCode() == KeyEvent.VK_LEFT ) {
  65.       gun.setDirection(Gun.LEFT_MOVE);
  66.     } else if (e.getKeyCode() == KeyEvent.VK_SPACE ) {
  67.       bullet = bullet == null ? new Bullet(gun.getGun().x, gun.getGun().y) : bullet;
  68.     }
  69.   }
  70.   public void keyReleased(KeyEvent e) {
  71.     if ( e.getKeyCode() == KeyEvent.VK_RIGHT || 
  72.       e.getKeyCode() == KeyEvent.VK_LEFT ) {
  73.       gun.setDirection(Gun.STAY_HERE);
  74.     }
  75.   }
  76.   public void keyTyped(KeyEvent e) {
  77.   }
  78. }
6行目は、仮想画面用のイメージデータを保持するBufferedImageのインポート宣言を行っています。
17行目は、BufferedImageオブジェクトを保持するオブジェクト変数を定義しています。
18行目は、仮想画面への描画処理を行うためのGraphics2Dオブジェクトを保持するオブジェクト変数を定義しています。
23、24行目は、アプレット表示領域の横幅と縦幅の取得を行っています。取得した情報はint型のクラス変数に格納しています。
25行目で取得した縦横の幅を元にBufferedImageオブジェクトを生成しています。 26行目では、BufferedImageオブジェクトを元にGrahics2Dオブジェクトを生成しています。 27行目は、Graphics2Dオブジェクトを介して仮想画面の背景色の設定を行っています。 54行目、描画処理は、仮想画面に対して行うため、この行は不要となります。 55、57、60行目、描画処理は、全て仮想画面に対して行います。 66行目、ここで仮想画面のイメージデータの実画面上へのコピーを行います
実は、59行目の赤字部分も変更しているのですが、これは、ダブルバッファリングの対応ではありません。最初にあげた不具合のうち 「弾が画面のはしに行く前に消えているように見える」という不具合への対応となります。
弾の描画は、楕円を現すクラス(Ellipse2D)のオブジェクトで表現しています。この楕円の表示位置のX-Y座標は、楕円を囲む四角形の左上の座標となっています。 前回のサンプルでは、弾の表示位置のY座標が0より小さい場合に弾オブジェクトを消滅させています。ただこのY座標は弾の上端となるため弾が画面から消えきらないうちに 弾を消滅させています。結果として弾が途中で消えているように見えてしまっています。そこで弾の表示位置が画面外にあるかどうかの判定のときは、弾の縦幅分を考慮に入れ-15以下だった 場合に弾オブジェクトを消滅させています。


■自機クラス(Gun.java)
弾クラスは、第4回と同じです。変更はありません。
 →前回分を見たい方はここをクリック

■弾クラス(Bullet.java)
弾クラスは、第5回と同じです。変更はありません。
 →前回分を見たい方はここをクリック

下記のボタンを押すと今回のプログラムを実行してみることが出来ます。
※実行は、別ウィンドウで開きます。実行には時間がかかることがありますのでご注意ください。




最終更新日:2019/02/13

2015-03-01からの訪問者数
  2962 人