SDL2 游戏开发之事件处理
- W_Z_C
- 共 1856 字,阅读约 5 分钟
本篇内容我们来学习一下 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 字段获取坐标信息,当然除了这两类之外,还有很多其它额外的描述信息,你可以根据自己程序的情况,查看相关感兴趣的字段描述。
所以其它的事件类型都可以按照这个套路获取相关信息,在游戏中随时可以获取相关的输入事件,执行游戏中对应的逻辑,更多相关信息,等待具体实战中涉及到的时候再详细讲解,对于初学者掌握上面的套路基本上已经可以应付绝大部分的情况了。