一个使用C++ Lambda函数的SFML输入处理程序

446 阅读7分钟

[

Anthony Morast

](anthony-a-morast.medium.com/?source=pos…)

安东尼-莫拉斯特

关注

6月10日

-

7分钟阅读

[

拯救

](medium.com/m/signin?ac…)

一个使用C++ Lambda函数的SFML输入处理程序

通过以下方式支持我的内容 成为媒介成员 或了解更多关于 有效 现代 C++ 开发(亚马逊联盟链接)。

传统上,在处理计算机图形应用的输入时,例如用户界面、视频游戏等,会使用一个越来越长的switch语句来确定哪个键被按下或释放,或者哪个鼠标按钮被点击。最近,我正在用SFML在C++中创建一个用户界面,以绘制我最近关于现代投资组合理论的博文中的点,以便更容易地查看数据(我知道我可以用Python完成这个工作,但一直想学习SFML,现在已经有一段时间了)。在这样做的过程中,我发现了一个有趣的解决方案,即使用一组std::maps 和lambda函数来处理用户输入的问题。在这篇文章中,我将通过一个简单的演示来介绍这种处理输入的方法。

演示

为了进行演示,我们创建了一个简单的2-D SFML应用程序。这个应用程序在屏幕中间画了一个圆。用户可以用方向键来移动这个圆圈(玩家)。相机可以用数字键盘移动,包括对角线。加号和减号用于放大和缩小,按Escape键可以关闭窗口并结束演示。此外,通过为鼠标左键添加一个回调,提供了一个鼠标输入的例子,当按下鼠标左键时,如果光标在玩家对象内,就会有一条信息打印到屏幕上。

该演示的大部分代码显示在下面。完整的代码在这篇文章的最后提供。

兰姆达表达式

Lambda表达式是在C++11中引入的,并发现了许多实际的使用案例。从本质上讲,Lambda表达式允许开发者将C++函数作为变量来处理,其方式明显比传递函数的指针要干净。兰姆达是许多其他编程语言的特征,包括Python和Java,并且已经成为许多类型的问题解决的主流。我不会在这里详细介绍λ表达式的基础知识,但感兴趣的读者可以在这篇媒介文章C++文档中了解更多。

在我们的例子中,我们将把每个回调函数定义为一个lambda表达式,把它们存储在一个map中,并在需要时(即当相应的按钮被按下时)调用它们。

函数参数

可能立即想到的一个问题是:我们将如何处理不同输入可能需要的不同参数?很简单。我们创建了一个类,InputCallbackParameters,它持有任何回调函数都需要的所有参数。这样做的好处是为每个回调创建一个共同的函数定义,所以输入处理类可以非常通用。由于这个类很快就会变得相当大(内存方面),我建议尽可能通过引用 或通过 const reference 来传递,这样就不会在复制对象上花费太多的计算资源。

在演示中,InputCallbackParameter类是相当小的

该类将有四个可公开访问的成员。

  1. 一个指向SFML窗口的指针,用于对窗口进行更新并确定某些游戏参数。注意,这必须是一个指针,因为sf::window类是不可复制的。
  2. 最后按下的键。这是用来处理需要连续按一个以上的键的输入。
  3. 玩家",它实际上只是一个sf::CircleShape,即一个在屏幕上移动的圆。
  4. 一个bool值,用来存储当鼠标左键在屏幕上任何地方被点击时,播放器是否被点击。

InputHandler类

有了这个回调参数类,输入处理类就可以被定义和实现。

正如在这里看到的,该类有三个私有的映射,用来保存从键盘按键/鼠标按键到lambda回调函数的映射。lambda回调必须被定义为C++功能对象(std::function)。语法被概括为

std::function<()>>。

所以我们的lambda函数将不返回任何东西(void),并接受一个参数,即PARAMETER,它被定义为InputCallbackParameters类,或者说是对它的引用。

using PARAMETER = InputCallbackParameters&;

在InputHandler类中还有5个方法,3个方法是为三个地图中的每一个添加键/功能对,2个方法是为被按下的按钮/键找到适当的功能来执行。这些实现如下所示。

InputHandler类的实现是非常简单和直接的。在Initialize*方法中,键/函数对被添加到适当的地图中,并根据GetCallback方法的参数,从适当的地图中获取回调。然后,这些函数被执行以执行所需的动作。

处理事件

一般来说,事件处理是在图形应用程序的循环中完成的。这是用下面的代码完成的。

这里显示,KeyReleased、KeyPressed和MouseButtonReleased事件被处理,InputHandler类的一个实例*(ih*)被用来检索和执行回调函数。

单一按钮释放

最简单的情况是处理用户按一次键时的输入。这些事件被称为按键释放事件,因为按键事件是在按键被按下(即被按住)时发生的。可以说,最有用的单次按键是关闭窗口的Escape键(否则关闭SFML应用程序实在是太麻烦了)。因此,我们将首先实现这个回调。

Escape键的回调很容易处理,只需在我们的InputHandler中用sf::Keyboard::Escape键初始化按键释放事件,以及一个启动窗口关闭事件的lambda函数即可。下面是一个演示,虽然不是很精彩(未来的演示会更精彩一些)。

按键(按住按键)

其他有用的功能是由按键被按下(或按下并按住)启动的。例如,移动角色,移动摄像机,以及放大和缩小。如果用户在每次要移动角色时都要按下并释放按键,那么这种行为就会显得比较烦人。所有这些都在下面的按键绑定中实现。

上面,数字键盘的1、2、3、4、6、7、8、9键用于移动摄像机(所画的物体保持静止),方向键用于移动角色(圆圈形状),加减键分别用于放大和缩小。下面是演示。观察 "最后按下的键 "的文字变化,以了解正在进行的动作。

缩放

摄像机移动

角色移动

连续的按钮组合

接下来,需要按一个以上的按钮的动作被添加到InputHandler中。请注意,这些是连续的按键。不幸的是,同时按下的按钮不能用这个方法来处理,尽管可以把它们看作是连续按下的按钮。为了证明这一点,如果在按下 "C "键之后再按下 "Y"、"R"、"B "或 "G "中的一个,玩家的颜色将被改变,这将使玩家的颜色分别变为黄色、红色、蓝色或绿色。

如果C键紧接着Y、R、G或B键被按下,这个逻辑会将播放器的填充颜色更新为指定的颜色。

演示

鼠标点击

最后,鼠标左击功能将被初始化,如果播放器的圆圈被点击,将在屏幕的顶部添加文本。如果点击屏幕上玩家目前不在的地方,文本将被清除。

这个方法依赖于LocalContains方法,如果sf::Vector2f参数在sf::CircleShape参数内,该方法返回true。sf::Vector2f参数是鼠标左键被点击时鼠标指针的位置。

演示

结论

在这篇文章中,我介绍了一种使用std::map对象和C++11 Lambda表达式来处理鼠标和键盘事件的方法。这个方法可以被扩展到处理滚轮事件和其他这里没有明确提到的按键。虽然不是很完美,但我觉得这个实现是一个受欢迎的替代方案,可以替代标准的300行switch语句或更短的switch语句,用许多不同的函数实现所需的功能。

完整的代码

main.cpp

InputHandler.cpp/hpp (包含InputCallbackParameters类)

制作文件

Roboto Font TTF

为了编译和运行代码,需要一个 font/roboto_bold.ttf文件是必需的。Roboto字体可以从谷歌下载。