人工知能に関する断創録

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

フルスクリーンモード

Javaでもフルスクリーンモードのゲーム作れます。ここでは、フルスクリーンモードの簡単なサンプルを書いてみます。またBufferStrategyという技術も使ってるので合わせて解説します。

Javaでフルスクリーンがサポートされたのは1.4からなのでそれ以前のJavaがインストールされている場合はできません。最新のJavaに入れ替えてください。

ESCでプログラムは終了します。フルスクリーンだとxボタンがないので必ずキーボードで終了する方法を用意する必要があります。一般的にはESC、Ctrl+Cなどですかね。

fullscreen.jar

フルスクリーンモード

    // フルスクリーン用
    // BufferStrategyのバッファの数
    private static final int NUM_BUFFERS = 2;
    // フルスクリーンモードにするか?
    private boolean isFullScreenMode = true;
    private GraphicsDevice device;
    private BufferStrategy bufferStrategy;

    // デフォルトフレームサイズ
    private int width = 640;
    private int height = 480;

    public FullScreenTest() {
        setTitle("フルスクリーン");
        setBounds(0, 0, width, height);
        setResizable(false);
        setIgnoreRepaint(true); // paintイベントを無効化

        // フルスクリーンモードの初期化
        if (isFullScreenMode) {
            initFullScreen();
        }

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);

        // BufferStrategyの設定
        createBufferStrategy(NUM_BUFFERS);
        bufferStrategy = getBufferStrategy();

        // その他の初期化処理
    }

isFullScreenModeはフルスクリーンモードにするかのフラグです。trueにするとフルスクリーンモードになり、falseにするとウィンドウモードになります。プログラムをデバッグするときはウィンドウモードにしておくといろいろ便利です。

フルスクリーン化の流れは下のようになります。

  1. GraphicsDeviceの取得
  2. フルスクリーン化
  3. ディスプレイモードの変更
  4. BufferStrategyの設定

最初の3つの処理はinitFullScreen()にまとめています。

    /**
     * フルスクリーンモードの初期化
     * 
     */
    private void initFullScreen() {
        GraphicsEnvironment ge =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        device = ge.getDefaultScreenDevice();  // GraphicsDeviceの取得

        setUndecorated(true); // タイトルバー・ボーダー非表示

        // 必要ならマウスカーソルを消す
        Cursor cursor = Toolkit.getDefaultToolkit().createCustomCursor(
                new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB),
                new Point(), "");
        setCursor(cursor);

        if (!device.isFullScreenSupported()) {
            System.out.println("フルスクリーンモードはサポートされていません。");
            System.exit(0);
        }

        // フルスクリーン化!
        device.setFullScreenWindow(this);
        // 変更可能なディスプレイモードを表示
        showDisplayModes();
        // ディスプレイモードの変更はフルスクリーン化後
        // 変更可能なディスプレイモードしか使えない
        // 640x480,800x600,1024x768の32bitあたりが妥当
        setDisplayMode(1024, 768, 32);
        showCurrentMode();

        width = getBounds().width;
        height = getBounds().height;
    }

コメントに書いたとおりなので説明は不要ですかね。マウスカーソルは必要に応じて消してください。RPGなんかだとマウスカーソルはいらないですね。

ディスプレイモード

重要なことですが、ディスプレイモードの変更はsetFullScreenWindow()でフルスクリーン化した後に行います。逆にすると動かないので注意してください。ここら辺はちょっと直感に反していて混乱しました。

ここでは、1024x768で色は32bitのディスプレイモードにしています。もっと高解像度のディスプレイを使える方もいると思いますが、640x480、800x600、1024x768の32ビットモードのどれかを使うのが妥当だと思います。

setDisplayMode()はディスプレイモードを変更する自作メソッドです。

    /**
     * ディスプレイモードを設定
     * 
     * @param width
     * @param height
     * @param bitDepth
     */
    private void setDisplayMode(int width, int height, int bitDepth) {
        if (!device.isDisplayChangeSupported()) {
            System.out.println("ディスプレイモードの変更はサポートされていません。");
            return;
        }

        DisplayMode dm = new DisplayMode(width, height, bitDepth,
                DisplayMode.REFRESH_RATE_UNKNOWN);
        device.setDisplayMode(dm);
    }

