◆◆ ウィンドウイベント ◆◆
SFML2.3 非公式日本語翻訳
さぁ、たくさんあるウィンドウイベントを、くわしく、こってりと、手取り足取り書き連ねるよ!
イベントの種類は、そりゃもう、たくさんあるわけで、いかに使うか? そして使わないか? それが生き残る秘訣でござる。
謎のデータ型「sf::Event」
さて、本題に入る前に「sf::Event」というデータ型について知っておきましょう。
「sf::Event」は、
共用体 です。キョウヨウタイ。union。えーっと、何でしたっけ、それ?
さぁ、C++のレッスンを思い出すんだ。
共用体ってのは、内部のメンバーが同じメモリ空間を共有してて……
つまり、複数あるメンバー変数のうち、同時に有効なのは、1つだけ、というもの。
で、そのとき発生してるイベントに関連するデータだけが有効になっている、ということ。
たとえば event.key っていうメンバー変数があるんですが、
この変数は KeyPressイベントが発生したときに有効になります。
このとき他のメンバーは無効なアドレスを指してるわけで、アクセスするとパルプンテです。発生してるイベントに無関係なデータはお触り厳禁。
sf::Event のインスタンスに値が設定されるのは、sf::Windowクラスの pollEvent(または waitEvent)メソッドを呼んだときです。
この2つのメソッドだけが、有効なイベントデータを生成できます。
pollEvent(または waitEvent)を通さずに sf::Event を使おうとする悪しき企ては
全てパルプンテの餌食になる運命なのですよー。キャー怖い!
そんなわけで、イベントループのサンプルは、こんな感じになります。
sf::Event event;
// 未処理のイベントが残っている間、ループ
while (window.pollEvent(event))
{
// イベントのタイプをチェック
switch (event.type)
{
// 閉じるボタン押下イベントの場合
case sf::Event::Closed:
window.close();
break;
// キー押下イベントの場合
case sf::Event::KeyPressed:
...
break;
// 上記以外のイベントは処理しない。
default:
break;
}
}
上の文章をもういっぺん読んで、しっかりと頭に叩き込んでおくように。
sf::Event 共用体は、うっかりなプログラマーにいたずらをするのが大好きなのじゃ!
では、前置きはここまで。
SFMLがどんなイベントをサポートしてるのか? どんな意味で、どう使えばいいのか?
今のキミならたぶん理解できる!
イベントの種類:ウィンドウ閉じる
ユーザーがウィンドウを閉じようとしたとき、sf::Event::Closedイベントが発生します。
いわゆる「閉じるボタン」だけじゃなく、キーボードショートカットとか、その他、OS がサポートしてる閉じる操作全部が対象だよ。
このイベントが意味してるのは「ユーザーがウィンドウを閉じる要求を出した」ってことだけです。
実際には、まだ閉じてない状態。
window.close() を呼べば実際にウィンドウが閉じます。
なので、このイベントに対する対応としては、単に window.close()を実行、というのがよくあるパターンです。
が、他にも何かしておくといいかもですよ。たとえば、データをセーブするとか、ユーザーに確認メッセージを出すとか。
何もしなければ、ウィンドウは開いたままだよ。
sf::Event共用体の中に、このイベントに関連するデータはありません。
// 単に閉じるサンプル
if (event.type == sf::Event::Closed) {
/*
つまりここで閉じる前の確認メッセージを出してみるとか。
*/
// 閉じる
window.close();
}
イベントの種類:ウィンドウサイズ変更
ウィンドウのサイズが変更されたとき、sf::Event::Resizedイベントが発生します。
ユーザーが操作したときも発生しますし、プログラマーが window.setSize なんかを呼んで内部的にサイズ変更したときも発生します。
このイベントをキャッチして、レンダリングの設定を再設定するとよいです。
つまり、OpenGL を使ってる場合だと、ビューポート。 SFML のグラフィックスモジュールならカメラというか視点というか、ビュー(view)ですね。
イベント共用体の event.size に、変更後の新たなウィンドウサイズが入ってます。
if (event.type == sf::Event::Resized)
{
std::cout << "new width: " << event.size.width << std::endl;
std::cout << "new height: " << event.size.height << std::endl;
}
イベントの種類:フォーカスを失った / フォーカスを得た
ウィンドウがフォーカスを失ったり得たりしたとき、sf::Event::LostFocus イベントおよび sf::Event::GainedFocus イベントが発生します。
つまり、最前面のウィンドウを切り替えたときのイベント、ということです。
ちなみに、ウィンドウがフォーカスを持ってないときは、キーボードイベントは発生しません。
このイベントをキャッチすると、たとえば、アクションゲームなんかで、ウィンドウが非アクティブになったときに一時停止、
みたいなことができます。
sf::Event共用体に、このイベントに関連するデータはありません。
if (event.type == sf::Event::LostFocus) {
myGame.pause();
}
if (event.type == sf::Event::GainedFocus) {
myGame.resume();
}
イベントの種類:テキストが入力された
文字が入力されたとき、テキスト入力イベント(sf::Event::TextEntered)が発生します。キー押下イベントと混同しないでね!
このイベントは、ユーザーの入力内容を解釈して、それに合わせた文字を出力します。
たとえばフランス語のキーボードで '^' と 'e' を押すと、'ê' が出るわけですけど、
このとき、キー押下イベントは2回発生しますが、テキスト入力イベントは1回だけです。
このイベントは、OS がサポートしてるどんな入力方法にでも対応してます。「どんな入力方法でも」です。
もしも OS が念力での入力をサポートしているなら、念力でテキスト入力イベントを発生させられるってことだね。
ユーザーがテキスト欄に文字を入力したことを検知する、というのが、よくある使い方です。
イベント共用体の event.text に、入力された文字のユニコード値が入ってます。
この値は SFMLの文字列型 sf::String に直接入れれます。ASCIIの 0~127 の範囲内なら、char型にも入れれます。やったね。
if (event.type == sf::Event::TextEntered)
{
if (event.text.unicode < 128) {
std::cout << "ASCII character typed: "
<< static_cast<char>(event.text.unicode) << std::endl;
}
}
注意点。
これはですね、ユニコードの規約に合わせて、なのですけど、このイベントで生成される文字には、
バックスペースとか、表示できない文字が含まれる可能性があります。うざいと思いますけどガマンしてね。
本当にあった怖い話。
これは私の知り合いの友達の親戚の知人の隣人のプログラマーの話なんですけど、
その人は文字入力イベントの存在を知らなくてですね、キー押下イベントを使って、
キーボード上の全部の組み合わせを調べて、
どんな文字が入力されたかを判定しようとしてたんですって。キャー怖い! よい子のみなさんはマネしないでね。
イベントの種類:「キー押下」「キーリリース」
キーボードのキーを押したらキー押下イベント(sf::Event::KeyPressed)、
キーを放したらキーリリースイベント(sf::Event::KeyReleased)が発生します。
キーを押しっぱなしにしていると、OSに設定してあるキーリピートの設定に従って、1テンポ置いて、キー押下イベントが何回も発生します。
つまり、テキストエディタの上で a...aaaaaaaaaaaaaaaaaaaってなるアレと同じです。
window.setKeyRepeatEnabled(false) ってすると、リピートしなくなるよ。
それとは対象的に! キーリリースイベントは決してリピートしません。はーい、あたりまえのこと言いました。
このイベントは、キーの操作で1回だけ実行したい動きに使うとよいです。
たとえば、スペースキーでマ○オ的な何かをジャンプさせるとか、ESCキーで外に出るとか。
人というものは、キー押下イベントでスムーズな動きを実装したがる生き物です。
しかしキー押下イベントではその願いが叶うことはありません。
なぜなら、キーを押している間、ずっとイベントが発生するわけではないからです(OS のキーリピート設定のことを思い出したまえ)。
この願いを叶えるには、まずブーリアン型の変数を用意します。そしてキー押下で値を true に、キーリリースで値を false にするようにし、
値が true の間だけキャラクターを動かす、というアルゴリズムにするとよいです。
でもきっと、SFML のリアルタイム入出力検知の機能を使う方が楽ちんだよ!(
別ページ参照)
イベント共用体の event.key に、押されたり放されたりしたキーのキーコードが入ってます。シフトなどの修飾キーの状態も取得できるよ。
if (event.type == sf::Event::KeyPressed)
{
// 押下されたキーは ESCキー?
if (event.key.code == sf::Keyboard::Escape)
{
std::cout << "ESCキーが押された" << std::endl;
std::cout << "Ctrlキーの状態 : " << event.key.control << std::endl;
std::cout << "Altキーの状態 : " << event.key.alt << std::endl;
std::cout << "Shiftキーの状態 : " << event.key.shift << std::endl;
std::cout << "Systemキーの状態 : " << event.key.system << std::endl;
}
}
注意点。
キーによっては、OS に対して特別な意味を持ってて、うかつに触るとおかしな動きを誘発してしまうことがありますよね。
たとえばウィンドウズで F10 を押したらフォーカスがメニューバーに移ってしまうとか、VisualStudio を使ってるときに
F12を押すとデバッガーが起動してしまうとか。
この問題を解決するには……SFMLの今後のバージョンアップに期待してね。てへ。
イベントの種類:マウスホイールが動かされた
SFML2.3 以降、sf::Event::MouseWheelMoved イベントは 非推奨になりました!
代わりに sf::Event::MouseWheelScrolled イベントを使ってね。(↓の項目)
イベントの種類:マウスホイールが転がされた
マウスホイールを上下にぐりぐりしたとき、sf::Event::MouseWheelScrolled イベントが発生します。
マウスが対応していれば横スクロールのホイールにも反応します。
event.mouseWheelScroll に、移動量と方向、マウスカーソルの現在位置が入ってきます。
if (event.type == sf::Event::MouseWheelScrolled)
{
if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel)
{
std::cout << "wheel type: vertical" << std::endl;
}
else if (event.mouseWheelScroll.wheel == sf::Mouse::HorizontalWheel)
{
std::cout << "wheel type: horizontal" << std::endl;
}
else
{
std::cout << "wheel type: unknown" << std::endl;
}
std::cout << "wheel movement: " << event.mouseWheelScroll.delta << std::endl;
std::cout << "mouse x: " << event.mouseWheelScroll.x << std::endl;
std::cout << "mouse y: " << event.mouseWheelScroll.y << std::endl;
}
イベントの種類:マウスのボタンを押下 / リリース
マウスのボタンを押したり放したりしたとき、
マウス押下イベント(sf::Event::MouseButtonPressed)、およびマウスリリースイベント(sf::Event::MouseButtonReleased)が発生します。
SFMLでは5つまで、マウスのボタンをサポートしてます。
左、右、真ん中(ホイール)の3つと、2つのエキストラボタンです。
イベント共用体の event.mouseButton に、押下またはリリースされたボタンのコードが入ってます。
マウスの現在位置も入ってます。
if (event.type == sf::Event::MouseButtonPressed)
{
// 押されたのは、マウスの右ボタン?
if (event.mouseButton.button == sf::Mouse::Right)
{
std::cout << "右ボタンが押された" << std::endl;
std::cout << "マウスの位置 X : " << event.mouseButton.x << std::endl;
std::cout << "マウスの位置 Y : " << event.mouseButton.y << std::endl;
}
}
イベントの種類:マウスが動いた
ウィンドウの中でマウスを動かしたとき、マウスムーブイベント(sf::Event::MouseMoved)が発生します。
このイベントは、ウィンドウにフォーカスがないときでも発生します。
が、マウスがクライアントエリアの上を動いたときだけです。タイトルバーや境界線上では発生しません。
イベント共用体の event.mouseMove にマウスの現在位置が入っています。ウィンドウ上での座標です。
if (event.type == sf::Event::MouseMoved)
{
std::cout << "マウスの現在位置 X : " << event.mouseMove.x << std::endl;
std::cout << "マウスの現在位置 Y : " << event.mouseMove.y << std::endl;
}
イベントの種類:マウスがウィンドウに乗った/出た
マウスカーソルがウィンドウの上に乗ったり、ウィンドウの外に出たとき、
マウスエンターイベント(sf::Event:MouseEntered)および、マウス退去イベント(sf::Event::MouseLeft)が発生します。
イベント共用体に、このイベントに関連するデータはありません。
if (event.type == sf::Event::MouseEntered) {
std::cout << "マウスがウィンドウに乗った" << std::endl;
}
if (event.type == sf::Event::MouseLeft) {
std::cout << "マウスがウィンドウの上から出た" << std::endl;
}
イベントの種類:パッドのボタン押下 / リリース
パッドのボタンを押したり放したりしたとき、それぞれに対応したイベントが発生します。
(sf::Event::JoystickButtonPressed および sf::Event::JoystickButtonReleased)
SFMLでは、パッドは8個まで、ボタンは全部で32個まで扱えます。
イベント共用体の event.joystickButton に、パッドのIDと、押下/リリースされたボタンの番号が入ってます。
if (event.type == sf::Event::JoystickButtonPressed)
{
std::cout << "パッドのボタンが押された!" << std::endl;
std::cout << "パッドのID : " << event.joystickButton.joystickId << std::endl;
std::cout << "ボタン番号 : " << event.joystickButton.button << std::endl;
}
イベントの種類:パッドの方向キー押下
パッドの方向キーというか、ジョイスティックの「スティック」が傾けられたとき、sf::Event::JoystickMovedイベントが発生します。
「スティック」はとっても敏感なので、SFML では「閾値」を設定することで、
イベントループに大量のイベントが飛ぶのを防いでます。閾値は Window::setJoystickThreshold 関数で変更できます。
SFMLでは「スティック」の「軸」として8つをサポートしてます。(X, Y, Z, R, U, V, POV X and POV Y)
これらがどんなふうにマッピングされてるかは、ジョイスティックのドライバによって異なります。
イベント共用体の event.joystickMove に、パッドのIDと「軸」の名前、現在位置(-100~+100)が入ってます。
if (event.type == sf::Event::JoystickMoved)
{
// X軸の操作?
if (event.joystickMove.axis == sf::Joystick::X)
{
std::cout << "X軸が動いた!" << std::endl;
std::cout << "パッドのID : " << event.joystickMove.joystickId << std::endl;
std::cout << "現在位置 : " << event.joystickMove.position << std::endl;
}
}
イベントの種類:パッド接続された / 取り外された
パッドがマシンに刺されたら sf::Event::JoystickConnectedイベントが発生します。抜かれたら sf::Event::JoystickDisconnectedイベントが発生します。
イベント共用体の event.joystickConnect に、刺された/抜かれた パッドのID が入ってます。
if (event.type == sf::Event::JoystickConnected) {
std::cout << "刺されたパッドのID : "
<< event.joystickConnect.joystickId << std::endl;
}
if (event.type == sf::Event::JoystickDisconnected) {
std::cout << "抜かれたパッドのID : "
<< event.joystickConnect.joystickId << std::endl;
}