Tài trợ bài viết này và giới thiệu dịch vụ, sản phẩm, thương hiệu, nhu cầu tuyển dụng của doanh nghiệp đến với cộng đồng.
STDIO Xử lý sự kiện trong game vô cùng quan trọng, đòi hỏi chúng ta phải thao tác với bàn phím, chuột, hay touch đối với những dòng có màn hình cảm ứng. Framework SDL có hỗ trợ đầy đủ những sự kiện đó để chúng ta thao tác và xây dựng ứng dụng. Trong bài viết này tôi sẽ hướng dẫn các bạn xử lý sự kiện trong SDL.
Nội dung bài viết

Giới thiệu

Xử lý sự kiện trong game vô cùng quan trọng, đòi hỏi chúng ta phải thao tác với bàn phím, chuột, hay touch đối với những dòng có màn hình cảm ứng. Framework SDL có hỗ trợ đầy đủ những sự kiện đó để chúng ta thao tác và xây dựng ứng dụng. Trong bài viết này tôi sẽ hướng dẫn các bạn xử lý sự kiện trong SDL.

Tiền đề bài viết

Tiếp nối những bài viết hướng dẫn lập trình với framework SDL (Simple DirectMedia Layer).

Đối tượng hướng đến

Những lập trình viên đã có kiến thức vững chắc về ngôn ngữ lập trình C++, mong muốn xây dựng game cơ bản sử dụng framework SDL.

Sự kiện trong SDL

Cấu trúc SDL_Event

SDL có cung cấp một union có tên là SDL_Event để chứa tất cả các thông tin của tất cả các event trong bộ thư viện này. Cấu trúc của nó như sau

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 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 2.0.4)
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 multi finger gesture data
SDL_DollarGestureEvent dgesture multi finger gesture data
SDL_DropEvent drop drag and drop event data

Dưới đây là định nghĩa SDL_Event trong bộ thư viện SDL:

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;

Lấy thông tin Event

Tất cả các event của Windows đều được lưu ở hằng đợi (queue). SDL có định nghĩa cho chúng ta một hàm để lấy ra event từ hằng đợi đó. Hàm này có prototype như sau:

int SDL_PollEvent(SDL_Event* event)

Có tham số truyền vào là một con trỏ tới đối tượng SDL_Event. Trả về 1 nếu có event tồn tại trong queue ngược lại trả về 0 nếu queue rỗng. Và có chức năng là lấy thông tin của event ở queue và đổ dữ liệu vào đối tượng SDL_Event sau đó xóa event đó khỏi queue.

Ví dụ:

SDL_Event mainEvent;
SDL_PollEvent(&mainEvent);

Thao tác với Event trong ứng dụng

Ở bài Khởi Tạo Môi Trường Lập Trình Game Sử Dụng Thư Viện SDL (Simple DirectMedia Layer) tôi đã khởi tạo một cửa sổ và trong bài viết này tôi tiếp tục sử dụng project ở bài trước với mã nguồn như dưới đây:

#include <stdio.h>
#include <SDL.h>