とりあえずこれでフルスクリーン化できます。

BufferStrategyの利用

BufferStrategyというのは、画面描画の高度な技です。仕組み自体はダブルバッファリングと似ているのですが、ビデオメモリを利用し、ページフリッピングする点が異なります。ダブルバッファリングとBufferStrategyの違いを説明します。

ダブルバッファリングは、メインメモリ上にオフスクリーンバッファを作り、そこに描画します。オフスクリーンバッファへの描画は画面に表示されません。キャラクターやマップやらを全部描画し終わったらdrawImage()でオフスクリーンバッファを丸ごとビデオメモリにコピーして画面に表示します。こうすると描画過程が画面に表示されないためちらつきが起こりません。ただメインメモリからビデオメモリへのコピーに時間がかかって少し大変という問題があります。下図のようなイメージです。

f:id:aidiary:20090828223411g:plain

一方、BufferStrategyを使うとダブルバッファリングの問題が解決できます。BufferStrategyでは、2つのバッファをビデオメモリ上に作ります。1つはオンスクリーンバッファで画面に表示される領域、もう1つはオフスクリーンバッファで画面に表示されない領域です。ビデオポインタと呼ばれるポインタで指された方のバッファがオンスクリーンバッファになり画面に表示されます。ビデオポインタに指されてない方のバッファがオフスクリーンバッファになり描画対象になります。

2つのバッファは両方ともビデオメモリ上にあるためこのビデオポインタをぱたぱた切り替えるだけで一瞬で画面に表示することができます。ダブルバッファリングと異なり、画像をメインメモリからビデオメモリに転送する必要がないわけです。このビデオポインタの切替をページフリッピングと呼びます。

当然ですがキャラクターやマップやらの描画はオフスクリーンバッファに対して行うのでちらつきも生じません。下図のようなイメージです。

f:id:aidiary:20090828223412g:plain

じゃコードを見てみます。実装は難しそうに見えますが、BufferStrategyというクラスがほとんど面倒見てくれるため簡単です。

BufferStrategyオブジェクトの生成は、コンストラクタにあります。

    // BufferStrategyの設定
    createBufferStrategy(NUM_BUFFERS);
    bufferStrategy = getBufferStrategy();

NUM_BUFFERSはバッファの数です。普通は2つあれば十分だと思います。1つはオフスクリーンバッファ、もう1つはオンスクリーンバッファです。

ゲームループは、下のようになってます。アクティブレンダリングで解説したのとほとんど同じですが、paintScreen()をscreenUpdate()に名前を変えています。BufferStrategyでは、ビデオポインタの切替だけでpaintしてるわけではないためです。

    while (running) {
        gameUpdate();
        gameRender();
        screenUpdate();

        // 休止
    }

BufferStrategyに関係のあるのはgameRender()とscreenUpdate()です。

    /**
     * レンダリング
     * 
     */
    private void gameRender() {
        Graphics dbg = bufferStrategy.getDrawGraphics();

        // 背景
        dbg.setColor(Color.BLACK);
        dbg.fillRect(0, 0, width, height);

        // ボールの描画
        for (int i = 0; i < NUM_BALLS; i++) {
            ball[i].draw(dbg);
        }

        // FPSの描画
        dbg.setColor(Color.YELLOW);
        dbg.drawString("FPS: " + df.format(actualFPS), 4, 16);

        dbg.dispose();
    }

    /**
     * スクリーンの更新(BufferStrategyを使用)
     * 
     */
    private void screenUpdate() {
        if (!bufferStrategy.contentsLost()) {
            bufferStrategy.show();
        } else {
            System.out.println("Contents Lost");
        }
        Toolkit.getDefaultToolkit().sync();
    }

Graphicsデバイスの取得時にBufferStrategyクラスのgetDrawGraphics()メソッドで取得しています。これは、図中のオフスクリーンバッファの描画デバイスを取得してるわけです。gameRender()ではオフスクリーンバッファに対して描画します。

screenUpdate()では、BufferStrategyクラスのshow()メソッドでオンスクリーンバッファを画面に表示しています。ビデオポインタを切り替えてるわけです。