SDL2のイベント処理

SDL_Event構造体でイベント処理を実行します。

 

1 SDL_Event構造体

キーイベント、タッチイベント、マウスイベント等のイベントはSDL_Event構造体で扱います。

SDL_Event構造体は各種イベント向けの構造体を列挙体のメンバ変数に持ち、メンバ変数typeでイベントの種類を判別します。

/**
 *  \brief General event structure
 */
typedef union SDL_Event
{
    Uint32 type;                    /**< Event type, shared with all
    events */
    SDL_CommonEvent common;         /**< Common event data */
    SDL_WindowEvent window;         /**< Window event data */
    SDL_KeyboardEvent key;          /**< Keyboard event data */
    SDL_TextEditingEvent edit;      /**< Text editing event data */
    SDL_TextInputEvent text;        /**< Text input event data */
    SDL_MouseMotionEvent motion;    /**< Mouse motion event data */
    SDL_MouseButtonEvent button;    /**< Mouse button event data */
    SDL_MouseWheelEvent wheel;      /**< Mouse wheel event data */
    SDL_JoyAxisEvent jaxis;         /**< Joystick axis event data */
    SDL_JoyBallEvent jball;         /**< Joystick ball event data */
    SDL_JoyHatEvent jhat;           /**< Joystick hat event data */
    SDL_JoyButtonEvent jbutton;     /**< Joystick button event data */
    SDL_JoyDeviceEvent jdevice;     /**< Joystick device change event
    data */
    SDL_ControllerAxisEvent caxis;      /**< Game Controller axis
    event data */
    SDL_ControllerButtonEvent cbutton;  /**< Game Controller button
    event data */
    SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device
    event data */
    SDL_QuitEvent quit;             /**< Quit request event data */
    SDL_UserEvent user;             /**< Custom event data */
    SDL_SysWMEvent syswm;           /**< System dependent window
    event data */
    SDL_TouchFingerEvent tfinger;   /**< Touch finger event data */
    SDL_MultiGestureEvent mgesture; /**< Gesture event data */
    SDL_DollarGestureEvent dgesture; /**< Gesture event data */
    SDL_DropEvent drop;             /**< Drag and drop event data */

    /* This is necessary for ABI compatibility between Visual C++ and
    GCC
       Visual C++ will respect the push pack pragma and use 52 bytes
       for
       this structure, and GCC will use the alignment of the largest
       datatype
       within the union, which is 8 bytes.

       So... we'll add padding to force the size to be 56 bytes for
       both.
    */
    Uint8 padding[56];
} SDL_Event;

SDL_KEYDOWN、SDL_KEYUPはキーボードイベント、SDL_FINGERDOWN、SDL_FINGERMOTION、SDL_FINGERUPはタッチイベントです。SDL_QUITはウィンドウのxボタンを押したり、コンソール上でctrl-Cを送信した場合のイベントです。SDL_NONEはイベントが特にない場合の値です。

SDL_Event event;
SDL_PollEvent(&event);
/** event.typeでイベントの種類を判別 */
switch (event.type) {
case SDL_NOTE: /** イベントが発生していない場合 */
  break;
case SDL_QUIT: /** ウィンドウのxボタンやctrl-Cを押した場合 */
  return 0;
case SDL_KEYDOWN: /** あるいはSDL_KEYUP */
  /** SDL_KeyboardEvent keyにアクセス */
  break;
case SDL_FINGERDOWN: /** あるいはSDL_FINGERMOTION、SDL_FINGERUP */
  /** SDL_TouchFingerEvent tfingerにアクセス */
  break;
default:
  break;
}

イベントの種類はそこそこの数がある為、自分のプログラムで必要とするものを明確にしたいところです。


2 SDL_PollEvent

SDL_PollEventでイベントを取得できます。イベントキューにイベントがない場合にすぐに呼び出し元へ復帰します。

int SDL_PollEvent(SDL_Event* event)

描画処理とイベント処理を同一スレッドで実行するプログラムの場合に使用します。

スリープ処理との相性に懸念があります。FPSの制御の為、描画処理の後にスリープを入れる場合、スリープしている間にイベントキューが満タンになり、イベントを取りこぼす可能性があります。また、イベント処理の応答性が悪くなります。


3 SDL_WaitEvent

SDL_PollEventでイベントを取得できます。イベントキューにイベントがない場合にすぐに呼び出し元へ復帰せず、イベントが受信できるまでスリープします。

int SDL_WaitEvent(SDL_Event* event)

描画処理とイベント処理を別のスレッドで実行するプログラムの場合に使用します。

