入力イベントの処理

入力イベントの扱いについて解説します。
SDL では、定期的にイベント処理を行う必要があります。

イベント処理用の関数である SDL_PollEvent() を定期的に呼ばないと、他のウィンドウが画面に重なるたびに、描画内容が崩れてしまいます。以下が 文字列の描画 で作成したサンプルの実行中に、他のウィンドウを重ねた後の結果です。

input_handle_noupdated.png

他のウィンドウが重なり、一番下の "こんにちはー" が途切れている

イベント処理のシンプルな例は、このようになります。

SDL_Event event;
while (SDL_PollEvent(&event)) {
  switch (event.type) {
  case SDL_QUIT:
    // !!! ウィンドウのクローズボタンや、Ctrl-c が押されたときの処理
    exit(0);
    break;

  case SDL_KEYDOWN:
    // !!! キーが押された時の処理
    break;

  case SDL_MOUSEBUTTONDOWN:
    // !!! マウスのボタンが押されたときの処理
    break;
  }
} 

一定数以上の FPS を実現するためにも 基本となるループ処理 で説明した「入力読み出し」「演算」「描画(出力)」の基本ループは短い間隔で呼ぶべきです。つまり、ゲームを演出する上で「2秒かけてフェードアウトする」という処理を実現するには、基本ループをまたいだ待機が必要になります。「基本となるループを処理しながら、一定時間待つ」といった処理を行う方法は コルーチンの利用 で別途解説します。


入力イベントのシグナル処理

以下では、入力イベントをシグナルに変換するモジュールの実装について解説します。
このモジュールの目的は、各コンポーネントが要求している入力イベントがあったか、を扱い易くすることです。

具体的には、このモジュールにイベントを登録すると、そのイベントが発生したときにシグナルで通知される仕組みを作成します。

inline_dotgraph_2.dot

このモジュールが扱えるイベント、情報は以下のように定義します。

この処理を用いたサンプルを以下に示します。実装には、boost の signals ライブラリを用いています。

handleEvent.cpp

#include "Screen.h"
#include "CallbackEvent.h"
#include "LayerManager.h"
#include "delay.h"
#include <iostream>
#include <cstdlib>

using namespace qrk;
using namespace std;


namespace
{
  void enterCallback(void)
  {
    cout << "return key pressed." << endl;
  }
}


int main(int argc, char *argv[])
{
  static_cast<void>(argc);
  static_cast<void>(argv);

  Screen screen;
  if (! screen.show()) {
    cout << "Screen::show: " << screen.what() << endl;
    exit(1);
  }

  // 複数イベントを登録した場合
  CallbackEvent quit_event;
  quit_event.key(SDLK_ESCAPE);
  quit_event.key(SDLK_q, SDL_KEYDOWN, KMOD_LCTRL);
  quit_event.key(SDLK_q, SDL_KEYDOWN, KMOD_RCTRL);
  quit_event.key(SDLK_F4, SDL_KEYDOWN, KMOD_LALT);
  quit_event.key(SDLK_F4, SDL_KEYDOWN, KMOD_RALT);

  // ポーリングのみで使う場合
  CallbackEvent space_event;
  space_event.key(SDLK_SPACE);

  // コールバックで使う場合
  CallbackEvent enter_event;
  enter_event.key(SDLK_RETURN);
  enter_event.key(SDLK_KP_ENTER);
  enter_event.setCallback(enterCallback);

  cout << "hit enter key, or space key." << endl;

  LayerManager layer_manager;
  layer_manager.insertEvent(&quit_event);
  layer_manager.insertEvent(&enter_event);
  layer_manager.insertEvent(&space_event);

  bool quit = false;
  while (! quit) {
    layer_manager.updateEvent();

    if (quit_event.isActive()) {
      quit = true;
    }
    if (space_event.isActive()) {
      cout << "space key pressed." << endl;
    }

    delay(10);
  }
  layer_manager.clearEvent();

  return 0;
}


CallbackEvent.cpp

#include "CallbackEvent.h"
#include "RectUtils.h"
#include <set>

using namespace qrk;
using namespace boost;
using namespace std;


