ウィンドウを表示する
前回で一応なんとか最初のゲームを作ることが出来ました。作った数字当てゲームの気に入らないところは、1行入力するまでプログラムの進行が止まってしまう点と、前回の入力とメッセージが画面に残ってしまう点です。コンソールで動かすプログラムなので仕方ないことにも思えますが、ゲームとしてみると致命的で、このようなものしか作れないとなるとなかなかやる気が出ません。
ncursesのようなライブラリを使うとコンソール画面の好きな場所に文字を表示したりすることが簡単に出来るようになって、ノスタルジックな雰囲気ながらもいくらか応答性のあるゲームを作ることが出来るようになります。今ゲームを作る理由はどちらかというとC++の練習のためであって、完成されたゲームを作ることが目的ではないので、ncursesのようなシンプルなライブラリを導入するのは理にかなっているように思えます。
しかし、コンソールはやっぱりコンソールであって、好き場所にピクセルを打ったりとか、三角形や円を描くとかいったゲームプログラムにとっての基本的なことを行うことすら難しいです。なので、駆け足になってしまいますがこの時点でウィンドウを表示するプログラムを作ってしまおうと思います。
ウィンドウを表示するにはX WindowやWayland、あるいはOSネイディブのシステムとして提供されている機能にアクセスする必要があります。こういったコードはきめ細やかな制御ができる反面、逐一ウィンドウを制御するコードを書かなければいけないので煩雑なコードになってしまいます。もう一つ問題としては、特定のプラットフォームでしか動かないコードになってしまう点です。こういう問題を回避するために、直接ウィンドウシステムを利用する代わりに、一枚層を挟む、というかGUIのためのライブラリを導入することにします。ライブラリを導入することの利点は、
- ある程度決まりきった処理はライブラリが行ってくれるので、クライアントのコードが簡潔になり、本来の興味の対象であるゲームのコードに集中できる。
- ライブラリがプラッフォームの差異を吸収してくれるので、クライントのコードは別のプラットフォームでも(理想的には)そのまま使えるようになる。
といったところでしょうか。逆に欠点は、
- ライブラリが巨大であると、プログラムに複雑性を持ち込んでしまう。
- ライブラリが何をやっているか分からずブラックボックス化してしまうと、プログラミングの学習の妨げになる。
という点が思いつきます。C++で使用できるGUIライブラリはかなり多くの数があります。有名なところだと、
- Qt
- GTK+
- wxWidgets
- FLTK
などがあります。ただ、これらはボタンやコンボボックスなどをうまく扱えるようになっていて、GUIアプリケーションを作るのに最も適したものです。今作ろとうしている初歩的なゲームにはそういったパーツは不要で、要はウィンドウを表示してその中に自由にグラフィクスを描画できればいいだけなので、もっとシンプルなものが求められます。この要望にマッチするSDLというライブラリがあります。SDLは2Dグラフィクスを扱え、キーボードやマウス、オーディオも扱えるので、ゲームに適しています。C++から利用するためのインターフェース(API、ライブラリの機能にアクセスするために定められた関数やデータ構造)も比較的シンプルで習得するのも簡単な方です。
SDLのインストール
ソースからビルドすることも出来ますが、今回はaptでインストールします。バージョンが1.2のものと2.0のものがあります。特に古いものを使う理由がないので2.0をインストールします。
sudo apt install libsdl2-dev
-devがつくことに注意です。-devがつかないのにはヘッダやライブラリが含まれません。他にlibsdl2-imageやlibsdl2-ttfなどは名前から判断して画像イメージやTTFフォントを扱うものだろうから必要になったときインストールするように覚えておきます。
インストールされたかどうか確認しておきます。
find /usr/lib -name 'libSDL2.*'
/usr/lib/x86_64-linux-gnu/libSDL2.so
/usr/lib/x86_64-linux-gnu/libSDL2.a
find /usr/include/ -name 'SDL.h'
/usr/include/SDL2/SDL.h
こうなっていれば使えるようになってるはずです。
ちゃんとリンクできるか試してみます。まずSDL2のヘッダをインクルードできるかと、ライブラリにリンクできるを確認するため以下のようなコードを作成します。
#include <SDL2/SDL.h>
int main()
{
}
このソースコードの名前をhellosdl.cppとでもしておきます。GCCでリンクするライブラリを指定してコンパイルするには-lname
のように-l
(小文字のエル)に続けてライブラリの名前を指定します。もしライブラリの名前がlibSDL2.soだったら、先頭のlibと末尾の.soは省略出来ます。
g++ hellosdl.cpp -lSDL2
ls -lt
合計 28
-rwxr-xr-x 1 userx userx 16464 12月 12 18:10 a.out
-rw-r--r-- 1 userx userx 347 12月 12 18:01 Makefile
-rw-r--r-- 1 userx userx 38 12月 12 17:56 hellosdl.cpp
うまくいったのでa.outが作成されました。今の段階では実行しても何も起きません。
SDLのウィンドウを表示させるコードを書く
さっそくウィンドウを表示させるコードを書いてみます。…といってもこれはいくら考えてもわからないので使い方を見ることします。SDLのドキュメントを検索すると初期化のコードが見つかりました。これを少し手直しして実行してみます。
#include <SDL2/SDL.h>
#include <iostream>
using namespace std;
int main()
{
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
cerr << "エラー: SDL_Init " << SDL_GetError() << '\n';
return 1;
}
SDL_Window* window = SDL_CreateWindow(
"Hello, SDL!", // ウィンドウタイトル
0, 0, // ウィンドウの位置 x座標とy座標
400, 400, // ウィンドウのサイズ 幅と高さ
SDL_WINDOW_SHOWN // フラグ
);
if (window == nullptr) {
cerr << "エラー: SDL_CreateWindow " << SDL_GetError() << '\n';
return 1;
}
SDL_Delay(3000);
SDL_DestroyWindow(window);
SDL_Quit();
}
実行してみると下のようになりました。
ちょっと分かりづらいですが一応ウィンドウは表示されています。クライアント領域(ウィンドウの内側)が透明で、おまけに左上隅に不気味な黒い四角形が出ています。最初の試みとしてはとりあえずウィンドウは表示されたのでまあOKだとしてもこれではあまりうれしくないです。この黒い四角形が出ないようにするのと、ウィンドウの中身を何かの色で塗りつぶすようにしておきたいところです。
色々試したのですが左上隅の黒い四角形は何なのか分かりませんでした。一度デスクトップを切り替えて、プログラムを全部終了してから実行すると表示されなくなりました。コードが原因ではなさそうでした。
ウィンドウの中身を塗りつぶすためには少しコードの追加が必要です。ここのチュートリアルを参考にして塗りつぶすコードを追加します。
#include <SDL2/SDL.h>
#include <iostream>
using namespace std;
int main()
{
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
cerr << "エラー: SDL_Init " << SDL_GetError() << '\n';
return 1;
}
SDL_Window* window = SDL_CreateWindow(
"Hello, SDL2!", // ウィンドウタイトル
0, 0, // ウィンドウの位置 x座標とy座標
400, 400, // ウィンドウのサイズ 幅と高さ
SDL_WINDOW_SHOWN // フラグ
);
if (window == nullptr) {
cerr << "エラー: SDL_CreateWindow " << SDL_GetError() << '\n';
return 1;
}
SDL_Surface* surface = SDL_GetWindowSurface(window);
SDL_FillRect(surface, nullptr, SDL_MapRGB(surface->format, 0, 0, 255)); // 青で塗りつぶす
SDL_UpdateWindowSurface(window);
SDL_Delay(3000);
SDL_DestroyWindow(window);
SDL_Quit();
}
なんとなくうまく行きそうなのですがダメでした。ウィンドウの中身は空っぽで透明のままです。SDL_UpdateWindowSurfaceが効いてないのかなと疑って、試しに次のように書いてみました。
...
SDL_Surface* surface = SDL_GetWindowSurface(window);
SDL_FillRect(surface, nullptr, SDL_MapRGB(surface->format, 0, 0, 255));
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
SDL_UpdateWindowSurface(window);
...
するとこうなりました。
青くはなりましたが半透明です。悪くないけど透過させるつもりはなく塗りつぶしたつもりなので意図したとおりにはなっていません。
今度はループでひたすらSDL_UpdateWindowSurfaceを呼び出すことにしてみます。
...
SDL_Surface* surface = SDL_GetWindowSurface(window);
SDL_FillRect(surface, nullptr, SDL_MapRGB(surface->format, 0, 0, 255));
while (1) {
SDL_UpdateWindowSurface(window);
SDL_Delay(10); // 10ミリ秒待つ
}
...
終了できなくなってしまいますが、うまくいきました。
少し気持ち悪い気もしますが、実際のゲームでは描画のコードはループの中に置かれることになるため、実用上の問題はないだろうということでこれ以上調べないことにします。
ともかく無事ウィンドウを表示することが出来ました。最後に、終了できないのだけ直しておきます。whileのところを次のようにします。
...
while (1) {
SDL_Event event;
if (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
break;
}
}
SDL_UpdateWindowSurface(window);
SDL_Delay(16); // 16ミリ秒待つ
}
...
ウィンドウの✘ボタンで終了できるようになりました。