描画処理とイベント処理を別のスレッドにすることで、FPS制御の為の描画処理にスリープを入れても問題ありません。イベント処理もイベント受信後に即座に実行できます。


4 SDL_PushEvent

SDL_PushEventでイベントキューにイベントを送信します。

int SDL_PushEvent(SDL_Event* event)

例えば、特定の条件が成立した場合にプログラムを終了したい場合には、SDL_QUITを送信すると良いでしょう。SDL_WaitEventでSDL_QUITを待っているスレッドがいる場合、特に有用です。

SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);

5 サンプルプログラム

キーボードの矢印キーで白い四角形を移動し、ウィンドウのxボタンが押された場合に終了するプログラムです。

#include <SDL.h>
#include <iostream>

#define SDL_WINDOW_TITLE "SDL2"
#define SDL_WINDOW_WIDTH (640)
#define SDL_WINDOW_HEIGHT (360)

/** NOTE: Windows on KVM cannot call direct3D.
    Then SDL_RENDERER_SOFTWARE will be needed. */
#ifdef NEED_RENDERER_SOFTWARE
#define SDL_RENDERER_FLAGS (SDL_RENDERER_SOFTWARE)
#else
#define SDL_RENDERER_FLAGS (0)
#endif

#define SDL_PrintError(name)                                    \
  do {                                                          \
    std::cerr << #name << ": " << SDL_GetError() << std::endl;  \
  } while (0)

/** NOTE: SDL_Renderer is referered with draw function like
    SDL_RenderDrawRect, SDL_RenderCopy and etc. for drawing.
    These draw functions and SDL_CreateRenderer must be called
    by same thread. */
static SDL_Window *gWindow;
static SDL_Renderer *gRenderer;

/** NOTE: Draw rect position. */
static int gRectX = 0;
static int gRectY = 0;

static bool initialize()
{
  if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
    SDL_PrintError(SDL_Init);
    return false;
  }

  gWindow = SDL_CreateWindow(SDL_WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED,
                             SDL_WINDOWPOS_UNDEFINED, SDL_WINDOW_WIDTH,
                             SDL_WINDOW_HEIGHT, 0);
  if (gWindow == nullptr) {
    SDL_PrintError(SDL_CreateWindow);
    goto err1;
  }

  gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_FLAGS);
  if (gRenderer == nullptr) {
    SDL_PrintError(SDL_CreateRenderer);
    goto err2;
  }

  return true;

 err2:
  SDL_DestroyWindow(gWindow);
 err1:
  SDL_Quit();
  return false;
}

static void finalize()
{
  SDL_DestroyRenderer(gRenderer);
  SDL_DestroyWindow(gWindow);
  SDL_Quit();
}

static void render()
{
  /** NOTE: Clear render with Color (0, 0, 0, 0) for erasing
      current drawing. */
  SDL_SetRenderDrawColor(gRenderer, 0, 0, 0, 0);
  SDL_RenderClear(gRenderer);

  /** NOTE: Draw rect (0, 0, 100, 100) with
      Color (254, 254, 254, 254) */
  SDL_Rect rect = { gRectX, gRectY, 100, 100 };
  SDL_SetRenderDrawColor(gRenderer, 254, 254, 254, 254);
  SDL_RenderFillRect(gRenderer, &rect);

  /** NOTE: Update drawing (switch double buffer). */
  SDL_RenderPresent(gRenderer);

  /** NOTE: Sleep 10 msec. */
  SDL_Delay(10);
}

static bool input()
{
  SDL_Event event;

  /** NOTE: SDL_PollEvent does not sleep while SDL_WaitEvent sleep
      till event comes. SDL_WaitEvent is more relaxible than
      SDL_PollEvent. If input is combined with rendering,
      SDL_WaitEvent cannot be used. */
  while (SDL_PollEvent(&event)) {
    switch (event.type) {
    case SDL_QUIT:
      return true;
      break;
    case SDL_KEYDOWN:
      switch (event.key.keysym.sym) {
      case SDLK_UP:
        gRectY--;
        break;
      case SDLK_RIGHT:
        gRectX++;
        break;
      case SDLK_DOWN:
        gRectY++;
        break;
      case SDLK_LEFT:
        gRectX--;
        break;
      default:
        break;
      }
      break;
    default:
      break;
    }
  }

  return false;
}

int main(int argc, char *argv[])
{
  if (!initialize())
    return 1;

  while (1) {
    if (input())
      break;
    render();
  }

  finalize();
  return 0;
}