java-beginner.com ブログ

プログラミングを学習するブログ(Javaをメインに)

当たり判定の練習

投稿日:

最終更新日:2018年01月22日

アイキャッチ

こんにちは。「Javaを復習する初心者」です。

GUIプログラミングでは、領域同士の重なりを判定することがあると思います。特にゲームを作る場合に判定することが多いでしょう。今回は単純な当たり判定をする練習をしてみました。

マウスポインタで長方形を描画

長方形がマウスで動いて、画像と重なりを判定するということをやってみようと思いました。

まずは、マウスポインタを起点にして長方形を描画することをしました。

ソース

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;

public class MyJFrame extends JFrame {

    private final static int WIDTH  = 600;
    private final static int HEIGHT = 600;

    private final static int WIDTH_OF_RECTANGLE  = 50;
    private final static int HEIGHT_OF_RECTANGLE = 50;

    private int x = 0;
    private int y = 0;

    public static void main(String[] args) {
        new MyJFrame();
    }

    public MyJFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setSize(WIDTH, HEIGHT);

        // リスナー
        MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
        addMouseMotionListener(myMouseAdapter);

        // 表示
        setVisible(true);
    }

    public void paint(Graphics g) {

        Image screen = createImage(WIDTH, HEIGHT);
        Graphics screenGraphics = screen.getGraphics();

        screenGraphics.drawRect(x, y, WIDTH_OF_RECTANGLE, HEIGHT_OF_RECTANGLE);

        g.drawImage(screen, 0, 0, this);
    }

    private class MyMouseAdapter extends MouseAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            // 座標取得
            x = e.getX();
            y = e.getY();

            // 描画する
            repaint();
        }
    }
}

マウスを動かしたときに、以下の動きをしています。

  1. mouseMoved()メソッドで座標用メンバ変数にマウスの座標を格納する。
  2. repaint()メソッドを呼び出す。
  3. paint()メソッドでは以下の処理をする。
    1. オフスクリーンを用意する
    2. 座標用メンバ変数を左上にして、オフスクリーンに長方形を描画する。
    3. オフスクリーンでフレーム全体を上書きする。

ちらつき防止のため、ダブル・バッファリングという方式を使いました。paint()メソッドでcreateImage()メソッドでオフクリーン用のImageオブジェクトを生成しています。createImage()メソッドはjava.awt.Componentクラスで定義されています。今回使ったメソッドでは第1引数、第2引数がそれぞれ横幅、高さを指定します。

java.awt.ImageではgetGraphics()が定義されていて、Graphicsオブジェクトが返却されます。このオブジェクトのメソッドを使ってオフスクリーンに描画することが出来ます。drawRect()メソッドを使って矩形の輪郭を描画しています。

マウスを動かすと四角も動くという見た目になってます。

単純な当たり判定

画像を表示させて、当たり判定をするプログラミングをしてみました。

ソース

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
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 WIDTH  = 600;
    private final static int HEIGHT = 600;

    private final static int X_IMAGE = 200;
    private final static int Y_IMAGE = 200;

    private final static int WIDTH_OF_RECTANGLE  = 50;
    private final static int HEIGHT_OF_RECTANGLE = 50;

    private int x = 0;
    private int y = 0;

    private BufferedImage image = null;

    boolean flag_hit = false;

    public static void main(String[] args) {
        new MyJFrame();
    }

    public MyJFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setSize(WIDTH, HEIGHT);

        // リスナー
        MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
        addMouseMotionListener(myMouseAdapter);

        // 素材読み込み
        try (InputStream inputStream = getClass().getResourceAsStream("penguin.png");) {
            image = ImageIO.read(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 表示
        setVisible(true);
    }

    public void paint(Graphics g) {

        Image screen = createImage(WIDTH, HEIGHT);
        Graphics screenGraphics = screen.getGraphics();

        // ペンギン描画
        screenGraphics.drawImage(image, X_IMAGE, Y_IMAGE, this);

        // 長方形描画
        if (flag_hit) {
            screenGraphics.setColor(Color.red);
        } else {
            screenGraphics.setColor(Color.black);
        }
        screenGraphics.drawRect(x, y, WIDTH_OF_RECTANGLE, HEIGHT_OF_RECTANGLE);

        g.drawImage(screen, 0, 0, this);
    }

    private class MyMouseAdapter extends MouseAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            // 座標取得
            x = e.getX();
            y = e.getY();

            // 下限、上限
            int x_lower_limit = X_IMAGE - WIDTH_OF_RECTANGLE;
            int x_upper_limit = X_IMAGE + image.getWidth();

            int y_lower_limit = Y_IMAGE - HEIGHT_OF_RECTANGLE;
            int y_upper_limit = Y_IMAGE + image.getHeight();

            // ヒット判定
            flag_hit =  x_lower_limit < x && x < x_upper_limit
                    && y_lower_limit < y && y < y_upper_limit;

            // 描画する
            repaint();
        }
    }
}