namespace
{
  class KeyInput
  {
    long compareKey(void) const
    {
      return ((SDLK_LAST * mod) + key);
    }


  public:
    SDLKey key;
    SDLMod mod;


    KeyInput(SDLKey key_, SDLMod mod_) : key(key_), mod(mod_)
    {
    }


    bool operator < (const KeyInput& rhs) const
    {
      return (compareKey() < rhs.compareKey());
    }
  };
  typedef set<KeyInput> Keys;

  typedef set<Uint8> Clicks;
  typedef set<Rect<long> > Areas;


  bool existKeyEvent(const Keys& table, const KeyInput& find_key)
  {
    return (table.find(find_key) != table.end());
  }


  bool existClickEvent(const Clicks& table, Uint8 find_button)
  {
    return (table.find(find_button) != table.end());
  }


  bool existEnterCursorEvent(const Areas& table,
                             const Point<long>& current,
                             const Point<long>& previous)
  {
    for (Areas::iterator it = table.begin(); it != table.end(); ++it) {

      bool current_isInner = isInner(*it, current);
      bool previous_isInner = isInner(*it, previous);

      if (current_isInner && (! previous_isInner)) {
        return true;
      }
    }
    return false;
  }
}


struct CallbackEvent::pImpl
{
  EventCallback callback_;
  signals::connection connection_;

  Keys key_pressed_;
  Keys key_released_;
  Clicks click_pressed_;
  Clicks click_released_;
  Areas cursor_area_in_;
  Areas cursor_area_out_;

  bool is_active_;
  bool move_cursor_;
  bool acceptable_;


  pImpl(void) : is_active_(false), move_cursor_(false), acceptable_(true)
  {
  }


  ~pImpl(void)
  {
    connection_.disconnect();
  }


  void activate(void)
  {
    if (! acceptable_) {
      return;
    }
    is_active_ |= true;
    callback_();
  }


  void checkKey(SDLKey key, Uint8 type, SDLMod mod)
  {
    KeyInput key_input(key, mod);
    Keys& table = (type == SDL_KEYUP) ? key_released_ : key_pressed_;
    if (existKeyEvent(table, key_input)) {
      activate();
    }
  }


  void checkClick(Uint8 mouse_button, Uint8 type)
  {
    Clicks& table =
      (type == SDL_MOUSEBUTTONUP) ? click_released_ : click_pressed_;
    if (existClickEvent(table, mouse_button)) {
      activate();
    }
  }


  void checkEnterCursor(const Point<long>& current,
                        const Point<long>& previous)
  {
    if (existEnterCursorEvent(cursor_area_in_, current, previous) ||
        existEnterCursorEvent(cursor_area_out_, previous, current)) {
      activate();
    }
  }
};


CallbackEvent::CallbackEvent(void) : pimpl(new pImpl)
{
}


CallbackEvent::~CallbackEvent(void)
{
}


void CallbackEvent::checkKey(SDLKey key, Uint8 type, SDLMod mod, Uint16 unicode)
{
  static_cast<void>(unicode);

  pimpl->checkKey(key, type, mod);
}


void CallbackEvent::checkClick(Uint8 mouse_button, Uint8 type)
{
  pimpl->checkClick(mouse_button, type);
}


void CallbackEvent::checkEnterCursor(const Point<long>& current,
                                     const Point<long>& previous)
{
  pimpl->checkEnterCursor(current, previous);
}


void CallbackEvent::cursorMoved(void)
{
  if (pimpl->move_cursor_) {
    pimpl->activate();
  }
}


void CallbackEvent::setEventAcceptable(bool acceptable)
{
  pimpl->acceptable_ = acceptable;
}


void CallbackEvent::clearEvent(void)
{
  pimpl->key_pressed_.clear();
  pimpl->key_released_.clear();
  pimpl->click_pressed_.clear();
  pimpl->click_released_.clear();
  pimpl->cursor_area_in_.clear();
  pimpl->cursor_area_out_.clear();
}


void CallbackEvent::key(Uint16 key, Uint8 type, Uint16 mod, Uint16 unicode)
{
  static_cast<void>(unicode);

  KeyInput key_input(static_cast<SDLKey>(key), static_cast<SDLMod>(mod));
  Keys& table = (type == SDL_KEYUP) ?
    pimpl->key_released_ : pimpl->key_pressed_;
  table.insert(key_input);
}


