用C++和SFML写游戏-事件的处理(4)

1,724 阅读5分钟

处理好来自用户的事件是一个很重要的话题。

首先我们来认识一下什么是事件

一般来说,事件是在某些情况下被触发的对象。它们依赖于操作系统,但是 SFML 为我们提供了一个很好的对象来处理事件,它是独立于操作系统的方式。sf::Event 类,这个类包含了各种事件。

建议阅读 SFML 官方文档去好好了解 SFML 事件的类型,地址是https://www.sfml-dev.org/tutorials/2.5/window-events.php。 注意 sf::Event 是一个联合体(union),使用的时候要恰当的选择我们需要的事件类型。

逻辑上,我们可以将事件分成四种类型:

  • 窗口
  • 键盘
  • 鼠标
  • 手柄

一、窗口事件

类型 相关成员 描述
Event::Closed None 当操作系统检测单用户想要关闭窗口时触发
Event::Resized Event::size 表示新窗口的大小 当操作系统检测单用户想要改变窗口大小时触发,或者调用Window::setSize()
Event::LostFocus Event::GainedFocus None 当窗口失去/得到焦点时触发,失去焦点的窗口不会收到键盘事件

二、键盘事件

类型 相关成员 描述
Event::KeyPressed Event::KeyReleased Event::key 表示的事当前按住/松开的按键 在得到焦点的窗口中,当一个按钮被按下或者松开的时候触发
Event::TextEntered Event::text 表示的是一个字符的UTF-32 unicode 任何时候一个字符输入的时候触发

三、鼠标事件

类型 相关成员 描述
Event::MouseMoved Event::mouseMove 表示鼠标新位置 当鼠标在窗口内移动时触发
Event::MouseButtonPressed Event::MouseButtonReleased Event::mouseButton 表示的是按下/松开的鼠标按钮以及它的位置 当鼠标上的按钮按下/松开的时候触发。目前支持5种类型的按钮:左、中、右、额外的按钮1和按钮2
Event::MouseWheelMoved Event::mouseWheel 表示的是滚轮以及鼠标位置 当鼠标上的滚轮滚动时触发

四、手柄事件

类型 相关成员 描述
Event::JoystickConnected Event::JoystickDisconnected Event::joystickConnect 表示的是手柄连接/断开连接时的ID 当手柄连接或者断开连接时触发
Event::JoystickButtonPressed Event::JoystickButtonReleased Event::joystickButton 表示的是手柄上按钮按下的数量以及手柄ID 当手柄上的按钮按下时触发,SFML为我们提供了最多8个手柄,每个手柄32个按钮的支持
Event::JoystickMoved Event::joystickMove 表示的是移动操纵杆的轴,操纵杆轴的位置以及手柄ID 移动操纵杆的轴时会触发此操作

对于窗口事件来说,我们通常用 bool Window::pollEvent(sf::Event& event) 函数,如果有事件将要被使用的话,函数会返回 trueevent参数会对应某个具体事件。==需要注意的是,同一时间下可能会有多个事件需要被处理,因此我们需要确保捕获到每一个事件。==代码如下:

while (window.isOpen()) {
    sf::Event event;
    while (window.pollEvent(event)) {
        
    }
    
    // 刷新帧
    // 绘制帧
}

运行上面这份代码之后,我们可以做出移动窗口,改变窗口大小或者是最小化窗口这些操作,但是有一个问题,那就是我们无法关闭窗口。当我们点击窗口的关闭按钮时,SFML 不会去关闭窗口,这一操作需要我们去实现。


五、 事件的使用

通过 Window::pollEvent() 捕获事件之后,我们可以通过 Event::type 检查事件类型。事件类型是 Event::EventTyp,它是 Event 类中的一个枚举类型。使用方法如下

sf::Event event;
while (window.pollEvent(event)) {
    if (event.type == sf::Event::EventType::Closed) {
        window.close();
    }
}

调用 Window::close() 可以关闭窗口。

如果我们想要使用更多的事件,我们的可以使用 switch 语法。如下:

sf::Event event;
while (window.pollEvent(event)) {
    switch (event.type) {
        case sf::Event::EventType::Closed:
            window.close();
            break;
        case sf::Event::EventType::KeyPressed:
            // 改变窗口标题当空格键按下的时候
            if (event.key.code == sf::Keyboard::Key::Space)
                window.setTitle("Space Pressed");
            break;
        case sf::Event::EventType::KeyReleased:
            // 再次改变窗口标题当空格键松开的时候
            if (event.key.code == sf::Keyboard::Key::Space)
                window.setTitle("Space Released");
            // 当 Esc 键松开的时候关闭窗口
            else if (event.key.code == sf::Keyboard::Key::Escape)
                window.close();
            break;
        default:
            break;
    }
}

上面这份代码的意思很容易理解,通过空格键可以改变窗口的标题,松开 Esc 键时关闭窗口。注意到 Event::key 包含了 code 成员,它是 Keyboard::Key 类型中的一个枚举类型。用上面这种方法可以处理其他事件类型。

然而,Event::EventType::TextEntered的使用有点意思。按键被按下/松开的时候可以被检测到且可以使用一种相对简单的方式去处理。但是当键入一些特殊字符的时候,事情变得有点复杂起来。例如,输入 ! 号的时候在大多数键盘中是使用 shift+1的。幸好 SFML 为我们提供了一个简单的方式去处理,仅当按下表示字符的组合键时才会发生事件,这意味着按下一个键(例如 shift)时不会触发事件。

下面这个例子使用了 TextEntered 事件组合了一个字符串,然后按下 Enter 键时这个字符串会显示在窗口标题中。

sf::String buffer;
while (window.isOpen()) {
    sf::Event event;
    while (window.pollEvent(event)) {
        switch (event.type) {
            case sf::Event::EventType::Closed:
	            window.close();
    	        break;
            case sf::Event::EventType::TextEntered:
                buffer += event.text.unicode;
                break;
            case sf::Event::EventType::KeyReleased:
                if (event.key.code == sf::Keyboard::Key::Return) {
                    window.setTitle(buffer);
                    buffer.clear();
                }
                break;
            default:
                break;
        }
    }
}

这里使用 sf::String 类型能够避免不同语言之间的字符编码转换。


这一节简单介绍了 SFML 中的事件处理,下一节将会学习图像以及精灵(sprite)的简单使用。😃