ゲーム開発の備忘録

趣味のゲーム開発でのノウハウや、技術的に嵌ったポイントを忘れないように書き記しておくブログです。

ボーダーレスウィンドウ表示形式を実装する

はじめに

 今回は、ボーダーレスウィンドウ表示形式を実装する方法を紹介します。目的はとてもシンプルですが、意外と解説記事が無く試行錯誤での実装となりました。

 ボーダーレスウィンドウ表示形式がどのようなものかの説明は割愛しますが、最近のPCゲームではほぼほぼ搭載されているメジャーな画面表示形式かと思います。

実装方針

 今回はWindows APIのみで実装しています。最初はDirectX12(DXGI)のスワップチェーンやビューポートの設定を色々弄って実装することを考えていましたが、試した結果不可能なことが分かりました。なお、Windows APIそのものの知識はお持ちであることを前提にして話を進めますので、あまりよく分からないという読者の方は先に本やWebサイトで確認してみて下さい。

 Windowsゲームでは1つのゲームにつき1つのウィンドウという固定観念を持ちがちですが、1つのアプリケーションで複数ウィンドウを表示させることが可能です。そこで、ゲーム画面を表示するウィンドウの裏側に画面全体を覆う単色のウィンドウをもう1つ用意することで、ボーダーレスウィンドウ表示形式の余白部分を表現することにしました。基本はこれだけですが、色々と厄介な部分があります……
 それについてはおいおい触れていきます。

 では、早速2つのウィンドウの設定内容について見ていきましょう。

メインウィンドウ

 ゲーム画面を表示するウィンドウです。本記事ではメインウィンドウと表記します。こちらは従来用意していたウィンドウの設定をほぼそのまま使います。ボーダーレスウィンドウ表示形式では枠とタイトルバーを消すことになりますので、ウィンドウスタイルにはWS_POPUPのみを指定します。

 なお、ボーダーレスウィンドウ表示形式では、ウィンドウの幅や高さのどちらかはディスプレイの幅または高さに合わせることが多いでしょう。そのような場合はGetSystemMetrics(SM_CXSCREEN)、GetSystemMetrics(SM_CYSCREEN)でディスプレイの幅や高さを取得して、そこから計算すれば良いです。

背景ウィンドウ

 黒一色などの単色で塗りつぶし、ディスプレイいっぱいに描画することでボーダーレスウィンドウ表示形式の余白部分を表現します。本記事では背景ウィンドウと表記します。背景ウィンドウのウィンドウスタイルもWS_POPUPのみの指定で良く、ウィンドウの幅と高さはGetSystemMetrics(SM_CXSCREEN)、GetSystemMetrics(SM_CYSCREEN)をそれぞれ指定すれば問題ありません。しかし、ここからが厄介なポイントです。

 背景の塗りつぶしはウィンドウクラス登録時に以下のような記述で簡単に実現できます。

window.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));

 しかし、背景色の設定が上手く反映されない場合があります。そのような場合は、ウィンドウプロシージャでWM_ERASEBKGNDメッセージを揉み消していないかどうかを確認してください。メインウィンドウではWM_ERASEBKGNDメッセージを揉み消すことで背景のちらつきを抑えるというテクニックが使えるのですが、Windows10以降でブラシで背景色設定時にWM_ERASEBKGNDメッセージを揉み消すと背景設定が適用されないという謎の不具合があります。そのため、メインウィンドウのウィンドウプロシージャを流用せずに、WM_ERASEBKGNDメッセージをDefWindowProc()に渡すウィンドウプロシージャを用意して対処しましょう。これはWindows APIを内部で利用しているフレームワークゲームエンジン等でもGitHubのissueで報告されている不具合です……

 また、背景ウィンドウはメインウィンドウの親・子ウィンドウである必要はなく、単にもう一つウィンドウを用意すれば問題ありません。ただし、背景ウィンドウに操作のフォーカスを奪われるのは問題ですので、毎フレームSetForegroundWindow()関数を呼び出して、メインウィンドウをフォアグラウンドにするようにしましょう。また、常に背景ウィンドウがメインウィンドウの裏側に表示されるようにしたいので、SetWindowPos()関数の第2引数にHWND_BOTTOMを指定して実行しておきましょう。

タスクバーを隠す

 ボーダーレスウィンドウ表示ではタスクバーを隠さないと、タスクバーがウィンドウの上に表示されて不自然な見た目になってしまいます。タスクバーは内部的にはウィンドウとして扱われていますので、FindWindow()関数でウィンドウハンドルを取得し、ShowWindow()関数でSW_HIDEを指定すれば良いです。タスクバーのウィンドウ名はShell_TrayWndで固定です。以下にサンプルコードを示します。

HWND taskBarhWnd = FindWindow(L"Shell_TrayWnd", nullptr);
ShowWindow(taskBarhWnd, SW_HIDE);

 ゲーム終了時にタスクバーを再表示させないと、ゲームが終わってもタスクバーが表示されないままになってしまいますので注意しましょう。再表示の際はShowWindow()関数でSW_RESTOREを指定します。

ゲーム中でウィンドウ表示形式を変更する

 ゲーム中でボーダーレスウィンドウ表示形式と他の表示形式を切り替える場合は、背景ウィンドウの表示・非表示、タスクバーの表示・非表示を切り替えることで実現します。

おわりに

 お疲れ様でした。正直、やり方さえわかれば、ボーダーレスウィンドウ表示形式そのものの実装よりも、ウィンドウサイズや表示形式の切替に関する部分の設計・実装のほうが大変かとは思いますが、そちらは純粋な設計力さえあれば対応できると思います。※特にDirectX11on12でDirectWriteを利用している環境だと大変難しい……
 とはいえ、Windows APIの知識だけでモダンに見えるボーダーレスウィンドウ表示形式を実現できるのは大変魅力的かと思いますので、是非トライしてみて下さい!