画像を動かす練習
こんにちは。「Javaを復習する初心者」です。GUIプログラミングでゲームを作る場合、画像を表示させて動かすことがあると思います。今回はサンプル画像を表示して動くように表示するということをしました。
星を表示させる
星を表示させて動かしてみようと思うので、最初に星の画像を表示させることをしました。star.pngという画像ファイルを用意して、フレームに表示させることをしてみました。
import java.awt.Graphics; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; import javax.swing.JFrame; public class MyJFrame extends JFrame { private final static int FRAME_LENGTH = 300; private final static String IMAGE_FILE_NAME ="star.png"; private BufferedImage bufferedImage = null; public static void main(String[] args) { new MyJFrame(); } public MyJFrame() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(FRAME_LENGTH, FRAME_LENGTH); // 星を読み込む try (InputStream inputStream = this.getClass().getResourceAsStream(IMAGE_FILE_NAME);){ bufferedImage = ImageIO.read(inputStream); } catch (IOException e) { e.printStackTrace(); } // 表示 setVisible(true); } public void paint(Graphics g) { g.drawImage(getScreen(), 0, 0, this); } private Image getScreen() { Image screen = createImage(FRAME_LENGTH, FRAME_LENGTH); Graphics g = screen.getGraphics(); g.drawImage(bufferedImage, 50, 50, this); return screen; } }
画像ファイルはクラスファイルと同じフォルダに格納しています。ClassクラスのgetResourceAsStream()メソッドで指定した画像ファイルのInputStreamインスタンスを取得することができます。ImageIOクラスのread()にそのインスタンスを指定することで、BufferedImageインスタンスを取得することができます。
BufferedImageはjava.awt.Imageを継承しています。そのため、GraphicsクラスのdrawImage()の引数にしていすることができます。
上記プログラムではフレームに直接、画像を描画することはしていません。いったん、バックスクリーンを用意して、そこに画像を描画しています。のちに画像を動かすことを考え、ちらつき防止のためにバックスクリーンを使うようにしました。
左上から右下に動かしてみる
フレームの左上から右下に移動させることをしてみました。タイマーを使って移動させることにしました。
import java.awt.Graphics; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.util.Timer; import java.util.TimerTask; import javax.imageio.ImageIO; import javax.swing.JFrame; public class MyJFrame extends JFrame { private final static int FRAME_LENGTH = 300; private final static String IMAGE_FILE_NAME ="star.png"; private BufferedImage bufferedImage = null; /** 星 位置 */ private int starX = 0; private int starY = 0; public static void main(String[] args) { new MyJFrame(); } public MyJFrame() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(FRAME_LENGTH, FRAME_LENGTH); // 星を読み込む try (InputStream inputStream = this.getClass().getResourceAsStream(IMAGE_FILE_NAME);){ bufferedImage = ImageIO.read(inputStream); } catch (IOException e) { e.printStackTrace(); } // 星の位置初期化 starX = -bufferedImage.getWidth(); starY = -bufferedImage.getHeight(); // タイマー Timer timer = new java.util.Timer(); timer.schedule(new MyTimeTask(), 1l, 10l); // 表示 setVisible(true); } public void paint(Graphics g) { g.drawImage(getScreen(), 0, 0, this); } private Image getScreen() { Image screen = createImage(FRAME_LENGTH, FRAME_LENGTH); // 星の移動 if (starX <= FRAME_LENGTH && starY <= FRAME_LENGTH ) { starX ++; starY ++; Graphics g = screen.getGraphics(); g.drawImage(bufferedImage, starX, starY, this); } else { starX = -bufferedImage.getWidth(); starY = -bufferedImage.getHeight(); } return screen; } private class MyTimeTask extends TimerTask { @Override public void run() { repaint(); } } }
メンバ変数にint型変数starXとstarYを定義しました。星の画像の表示の指定で、左上の座標を(starX, starY)にするという考え方です。星の画像を読み込んだ直後で、starXとstarYの値を設定しています。これはちょうど画像の右下が画面左上になって、画像が画面の外に出ている状態です。
画像描画時ではstarXとstarYをそれぞれ「+1」してから描画しています。タイマーでrepaint()メソッドを繰り返し呼び出すことで星が移動しているように描画されました。
回転
星を移動させることをしましたが、今度は星をその場で回転させることをしてみようと思いました。AffineTransformクラスというのがあり、これを使って回転が簡単にできました。以下のプログラムでは星をその場で回転させてます。
import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.util.Timer; import java.util.TimerTask; import javax.imageio.ImageIO; import javax.swing.JFrame; public class MyJFrame extends JFrame { private final static int FRAME_LENGTH = 300; private final static String IMAGE_FILE_NAME ="star.png"; private BufferedImage bufferedImage = null; /** 星 回転 */ private int degrees = 0; public static void main(String[] args) { new MyJFrame(); } public MyJFrame() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(FRAME_LENGTH, FRAME_LENGTH); // 星を読み込む try (InputStream inputStream = this.getClass().getResourceAsStream(IMAGE_FILE_NAME);){ bufferedImage = ImageIO.read(inputStream); } catch (IOException e) { e.printStackTrace(); } // タイマー Timer timer = new java.util.Timer(); timer.schedule(new MyTimeTask(), 1l, 10l); // 表示 setVisible(true); } public void paint(Graphics g) { g.drawImage(getScreen(), 0, 0, this); } private Image getScreen() { Image image = createImage(bufferedImage.getWidth(), bufferedImage.getHeight()); Graphics2D g2 = (Graphics2D) image.getGraphics(); AffineTransform affineTransform = new AffineTransform(); degrees ++; affineTransform.rotate(degrees * Math.PI / 180, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2); g2.setTransform(affineTransform); g2.drawImage(bufferedImage, 0, 0, this); Image screen = createImage(FRAME_LENGTH, FRAME_LENGTH); Graphics g = screen.getGraphics(); g.drawImage(image, 50, 50, this); return screen; } private class MyTimeTask extends TimerTask { @Override public void run() { repaint(); } } }
画像の表示位置は左上の座標(50, 50)固定にしました。この位置で星を回転させることをしています。
回転のさせるための箇所は以下のような処理の流れです。
- Imageクラスのインスタンスを生成する。
- 上記インスタンスに対して、Graphics2Dクラスのインスタンスを取得する。
- AffineTransformクラスのインスタンスを生成する。
- AffineTransformクラスのrotate()メソッドを呼び出す。
- Graphics2DクラスのsetTransform()メソッドの引数に上記インスタンスを指定する。
- Graphics2DクラスのdrawImage()メソッドを呼び出す。
Imageクラスのインスタンスを生成して、そこに画像を描画するという考え方です。座標変換をすることで、画像を回転させて描画することができます。
Graphics2Dクラスに定義されているsetTransform()メソッドで座標を変換することができます。このメソッドで指定するのがAffineTransformのインスタンスです。このクラスにはrotate()メソッドがいくつか用意されています。今回使ったのは回転角度と原点を引数で指定するメソッドです。原点は、imageオブジェクトの中心になるようにしました。そうすることで、のちのdrawImage()メソッドを呼び出すときの引数が簡単になりました。
回転して移動させる
ここまでくると、回転させて移動させるは簡単です。drawImage()メソッドの引数に変数を使えばよいです。
g.drawImage(image, starX, starY, this);
描画するごとにstarXとstarYを変化させることで、回転して移動することができます。描画ごとにstarXとstarYを「+1」するようにした場合、左上から右下へ回転しながら移動します。
今回は座標を「+1」するだけだったので斜めの移動だけですが、工夫すれば様々な軌道を動くように出来ると思います。