词汇表
大多数(如果不是全部)的您已经熟悉这两个非常常见的对象,因此让我们简要定义它们。
纹理是图片。但是我们称它为“纹理”,因为它有一个非常特定的作用:被映射到2D实体上。
精灵不过是纹理矩形。
好吧,这很简短,但如果你真的不明白精灵和纹理是什么,那么你可以在维基百科上找到更好的描述。
加载纹理
在创建任何精灵之前,我们需要一个有效的纹理。SFML中封装纹理的类是sf :: Texture。由于纹理的唯一作用是加载并映射到图形实体,因此几乎所有它的函数都是关于加载和更新它的。
最常见的加载纹理的方式是从磁盘上的图像文件中加载,这可以使用loadFromFile函数完成。
sf::Texture texture;
if (!texture.loadFromFile("image.png"))
{
// error...
}
loadFromFile函数有时会无缘无故地失败。首先,请检查SFML打印到标准输出的错误消息(检查控制台)。如果消息是无法打开文件,请确保工作目录(任何文件路径将相对于其解释的目录)是您认为的那个:当您从桌面环境运行应用程序时,工作目录是可执行文件夹。但是,当您从IDE(Visual Studio,Code :: Blocks等)启动程序时,工作目录有时可能设置为项目目录。这通常可以在项目设置中轻松更改。
你也可以从内存(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...
}
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
你必须正确管理纹理的生命周期,并确保它们在被任何精灵使用时都存在。
使用尽可能少的纹理的重要性
尽可能使用尽可能少的纹理是一个好策略,原因很简单:改变当前纹理是一个昂贵的操作,会占用显卡的大量资源。因此,使用许多使用相同纹理的精灵将产生最佳性能。
此外,使用单个纹理可以将静态几何体分组为单个实体(每个绘制调用只能使用一个纹理),这比绘制许多实体要快得多。批处理静态几何体涉及其他类,因此超出了本教程的范围,有关详细信息,请参见顶点数组教程。
在创建动画表或平铺集时,请尽量记住这一点:尽量使用尽可能少的纹理。
与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);