Одной из основ разработки игр является обработка сообщений поступающих во время каких-либо событий (Events). Все видеоигры, от тенниса до очень сложных игр для ПК и консолей, используют т.н. события для взаимодействия с игроком. Эти события могут поступать от клавиатур, мышей, джойстиков, геймпадов, и т.д., а также от самой операционной системы. Важно понимать, как они работают, если мы хотим надлежащим образом обрабатывать взаимодействие пользователя с игрой. Мы уже использовали события, но только для закрытия нашего окна, теперь мы будем глубже разбираться с тем, как получать события от пользователя.
Как вы уже заметили, каждый урок построен на основе предыдущего, поэтому мы не будем пока отступать от этой традиции. Для того, чтобы отслеживать все события и обрабатывать их в предназначенных для этого функциях, нужно создать новый класс. Добавьте в проект два файла CEvent.h и CEvent.cpp. В этих двух файлах мы будем обрабатывать приходящие сообщения, и вызвать соответствующую функцию. Наш класс CApp будет наследоваться от этого класса, поэтому, когда мы начнем обрабатывать события, просто будем переопределять его функции.
Откройте CEvent.h и добавьте следующий код:
Нехилый получился класс? Ну а теперь откройте CEvent.cpp, и добавьте следующий код:
Да… Очень много кода, но все SDL события должны быть покрыты (т.е. как-то обрабатываться). В этом обработчике, мы принимаем указатель на событие типа SDL_Event, а затем, в зависимости от вида события (нажатие клавиши или перемещение мыши) вызываем соответствующую функцию. Не пугайтесь такого объема кода, на самом деле тут всё предельно просто.
Теперь, когда у нас всё настроено, давайте перейдем в CApp.h и добавим созданный класс:
Все должно нормально откомпилироваться. У нас есть настроенный класс обработки событий, осталось связать его с основным классом игры. Откройте CApp_OnEvent.cpp и редактировать следующие функции:
Теперь наше сообщение будет передаваться в класс и там корректно обрабатываться. Наша функция проверки события теперь переопределена. Мы избавились от проверки на SDL_Quit, и вместо этого передаем событие во внутреннюю функцию. Снова откройте CApp.h снова, и добавить следующие функции:
Функция OnExit будет обрабатывать событие SDL_Quit (нажатие пользователем крестика). Итак, прототип у нас есть, осталось запрограммировать его функционал. Откройте CApp_OnEvent.cpp, и добавьте следующее:
Перекомпилируйте, и запустите. Вы можете закрыть приложение, совсем как и раньше.
Я рекомендую вам ознакомиться с другими видами событий событий, попробовать прописать ответ на них в соответствующих функциях-обработчиках, потому-что в дальнейшем мы будем использовать некоторые из этих событий в наших играх.
В следующем уроке нас ждет увлекательное путешествие, в конце которого мы создадим нашу первую реальную игру — Tic-Tac-Toe (Крестики/Нолики).
Какой-то маленький получился урок, несмотря на большой объем кода. Видимо придется мне дополнить его небольшим примером обработки клавиш UP, DOWN, RIGHT и LEFT (стрелочки).
Откройте CEvent.cpp, найдите там функцию CEvent::OnKeyDown и напишите в ней следующее:
Откомпилируйте и запустите, а затем понажимайте стрелочки и закройте окно игры. Вы должны наблюдать примерно это:

