人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ラケットでボールを打ち返す

ボールとラケットの当たり判定処理を入れてボールがラケットに当たったとき跳ね返るようにします。ブロックがなくてボールをただ打ち返して対戦するゲームは「ぽん」っていいますが、これは1人用の「ぽん」に相当しますね。ブロック崩しではなくぽんを作るのも面白いかも。

breakout03.jar

まずMainPanelクラスを見てみます。

    /**
     * ゲームループ
     * 
     */
    public void run() {
        while (true) {
            // ボールの移動
            ball.move();

            // もしラケットと衝突したらボールをバウンド
            if (racket.collideWith(ball)) {
                ball.boundY();
            }

            repaint();

            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

ボールを移動した後にラケットとボールが衝突したらボールをY方向にバウンドします。Y方向にバウンドすると下に移動していたボールが上に移動します。衝突の判定はracket.collideWith(ball)です。ラケットにボールが当たるとtrueを返します。

次にcollideWith()を見てみます。Racketクラスです。

    /**
     * ボールに当たったらtrueを返す
     * 
     * @param ball ボール
     * @return ボールに当たったらtrue
     */
    public boolean collideWith(Ball ball) {
        // ラケットの矩形
        Rectangle racketRect = new Rectangle(
                centerPos - WIDTH / 2, MainPanel.HEIGHT - HEIGHT,
                WIDTH, HEIGHT);
        // ボールの矩形
        Rectangle ballRect = new Rectangle(
                ball.getX(), ball.getY(),
                ball.getSize(), ball.getSize());

        // ラケットとボールの矩形領域が重なったら当たっている
        if (racketRect.intersects(ballRect)) {
            return true;
        }

        return false;
    }

ラケットとボールの衝突判定は、ラケットとボールの矩形が重なったかどうかで判定しています。 racketRectはラケットの矩形、ballRectはボールの矩形です。ボールは円形ですが、円を囲む四角形としています。2つの矩形が重なっているかはRectangleクラスのintersects()メソッドでわかります。矩形が重なった場合、trueを返します。

跳ね回るボール

ブロックを壊すボールを実装します。ボールは壁に当たったら跳ね返ります。実行するとわかりますがラケットに当てなくてもボールが跳ね返るようになっています。次回はボールとラケットの当たり判定処理を入れてボールがラケットに当たったとき跳ね返るようにします。

breakout02.jar

ボールクラスです。ボールの実装はボールを動かす(2004/9/19)とボールが跳ね返る処理(2004/9/20)で詳しく解説したので見てください。

public class Ball {
    // サイズ
    private static final int SIZE = 8;

    // 位置(ボールを囲む矩形の左上隅)
    private int x, y;
    // 速度
    private int vx, vy;

    // 乱数生成器
    private Random rand;

    public Ball() {
        rand = new Random(System.currentTimeMillis());

        // 位置を初期化
        x = rand.nextInt(MainPanel.WIDTH - SIZE);
        y = 0;

        // 速度を初期化(とりあえず固定)
        vx = 5;
        vy = 5;
    }

    /**
     * ボールを描画
     * 
     * @param g
     */
    public void draw(Graphics g) {
        g.setColor(Color.YELLOW);
        g.fillOval(x, y, SIZE, SIZE);
    }

    /**
     * ボールの移動
     * 
     */
    public void move() {
        x += vx;
        y += vy;

        // 左右の壁にぶつかった場合にバウンド
        if (x < 0 || x > MainPanel.WIDTH - SIZE) {
            vx = -vx;
        }

        // 上下の壁にぶつかった場合にバウンド
        if (y < 0 || y > MainPanel.HEIGHT - SIZE) {
            vy = -vy;
        }
    }
}

ラケットを動かす

マウスの動きに合わせてラケットが左右に動くプログラムから作ります。

breakout01.jar

マウス座標の取得

マウスの動きに反応するプログラムを作るにはMouseMotionListenerを使います。下はMainPanel.javaの抜粋です。

public class MainPanel extends JPanel implements MouseMotionListener {
    private Racket racket; // ラケット

    public MainPanel() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        addMouseMotionListener(this);

        // ラケットを作成
        racket = new Racket();
    }

    // マウスを動かしたとき呼び出される
    public void mouseMoved(MouseEvent e) {
        int x = e.getX(); // マウスのX座標
        racket.move(x); // ラケットを移動
        repaint();
    }

    // マウスをドラッグしたとき呼び出される
    public void mouseDragged(MouseEvent e) {
    }
}

MainPanelクラスにMouseMotionListenerをimplementsしてマウスの動きを感知できるようにしています。またaddMouseMotionListener(this)も忘れずに呼び出す必要があります。ここらはマウスクリックと非常によく似ています。マウスクリック(2004/10/16)の場合は、マウスのクリックに反応するのに対し、今回はマウスの動きに反応します

マウスの動きに対して何をするかはmouseMoved()とmouseDragged()に処理を書きます。mouseMoved()はマウスを動かしたときの反応を書く場所でmouseDragged()はマウスをドラッグしたときの反応を書く場所です。今回はmouseMoved()だけ実装します。内容は簡単でマウスの座標を取得し、マウスの位置にラケットを移動するだけです。ただし、移動するのはX方向(横方向)だけです

Racketクラス

ボールを打ち返すラケットのクラスです。

public class Racket {
    // ラケットのサイズ
    public static final int WIDTH = 80;
    public static final int HEIGHT = 5;

    // ラケットの中心位置
    private int centerPos;

    public Racket() {
        // ラケットの位置を画面の真ん中で初期化
        centerPos = MainPanel.WIDTH / 2;
    }

    /**
     * ラケットの描画
     * 
     * @param g
     */
    public void draw(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(centerPos - WIDTH / 2, MainPanel.HEIGHT - HEIGHT,
                   WIDTH, HEIGHT);
    }

    /**
     * ラケットの移動
     * 
     * @param pos 移動先座標
     */
    public void move(int pos) {
        centerPos = pos;

        // ラケットが画面の端から飛び出ないようにする
        if (centerPos < WIDTH / 2) { // 左端
            centerPos = WIDTH / 2;
        } else if (centerPos > MainPanel.WIDTH - WIDTH / 2) { // 右端
            centerPos = MainPanel.WIDTH - WIDTH / 2;
        }
    }
}

ラケットの位置はcenterPosです。これは、ラケットの真ん中のX座標を表しているので注意してください。長方形の左上のX座標ではないです。またラケットは常に画面の下にあるのでY座標は特に必要ないです。ラケットを移動するメソッドmove()ではラケットが画面の外に飛び出さないようにしています。

今回はマウスで動かすようにしますがキーボードの左右キーで動かせるように拡張してみるのもよいかと思います。