本篇内容我们来学习一下 SDL 的事件处理。在常规的 Windows 开发中,一般的键盘、鼠标等事件都被封装为一个个的消息,这些消息都可以在窗口过程函数中被捕获,并执行开发人员想要运行的逻辑。SDL 中已经将这些内容全部进行了二次封装,换了一个叫做“事件”的名称。

这些事件被封装在一个叫做 SDL_Event 的结构中,它是这些多有事件描述的一个并集。

typedef union SDL_Event
{
    Uint32 type;                    /**< Event type, shared with all events */
    SDL_CommonEvent common;         /**< Common event data */
    SDL_DisplayEvent display;       /**< Window 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_AudioDeviceEvent adevice;   /**< Audio device event data */
    SDL_SensorEvent sensor;         /**< Sensor 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_Event 结构是 SDL 中所有事件处理的核心,它是一个共用体对象,在使用中你关注哪些事件,就去对应的字段中读取数据即可。

SDL_Event 共用体内部的成员非常多,但常用的就那么几个:

  • SDL_QuitEvent 退出事件请求
  • SDL_KeyboardEvent 键盘事件
  • SDL_Mouse* 鼠标相关的事件

除了上面一些还包括拖拽相关的事件 SDL_DropEvent,以及手柄相关的 SDL_Joy* 等等,如果需要直接去官方 wiki 查看,这些数据大多是和事件产生设备的类型息息相关。

SDL 内部和 Windows 一样,同样维护这一个队列,你可以使用 SDL_PollEvent 来查看事件队列中的内容:

int SDL_PollEvent(SDL_Event* event)

如果事件队列中存在事件,函数返回 1,具体的事件内容保存在 event 参数中,需要注意 SDL_PollEvent 在获取事件成功后,会将该事件从事件队列中删除。如果在调用函数的时候,event 被设置为 NULL,则相当于检测队列中是否存在事件,存在则返回 1,否则返回 0,函数在执行过程中并且不会删除事件队列中的内容。

一般在程序中,我们会将 SDL_PollEvent 函数放在一个 while 循环中不断读取事件队列中的内容,并根据具体的事件信息,执行相关的动作:

int quit = 0;
SDL_Event evt;

while (!quit) {
	if (SDL_PollEvent(&evt)) {
		if (evt.type == SDL_QUIT) {
			quit = 1;
		}
		else if (evt.type == SDL_KEYDOWN) {
			//键盘按下事件,可以查看 SDL_KeyboardEvent 结构体内容
			//evt.key.keysym
		}
		else if (evt.type == SDL_MOUSEBUTTONDOWN) {
			//鼠标按钮按下事件,可以查看 SDL_MouseButtonEvent 结构体内容
			//evt.button.x, evt.button.y
		}
	}
	else {
		//TODO: 渲染窗口
	}
}

上述代码中涉及了三种事件类型,第一种 SDL_QUIT 程序退出事件,这个就是固定值,没有什么好说的,下面主要讲解一下常用的键盘和鼠标按下事件,其它的事件处理方式都可以按照这个流程。

在 SDL_Event 结构中有一个名字叫做 type 的字段,这个字段主要用来描述从事件队列中获取的事件类型,具体的事件类型可以参考官网的描述:

Event Type Event Structure SDL_Event Field
SDL_KEYDOWN SDL_KEYUP SDL_KeyboardEvent key
SDL_MOUSEBUTTONDOWN SDL_MOUSEBUTTONUP SDL_MouseButtonEvent button
SDL_QUIT SDL_QuitEvent quit
…… …… ……

这个列表非常长,这里只列出了上面案例中的三个事件类型,我们可以看到这些事件类型都对应这一个结构体类型。它的含义是告诉你,如果你获取了第一列的事件类型,那么就去第二列的结构体中获取类型相关的事件描述,第三列是这个结构体类型的字段在 SDL_Event 结构中的名称。

例如,SDL_KEYDOWN 键盘按下事件,需要查看 SDL_KeyboardEvent 结构体,这个结构体的成员在 SDL_Event 中可以查找到:

//SDL_Event evt;
evt.key

该结构体的成员如下:

typedef struct SDL_KeyboardEvent
{
    Uint32 type;        /**< ::SDL_KEYDOWN or ::SDL_KEYUP */
    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
    Uint32 windowID;    /**< The window with keyboard focus, if any */
    Uint8 state;        /**< ::SDL_PRESSED or ::SDL_RELEASED */
    Uint8 repeat;       /**< Non-zero if this is a key repeat */
    Uint8 padding2;
    Uint8 padding3;
    SDL_Keysym keysym;  /**< The key that was pressed or released */
} SDL_KeyboardEvent;

查看文档,我们知道如果想要获取按键的类型,可以查看 keysym 字段,它同样是一个结构体类型:

typedef struct SDL_Keysym
{
    SDL_Scancode scancode;      /**< SDL physical key code - see ::SDL_Scancode for details */
    SDL_Keycode sym;            /**< SDL virtual key code - see ::SDL_Keycode for details */
    Uint16 mod;                 /**< current key modifiers */
    Uint32 unused;
} SDL_Keysym;

我们这里可以通过 sym 或者 scancode 来区分键盘的按键类型。

同理,鼠标事件和键盘事件类似,可以在 SDL_Event 中查到:

//SDL_Event evt;
evt.button

该结构体的成员如下:

typedef struct SDL_MouseButtonEvent
{
    Uint32 type;        /**< ::SDL_MOUSEBUTTONDOWN or ::SDL_MOUSEBUTTONUP */
    Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
    Uint32 windowID;    /**< The window with mouse focus, if any */
    Uint32 which;       /**< The mouse instance id, or SDL_TOUCH_MOUSEID */
    Uint8 button;       /**< The mouse button index */
    Uint8 state;        /**< ::SDL_PRESSED or ::SDL_RELEASED */
    Uint8 clicks;       /**< 1 for single-click, 2 for double-click, etc. */
    Uint8 padding1;
    Sint32 x;           /**< X coordinate, relative to window */
    Sint32 y;           /**< Y coordinate, relative to window */
} SDL_MouseButtonEvent;

这里已经可以通过按键索引字段 button 来区分按下哪个按钮,并可以通过 x,y 字段获取坐标信息,当然除了这两类之外,还有很多其它额外的描述信息,你可以根据自己程序的情况,查看相关感兴趣的字段描述。

所以其它的事件类型都可以按照这个套路获取相关信息,在游戏中随时可以获取相关的输入事件,执行游戏中对应的逻辑,更多相关信息,等待具体实战中涉及到的时候再详细讲解,对于初学者掌握上面的套路基本上已经可以应付绝大部分的情况了。