在 2D 游戏开发过程中有时候会根据窗口的大小按比例缩放 UI 界面,默认情况下,SDL 的 SDL_RenderCopy 函数是支持自动缩放显示的:

int SDL_RenderCopy(SDL_Renderer*   renderer,
                   SDL_Texture*    texture,
                   const SDL_Rect* srcrect,
                   const SDL_Rect* dstrect)

如果函数最后一个参数 dstrect 为空或者和原始矩形 srcrect 的内容不一致,则纹理渲染的过程中会自动进行伸缩匹配。

1. 复杂界面的绘制问题

如果游戏中显示的界面比较简单,这样的方式是可行的,但是大多数的 UI 界面都比较复杂,我们这里用扫雷的界面举例。

扫雷界面

扫雷游戏是支持窗口拖拽的,在拖拽的过程中,窗口显示的 UI 界面也会成比例缩放。如果你用素材提取工具扫描扫雷程序,会发现该界面的素材是用下面的几种精灵图片组合而成:

素材

注意界面周围银灰色的部分:

UI 拆分

扫雷程序周围的 UI 是由 12 个部分构成,如果你在绘制扫雷程序的时候仍然使用前面提到的 SDL_RenderCopy 函数手动指定显示位置,你的逻辑写起来会非常复杂,比较常见的方式是类似原来 GDI 程序显示图片的双缓冲方法。

在现实界面的时候,先将界面绘制到指定的表面,然后再使用 SDL_RenderCopy 函数一次性的将绘制好的图片显示到界面上。SDL 提供了几种实现的方法,这里只讲解其中一种。

2. UI 缩放的解决方案

第一步,先创建一个表面:

SDL_Surface* softSurface = SDL_CreateRGBSurface(0, w, h, 32, 0, 0, 0, 0);

其中参数 w,h 分别指定表面的宽高,推荐直接使用窗口大小。后面的四个参数是指定像素位数,这里直接使用默认值。

因为我们需要在该表面绘制图像,所以可以使用该表面创建一个渲染器:

SDL_Renderer* softRenderer = SDL_CreateSoftwareRenderer(softSurface);

现在我们就可以用过渲染器来绘制图像了。除了这种方法,SDL 还提供了 SDL_BlitSurface 函数,貌似可以直接在表面之间 Copy,如果大家感兴趣可以尝试一下这种方式。

接下来就是绘制界面的过程,直接使用 SDL_RenderCopy 将纹理 Copy 到表面即可:

SDL_RenderCopy(softRenderer, panelTexture[RES_IMAGE_OTHERSHEET], ......);

这里有个地方需要注意,使用 SDL_RenderCopy 函数之前,需要确保在加载 panelTexture[RES_IMAGE_OTHERSHEET] 纹理时,指定了 softRenderer 渲染器:

panelTexture[RES_IMAGE_OTHERSHEET] = IMG_LoadTexture_RW(softRenderer, rwOps, 1);

否则会因为渲染器和纹理指定的渲染器不匹配而报错。

将所有的 UI 界面绘制完毕之后,可以最后在使用一次 SDL_RenderCopy 拷贝到最终的界面上:

SDL_Texture* texture = SDL_CreateTextureFromSurface(pModule->pRenderer, softSurface);
SDL_RenderCopy(pModule->pRenderer, texture, &rtSrc, &rtDst);
SDL_DestroyTexture(texture);

因为 SDL_RenderCopy 支持自动缩放,所以这里可以直接指定最终渲染的大小实现整体缩放效果。

缩放演示

3. 非 UI 缩放的解决方案

除了上述缩放方式之外,UI 显示还存在另一种方式,这种方式一般为了解决宽高比发生变换的时候。例如,假设现有 UI 界面是 1 :1 的效果,现在想改为 1 :5,因为宽高的缩放比例不一致,如果直接强行伸缩,会导致比例失调,为了解决这个问题,一般 UI 界面上都会引入“冗余块”。

前面 UI 拆时候的图片里,其中 2、5、8、11 都是“冗余块”。

UI 拆分

简单的说,这些图片素材是可以无限延伸的。假设你想把 UI 比例调成 1 :5,那只需要把 UI 额外的高度部分全部用图片 5 和 11 来填充,这样会显得 UI 非常自然,不会出现以为缩放而导致的比例失调问题。

素材延长