用C++和SFML写游戏-纹理及精灵(5.1)

对于 2D3D 游戏来说,纹理是一个很重要的东西,它能够将一张图片映射到我们程序中的对象里。而精灵可以看作是包含其他显示对象的容器。

在这篇文章中,将会讲解一下内容:

  • 读取纹理
  • 将纹理绘制在shape中
  • 什么是精灵
  • 资源管理 </br>

一、读取纹理

纹理是一个非常简单的对象。一个 2D 纹理本质上是一幅图片,通常存储在 GPU 中。SFML 使用 Image 类处理图片,使用 Texture 类绘制图片。

二、创建 images

在介绍纹理之前,我们先看看如何创建和读取图片。Image 类中的很多方法在 Texture 都可以使用。例如,我们想要创建一个 50 x 50 且被红色填充的图片。

1
2
sf::Image image;
image.create(50, 50, sf::Color::Red);

create() 方法第一个参数是图片的宽,第二个参数是高,第三个参数是图片的颜色(RGBA)

除此之外,我们也可以使用像素数组来创建。数组每个元素是 Uint8 类型(一个字节)。一个像素的大小是 4 个字节,表示 RGBA。如下面的代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
const unsigned int kWidth = 5, kheight = 5
// 数组大小 kWidth * kHeight * 4
sf::Uint8 pixels[kWidth * kHeight * 4] = {
255, 255, 255, 255, // 白
0, 0, 0, 255, // 黑
255, 0, 0, 255, // 红
128, 128, 128, 255, // 灰

// ... 其他像素颜色
};
sf::Image image;
image.create(kWidth, kHeight, pixels);

当然,我们也可以从现有的图片中创建。如下:

1
2
sf::Image image;
image.loadFromFile("myImage.png");

SFML 能够处理的图片格式包括:bmp, png, tga, gif, psd, hdr, pic 以及 jpg(不包括jpeg)

如果图片不存在, loadFromFile() 会返回 false,导致不必要的 bug。因此上面的代码改为:

1
2
3
4
sf::Image image;
if (!image.loadFromFile("myImage.png")) {
return -1;
}

</br>

这里强烈推荐使用无损格式的图片,比如 png 格式。除非我们不关心图片质量或者内存有限的情况下,对图片恰当的进行压缩。

Image 类提供了一些常用的方法,

  • Image::getPixel()Image::setPixel() 改变图片某个像素
  • Image::getPixelPtr() 获取所有的图片像素,它返回的事数组的起始地址,数组的定义跟前面我们使用的像素数组一眼。

</br>

三、创建 Textures

Texture 类跟 Image 类很多方法都是类似的,例如我们创建一个纹理:

1
2
3
sf::Texture texture;
if (texture.loadFromFile("myTexture.png"))
return -1;

loadFromFile() 还允许我们只截取图片的部分区域,代码如下:

1
2
3
sf::Texture texture;
if (texture.loadFromFile("myTexture.png", sf::IntRect(0, 0, 32, 32)))
return -1;

上面的代码截取图片 3232 大小,原点坐标在图片的左上角。如果需要多次截取图片,我们可以用 *Image 读取图片一次,然后用这个图片去创建纹理。

1
2
3
4
5
6
sf::Image image;
image.create(50, 50, sf::Color::Red);


sf::Texture texture;
texture.loadFromFile(image);

</br>

四、将纹理绘制在shape中

前面都是在介绍怎么创建或者读取图片,这里将介绍我们如何将图片显示出来。

Texture 是像素的集合,它不能直接绘制到屏幕上,还需要设置它的位置,旋转角度等参数。Shape 类能够起到这么一个作用。

1
2
3
4
5
6
7
8
9
10
11
sf::Texture texture;
texture.loadFromFile("myTexture.png");

sf::RectangleShape rectShape(sf::Vector2f(300, 150));
rectShape.setTexture(&texture);

while (window.isOpen()) {
window.clear(sf::Color::Black);
window.draw(rectShape);
window.display();
}

上面的代码将 Texture 对象放入 RectangleShape 中。Texture 对象会适当的缩放自身使之适合 RectangleShape 的大小。例如,我们的 Texture 如果是 200 200 大小的, RectangleShape 是 300 150 大小的。那么 Texture 将会在 x 方向拉伸显示,而在 y 方向压缩显示。

未设置正确大小

一般来说,我们不希望看到这样的效果。。。

为此,我们需要正确的设置 RectangleShape 的大小,可以使用 Texture::getSize() 获取我们 Texture 的大小。

因此,代码可以修改为:

1
2
3
4
5
6
sf::Vector2u textureSize = texture.getSize();
float rectWidth = static_cast<float>(textureSize.x);
float rectHeight = static_cast<float>(textureSize.y);

sf::RectangleShape rectShape(sf::Vector2f(rectWidth, rectHeight));
rectShape.setTexture(&texture);

设置正确大小

除此之外,Texture 对象还可以绘制到其他 Shape 当中,例如 CircleShapeConvexShape

</br>

某些时候,一张大图片可能由一张小图片多次出现组成,如下图所示。

这时候,我们可以通过 SFML 实现,以减少空间的占用。 Texture 中有一个方法叫做 Texture::setRepeated(),设置为 True 的时候表示当前 Texture 对象是可重复的。

例如我们有一张 128 * 221大小的图片,我们需要在 x 轴上重复 3 次,在 y 轴上重复 2 次。

1
2
3
4
5
6
7
sf::Texture texture;
texture.loadFromFile("rep.png");
texture.setRepeated(true);

sf::RectangleShape rectShape(sf::Vector2f(128 * 3, 221 * 2));

rectShape.setTexture(&texture);

但是上面这份代码达不到我们需要的效果,它会将原图片单纯的拉伸显示而不是重复显示。这里的解决办法是将这个纹理矩形设置的比原来的要大。

1
2
3
4
5
6
7
8
9
sf::Texture texture;
texture.loadFromFile("rep.png");
texture.setRepeated(true);

sf::RectangleShape rectShape(sf::Vector2f(128 * 3, 221 * 2));
// 将纹理矩形设置的比原来的要大
rectShape.setTextureRect(sf::IntRect(0, 0, 128 * 3, 221 * 2));

rectShape.setTexture(&texture);

这样就达到了我们需要的效果:

下面这个图显示了一个默认的纹理矩形如何映射到一个比它大的显示区域中,以及当纹理矩形设置的比原来的纹理要大的时候又是如何映射的。

示例代码在 Github

这部分内容先写到这里,下半部分将介绍精灵的使用😃。

文章作者: Sshpark
文章链接: http://sshpark.com.cn/2019/08/25/用C++和SFML写游戏-纹理及精灵(5.1)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Sshpark