void CallbackEvent::click(Uint8 mouse_button, Uint8 type)
{
  Clicks& table = (type == SDL_MOUSEBUTTONUP) ?
    pimpl->click_released_ : pimpl->click_pressed_;
  table.insert(mouse_button);
}


void CallbackEvent::enterCursor(const Rect<long>& area, bool enter)
{
  Areas& table = (enter) ? pimpl->cursor_area_in_ : pimpl->cursor_area_out_;
  table.insert(area);
}


void CallbackEvent::moveCursor(bool move)
{
  pimpl->move_cursor_ = move;
}


bool CallbackEvent::isActive(void)
{
  bool is_active = pimpl->is_active_;
  pimpl->is_active_ = false;
  return is_active;
}


void CallbackEvent::setCallback(const EventCallback::slot_type& callback)
{
  pimpl->connection_ = pimpl->callback_.connect(callback);
}


EventManager.cpp

#include "EventManager.h"
#include "Event.h"
#include <boost/bind.hpp>
#include <algorithm>
#include <list>

using namespace qrk;
using namespace boost;
using namespace std;


namespace
{
  typedef list<Event*> Events;
}


struct EventManager::pImpl
{
  Events events_;


  void remove(Event* event)
  {
    Events::iterator it = find(events_.begin(), events_.end(), event);
    if (it != events_.end()) {
      events_.erase(it);
    }
  }
};


EventManager::EventManager(void) : pimpl(new pImpl)
{
}


EventManager::~EventManager(void)
{
}


void EventManager::clear(void)
{
  for_each(pimpl->events_.begin(), pimpl->events_.end(),
           bind(&Event::clearEvent, _1));
}


void EventManager::insert(Event* event)
{
  pimpl->events_.push_back(event);
}


void EventManager::remove(Event* event)
{
  pimpl->remove(event);
}


void EventManager::checkKey(SDLKey key, Uint8 type, SDLMod mod, Uint16 unicode)
{
  for_each(pimpl->events_.begin(), pimpl->events_.end(),
           bind(&Event::checkKey, _1, key, type, mod, unicode));
}


void EventManager::checkClick(Uint8 mouse_button, Uint8 type)
{
  for_each(pimpl->events_.begin(), pimpl->events_.end(),
           bind(&Event::checkClick, _1, mouse_button, type));
}


void EventManager::checkEnterCursor(const Point<long>& current,
                                    const Point<long>& previous)
{
  for_each(pimpl->events_.begin(), pimpl->events_.end(),
           bind(&Event::checkEnterCursor, _1, current, previous));
}


void EventManager::cursorMoved(void)
{
  for_each(pimpl->events_.begin(), pimpl->events_.end(),
           bind(&Event::cursorMoved, _1));
}


LayerManager.cpp

  void updateEvent(void)
  {
    bool cursor_moved = false;
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
      switch (event.type) {

      case SDL_QUIT:
        // !!! コールバックを呼ぶか、ここで終了させる
        exit(0);
        break;

      case SDL_KEYDOWN:
      case SDL_KEYUP:
        event_manager_.checkKey(event.key.keysym.sym, event.key.type,
                                event.key.keysym.mod, event.key.keysym.unicode);
        break;

      case SDL_MOUSEBUTTONDOWN:
      case SDL_MOUSEBUTTONUP:
        event_manager_.checkClick(event.button.button, event.button.type);
        break;

      case SDL_MOUSEMOTION:
        cursor_moved = true;
        current_cursor_ = Point<long>(event.motion.x, event.motion.y);
        event_manager_.checkEnterCursor(current_cursor_, previous_cursor_);
        previous_cursor_ = current_cursor_;
        event_manager_.cursorMoved();
        break;

      default:
        break;
      }
    }

簡単ですね。
signals ライブラリを用いると boost_signals ライブラリのリンクが必要になる点に注意して下さい。


コメントページ



Generated on Mon May 18 11:11:10 2009 by  doxygen 1.5.7.1