ペンギンの画像を表示させて、当たり判定をしています。ただし、画像の長方形領域での当たり判定です。flag_hitに結果を格納しています。マウスポインタを基準した長方形と、画像の長方形同士が重なっているかどうかを判定しています。x座標とy座標がそれぞれ上限と下限の間にあるかどうかを不等式で表しています。もし、複雑な図形にするならもっと式が増えます。

画像と重なると赤い四角が表示されます。

Polygonクラスを使う

Polygonクラスにはintersects()メソッドが定義されていて、指定した長方形領域と重なっているかを判定してくれます。今回はPolygonクラスを使って、少しだけ複雑な図形にして、当たり判定を試してみました。

ソース

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class MyJFrame extends JFrame {

    private final static int WIDTH  = 600;
    private final static int HEIGHT = 600;

    private final static int X_IMAGE = 200;
    private final static int Y_IMAGE = 200;

    private final static int WIDTH_OF_RECTANGLE  = 25;
    private final static int HEIGHT_OF_RECTANGLE = 25;

    private BufferedImage image = null;
    private Polygon polygon = null;

    private Rectangle rectangle = new Rectangle(WIDTH_OF_RECTANGLE, HEIGHT_OF_RECTANGLE);

    public static void main(String[] args) {
        new MyJFrame();
    }

    public MyJFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setSize(WIDTH, HEIGHT);

        // リスナー
        MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
        addMouseMotionListener(myMouseAdapter);

        // ペンギン読み込み
        try (InputStream inputStream = getClass().getResourceAsStream("penguin.png");) {
            image = ImageIO.read(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // ---------------------------------------------
        // ペンギンに対応する当たり判定用領域を作成 開始
        // ---------------------------------------------
        ArrayList<Integer> xPoints = new ArrayList<>();
        ArrayList<Integer> yPoints = new ArrayList<>();
        int px, py;
        // 左下を起点
        px = X_IMAGE;
        py = Y_IMAGE + image.getHeight();
        xPoints.add(px);
        yPoints.add(py);
        // 右に移動
        px = px + image.getWidth();
        xPoints.add(px);
        yPoints.add(py);
        // 上に移動
        py = py - image.getHeight() + 50;
        xPoints.add(px);
        yPoints.add(py);
        // 左に移動
        px = px - 50;
        xPoints.add(px);
        yPoints.add(py);
        // 上に移動
        py = py - 50;
        xPoints.add(px);
        yPoints.add(py);
        // 左に移動
        px = px - image.getWidth() + 100;
        xPoints.add(px);
        yPoints.add(py);
        // 下に移動
        py = py + 50;
        xPoints.add(px);
        yPoints.add(py);
        // 左に移動
        px = px - 50;
        xPoints.add(px);
        yPoints.add(py);

        polygon = createPolygon(xPoints, yPoints, xPoints.size());
        // ---------------------------------------------
        // ペンギンに対応する当たり判定用領域を作成 修了
        // ---------------------------------------------

        // 表示
        setVisible(true);
    }

    private Polygon createPolygon(List<Integer> xPoints, List<Integer> yPoints, int length) {

        int[] x = new int[length];
        int[] y = new int[length];

        for (int i = 0; i < length; i++) {
            x[i] = xPoints.get(i);
            y[i] = yPoints.get(i);
        }

        return new Polygon(x, y, length);
    }

    public void paint(Graphics g) {
        g.drawImage(getScreen(), 0, 0, this);
    }

    private Image getScreen() {
        Image screen = createImage(WIDTH, HEIGHT);
        Graphics2D g = (Graphics2D)screen.getGraphics();

        // ペンギン描画
        g.drawImage(image, X_IMAGE, Y_IMAGE, this);

        // TODO デバッグ用
        g.setColor(Color.black);
        g.draw(polygon);

        // 長方形描画
        if (polygon.intersects(rectangle)) {
            g.setColor(Color.red);
        } else {
            g.setColor(Color.black);
        }
        g.draw(rectangle);

        return screen;
    }

    private class MyMouseAdapter extends MouseAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            // 長方形の位置変更
            rectangle.setLocation(e.getX(), e.getY());

            // 描画する
            repaint();
        }
    }
}

Polygonクラスの以下のメソッドを使うことにしました。

    intersects(Rectangle2D r)

引数にはRectangleオブジェクトを指定することが出来ます。そのため、メンバ変数にRectangleオブジェクトを格納して、マウスが動いたときはそのオブジェクトの位置を変更するということをしています。

Polygonオブジェクトを使って、凸型の領域を定義しています。画像のペンギンの形に少し近づいています。引数を持つコンストラクタは以下のみ定義されています。

    Polygon(int[] xpoints, int[] ypoints, int npoints)

点の集合を引数に配列として指定するというもののようです。配列に格納された座標の順番で直線が引かれるという動きをしました。今回はArrayListに座標を格納して、配列に直してコンストラクタを呼び出すということをしています。座標の指定が難しく感じたので、タートルグラフィックス的な考えで座標をArrayListに格納するということをしました。もっと良い記述があるかもしれません。

Polygonオブジェクトにヒットした場合、マウスポインタを起点とした長方形を赤く表示しています。