介绍
本教程是窗口事件的详细列表。描述它们,并展示如何正确的(以及不要如何)使用它们。
sf::Event类型
在处理事件之前,了解什么是 sf::Event 类型以及如何正确使用它非常重要。sf::Event 是一个联合体,这意味着它的成员在同一时间内只有一个是有效的(记住你的 C++ 课程:联合体的所有成员共享同一内存空间)。有效的成员是与事件类型匹配的成员,例如 KeyPressed 事件的 event.key。尝试读取任何其他成员将导致未定义的行为(最有可能的是:随机或无效值)。重要的是永远不要尝试使用与其类型不匹配的事件成员。
sf::Event 实例是由 sf::Window 类的 pollEvent(或 waitEvent)函数填充的。只有这两个函数可以产生有效的事件,任何尝试使用未由 pollEvent(或 waitEvent)的成功调用返回的 sf::Event 将导致上述相同的未定义行为。
为了清楚起见,这是一个典型的事件循环的样子:
sf::Event event;
// while there are pending events...
while (window.pollEvent(event))
{
// check the type of the event...
switch (event.type)
{
// window closed
case sf::Event::Closed:
window.close();
break;
// key pressed
case sf::Event::KeyPressed:
...
break;
// we don't process other types of events
default:
break;
}
}
再读一遍上面的段落,确保您完全理解了它,对于缺乏经验的程序员来说,sf::Event 联合体是许多问题的根源。
好的,现在我们可以看到SFML支持哪些事件,它们的含义以及如何正确使用它们。
关闭事件
当用户通过窗口管理器提供的任何方式(如“关闭”按钮、键盘快捷键等)想要关闭窗口时,会触发 sf::Event::Closed 事件。此事件仅代表关闭请求,在收到事件时窗口尚未关闭。
典型的代码只会在收到此事件后调用 window.close() 来关闭窗口。然而,您可能还想做一些其他事情,比如保存当前应用程序状态或询问用户要做什么。如果您什么也不做,则窗口仍然保持打开状态。
在 sf::Event 联合体中,此事件没有与之关联的成员。
if (event.type == sf::Event::Closed)
window.close();
调整大小事件
当窗口的大小被调整时(无论是通过用户操作还是通过调用 window.setSize 进行编程),会触发 sf::Event::Resized 事件。
您可以使用此事件来调整渲染设置:如果直接使用 OpenGL,则可以调整视口;如果使用 sfml-graphics,则可以调整当前视图。
与此事件关联的成员是 event.size,它包含窗口的新大小。
if (event.type == sf::Event::Resized)
{
std::cout << "new width: " << event.size.width << std::endl;
std::cout << "new height: " << event.size.height << std::endl;
}
失去焦点和获取焦点事件
当窗口失去/获得焦点时,即当用户切换当前活动窗口时,将触发 sf::Event::LostFocus 和 sf::Event::GainedFocus 事件。当窗口失去焦点时,它不会接收到键盘事件。
例如,如果您想在窗口不活动时暂停游戏,可以使用此事件。
在 sf::Event 联合中,这些事件没有关联成员。
if (event.type == sf::Event::LostFocus)
myGame.pause();
if (event.type == sf::Event::GainedFocus)
myGame.resume();
文本输入事件
当键入一个字符时,将触发 sf::Event::TextEntered 事件。这不应与 KeyPressed 事件混淆:TextEntered 解释用户输入并生成相应的可打印字符。例如,在法国键盘上按 ‘^’ 然后按 ‘e’ 将产生两个 KeyPressed 事件,但是一个包含 ‘ê’ 字符的单个 TextEntered 事件。它适用于操作系统提供的所有输入方法,甚至是最特定或最复杂的方法。
通常使用此事件来捕获文本字段中的用户输入。
在 sf::Event 联合中,与此事件相关联的成员是 event.text,它包含输入字符的 Unicode 值。您可以直接将其放入 sf::String 中,或在确保其在 ASCII 范围(0-127)内后将其转换为 char。
if (event.type == sf::Event::TextEntered)
{
if (event.text.unicode < 128)
std::cout << "ASCII character typed: " << static_cast<char>(event.text.unicode) << std::endl;
}
请注意,由于它们是Unicode标准的一部分,因此此事件会生成一些不可打印的字符,例如backspace。在大多数情况下,您需要过滤掉它们。
许多程序员使用KeyPressed事件获取用户输入,并开始实现疯狂的算法,试图解释所有可能的组合键以生成正确的字符。不要那样做!
按键按下和按键释放事件
当按下/释放键盘上的按键时,将触发 sf::Event::KeyPressed 和 sf::Event::KeyReleased 事件。
如果按住某个键不放,将生成多个 KeyPressed 事件,以默认操作系统延迟(即在文本编辑器中按住字母时应用的延迟相同)。要禁用重复的 KeyPressed 事件,可以调用 window.setKeyRepeatEnabled(false)。与此相反,显然 KeyReleased 事件永远不会重复。
如果您想在按键按下或释放时仅触发一次操作(例如,使用空格让角色跳跃,或使用 Esc 退出游戏),则使用此事件。
有时,人们尝试直接响应 KeyPressed 事件以实现流畅的移动。这样做将无法产生预期的效果,因为当您按住键时,您只会得到多个事件(请记住,有重复延迟)。要使用事件实现平滑的移动,必须使用一个布尔值,在 KeyPressed 上设置,在 KeyReleased 上清除;只要布尔值被设置,就可以移动(与事件无关)。
另一种(更简单)实现平滑移动的解决方案是使用 sf::Keyboard 的实时键盘输入(请参见专门的教程)。
与这些事件相关联的成员是 event.key,它包含所按下/释放的按键的scancode和key code,以及修饰键(alt、control、shift、system)的当前状态。
scancode是键盘上每个物理键的唯一值,不考虑语言或布局,而key code是基于用户选择的布局表示键。例如,在美国布局中,Z键位于X键左侧的底行。使用Z的scancode可以在任何键盘上标识此物理键位置。然而,在德国布局中,相同的物理键被标记为Y。因此,使用Y的key code可能会导致不同的物理键位置,具体取决于选择的布局。
如果键盘键的物理位置比取决于键盘布局的键值更重要(例如使用 WASD 键进行移动),强烈建议使用scancode而不是key code。
if (event.type == sf::Event::KeyPressed)
{
if (event.key.scancode == sf::Keyboard::Scan::Escape)
{
std::cout << "the escape key was pressed" << std::endl;
std::cout << "scancode: " << event.key.scancode << std::endl;
std::cout << "code: " << event.key.code << std::endl;
std::cout << "control: " << event.key.control << std::endl;
std::cout << "alt: " << event.key.alt << std::endl;
std::cout << "shift: " << event.key.shift << std::endl;
std::cout << "system: " << event.key.system << std::endl;
std::cout << "description: " << sf::Keyboard::getDescription(event.key.scancode).toAnsiString() << std::endl;
std::cout << "localize: " << sf::Keyboard::localize(event.key.scancode) << std::endl;
std::cout << "delocalize: " << sf::Keyboard::delocalize(event.key.code) << std::endl;
}
}
请注意,某些键对于操作系统具有特殊含义,并且会导致意外行为。例如,Windows上的F10键“窃取”焦点,或者使用Visual Studio时启动调试器的F12键。
鼠标滚轮移动事件
自SFML 2.3起,sf::Event::MouseWheelMoved事件已被弃用,请改为使用MouseWheelScrolled事件。
鼠标滚轮滚动事件
当鼠标滚轮向上或向下移动时,以及如果鼠标支持,则会在水平方向上移动时,触发sf::Event::MouseWheelScrolled事件。
与此事件相关联的成员是event.mouseWheelScroll,它包含滚轮移动的滴答数、滚轮的方向以及鼠标指针的当前位置。
if (event.type == sf::Event::MouseWheelScrolled)
{
if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel)
std::cout << "wheel type: vertical" << std::endl;
else if (event.mouseWheelScroll.wheel == sf::Mouse::HorizontalWheel)
std::cout << "wheel type: horizontal" << std::endl;
else
std::cout << "wheel type: unknown" << std::endl;
std::cout << "wheel movement: " << event.mouseWheelScroll.delta << std::endl;
std::cout << "mouse x: " << event.mouseWheelScroll.x << std::endl;
std::cout << "mouse y: " << event.mouseWheelScroll.y << std::endl;
}
鼠标按钮按下和鼠标按钮释放事件
当鼠标按下/释放时,触发sf::Event::MouseButtonPressed和sf::Event::MouseButtonReleased事件。
SFML支持5个鼠标按钮:左键、右键、中键(滚轮)、额外按钮1和额外按钮2(侧面按钮)。
与这些事件相关联的成员是event.mouseButton,它包含按下/释放按钮的代码,以及鼠标指针的当前位置。
if (event.type == sf::Event::MouseButtonPressed)
{
if (event.mouseButton.button == sf::Mouse::Right)
{
std::cout << "the right button was pressed" << std::endl;
std::cout << "mouse x: " << event.mouseButton.x << std::endl;
std::cout << "mouse y: " << event.mouseButton.y << std::endl;
}
}
鼠标移动事件
当鼠标在窗口内移动时,会触发sf::Event::MouseMoved事件。
即使窗口没有获得焦点,此事件也会被触发。但是,它仅在鼠标移动到窗口的内部区域时触发,而不是在移动到标题栏或边框时触发。
与这些事件相关联的成员是event.mouseMove,它包含鼠标指针相对于窗口的当前位置。
if (event.type == sf::Event::MouseMoved)
{
std::cout << "new mouse x: " << event.mouseMove.x << std::endl;
std::cout << "new mouse y: " << event.mouseMove.y << std::endl;
}
鼠标进入和鼠标离开事件
当鼠标光标进入/离开窗口时,会触发sf::Event::MouseEntered和sf::Event::MouseLeft事件。
在sf::Event联合中没有与这些事件相关联的成员。
if (event.type == sf::Event::MouseEntered)
std::cout << "the mouse cursor has entered the window" << std::endl;
if (event.type == sf::Event::MouseLeft)
std::cout << "the mouse cursor has left the window" << std::endl;
手柄按钮按下和手柄按钮释放事件
当按下/释放一个手柄按钮时,会触发sf::Event::JoystickButtonPressed和sf::Event::JoystickButtonReleased事件。
SFML支持最多8个手柄和32个按钮。
与这些事件相关联的成员是event.joystickButton,它包含手柄的标识符和按下/释放的按钮的索引。
if (event.type == sf::Event::JoystickButtonPressed)
{
std::cout << "joystick button pressed!" << std::endl;
std::cout << "joystick id: " << event.joystickButton.joystickId << std::endl;
std::cout << "button: " << event.joystickButton.button << std::endl;
}
手柄移动事件
当手柄轴移动时,会触发sf::Event::JoystickMoved事件。
手柄轴通常非常灵敏,这就是为什么SFML使用检测阈值来避免在您的事件循环中产生大量的JoystickMoved事件。可以使用Window::setJoystickThreshold函数更改此阈值,以便接收更多或更少的手柄移动事件。
SFML支持8个手柄轴:X、Y、Z、R、U、V、POV X和POV Y。它们与您的手柄的驱动程序的映射取决于其驱动程序。
与此事件相关联的成员是event.joystickMove,它包含手柄的标识符、轴的名称和其当前位置(范围为[-100,100])。
if (event.type == sf::Event::JoystickMoved)
{
if (event.joystickMove.axis == sf::Joystick::X)
{
std::cout << "X axis moved!" << std::endl;
std::cout << "joystick id: " << event.joystickMove.joystickId << std::endl;
std::cout << "new position: " << event.joystickMove.position << std::endl;
}
}
手柄连接和手柄断开连接事件
当手柄连接或断开连接时,将触发sf::Event::JoystickConnected和sf::Event::JoystickDisconnected事件。
与此事件相关联的成员是event.joystickConnect,它包含连接或断开连接的手柄的标识符。
if (event.type == sf::Event::JoystickConnected)
std::cout << "joystick connected: " << event.joystickConnect.joystickId << std::endl;
if (event.type == sf::Event::JoystickDisconnected)
std::cout << "joystick disconnected: " << event.joystickConnect.joystickId << std::endl;