◆◆ ウィンドウイベント ◆◆
非公式日本語翻訳
さぁ、たくさんあるウィンドウイベントを、くわしく、こってりと、手取り足取り書き連ねるよ!
イベントの種類は、そりゃもう、たくさんあるわけで、いかに使うか? そして使わないか? それが生き残る秘訣でござる。
謎のデータ型「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イベントが発生します。
つまり、最前面のウィンドウを切り替えたときのイベント、ということです。
ちなみに、ウィンドウがフォーカスを持ってないときは、キーボードイベントは発生しません。
たとえば、アクションゲームなんかで、ウィンドウが非アクティブになったときに一時停止、
なんてときに、このイベントが利用できるよ。
if (event.type == sf::Event::LostFocus) {
myGame.pause();
}
if (event.type == sf::Event::GainedFocus) {
myGame.resume();
}
sf::Event共用体に、このイベントに関連するデータはありません。
イベントの種類:テキストが入力された
文字が入力されたとき、テキスト入力イベント(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(event.text.unicode) << std::endl;
}
}
注意点。
これはですね、ユニコードの規約に合わせて、なのですけど、このイベントで生成される文字には、
バックスペースとか、表示できない文字が含まれる可能性があります。うざいと思いますけどガマンしてね。
本当にあった怖い話。
これは私の知り合いの友達の親戚の知人の隣人のプログラマーの話なんですけど、
その人は文字入力イベントの存在を知らなくてですね、キー押下イベントを使って、
キーボード上の全部の組み合わせを調べて、
どんな文字が入力されたかを判定しようとしてたんですって。キャー怖い! よい子のみなさんはマネしないでね。
イベントの種類:「キー押下」「キーリリース」
キーボードのキーを押したらキー押下イベント(sf::Event::KeyPressed)、
キーを放したらキーリリースイベント(sf::Event::KeyReleased)が発生します。
キーを押しっぱなしにしていると、OSに設定してあるキーリピートの設定に従って、1テンポ置いて、キー押下イベントが何回も発生します。
つまり、テキストエディタの上で aaaaaaaaaaaaaaaaaaaaってなるアレと同じです。
window.setKeyRepeatEnabled(false) ってすると、リピートしなくなるよ。
それとは対象的に! キーリリースイベントは決してリピートしません。はーい、あたりまえのこと言いました。
このイベントは、キーの操作で1回だけ実行したい動きに使うとよいです。
たとえば、スペースキーでマ○オ的な何かをジャンプさせるとか、ESCキーで外に出るとか。
人というものは、キー押下イベントでスムーズな動きを実装したがる生き物です。
しかしキー押下イベントではその願いが叶うことはありません。
なぜなら、キーを押している間、ずっとイベントが発生するわけではないからです(OSのキーリピート設定のことを思い出したまえ)。
スムーズな動きは、sf::Keyboard を使ったリアルタイム入力状態取得メソッドでのみ実現可能です。(
別ページ参照)
※訳者註:「スムーズな動き」というのは、たとえば矢印キーを押している間、キャラを歩かせる、などのことだと思います。
ちなみに、キー押下とリリースイベントを組み合わせることで、スムーズな動きは
実装可能です。
たとえば、右キーを押すと「右移動フラグ」をオンにする、右キーを放すと「右移動フラグ」をオフにする、
で、そのフラグがオンになっている間、キャラを右に移動させる、という具合です。
でも、SFMLに用意されてるリアルタイム入力状態取得メソッドを使った方がたぶん楽。ニクイぞ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の今後のバージョンアップに期待してね。てへ。
イベントの種類:マウスホイール
マウスホイールをぐりぐりすると、sf::Event::MouseWheelMovedイベントが発生します。
イベント共用体の、event.mouseWheel に、ホイールがぐりぐりされた量が入ってます。マウスの現在位置も入ってます。
if (event.type == sf::Event::MouseWheelMoved)
{
std::cout << "ホイールぐりぐり度 : " << event.mouseWheel.delta << std::endl;
std::cout << "マウスの位置 X : " << event.mouseWheel.x << std::endl;
std::cout << "マウスの位置 Y : " << event.mouseWheel.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;
}