人工知能に関する断創録

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

弾の発射

プレイヤーが1発だけ弾を発射できるようにします。

invader02.jar

弾の実装

弾1つ1つをオブジェクトとして扱うためShotクラスを用意します。弾もプレイヤーと同じく下のような属性が必要になります。

    // 弾のスピード
    private static final int SPEED = 10;
    // 弾の保管座標(画面に表示されない場所)
    private static final Point STORAGE = new Point(-20, -20);
    // 弾の位置(x座標)
    private int x;
    // 弾の位置(y座標)
    private int y;
    // 弾の幅
    private int width;
    // 弾の高さ
    private int height;
    // 弾の画像
    private Image image;

弾の保管座標というのは、プレイヤーが撃っていない弾を置いておく場所です。(-20,-20)と画面外の座標を指定しておくのがミソです。Shotのコンストラクタを見てください。

    public Shot(MainPanel panel) {
        x = STORAGE.x;
        y = STORAGE.y;

        ・・・
    }

弾オブジェクトの位置は保管庫の座標(画面外の座標)で初期化しています。つまり作られた弾オブジェクトは保管庫に入るため画面に表示されません。なぜこんな作り方をしているかは後で解説します。

次にmove()です。

    /**
     * 弾を移動する
     *  
     */
    public void move() {
        // 保管庫に入っているなら何もしない
        if (isInStorage())
            return;

        // 弾はy方向にしか移動しない
        y -= SPEED;
        // 画面外の弾は保管庫行き
        if (y < 0) {
            store();
        }
    }

プレイヤーの移動とほとんど同じですね。SPEED分だけ弾のy座標を更新しています。こうすると弾は上に飛んでいきます。もし画面外(一番上はy=0)に出たらstore()を呼び出してこの弾を保管庫に移動しています。つまり環境を考慮してリサイクルするわけです。

    /**
     * 弾を保管庫に入れる
     *  
     */
    public void store() {
        x = STORAGE.x;
        y = STORAGE.y;
    }

あと保管庫に入っている弾は移動させないでそのままreturnしています。保管庫に入ってるのに移動されちゃ困りますし。

弾の発射

次に弾の発射処理を解説します。MainPanelクラスを見てください。弾の発射ボタンはスペースキーです。フラグfirePressedを追加してスペースキーが押されていたらtrue、離されたらfalseになるようにしています。

ゲームループrun()を見てください。

    while (true) {
        // 発射ボタンが押されたら弾を発射
        if (firePressed) {
            if (shot.isInStorage()) {
                // 弾が保管庫にあれば発射できる
                // 弾の座標をプレイヤーの座標にすれば発射される
                Point pos = player.getPos();
                shot.setPos(pos.x + player.getWidth() / 2, pos.y);
            }
        }

        // 弾を移動する
        shot.move();
        
        ・・・
    }

もしスペースキーが押されていてfirePressedがtrueになっていたら弾を発射しています。ただし、弾が発射されるのは保管庫に弾がある場合のみです。今は1発しか弾オブジェクトを作っていないので1発しか撃てません。

発射する弾はプレイヤーの位置にセットします。あとはゲームループ内でmove()を呼ぶだけでまっすぐ上に飛んでいきます。move()内で「保管庫にある弾は移動しない」という条件チェックをしているのでここではチェックする必要はありません。

このプログラムでは弾オブジェクトはコンストラクタで最初に作ってしまっています。

    public MainPanel() {
        ・・・

        // プレイヤーを作成
        player = new Player(0, HEIGHT - 20, this);

        // 弾を作成
        shot = new Shot(this);

        ・・・
    }

ここで「なぜスペースキーを押して弾が発射されるときにShotオブジェクトを作らないのか?」と疑問に思った方がいると思います。発射するときに弾オブジェクトを作り、敵に当たったらオブジェクトを消滅させる方がどう考えても自然でしょう。

そうしなかった理由は弾を発射するたびにいちいちオブジェクトを作っていては処理が遅くなってしまうからです。1 発ではまだいいですが連続して発射する場合のオブジェクト生成コストは大きいです。こういう場合は予め弾を全部作って用意しておきます。その際、作られた弾が画面に表示されないように画面外の「保管庫」を用意したわけです。この方法はスピードは速いですがメモリを消費するという欠点があります。スピードを取るかメモリを取るか、それが問題です。

次は弾をたくさん撃てるようにします。オブジェクト指向の便利さが身にしみてわかります。