SMFL 笔记
作者:互联网
本文大部分来自官方教程的Google翻译 但是加了一点点个人的理解和其他相关知识
转载请注明 原文链接 :https://www.cnblogs.com/Multya/p/16273753.html
官方教程: https://www.sfml-dev.org/tutorials/2.5/
持续更新中
配置cmake
这里使用了预编译的二进制库文件 不嫌麻烦可以自行编译
cmake文件需额外添加以下 其中Gm为项目名称 SFML在根目录下 dependence/SFML
源文件在根目录下 src/
#提供查找包的路径
set(SFML_DIR "dependence/SFML/lib/cmake/SFML")
#查找SFMLConfig.cmake文件并通过预置查找包工具查找
find_package(SFML REQUIRED COMPONENTS audio network graphics window system)
#头文件目录绑定
include_directories(${SFML_INCLUDE_DIR})
#添加编译为可执行文件
add_executable(Gm src/main.cpp)
#链接二进制库文件
target_link_libraries(Gm sfml-audio sfml-network sfml-graphics sfml-window sfml-system)
窗口
打开一个窗口
SFML 中的窗口由 sf::Window
类定义。可以在构建时直接创建和打开窗口:
#include <SFML/Window.hpp>
int main()
{
sf::Window window(sf::VideoMode(800, 600), "My window");
...
return 0;
}
第一个参数video mode定义了窗口的大小(内部大小,没有标题栏和边框)。在这里,我们创建一个大小为 800x600 像素的窗口。
该类sf::VideoMode
有一些有趣的静态函数来获取桌面分辨率,或全屏模式的有效视频模式列表。不要犹豫,看看它的文档。
第二个参数只是窗口的标题。
此构造函数接受第三个可选参数:样式,它允许您选择所需的装饰和功能。您可以使用以下样式的任意组合:
sf::Style::None |
完全没有装饰(例如,对于启动画面很有用);这种风格不能与其他风格结合 |
---|---|
sf::Style::Titlebar |
窗口有一个标题栏 |
sf::Style::Resize |
窗口可以调整大小并有一个最大化按钮 |
sf::Style::Close |
窗口有一个关闭按钮 |
sf::Style::Fullscreen |
窗口以全屏模式显示;此样式不能与其他样式组合,并且需要有效的视频模式 |
sf::Style::Default |
默认样式,它是`Titlebar |
还有第四个可选参数,它定义了 OpenGL 特定的选项,这些选项在 专门的 OpenGL 教程中进行了解释。
如果您想在实例构建后sf::Window
创建窗口,或者用不同的视频模式或标题重新创建它,您可以使用该create
函数来代替。它采用与构造函数完全相同的参数。
#include <SFML/Window.hpp>
int main()
{
sf::Window window;
window.create(sf::VideoMode(800, 600), "My window");
...
return 0;
}
让窗口焕发生机
如果您尝试执行上面的代码而不用任何东西代替“...”,您将几乎看不到任何东西。首先,因为程序立即结束。其次,因为没有事件处理——所以即使你在这段代码中添加了一个无限循环,你也会看到一个死窗口,无法移动、调整大小或关闭。
让我们添加一些代码让这个程序更有趣:
#include <SFML/Window.hpp>
#include <bits/stdc++.h>
int main() {
sf::Window window(sf::VideoMode(800, 600), "My window");
// run the program as long as the window is open
while (window.isOpen()) {
// check all the window's events that were triggered since the last iteration of the loop
sf::Event event;
while (window.pollEvent(event)) {
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
}
return 0;
}
上面的代码将打开一个窗口,并在用户关闭它时终止。让我们详细看看它是如何工作的。
首先,我们添加了一个循环,以确保应用程序将被刷新/更新,直到窗口关闭。大多数(如果不是全部)SFML 程序都会有这种循环,有时称为主循环或游戏循环。
然后,我们想要在游戏循环中做的第一件事是检查发生的任何事件。请注意,我们使用while
循环,以便在有多个事件的情况下处理所有未决事件。如果事件未决,则该pollEvent
函数返回 true,如果没有,则返回 false。
每当我们得到一个事件时,我们必须检查它的类型(窗口关闭?按键被按下?鼠标移动?操纵杆连接?...),如果我们对它感兴趣,就做出相应的反应。在这种情况下,我们只关心Event::Closed
当用户想要关闭窗口时触发的事件。此时,窗口仍处于打开状态,我们必须使用close
函数显式关闭它。这使您可以在窗口关闭之前执行某些操作,例如保存应用程序的当前状态或显示消息。
人们经常犯的一个错误是忘记事件循环,仅仅是因为他们还不关心处理事件(他们使用实时输入代替)。如果没有事件循环,窗口将变得无响应。值得注意的是,事件循环有两个角色:除了向用户提供事件之外,它还让窗口有机会处理其内部事件,这是必需的,以便它可以对移动或调整用户操作做出反应。
窗口关闭后,主循环退出,程序终止。
在这一点上,您可能注意到我们还没有讨论在窗口上绘制一些东西。如介绍中所述,这不是 sfml-window 模块的工作,如果您想绘制精灵、文本或形状等内容,则必须跳转到 sfml-graphics 教程。
要绘制东西,您也可以直接使用 OpenGL,完全忽略 sfml-graphics 模块。sf::Window
在内部创建一个 OpenGL 上下文并准备好接受您的 OpenGL 调用。您可以在相应的教程中了解更多相关信息。
不要期望在此窗口中看到有趣的东西:您可能会看到统一的颜色(黑色或白色),或者之前使用 OpenGL 的应用程序的最后内容,或者......其他的东西。
玩窗口
当然,SFML 允许您稍微玩一下自己的窗口。支持更改大小、位置、标题或图标等基本窗口操作,但与专用 GUI 库(Qt、wxWidgets)不同,SFML 不提供高级功能。SFML 窗口仅用于为 OpenGL 或 SFML 绘图提供环境。
// change the position of the window (relatively to the desktop)
window.setPosition(sf::Vector2i(10, 50));
// change the size of the window
window.setSize(sf::Vector2u(640, 480));
// change the title of the window
window.setTitle("SFML window");
// get the size of the window
sf::Vector2u size = window.getSize();
unsigned int width = size.x;
unsigned int height = size.y;
// check whether the window has the focus
bool focus = window.hasFocus();
...
您可以参考 API 文档以获取sf::Window
函数的完整列表。
如果您确实需要窗口的高级功能,您可以使用另一个库创建一个(甚至是完整的 GUI),并将 SFML 嵌入其中。为此,您可以使用其他构造函数或create
函数,sf::Window
它采用现有窗口的特定于操作系统的句柄。在这种情况下,SFML 将在给定窗口内创建一个绘图上下文,并在不干扰父窗口管理的情况下捕获其所有事件。
sf::WindowHandle handle = /* specific to what you're doing and the library you're using */;
sf::Window window(handle);
如果您只想要一个额外的、非常具体的功能,您也可以反过来做:创建一个 SFML 窗口并获取其特定于操作系统的句柄来实现 SFML 本身不支持的东西。
sf::Window window(sf::VideoMode(800, 600), "SFML window");
sf::WindowHandle handle = window.getSystemHandle();
// you can now use the handle with OS specific functions
将 SFML 与其他库集成需要一些工作,此处不再赘述,但您可以参考专门的教程、示例或论坛帖子。
控制帧率
有时,当您的应用程序快速运行时,您可能会注意到诸如撕裂之类的视觉伪影。原因是您的应用程序的刷新率与显示器的垂直频率不同步,因此前一帧的底部与下一帧的顶部混合在一起。
解决这个问题的方法是激活垂直同步。由显卡自动处理,可通过以下setVerticalSyncEnabled
功能轻松开启和关闭:
window.setVerticalSyncEnabled(true); // call it once, after creating the window
在此调用之后,您的应用程序将以与显示器刷新率相同的频率运行。
有时setVerticalSyncEnabled
没有效果:这很可能是因为垂直同步在您的图形驱动程序设置中被强制“关闭”。它应该设置为“由应用程序控制”。
在其他情况下,您可能还希望应用程序以给定的帧速率运行,而不是监视器的频率。这可以通过调用来完成 setFramerateLimit
:
window.setFramerateLimit(60); // call it once, after creating the window
与 不同setVerticalSyncEnabled
的是,此功能由 SFML 本身实现,使用sf::Clock
和的组合sf::sleep
。一个重要的后果是它不是 100% 可靠的,尤其是对于高帧率:sf::sleep
的分辨率取决于底层操作系统和硬件,可能高达 10 或 15 毫秒。不要依赖此功能来实现精确计时。
切勿同时使用setVerticalSyncEnabled
两者setFramerateLimit
!他们会严重混合并使事情变得更糟。
关于窗口要知道的事
下面简要列出了您可以使用 SFML 窗口做什么和不能做什么。
您可以创建多个窗口
SFML 允许您创建多个窗口,并在主线程中处理它们,或者在其自己的线程中处理它们(但...见下文)。在这种情况下,不要忘记为每个窗口设置一个事件循环。
尚未正确支持多台显示器
SFML 没有明确管理多个监视器。因此,您将无法选择窗口出现在哪个监视器上,并且您将无法创建多个全屏窗口。这应该在未来的版本中得到改进。
必须在窗口的线程中轮询事件
这是大多数操作系统的一个重要限制:事件循环(更准确地说是pollEvent
orwaitEvent
函数)必须在创建窗口的同一线程中调用。这意味着如果你想为事件处理创建一个专用线程,你必须确保窗口也是在这个线程中创建的。如果您真的想在线程之间拆分事物,将事件处理保留在主线程中并将其余部分(渲染、物理、逻辑......)移到单独的线程中会更方便。这种配置也将与下面描述的其他限制兼容。
在 macOS 上,窗口和事件必须在主线程中进行管理
是的,这是真的;如果您尝试在主线程以外的线程中创建窗口或处理事件,macOS 将不会同意。
在 Windows 上,大于桌面的窗口将无法正常运行
出于某种原因,Windows 不喜欢比桌面更大的窗口。这包括使用 VideoMode::getDesktopMode()
: 添加的窗口装饰(边框和标题栏)创建的窗口,您最终会得到一个比桌面稍大的窗口。
获取事件
sf::Event 类型
在处理事件之前,重要的是要了解sf::Event
类型是什么,以及如何正确使用它。 sf::Event
是一个union,这意味着一次只有一个成员是有效的(记住你的 C++ 课程:一个 union 的所有成员共享相同的内存空间)。有效成员是与事件类型匹配的成员,例如event.key
事件 KeyPressed
。尝试读取任何其他union将导致未定义的行为(很可能:随机或无效值)。永远不要尝试使用与其类型不匹配的事件成员,这一点很重要。
//Event 内部实现
union
{
SizeEvent size; ///< Size event parameters (Event::Resized)
KeyEvent key; ///< Key event parameters (Event::KeyPressed, Event::KeyReleased)
TextEvent text; ///< Text event parameters (Event::TextEntered)
MouseMoveEvent mouseMove; ///< Mouse move event parameters (Event::MouseMoved)
MouseButtonEvent mouseButton; ///< Mouse button event parameters (Event::MouseButtonPressed, Event::MouseButtonReleased)
MouseWheelEvent mouseWheel; ///< Mouse wheel event parameters (Event::MouseWheelMoved) (deprecated)
MouseWheelScrollEvent mouseWheelScroll; ///< Mouse wheel event parameters (Event::MouseWheelScrolled)
JoystickMoveEvent joystickMove; ///< Joystick move event parameters (Event::JoystickMoved)
JoystickButtonEvent joystickButton; ///< Joystick button event parameters (Event::JoystickButtonPressed, Event::JoystickButtonReleased)
JoystickConnectEvent joystickConnect; ///< Joystick (dis)connect event parameters (Event::JoystickConnected, Event::JoystickDisconnected)
TouchEvent touch; ///< Touch events parameters (Event::TouchBegan, Event::TouchMoved, Event::TouchEnded)
SensorEvent sensor; ///< Sensor event parameters (Event::SensorChanged)
};
sf::Event
实例由类的pollEvent
(或waitEvent
)函数填充sf::Window
。只有这两个函数可以产生有效事件,任何尝试使用sf::Event
未通过成功调用 pollEvent
(or waitEvent
) 返回的都会导致与上面提到的相同的未定义行为。
需要明确的是,典型的事件循环如下所示:
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
union类型是导致没有经验的程序员许多bug的原因。
好的,现在我们可以看到 SFML 支持哪些事件,它们的含义以及如何正确使用它们。
关闭的事件
sf::Event::Closed
当用户想要关闭窗口时,通过窗口管理器提供的任何可能的方法(“关闭”按钮、键盘快捷键等)触发 该事件。该事件仅代表关闭请求,收到事件时窗口尚未关闭。
典型的代码只会调用window.close()
这个事件来实际关闭窗口。但是,您可能还想先做其他事情,例如保存当前应用程序状态或询问用户要做什么。如果您不执行任何操作,窗口将保持打开状态。
sf::Event
union 中没有与此事件相关的成员。
if (event.type == sf::Event::Closed)
window.close();
调整大小的事件
sf::Event::Resized
当通过用户操作或通过调用以编程方式调整窗口大小时触发 该事件window.setSize
。
您可以使用此事件来调整渲染设置:如果您直接使用 OpenGL,则为viewport,如果您使用 sfml-graphics,则为current view。
与此事件关联的成员是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;
}
LostFocus 和 GainedFocus 事件
sf::Event::LostFocus
和事件 在sf::Event::GainedFocus
窗口失去/获得焦点时触发,这发生在用户切换当前活动窗口时。当窗口失焦时,它不会接收键盘事件。
例如,如果您想在窗口不活动时暂停游戏,可以使用此事件。
sf::Event
union中没有与这些事件相关的成员。
if (event.type == sf::Event::LostFocus)
myGame.pause();
if (event.type == sf::Event::GainedFocus)
myGame.resume();
文本输入事件
sf::Event::TextEntered
输入字符时触发 该事件。这不能与KeyPressed
事件混淆:TextEntered
解释用户输入并产生适当的可打印字符。例如,在法语键盘上按“^”然后按“e”将产生两个KeyPressed
事件,但一个TextEntered
事件包含“ê”字符。它适用于操作系统提供的所有输入法,即使是最具体或最复杂的输入法。
此事件通常用于捕获文本字段中的用户输入。
与此事件关联的成员是event.text
,它包含输入字符的 Unicode 值。您可以将其直接放在 a 中sf::String
,也可以char
在确保它在 ASCII 范围内 (0 - 127) 后将其转换为 a 。
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 标准的一部分,因此此事件会生成 一些不可打印的字符,例如退格。在大多数情况下,您需要将它们过滤掉。
许多程序员使用该KeyPressed
事件来获取用户输入,并开始实施疯狂的算法,试图解释所有可能的键组合以产生正确的字符。不要那样做!
KeyPressed 和 KeyReleased 事件
sf::Event::KeyPressed
和事件 在sf::Event::KeyReleased
按下/释放键盘键时触发。
如果按住某个键,KeyPressed
则会在默认的操作系统延迟下生成多个事件(即,与您在文本编辑器中按住字母时应用的延迟相同)。要禁用重复KeyPressed
事件,您可以调用window.setKeyRepeatEnabled(false)
. 另一方面,很明显,KeyReleased
事件永远不会重演。
如果您想在按下或释放某个键时仅触发一次动作,例如使角色随空格跳跃,或通过转义退出某事,则可以使用此事件。
有时,人们试图KeyPressed
直接对事件做出反应以实现平稳的运动。这样做不会产生预期的效果,因为当你按住一个键时,你只会得到几个事件(记住,重复延迟)。要实现事件的平滑移动,您必须使用设置为 onKeyPressed
和 clear on的布尔值KeyReleased
;只要设置了布尔值,您就可以移动(独立于事件)。
产生平滑移动的另一个(更简单)解决方案是使用实时键盘输入sf::Keyboard
(参见 专用教程)。
与这些事件关联的成员是event.key
,它包含按下/释放键的代码,以及修饰键的当前状态(alt、control、shift、system)。
if (event.type == sf::Event::KeyPressed)
{
if (event.key.code == sf::Keyboard::Escape)
{
std::cout << "the escape key was pressed" << 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;
}
}
请注意,某些键对操作系统具有特殊含义,会导致意外行为。例如,Windows 上的 F10 键“窃取”焦点,或者使用 Visual Studio 时启动调试器的 F12 键。这可能会在 SFML 的未来版本中得到解决。
MouseWheelMoved 事件
sf::Event::MouseWheelMoved
事件自 SFML 2.3 起已弃用,请改用 MouseWheelScrolled 事件。
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;
}
MouseButtonPressed 和 MouseButtonReleased 事件
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;
}
MouseEntered 和 MouseLeft 事件
sf::Event::MouseEntered
和事件 在sf::Event::MouseLeft
鼠标光标进入/离开窗口时触发。
sf::Event
union中没有与这些事件相关的成员。
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::Keyboard
. 它只包含一个函数 ,isKeyPressed
它检查一个键的当前状态(按下或释放)。它是一个静态函数,因此您无需实例化sf::Keyboard
即可使用它。
此函数直接读取键盘状态,忽略窗口的焦点状态。这意味着isKeyPressed
即使您的窗口处于非活动状态,它也可能返回 true。(因此可能需要适当的判断防止意外情况的发生)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
// left key is pressed: move our character
character.move(1.f, 0.f);
}
key codes在sf::Keyboard::Key
枚举中定义。
class SFML_WINDOW_API Keyboard
{
public:
////////////////////////////////////////////////////////////
/// Key codes
////////////////////////////////////////////////////////////
enum Key
{
Unknown = -1, ///< Unhandled key
A = 0, ///< The A key
B, ///< The B key
C, ///< The C key
D, ///< The D key
...
KeyCount, ///< Keep last -- the total number of keyboard keys
// Deprecated values:
Dash = Hyphen, ///< Use Hyphen instead
BackSpace = Backspace, ///< Use Backspace instead
BackSlash = Backslash, ///< Use Backslash instead
SemiColon = Semicolon, ///< Use Semicolon instead
Return = Enter ///< Use Enter instead
}
...
}
根据您的操作系统和键盘布局,某些键代码可能会丢失或解释不正确。这将在 SFML 的未来版本中得到改进。
鼠标
提供对鼠标状态的访问的类是sf::Mouse
. 和它的朋友sf::Keyboard
一样, sf::Mouse
只包含静态函数,并不打算实例化(SFML 暂时只处理单个鼠标)。
您可以检查按钮是否被按下:
if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
// left mouse button is pressed: shoot
gun.fire();
}
鼠标按钮代码在sf::Mouse::Button
枚举中定义。SFML 最多支持 5 个按钮:左、右、中间(滚轮)和两个附加按钮,无论它们是什么。
您还可以获取和设置鼠标相对于桌面或窗口的当前位置:
// get the global mouse position (relative to the desktop)
sf::Vector2i globalPosition = sf::Mouse::getPosition();
// get the local mouse position (relative to a window)
sf::Vector2i localPosition = sf::Mouse::getPosition(window); // window is a sf::Window
// set the mouse position globally (relative to the desktop)
sf::Mouse::setPosition(sf::Vector2i(10, 50));
// set the mouse position locally (relative to a window)
sf::Mouse::setPosition(sf::Vector2i(10, 50), window); // window is a sf::Window
没有读取鼠标滚轮当前状态的功能。由于轮子只能相对移动,所以没有可以查询的绝对状态。通过查看一个键,您可以判断它是按下还是释放。通过查看鼠标光标,您可以知道它在屏幕上的位置。但是,查看鼠标滚轮并不能告诉您它在哪个“刻度”上。所以只能在它移动(MouseWheelScrolled
事件)时收到通知。
小总结程序
#include <SFML/Graphics.hpp>
#include <bits/stdc++.h>
int main() {
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
sf::CircleShape shape(100.f);
shape.setFillColor(sf::Color::Green);
sf::Mouse::setPosition(sf::Vector2i(10, 50), window);
while (window.isOpen()) {
sf::Event event{};
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
std::cout << "good byyyye" << std::endl;
window.close();
break;
case sf::Event::Resized:
std::cout << "height:" << event.size.height << std::endl;
std::cout << "weight:" << event.size.width << std::endl;
break;
case sf::Event::LostFocus:
std::cout << "hei! what are you doing!\n";
break;
case sf::Event::GainedFocus:
std::cout << "ok.." << std::endl;
break;
case sf::Event::KeyPressed:
std::cout << event.key.code << std::endl;
// std::boolalpha(std::cout);
// std::cout << "the escape key was pressed" << 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;
break;
case sf::Event::TextEntered:
if (event.text.unicode < 128)
std::cout << "ASCII character typed :" << static_cast<char>(event.text.unicode) << std::endl;
break;
case 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 << "while type: unknown" << std::endl;
std::cout << "wheel delta:" << event.mouseWheelScroll.delta << std::endl;
std::cout << "wheel x:" << event.mouseWheelScroll.x << std::endl;
std::cout << "wheel y:" << event.mouseWheelScroll.y << std::endl;
break;
case sf::Event::MouseButtonPressed:
if (event.mouseButton.button == sf::Mouse::Right)
std::cout << "right button pressed" << std::endl;
else if (event.mouseButton.button == sf::Mouse::Left)
std::cout << "left button pressed" << std::endl;
else if(event.mouseButton.button == sf::Mouse::Middle)
std::cout << "middle button pressed" << std::endl;
std::cout << "mouse x:" << event.mouseButton.x << std::endl;
std::cout << "mouse y:" << event.mouseButton.y << std::endl;
break;
case sf::Event::MouseButtonReleased:
if (event.mouseButton.button == sf::Mouse::Right)
std::cout << "right button Released" << std::endl;
else if (event.mouseButton.button == sf::Mouse::Left)
std::cout << "left button Released" << std::endl;
else if(event.mouseButton.button == sf::Mouse::Middle)
std::cout << "middle button Released" << std::endl;
std::cout << "mouse x:" << event.mouseButton.x << std::endl;
std::cout << "mouse y:" << event.mouseButton.y << std::endl;
break;
case sf::Event::MouseMoved:
// std::cout << "new mouse x " << event.mouseMove.x << std::endl;
// std::cout << "new mouse y " << event.mouseMove.y << std::endl;
default:
break;
}
}
window.clear();
window.draw(shape);
window.display();
}
return 0;
}
处理时间
SFML 中的时间
与许多其他时间是 uint32 毫秒数或浮点数秒数的库不同,SFML 没有为时间值强加任何特定的单位或类型。相反,它通过一个灵活的类将这个选择留给用户:sf::Time
. 所有处理时间值的 SFML 类和函数都使用这个类。
sf::Time
表示一个时间段(换句话说,两个事件之间经过的时间)。它不是将当前年/月/日/小时/分钟/秒表示为时间戳的日期时间类,它只是表示一定时间量的值,如何解释它取决于上下文的使用。
转换时间
sf::Time
可以从不同的单位构造一个值:秒、毫秒和微秒。有一个(非成员)函数可以将它们中的每一个变成sf::Time
:
sf::Time t1 = sf::microseconds(10000);
sf::Time t2 = sf::milliseconds(10);
sf::Time t3 = sf::seconds(0.01f);
请注意,这三个时间都是相等的。
同样,sf::Time
可以转换回秒、毫秒或微秒:
sf::Time time = ...;
sf::Int64 usec = time.asMicroseconds();
sf::Int32 msec = time.asMilliseconds();
float sec = time.asSeconds();
计算时间
sf::Time
只是一个时间量,所以它支持算术运算,如加法、减法、比较等。时间也可以是负数。
sf::Time t1 = ...;
sf::Time t2 = t1 * 2;
sf::Time t3 = t1 + t2;
sf::Time t4 = -t3;
bool b1 = (t1 == t2);
bool b2 = (t3 > t4);
测量时间
现在我们已经了解了如何使用 SFML 操作时间值,让我们看看如何做几乎每个程序都需要的事情:测量经过的时间。
SFML 有一个非常简单的时间测量类:sf::Clock
. 它只有两个功能:getElapsedTime
, 检索自时钟启动以来经过的时间,以及restart
, 重新启动时钟。
sf::Clock clock; // starts the clock
...
sf::Time elapsed1 = clock.getElapsedTime();
std::cout << elapsed1.asSeconds() << std::endl;
clock.restart();
...
sf::Time elapsed2 = clock.getElapsedTime();
std::cout << elapsed2.asSeconds() << std::endl;
请注意,调用restart
还会返回经过的时间,这样您就可以避免 getElapsedTime
之前必须显式调用时存在的微小间隙restart
。
这是一个使用游戏循环的每次迭代所经过的时间来更新游戏逻辑的示例:
sf::Clock clock;
while (window.isOpen())
{
sf::Time elapsed = clock.restart();
updateGame(elapsed);
...
}
用户数据流
介绍
SFML 有几个资源类:图像、字体、声音等。在大多数程序中,这些资源将借助它们的 loadFromFile
功能从文件中加载。在其他一些情况下,资源将直接打包到可执行文件或大数据文件中,并使用loadFromMemory
. 这些功能几乎涵盖了所有可能的用例——但不是全部。
有时您想从不寻常的地方加载文件,例如压缩/加密存档或远程网络位置。针对这些特殊情况,SFML 提供了第三种加载函数:loadFromStream
. 此函数使用抽象 sf::InputStream
接口读取数据,它允许您提供自己的与 SFML 一起使用的流类的实现。
在本教程中,您将学习如何编写和使用您自己的派生输入流。
And标准流?
像许多其他语言一样,C++ 已经有一个用于输入数据流的类:std::istream
. 实际上它有两个:std::istream
只是前端,自定义数据的抽象接口是std::streambuf
.
不幸的是,这些类对用户不是很友好,如果你想实现一些重要的东西,它们会变得非常复杂。Boost.Iostreams库试图为标准流提供更简单的接口,但 Boost 是一个很大的依赖项,SFML 不能依赖它。
这就是为什么 SFML 提供了自己的流接口,希望它更加简单和快速。
输入流
该类sf::InputStream
声明了四个虚函数:
class InputStream
{
public :
virtual ~InputStream() {}
virtual Int64 read(void* data, Int64 size) = 0;
virtual Int64 seek(Int64 position) = 0;
virtual Int64 tell() = 0;
virtual Int64 getSize() = 0;
};
read 必须从流中提取size个字节的数据,并将它们复制到提供的数据地址。它返回读取的字节数,错误时返回 -1。
**seek **必须更改流中的当前读取位置。它的位置参数是要跳转到的绝对字节偏移量(因此它是相对于数据的开头,而不是相对于当前位置)。它返回新位置,或错误时返回 -1。
**tell **必须返回流中的当前读取位置(以字节为单位),如果出错则返回 -1。
**getSize **必须返回包含在流中的数据的总大小(以字节为单位),如果出错则返回 -1。
要创建自己的工作流,您必须根据他们的要求实现这四个功能中的每一个。
FileInputStream 和 MemoryInputStream
从 SFML 2.3 开始,创建了两个新类来为新的内部音频管理提供流。sf::FileInputStream
提供文件的只读数据流,同时sf::MemoryInputStream
提供来自内存的只读流。两者都源自sf::InputStream
多态,因此可以使用多态。
使用输入流
使用自定义流类很简单:实例化它,并将其传递给要加载的对象 的loadFromStream
(或openFromStream
)函数。
sf::FileStream stream;
stream.open("image.png");
sf::Texture texture;
texture.loadFromStream(stream);
例子
如果您需要一个演示来帮助您专注于代码的工作原理,而不是迷失在实现细节上,您可以查看sf::FileInputStream
或sf::MemoryInputStream
的实现。
不要忘记查看论坛和维基。很有可能另一个用户已经编写了一个sf::InputStream
适合您需要的类。如果您写了一篇新文章,并且觉得它对其他人也有用,请不要犹豫分享!
常见错误
某些资源类在loadFromStream
被调用后没有完全加载。相反,只要它们被使用,它们就会继续从它们的数据源中读取。sf::Music
在播放音频样本时流式传输音频样本,而对于 sf::Font
,它根据显示的文本动态加载字形。
因此,您用于加载音乐或字体的流实例及其数据源必须在资源使用它时保持活动状态。如果它在仍在使用时被销毁,则会导致未定义的行为(可能是崩溃、损坏的数据或不可见)。
另一个常见的错误是返回内部函数直接返回的任何内容,但有时它与 SFML 所期望的不匹配。例如,在sf::FileInputStream
代码中,可能会想将seek
函数编写如下:
sf::Int64 FileInputStream::seek(sf::Int64 position)
{
return std::fseek(m_file, position, SEEK_SET);
}
此代码是错误的,因为std::fseek
成功时返回零,而 SFML 期望返回新位置。
绘制 2D
介绍
正如在前面的教程中所了解的,SFML 的窗口模块提供了一种简单的方法来打开 OpenGL 窗口并处理其事件,但是在绘制某些东西时它并没有帮助。留给您的唯一选择是使用功能强大但复杂且低级的 OpenGL API。
幸运的是,SFML 提供了一个图形模块,它可以帮助您以比 OpenGL 更简单的方式绘制 2D 实体。
绘图窗口
要绘制图形模块提供的实体,您必须使用专门的窗口类:sf::RenderWindow
. 该类派生自sf::Window
,并继承其所有功能。您所了解的所有内容sf::Window
(创建、事件处理、控制帧率、与 OpenGL 混合等)也适用sf::RenderWindow
。
最重要的是,sf::RenderWindow
添加高级功能以帮助您轻松绘制事物。在本教程中,我们将关注其中两个函数:clear
和draw
. 它们就像它们的名字所暗示的那样简单:clear
用选定的颜色清除整个窗口,并draw
绘制你传递给它的任何对象。
这是带有渲染窗口的典型主循环的样子:
#include <SFML/Graphics.hpp>
int main()
{
// create the window
sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
// run the program as long as the window is open
while (window.isOpen())
{
// 检查自上次循环迭代以来触发的所有窗口事件
sf::Event event;
while (window.pollEvent(event))
{
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
// clear the window with black color
window.clear(sf::Color::Black);
// draw everything here...
// window.draw(...);
// end the current frame
window.display();
}
return 0;
}
在绘制任何内容之前调用clear
是强制性的,否则之前帧的内容将出现在您绘制的任何内容后面。唯一的例外是当您用绘制的内容覆盖整个窗口时,不会绘制任何像素。在这种情况下,您可以避免调用clear
(尽管它不会对性能产生明显影响)。
调用display
也是强制性的,它获取自上次调用以来绘制的内容display
并将其显示在窗口上。确实,事物不是直接绘制到窗口,而是绘制到隐藏的缓冲区。然后在您调用时将此缓冲区复制到窗口display
- 这称为双缓冲。
这种清除/绘制/显示循环是绘制事物的唯一好方法。不要尝试其他策略,例如保留前一帧的像素,“擦除”像素,或绘制一次并多次调用 display。由于双缓冲,你会得到奇怪的结果。
现代图形硬件和 API确实是为重复的清除/绘制/显示循环而设计的,在主循环的每次迭代中,所有内容都会完全刷新。不要害怕每秒绘制 1000 个精灵 60 次,你远远低于计算机可以处理的数百万个三角形。
我现在可以画什么?
现在您已经准备好绘制一个主循环,让我们看看您可以在那里实际绘制什么以及如何绘制。
SFML 提供了四种可绘制实体:其中三种可供使用(精灵、文本和形状),最后一种是帮助您创建自己的可绘制实体(顶点数组)的构建块。
尽管它们具有一些共同的属性,但这些实体中的每一个都有自己的细微差别,因此在专门的教程中进行了解释:
准备程序
#include <SFML/Graphics.hpp>
#include <bits/stdc++.h>
int main() {
sf::RenderWindow window(sf::VideoMode(700, 500), "title");
while (window.isOpen()) {
sf::Event event{};
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
std::cout << "success exit" << std::endl;
window.close();
break;
default:
break;
}
}
window.clear();
window.display();
}
return 0;
}
精灵图和纹理
什么是精灵图
CSS Sprites通常被称为css精灵图, 在国内也被意译为css图片整合和css贴图定位,也有人称他为雪碧图。 就是将导航的背景图,按钮的背景图等有规则的合并成一张背景图,即多张图合并为一张整图, 然后再利用background-position进行背景图定位的一种技术
(下面都翻译成精灵)
词汇
大多数人(如果不是全部)已经熟悉这两个非常常见的对象,所以让我们非常简要地定义它们。
纹理是图像。但我们称其为“纹理”,因为它具有非常特殊的作用:被映射到 2D 实体。
精灵只不过是一个带纹理的矩形。
好的,这很简短,但如果您真的不了解精灵和纹理是什么,那么您会在 Wikipedia 上找到更好的描述。
加载纹理
在创建任何精灵之前,我们需要一个有效的纹理。令人惊讶的是,在 SFML 中封装纹理的类是sf::Texture
. 由于纹理的唯一作用是加载和映射到图形实体,因此几乎所有的功能都是关于加载和更新它。
加载纹理的最常见方法是从磁盘上的图像文件,这是通过loadFromFile
函数完成的。
sf::Texture texture;
if (!texture.loadFromFile("image.png"))
{
// error...
}
该loadFromFile
功能有时会在没有明显原因的情况下失败。首先,检查 SFML 打印到标准输出的错误消息(检查控制台)。如果消息无法打开文件,请确保工作目录(任何文件路径都将被解释为相对的目录)是您认为的:当您从桌面环境运行应用程序时,工作目录是可执行文件夹。但是,当您从 IDE(Visual Studio、Code::Blocks、...)启动程序时,有时可能会将工作目录设置为项目目录。这通常可以在项目设置中很容易地更改。(在Clion中是从可执行文件为起点的)
您还可以从内存 ( loadFromMemory
)、自定义输入流( loadFromStream
) 或已加载的图像( ) 加载图像文件loadFromImage
。后者从 加载纹理sf::Image
,这是一个实用程序类,可帮助存储和操作图像数据(修改像素,创建透明度通道等)。留在系统内存中的像素sf::Image
,确保对它们的操作将尽可能快,与驻留在视频内存中的纹理像素形成对比,因此检索或更新缓慢但绘制速度非常快。
SFML 支持最常见的图像文件格式。API 文档中提供了完整列表。
所有这些加载函数都有一个可选参数,如果你想加载图像的一小部分,可以使用它。
// load a 32x32 rectangle that starts at (10, 10)
if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32)))
{
// error...
}
该类sf::IntRect
是一个表示矩形的简单实用程序类型。它的构造函数获取左上角的坐标和矩形的大小。
如果您不想从图像加载纹理,而是想直接从像素数组更新它,您可以将其创建为空并稍后更新:
// create an empty 200x200 texture
if (!texture.create(200, 200))
{
// error...
}
请注意,此时纹理的内容是未定义的。
要更新现有纹理的像素,您必须使用该update
函数。它具有多种数据源的重载:
// update a texture from an array of pixels
sf::Uint8* pixels = new sf::Uint8[width * height * 4]; // * 4 because pixels have 4 components (RGBA)
...
texture.update(pixels);
// update a texture from a sf::Image
sf::Image image;
...
texture.update(image);
// update the texture from the current contents of the window
sf::RenderWindow window;
...
texture.update(window);
这些示例都假设源与纹理大小相同。如果不是这种情况,即如果您只想更新纹理的一部分,您可以指定要更新的子矩形的坐标。您可以参考文档以获取更多详细信息。
此外,纹理有两个属性可以改变它的渲染方式。
第一个属性允许平滑纹理。平滑纹理使像素边界不那么明显(但图像更模糊),如果放大它可能是可取的。
texture.setSmooth(true);
由于对纹理中相邻像素的采样也进行了平滑处理,因此可能会导致不希望的副作用,即在选定纹理区域之外考虑像素。当您的精灵位于非整数坐标时,可能会发生这种情况。
第二个属性允许纹理在单个精灵中重复平铺。
texture.setRepeated(true);
这仅在您的精灵配置为显示大于纹理的矩形时才有效,否则此属性无效。
好的,我现在可以拥有我的精灵了吗?
是的,你现在可以创建你的精灵了。
sf::Sprite sprite;
sprite.setTexture(texture);
……最后画出来。
// inside the main loop, between window.clear() and window.display()
window.draw(sprite);
如果你不想让你的精灵使用整个纹理,你可以设置它的纹理矩形。
sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));
您还可以更改精灵的颜色。您设置的颜色会随着精灵的纹理进行调制(相乘)。这也可以用来改变精灵的全局透明度(alpha)。
sprite.setColor(sf::Color(0, 255, 0)); // green
sprite.setColor(sf::Color(255, 255, 255, 128)); // half transparent
这些精灵都使用相同的纹理,但颜色不同:
精灵也可以变换:它们有一个位置、一个方向和一个比例。
// position
sprite.setPosition(sf::Vector2f(10.f, 50.f)); // absolute position
sprite.move(sf::Vector2f(5.f, 10.f)); // offset relative to the current position
// rotation
sprite.setRotation(90.f); // absolute angle
sprite.rotate(15.f); // offset relative to the current angle
// scale
sprite.setScale(sf::Vector2f(0.5f, 2.f)); // absolute scale factor
sprite.scale(sf::Vector2f(1.5f, 3.f)); // factor relative to the current scale
默认情况下,这三个变换的原点是精灵的左上角。如果您想将原点设置为不同的点(例如精灵的中心,或另一个角),您可以使用该setOrigin
功能。
sprite.setOrigin(sf::Vector2f(25.f, 25.f));
由于转换函数对所有 SFML 实体都是通用的,因此在单独的教程中对它们进行了说明: 转换实体。
白方块问题
您成功加载了纹理,正确构建了精灵,并且……您现在在屏幕上看到的只是一个白色方块。发生了什么?
这是一个常见的错误。当您设置精灵的纹理时,它在内部所做的只是存储指向纹理实例的指针。因此,如果纹理被破坏或移动到内存中的其他位置,则精灵最终会得到一个无效的纹理指针。
编写此类函数时会出现此问题:
sf::Sprite loadSprite(std::string filename)
{
sf::Texture texture;
texture.loadFromFile(filename);
return sf::Sprite(texture);
} // error: the texture is destroyed here
您必须正确管理纹理的生命周期,并确保它们在被任何精灵使用时一直存在。
使用尽可能少的纹理的重要性
使用尽可能少的纹理是一个好策略,原因很简单:更改当前纹理对于显卡来说是一项昂贵的操作。绘制许多使用相同纹理的精灵将产生最佳性能。
此外,使用单个纹理允许您将静态几何体组合到单个实体中(每次调用只能使用一个纹理draw
),这将比一组许多实体更快地绘制。批处理静态几何体涉及其他类,因此超出了本教程的范围,有关更多详细信息,请参阅顶点数组教程。
在创建动画表或图块集时请记住这一点:尽可能少地使用纹理。
在 OpenGL 代码中使用 sf::Texture
如果您使用的是 OpenGL 而不是 SFML 的图形实体,您仍然可以将sf::Texture
其用作 OpenGL 纹理对象的包装器,并将其与其余的 OpenGL 代码一起使用。
要绑定sf::Texture
绘图(基本上glBindTexture
),您调用bind
静态函数:
sf::Texture texture;
...
// bind the texture
sf::Texture::bind(&texture);
// draw your textured OpenGL entity here...
// bind no texture
sf::Texture::bind(NULL);
小总结程序
在根目录下access/pi'c
中放了一张图片loading,png
程序将不停的旋转pic该图片
#include <SFML/Graphics.hpp>
//#include <GLFW/glfw3.h>
#include <bits/stdc++.h>
int main() {
sf::RenderWindow window(sf::VideoMode(700, 500), "title");
window.setVerticalSyncEnabled(true);
sf::Texture texture;
if (!texture.loadFromFile("../access/pic/loading.png")) {
std::cerr << "load texture failed!" << std::endl;
}
sf::Sprite sprite;
sprite.setTexture(texture);
sprite.scale((float) window.getSize().x / (float) texture.getSize().x,
(float) window.getSize().y / (float) texture.getSize().y);
sprite.setOrigin((float) window.getSize().x / 2.f,
(float) window.getSize().y / 2.f);
sprite.setPosition((float) window.getSize().x / 2.f,
(float) window.getSize().y / 2.f);
while (window.isOpen()) {
sf::Event event{};
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
std::cout << "success exit" << std::endl;
window.close();
break;
default:
break;
}
}
window.clear();
sprite.rotate(1.f);
window.draw(sprite);
window.display();
}
return 0;
}
离屏绘图
SFML 还提供了一种绘制到纹理而不是直接绘制到窗口的方法。为此,请使用 sf::RenderTexture
而不是 sf::RenderWindow
。它具有相同的绘图功能,继承自它们的共同基础:sf::RenderTarget
.
// create a 500x500 render-texture
sf::RenderTexture renderTexture;
if (!renderTexture.create(500, 500))
{
// error...
}
// drawing uses the same functions
renderTexture.clear();
renderTexture.draw(sprite); // or any other drawable
renderTexture.display();
// get the target texture (where the stuff has been drawn)
const sf::Texture& texture = renderTexture.getTexture();
// draw it to the window
sf::Sprite sprite(texture);
window.draw(sprite);
该getTexture
函数返回一个只读纹理,这意味着您只能使用它,不能修改它。如果您需要在使用前对其进行修改,您可以将其复制到您自己的sf::Texture
实例中并进行修改。
sf::RenderTexture
还具有与处理视图和 OpenGL 相同的功能sf::RenderWindow
(有关详细信息,请参阅相应的教程)。如果您使用 OpenGL 绘制到渲染纹理,您可以使用函数的第三个可选参数请求创建深度缓冲区create
。
renderTexture.create(500, 500, true); // enable depth buffer
从线程中绘制*(此处用std::thread更好)
SFML 支持多线程绘图,你甚至不需要做任何事情来让它工作。唯一要记住的是在另一个线程中使用它之前停用一个窗口。这是因为一个窗口(更准确地说是它的 OpenGL 上下文)不能同时在多个线程中处于活动状态。
void renderingThread(sf::RenderWindow* window)
{
// activate the window's context
window->setActive(true);
// the rendering loop
while (window->isOpen())
{
// draw...
// end the current frame
window->display();
}
}
int main()
{
// create the window (remember: it's safer to create it in the main thread due to OS limitations)
sf::RenderWindow window(sf::VideoMode(800, 600), "OpenGL");
// deactivate its OpenGL context
window.setActive(false);
// launch the rendering thread
sf::Thread thread(&renderingThread, &window);
thread.launch();
// the event/logic/whatever loop
while (window.isOpen())
{
...
}
return 0;
}
如您所见,您甚至不需要在渲染线程中激活窗口,SFML 会在需要时自动为您完成。
请记住始终在主线程中创建窗口并处理其事件,以获得最大的可移植性。
文字和字体
加载字体
在绘制任何文本之前,您需要有一个可用的字体,就像任何其他打印文本的程序一样。字体封装在sf::Font
该类中,该类提供三个主要功能:加载字体、从中获取字形(即视觉字符)以及读取其属性。在一个典型的程序中,你只需要使用第一个特性,加载字体,所以让我们首先关注它。
加载字体最常见的方法是从磁盘上的文件中加载,这是通过loadFromFile
函数完成的。
sf::Font font;
if (!font.loadFromFile("arial.ttf"))
{
// error...
}
请注意,SFML 不会自动加载您的系统字体,即font.loadFromFile("Courier New")
不会工作。首先,因为 SFML 需要文件名,而不是字体名称,其次,因为 SFML没有对系统字体文件夹的神奇访问权限。如果要加载字体,则需要在应用程序中包含字体文件,就像所有其他资源(图像、声音等)一样。
在windows下 C:\Windows\Fonts
中就有字体文件 可以将其复制到 access/font
下
推荐自然是 consola
字体啦
sf::Font font;
if (!font.loadFromFile("../access/font/consola.ttf")) {
std::cerr << "load texture failed!" << std::endl;
}
该loadFromFile
功能有时会在没有明显原因的情况下失败。首先,检查 SFML 打印到标准输出的错误消息(检查控制台)。如果消息无法打开文件,请确保工作目录(任何文件路径都将被解释为相对的目录)是您认为的:当您从桌面环境运行应用程序时,工作目录是可执行文件夹。但是,当您从 IDE启动程序时,有时可能会将工作目录设置为项目目录。这通常可以在项目设置中很容易地更改。
您还可以从内存 ( loadFromMemory
) 或自定义输入流( loadFromStream
) 加载字体文件。
SFML 支持最常见的字体格式。API 文档中提供了完整列表。
这就是你需要做的。加载字体后,您可以开始绘制文本。
绘图文本
要绘制文本,您将使用sf::Text
该类。使用非常简单:
sf::Text text;
// select the font
text.setFont(font); // font is a sf::Font
// set the string to display
text.setString("Hello world");
// set the character size
text.setCharacterSize(24); // in pixels, not points!
// set the color
text.setFillColor(sf::Color::Red);
// set the text style
text.setStyle(sf::Text::Bold | sf::Text::Underlined);
...
// inside the main loop, between window.clear() and window.display()
window.draw(text);
文本也可以转换:它们具有位置、方向和比例。涉及的功能与 sf::Sprite
类和其他 SFML 实体的功能相同。它们在 转换实体教程中进行了解释。
小总结程序
在图片的基础上加了一行loading...
#include <SFML/Graphics.hpp>
//#include <GLFW/glfw3.h>
#include <bits/stdc++.h>
int main() {
sf::RenderWindow window(sf::VideoMode(700, 500), "title");
window.setVerticalSyncEnabled(true);
sf::Texture texture;
if (!texture.loadFromFile("../access/pic/loading.png")) {
std::cerr << "load texture failed!" << std::endl;
}
sf::Sprite sprite;
sprite.setTexture(texture);
sprite.scale((float) window.getSize().x / (float) texture.getSize().x,
(float) window.getSize().y / (float) texture.getSize().y);
sprite.setOrigin((float) window.getSize().x / 2.f,
(float) window.getSize().y / 2.f);
sprite.setPosition((float) window.getSize().x / 2.f,
(float) window.getSize().y / 2.f);
sf::Font font;
if (!font.loadFromFile("../access/font/consola.ttf")) {
std::cerr << "load texture failed!" << std::endl;
}
sf::Text text;
text.setFont(font);
text.setString("loading...");
text.setCharacterSize(50);
text.setFillColor(sf::Color::Blue);
text.setPosition((float) window.getSize().x / 2.f,
(float) window.getSize().y / 2.f);
while (window.isOpen()) {
sf::Event event{};
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
std::cout << "success exit" << std::endl;
window.close();
break;
default:
break;
}
}
window.clear();
sprite.rotate(1.f);
window.draw(sprite);
window.draw(text);
window.display();
}
return 0;
}
如何避免非 ASCII 字符的问题?
正确处理非 ASCII 字符(例如重音欧洲字符、阿拉伯字符或中文字符)可能很棘手。它需要对解释和绘制文本过程中涉及的各种编码有很好的理解。为了避免这些编码的困扰,有一个简单的解决方案:使用宽文本字符串。
text.setString(L"יטאח");
正是字符串前面的这个简单的“L”前缀通过告诉编译器生成一个宽字符串来使其工作。宽字符串在 C++ 中是一种奇怪的野兽:标准没有说明它们的大小(16 位?32 位?),也没有说明它们使用的编码(UTF-16?UTF-32?)。但是我们知道,在大多数平台上(如果不是全部),它们都会生成 Unicode 字符串,并且 SFML 知道如何正确处理它们。
请注意,C++11 标准支持新的字符类型和前缀来构建 UTF-8、UTF-16 和 UTF-32 字符串文字,但 SFML 还不支持它们。
这似乎很明显,但您还必须确保您使用的字体包含您要绘制的字符。实际上,字体并不包含所有可能字符的字形(Unicode 标准中有超过 100000 个字形!),例如,阿拉伯字体将无法显示日文文本。
制作自己的文本类
Ifsf::Text
太有限,或者如果你想用预渲染的字形做其他事情,sf::Font
提供你需要的一切。
您可以检索包含特定大小的所有预渲染字形的纹理:
const sf::Texture& texture = font.getTexture(characterSize);
需要注意的是,字形会在请求时添加到纹理中。字符太多(记住,超过 100000 个),加载字体时无法全部生成。相反,它们会在您调用getGlyph
函数时即时呈现(见下文)。
要对字体纹理做一些有意义的事情,您必须获取其中包含的字形的纹理坐标:
sf::Glyph glyph = font.getGlyph(character, characterSize, bold);
character
是要获取其字形的字符的 UTF-32 代码。您还必须指定字符大小,以及是否需要粗体或常规版本的字形。
该sf::Glyph
结构包含三个成员:
textureRect
包含纹理内字形的纹理坐标bounds
包含字形的边界矩形,这有助于相对于文本的基线定位它advance
是用于获取文本中下一个字形的起始位置的水平偏移量
您还可以获得一些字体的其他指标,例如两个字符之间的字距或行间距(总是针对特定字符大小):
int lineSpacing = font.getLineSpacing(characterSize);
int kerning = font.getKerning(character1, character2, characterSize);
持续更新中。。。
标签:event,窗口,SMFL,笔记,sf,window,SFML,Event 来源: https://www.cnblogs.com/Multya/p/16273753.html