Видите консольный вывод? Он отображает результат того что в функции CEvent::OnKeyDown мы теперь отслеживаем факт нажатия одной из стрелочек и выводим описание того какая из них была нажата (ну и клавиша Escape туда попала).
Ссылки на исходный код:
Ссылки на все уроки:
Как вы уже заметили, каждый урок построен на основе предыдущего, поэтому мы не будем пока отступать от этой традиции. Для того, чтобы отслеживать все события и обрабатывать их в предназначенных для этого функциях, нужно создать новый класс. Добавьте в проект два файла CEvent.h и CEvent.cpp. В этих двух файлах мы будем обрабатывать приходящие сообщения, и вызвать соответствующую функцию. Наш класс CApp будет наследоваться от этого класса, поэтому, когда мы начнем обрабатывать события, просто будем переопределять его функции.
Откройте CEvent.h и добавьте следующий код:
CEvent.h
#ifndef _CEVENT_H_
#define _CEVENT_H_
#include <SDL.h>
class CEvent {
public:
CEvent();
virtual ~CEvent();
virtual void OnEvent(SDL_Event* Event);
virtual void OnInputFocus();
virtual void OnInputBlur();
virtual void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode);
virtual void OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode);
virtual void OnMouseFocus();
virtual void OnMouseBlur();
virtual void OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle);
virtual void OnMouseWheel(bool Up, bool Down); //Not implemented
virtual void OnLButtonDown(int mX, int mY);
virtual void OnLButtonUp(int mX, int mY);
virtual void OnRButtonDown(int mX, int mY);
virtual void OnRButtonUp(int mX, int mY);
virtual void OnMButtonDown(int mX, int mY);
virtual void OnMButtonUp(int mX, int mY);
virtual void OnJoyAxis(Uint8 which, Uint8 axis, Sint16 value);
virtual void OnJoyButtonDown(Uint8 which, Uint8 button);
virtual void OnJoyButtonUp(Uint8 which, Uint8 button);
virtual void OnJoyHat(Uint8 which, Uint8 hat, Uint8 value);
virtual void OnJoyBall(Uint8 which, Uint8 ball, Sint16 xrel, Sint16 yrel);
virtual void OnMinimize();
virtual void OnRestore();
virtual void OnResize(int w,int h);
virtual void OnExpose();
virtual void OnExit();
virtual void OnUser(Uint8 type, int code, void* data1, void* data2);
};
#endif
Нехилый получился класс? Ну а теперь откройте CEvent.cpp, и добавьте следующий код:
CEvent.cpp
#include "CEvent.h"
CEvent::CEvent() {
}
CEvent::~CEvent() {
//Do nothing
}
void CEvent::OnEvent(SDL_Event* Event) {
switch(Event->type) {
case SDL_ACTIVEEVENT: {
switch(Event->active.state) {
case SDL_APPMOUSEFOCUS: {
if ( Event->active.gain ) OnMouseFocus();
else OnMouseBlur();
break;
}
case SDL_APPINPUTFOCUS: {
if ( Event->active.gain ) OnInputFocus();
else OnInputBlur();
break;
}
case SDL_APPACTIVE: {
if ( Event->active.gain ) OnRestore();
else OnMinimize();
break;
}
}
break;
}
case SDL_KEYDOWN: {
OnKeyDown(Event->key.keysym.sym,Event->key.keysym.mod,Event->key.keysym.unicode);
break;
}
case SDL_KEYUP: {
OnKeyUp(Event->key.keysym.sym,Event->key.keysym.mod,Event->key.keysym.unicode);
break;
}
case SDL_MOUSEMOTION: {
OnMouseMove(Event->motion.x,Event->motion.y,Event->motion.xrel,Event->motion.yrel,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_LEFT))!=0,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_RIGHT))!=0,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_MIDDLE))!=0);
break;
}
case SDL_MOUSEBUTTONDOWN: {
switch(Event->button.button) {
case SDL_BUTTON_LEFT: {
OnLButtonDown(Event->button.x,Event->button.y);
break;
}
case SDL_BUTTON_RIGHT: {
OnRButtonDown(Event->button.x,Event->button.y);
break;
}
case SDL_BUTTON_MIDDLE: {
OnMButtonDown(Event->button.x,Event->button.y);
break;
}
}
break;
}
case SDL_MOUSEBUTTONUP: {
switch(Event->button.button) {
case SDL_BUTTON_LEFT: {
OnLButtonUp(Event->button.x,Event->button.y);
break;
}
case SDL_BUTTON_RIGHT: {
OnRButtonUp(Event->button.x,Event->button.y);
break;
}
case SDL_BUTTON_MIDDLE: {
OnMButtonUp(Event->button.x,Event->button.y);
break;
}
}
break;
}
case SDL_JOYAXISMOTION: {
OnJoyAxis(Event->jaxis.which,Event->jaxis.axis,Event->jaxis.value);
break;
}
case SDL_JOYBALLMOTION: {
OnJoyBall(Event->jball.which,Event->jball.ball,Event->jball.xrel,Event->jball.yrel);
break;
}
case SDL_JOYHATMOTION: {
OnJoyHat(Event->jhat.which,Event->jhat.hat,Event->jhat.value);
break;
}
case SDL_JOYBUTTONDOWN: {
OnJoyButtonDown(Event->jbutton.which,Event->jbutton.button);
break;
}
case SDL_JOYBUTTONUP: {
OnJoyButtonUp(Event->jbutton.which,Event->jbutton.button);
break;
}
case SDL_QUIT: {
OnExit();
break;
}
case SDL_SYSWMEVENT: {
//Ignore
break;
}
case SDL_VIDEORESIZE: {
OnResize(Event->resize.w,Event->resize.h);
break;
}
case SDL_VIDEOEXPOSE: {
OnExpose();
break;
}
default: {
OnUser(Event->user.type,Event->user.code,Event->user.data1,Event->user.data2);
break;
}
}
}
void CEvent::OnInputFocus() {
//Pure virtual, do nothing
}
void CEvent::OnInputBlur() {
//Pure virtual, do nothing
}
void CEvent::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
//Pure virtual, do nothing
}
void CEvent::OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode) {
//Pure virtual, do nothing
}
void CEvent::OnMouseFocus() {
//Pure virtual, do nothing
}
void CEvent::OnMouseBlur() {
//Pure virtual, do nothing
}
void CEvent::OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle) {
//Pure virtual, do nothing
}
void CEvent::OnMouseWheel(bool Up, bool Down) {
//Pure virtual, do nothing
}
void CEvent::OnLButtonDown(int mX, int mY) {
//Pure virtual, do nothing
}
void CEvent::OnLButtonUp(int mX, int mY) {
//Pure virtual, do nothing
}
void CEvent::OnRButtonDown(int mX, int mY) {
//Pure virtual, do nothing
}
void CEvent::OnRButtonUp(int mX, int mY) {
//Pure virtual, do nothing
}
void CEvent::OnMButtonDown(int mX, int mY) {
//Pure virtual, do nothing
}
void CEvent::OnMButtonUp(int mX, int mY) {
//Pure virtual, do nothing
}
void CEvent::OnJoyAxis(Uint8 which,Uint8 axis,Sint16 value) {
//Pure virtual, do nothing
}
void CEvent::OnJoyButtonDown(Uint8 which,Uint8 button) {
//Pure virtual, do nothing
}
void CEvent::OnJoyButtonUp(Uint8 which,Uint8 button) {
//Pure virtual, do nothing
}
void CEvent::OnJoyHat(Uint8 which,Uint8 hat,Uint8 value) {
//Pure virtual, do nothing
}
void CEvent::OnJoyBall(Uint8 which,Uint8 ball,Sint16 xrel,Sint16 yrel) {
//Pure virtual, do nothing
}
void CEvent::OnMinimize() {
//Pure virtual, do nothing
}
void CEvent::OnRestore() {
//Pure virtual, do nothing
}
void CEvent::OnResize(int w,int h) {
//Pure virtual, do nothing
}
void CEvent::OnExpose() {
//Pure virtual, do nothing
}
void CEvent::OnExit() {
//Pure virtual, do nothing
}
void CEvent::OnUser(Uint8 type, int code, void* data1, void* data2) {
//Pure virtual, do nothing
}
Да… Очень много кода, но все SDL события должны быть покрыты (т.е. как-то обрабатываться). В этом обработчике, мы принимаем указатель на событие типа SDL_Event, а затем, в зависимости от вида события (нажатие клавиши или перемещение мыши) вызываем соответствующую функцию. Не пугайтесь такого объема кода, на самом деле тут всё предельно просто.
Теперь, когда у нас всё настроено, давайте перейдем в CApp.h и добавим созданный класс:
CApp.h
#ifndef _CAPP_H_
#define _CAPP_H_
#include <SDL.h>
#include "CEvent.h"
#include "CSurface.h"
class CApp : public CEvent {
private:
bool Running;
SDL_Surface* Surf_Display;
SDL_Surface* Surf_Test;
public:
CApp();
int OnExecute();
public:
bool OnInit();
void OnEvent(SDL_Event* Event);
void OnLoop();
void OnRender();
void OnCleanup();
};
#endif
Все должно нормально откомпилироваться. У нас есть настроенный класс обработки событий, осталось связать его с основным классом игры. Откройте CApp_OnEvent.cpp и редактировать следующие функции:
CApp_OnEvent.cpp
#include "CApp.h"
void CApp::OnEvent(SDL_Event* Event) {
CEvent::OnEvent(Event);
}
Теперь наше сообщение будет передаваться в класс и там корректно обрабатываться. Наша функция проверки события теперь переопределена. Мы избавились от проверки на SDL_Quit, и вместо этого передаем событие во внутреннюю функцию. Снова откройте CApp.h снова, и добавить следующие функции:
CApp.h
#ifndef _CAPP_H_
#define _CAPP_H_
#include <SDL.h>
#include "CEvent.h"
#include "CSurface.h"
class CApp : public CEvent {
private:
bool Running;
SDL_Surface* Surf_Display;
SDL_Surface* Surf_Test;
public:
CApp();
int OnExecute();
public:
bool OnInit();
void OnEvent(SDL_Event* Event);
void OnExit();
void OnLoop();
void OnRender();
void OnCleanup();
};
#endif
Функция OnExit будет обрабатывать событие SDL_Quit (нажатие пользователем крестика). Итак, прототип у нас есть, осталось запрограммировать его функционал. Откройте CApp_OnEvent.cpp, и добавьте следующее:
CApp_OnEvent.cpp
#include "CApp.h"
void CApp::OnEvent(SDL_Event* Event) {
CEvent::OnEvent(Event);
}
void CApp::OnExit() {
Running = false;
}
Перекомпилируйте, и запустите. Вы можете закрыть приложение, совсем как и раньше.
Я рекомендую вам ознакомиться с другими видами событий событий, попробовать прописать ответ на них в соответствующих функциях-обработчиках, потому-что в дальнейшем мы будем использовать некоторые из этих событий в наших играх.
В следующем уроке нас ждет увлекательное путешествие, в конце которого мы создадим нашу первую реальную игру — Tic-Tac-Toe (Крестики/Нолики).
Какой-то маленький получился урок, несмотря на большой объем кода. Видимо придется мне дополнить его небольшим примером обработки клавиш UP, DOWN, RIGHT и LEFT (стрелочки).
Откройте CEvent.cpp, найдите там функцию CEvent::OnKeyDown и напишите в ней следующее:
CEvent.cpp
В самом начале файла добавьте
А потом перепишите функционал CEvent::OnKeyDown
#include <iostream>
using namespace std;
А потом перепишите функционал CEvent::OnKeyDown
void CEvent::OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode) {
switch (sym)
{
case SDLK_ESCAPE:
{
cout << "Hey, HABR! Escape pressed by m0sk1t\n";
break;
}
case SDLK_UP:
{
cout << "Hey, HABR! UP pressed by m0sk1t\n";
break;
}
case SDLK_DOWN:
{
cout << "Hey, HABR! DOWN pressed by m0sk1t\n";
break;
}
case SDLK_LEFT:
{
cout << "Hey, HABR! LEFT pressed by m0sk1t\n";
break;
}
case SDLK_RIGHT:
{
cout << "Hey, HABR! RIGHT pressed by m0sk1t\n";
break;
}
default: break;
}
}
Откомпилируйте и запустите, а затем понажимайте стрелочки и закройте окно игры. Вы должны наблюдать примерно это:

Видите консольный вывод? Он отображает результат того что в функции CEvent::OnKeyDown мы теперь отслеживаем факт нажатия одной из стрелочек и выводим описание того какая из них была нажата (ну и клавиша Escape туда попала).
Ссылки на исходный код:
Ссылки на все уроки:
- Разработка игрового фрэймворка. Часть 1 — Основы SDL
- Разработка игрового фрэймворка. Часть 2 — Координаты и отображение
- Разработка игрового фрэймворка. Часть 3 — События
- Разработка игрового фрэймворка. Часть 4 — Крестики-Нолики
- Разработка игрового фрэймворка. Часть 5 — Анимация
- Разработка игрового фрэймворка. Часть 6 — Сущности