#undef main
int main()
{
	//initializes  the subsystems
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
	{
		printf("Unable to initialize SDL %s\n", SDL_GetError());
		return -1;
	}

	SDL_Window* window = NULL;
	//Create window
	window = SDL_CreateWindow("Stdio.vn - SDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 640, SDL_WINDOW_SHOWN);
	if (window == NULL)
	{
		printf("Could not create window %s", SDL_GetError());
		return -1;
	}
	
	//Delay 2s
	SDL_Delay(2000);

	//Destroy a window.
	SDL_DestroyWindow(window);

	//cleans up all initialized subsystems
	SDL_Quit();
	return 0;
}

Project trước tôi cho ứng delay 2s sau đó giải phóng tài nguyên và thoát chương trình. Do bản chất của game là cập nhật và render liên lục nên tôi tạo một vòng lặp while để duy trì vòng đời của game (main game, main loop). Trong vòng lặp đó sẽ lấy ra các sự kiện và xử lý riêng tùy theo nhu cầu của mỗi lập trình viên.

bool isRunning = true;
SDL_Event mainEvent;

while (isRunning)
{
	while (SDL_PollEvent(&mainEvent))
	{
		switch (mainEvent.type)
		{

		//User - requested quit
		case SDL_QUIT:
		{
			isRunning = false;
            break;
		}

		//Mouse button pressed
		case SDL_MOUSEBUTTONDOWN:
		{
			//Do Something
			break;
		}

		//Mouse button released
		case SDL_MOUSEBUTTONUP:
		{
			//Do Something
			break;
		}

		//Mouse moved
		case SDL_MOUSEMOTION:
		{
			//Do Something
			break;
		}
		
		//Mouse wheel motion
		case SDL_MOUSEWHEEL:
		{
			//Do Something
			break;
		}
		
		//Key released
		case SDL_KEYDOWN:
		{
			//Do Something
			break;
		}

		//Key pressed
		case SDL_KEYUP:
		{
			//Do Something
			break;
		}
		default:
			break;
		}
	}
}

Các bạn để ý rằng giá trị main.type được so sánh với các giá trị như SDL_KEYDOWN,  SDL_KEYUP để biết rằng đó là loại sự kiện gì.

Ta có mối quan hệ giữa trường dữ liệu type của đối tượng SDL_Event với các cấu trúc SDL_EventType như sau, dựa vào đó các bạn xử lý theo từng event cho phù hợp.

Event Type Event Structure SDL_Event Field
SDL_AUDIODEVICEADDED
SDL_AUDIODEVICEREMOVED
SDL_AudioDeviceEvent adevice
SDL_CONTROLLERAXISMOTION SDL_ControllerAxisEvent caxis
SDL_CONTROLLERBUTTONDOWN
SDL_CONTROLLERBUTTONUP
SDL_ControllerButtonEvent cbutton
SDL_CONTROLLERDEVICEADDED
SDL_CONTROLLERDEVICEREMOVED
SDL_CONTROLLERDEVICEREMAPPED
SDL_ControllerDeviceEvent cdevice
SDL_DOLLARGESTURE
SDL_DOLLARRECORD
SDL_DollarGestureEvent dgesture
SDL_DROPFILE SDL_DropEvent drop
SDL_FINGERMOTION
SDL_FINGERDOWN
SDL_FINGERUP
SDL_TouchFingerEvent tfinger
SDL_KEYDOWN
SDL_KEYUP
SDL_KeyboardEvent key
SDL_JOYAXISMOTION SDL_JoyAxisEvent jaxis
SDL_JOYBALLMOTION SDL_JoyBallEvent jball
SDL_JOYHATMOTION SDL_JoyHatEvent jhat
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
SDL_JoyButtonEvent jbutton
SDL_JOYDEVICEADDED
SDL_JOYDEVICEREMOVED
SDL_JoyDeviceEvent jdevice
SDL_MOUSEMOTION SDL_MouseMotionEvent motion
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
SDL_MouseButtonEvent button
SDL_MOUSEWHEEL SDL_MouseWheelEvent wheel
SDL_MULTIGESTURE SDL_MultiGestureEvent mgesture
SDL_QUIT SDL_QuitEvent quit
SDL_SYSWMEVENT SDL_SysWMEvent syswm
SDL_TEXTEDITING SDL_TextEditingEvent edit
SDL_TEXTINPUT SDL_TextInputEvent text
SDL_USEREVENT SDL_UserEvent user
SDL_WINDOWEVENT SDL_WindowEvent window

SDL_EventType được định nghĩa trong bộ thư viện SDL như sau:

typedef enum
{
    SDL_FIRSTEVENT     = 0,     /**< Unused (do not remove) */

    /* Application events */
    SDL_QUIT           = 0x100, /**< User-requested quit */

    /* These application events have special meaning on iOS, see README-ios.txt for details */
    SDL_APP_TERMINATING,        /**< The application is being terminated by the OS
                                     Called on iOS in applicationWillTerminate()
                                     Called on Android in onDestroy()
                                */
    SDL_APP_LOWMEMORY,          /**< The application is low on memory, free memory if possible.
                                     Called on iOS in applicationDidReceiveMemoryWarning()
                                     Called on Android in onLowMemory()
                                */
    SDL_APP_WILLENTERBACKGROUND, /**< The application is about to enter the background
                                     Called on iOS in applicationWillResignActive()
                                     Called on Android in onPause()
                                */
    SDL_APP_DIDENTERBACKGROUND, /**< The application did enter the background and may not get CPU for some time
                                     Called on iOS in applicationDidEnterBackground()
                                     Called on Android in onPause()
                                */
    SDL_APP_WILLENTERFOREGROUND, /**< The application is about to enter the foreground
                                     Called on iOS in applicationWillEnterForeground()
                                     Called on Android in onResume()
                                */
    SDL_APP_DIDENTERFOREGROUND, /**< The application is now interactive
                                     Called on iOS in applicationDidBecomeActive()
                                     Called on Android in onResume()
                                */

    /* Window events */
    SDL_WINDOWEVENT    = 0x200, /**< Window state change */
    SDL_SYSWMEVENT,             /**< System specific event */

    /* Keyboard events */
    SDL_KEYDOWN        = 0x300, /**< Key pressed */
    SDL_KEYUP,                  /**< Key released */
    SDL_TEXTEDITING,            /**< Keyboard text editing (composition) */
    SDL_TEXTINPUT,              /**< Keyboard text input */

    /* Mouse events */
    SDL_MOUSEMOTION    = 0x400, /**< Mouse moved */
    SDL_MOUSEBUTTONDOWN,        /**< Mouse button pressed */
    SDL_MOUSEBUTTONUP,          /**< Mouse button released */
    SDL_MOUSEWHEEL,             /**< Mouse wheel motion */

    /* Joystick events */
    SDL_JOYAXISMOTION  = 0x600, /**< Joystick axis motion */
    SDL_JOYBALLMOTION,          /**< Joystick trackball motion */
    SDL_JOYHATMOTION,           /**< Joystick hat position change */
    SDL_JOYBUTTONDOWN,          /**< Joystick button pressed */
    SDL_JOYBUTTONUP,            /**< Joystick button released */
    SDL_JOYDEVICEADDED,         /**< A new joystick has been inserted into the system */
    SDL_JOYDEVICEREMOVED,       /**< An opened joystick has been removed */

    /* Game controller events */
    SDL_CONTROLLERAXISMOTION  = 0x650, /**< Game controller axis motion */
    SDL_CONTROLLERBUTTONDOWN,          /**< Game controller button pressed */
    SDL_CONTROLLERBUTTONUP,            /**< Game controller button released */
    SDL_CONTROLLERDEVICEADDED,         /**< A new Game controller has been inserted into the system */
    SDL_CONTROLLERDEVICEREMOVED,       /**< An opened Game controller has been removed */
    SDL_CONTROLLERDEVICEREMAPPED,      /**< The controller mapping was updated */

    /* Touch events */
    SDL_FINGERDOWN      = 0x700,
    SDL_FINGERUP,
    SDL_FINGERMOTION,

    /* Gesture events */
    SDL_DOLLARGESTURE   = 0x800,
    SDL_DOLLARRECORD,
    SDL_MULTIGESTURE,

    /* Clipboard events */
    SDL_CLIPBOARDUPDATE = 0x900, /**< The clipboard changed */

    /* Drag and drop events */
    SDL_DROPFILE        = 0x1000, /**< The system requests a file open */

    /* Render events */
    SDL_RENDER_TARGETS_RESET = 0x2000, /**< The render targets have been reset */

    /** Events ::SDL_USEREVENT through ::SDL_LASTEVENT are for your use,
     *  and should be allocated with SDL_RegisterEvents()
     */
    SDL_USEREVENT    = 0x8000,

    /**
     *  This last event is only for bounding internal arrays
     */
    SDL_LASTEVENT    = 0xFFFF
} SDL_EventType;

Ví dụ Demo

#include <stdio.h>
#include <SDL.h>

#undef main
int main()
{
	//initializes  the subsystems
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
	{
		printf("Unable to initialize SDL %s\n", SDL_GetError());
		return -1;
	}

	SDL_Window* window = NULL;
	bool isRunning = true;
	SDL_Event mainEvent;
	//Create window
	window = SDL_CreateWindow("Stdio.vn - SDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 640, SDL_WINDOW_SHOWN);
	if (window == NULL)
	{
		printf("Could not create window %s", SDL_GetError());
		return -1;
	}

	while (isRunning)
	{
		while (SDL_PollEvent(&mainEvent))
		{
			switch (mainEvent.type)
			{

				//User - requested quit
			case SDL_QUIT:
			{
				isRunning = false;
				break;
			}

			//Mouse button pressed
			case SDL_MOUSEBUTTONDOWN:
			{
				if (mainEvent.button.button == SDL_BUTTON_LEFT)
				{
					printf("Left Mouse Clicked\n");
				}
				else if (mainEvent.button.button == SDL_BUTTON_RIGHT)
				{
					printf("Right Mouse Clicked\n");
				}
				break;
			}

			//Mouse button released
			case SDL_MOUSEBUTTONUP:
			{

				if (mainEvent.button.button == SDL_BUTTON_LEFT)
				{
					printf("Left Mouse Released\n");
				}
				else if (mainEvent.button.button == SDL_BUTTON_RIGHT)
				{
					printf("Right Mouse Released\n");
				}
				break;
			}

			//Mouse moved
			case SDL_MOUSEMOTION:
			{
				printf("Current Position Mouse: (%d, %d)\n", mainEvent.motion.x, mainEvent.motion.y);
				break;
			}

			//Mouse wheel motion
			case SDL_MOUSEWHEEL:
			{
				printf("Mouse Wheel Motion\n");
				break;
			}

			//Key pressed
			case SDL_KEYDOWN:
			{
				printf("%c released\n", mainEvent.key.keysym.sym);
				break;
			}

			//Key released
			case SDL_KEYUP:
			{
				printf("%c pressed\n", mainEvent.key.keysym.sym);
				break;
			}
			default:
				break;
			}
		}
	}

	//Destroy a window.
	SDL_DestroyWindow(window);

	//cleans up all initialized subsystems
	SDL_Quit();
	return 0;
}

Download demo

Stdio_SDL_Event_VS2013

THẢO LUẬN
ĐÓNG