前言
是的,这里有很多要介绍的内容......但我会尽我所能保持这个组织得井井有条、内容丰富且直截了当!
使用 C++ 中的HGE 库,我创建了一个简单的平铺引擎。
到目前为止,我已经实现了以下设计:
- 一个
CTile
类,表示 a 中的单个图块CTileLayer
,包含行/列信息以及一个HGE::hgeQuad
(存储顶点、颜色和纹理信息,请参阅此处了解详细信息)。 - 一个
CTileLayer
类,表示二维“平面”瓦片(存储为一维CTile
对象数组),包含行/列数、X/Y 世界坐标信息、瓦片像素宽度/高度信息,以及图层的整体宽度/高度(以像素为单位)。
ACTileLayer
负责渲染在虚拟相机“视口”边界内完全或部分可见的任何图块,并避免对超出此可见范围的任何图块执行此操作。在创建时,它会预先计算要存储在每个CTile
对象中的所有信息,因此引擎的核心有更多的呼吸空间,并且可以严格专注于渲染循环。当然,它还处理每个包含的 tile 的正确释放。
问题
我现在面临的问题基本上归结为以下架构/优化问题:
- 在我的渲染循环中,即使我没有渲染任何超出可见范围的图块,我仍然会循环遍历所有图块,这似乎对较大的图块地图(即超过 100x100 行/ 64x64 平铺尺寸的列仍会将帧速率降低 50% 或更多)。
- 最终,我打算创建一个花哨的瓷砖地图编辑器来配合这个引擎。
但是,由于我将所有二维信息存储在一个或多个一维数组中,所以我不知道在没有一些主要性能影响的情况下实现某种矩形选择和复制/粘贴功能的可能性有多大—— - 涉及每帧循环遍历每个图块两次。然而,如果我使用 2D 数组,FPS 下降会稍微少一些但更普遍!
漏洞
如前所述...在我的CTileLayer
对象渲染代码中,我已经根据它们是否在可视范围内优化了要绘制的图块。这很好用,对于较大的地图,我注意到只有 3-8 FPS 下降(与没有此优化的 100+ FPS 下降相比)。
但我认为我计算的范围不正确,因为在地图滚动到一半后,您可以开始看到没有渲染图块的间隙(在最顶部和最左侧),好像剪裁范围的增加速度比相机可以移动(即使它们都以相同的速度移动)。
沿着 X 轴和 Y 轴,这个间隙的大小会逐渐增加,最终在大地图上占据屏幕顶部和左侧的近一半。
代码
//
// [Allocate]
// For pre-calculating tile information
// - Rows/Columns = Map Dimensions (in tiles)
// - Width/Height = Tile Dimensions (in pixels)
//
void CTileLayer::Allocate(UINT numColumns, UINT numRows, float tileWidth, float tileHeight)
{
m_nColumns = numColumns;
m_nRows = numRows;
float x, y;
UINT column = 0, row = 0;
const ULONG nTiles = m_nColumns * m_nRows;
hgeQuad quad;
m_tileWidth = tileWidth;
m_tileHeight = tileHeight;
m_layerWidth = m_tileWidth * m_nColumns;
m_layerHeight = m_tileHeight * m_nRows;
if(m_tiles != NULL) Free();
m_tiles = new CTile[nTiles];
for(ULONG l = 0; l < nTiles; l++)
{
m_tiles[l] = CTile();
m_tiles[l].column = column;
m_tiles[l].row = row;
x = (float(column) * m_tileWidth) + m_offsetX;
y = (float(row) * m_tileHeight) + m_offsetY;
quad.blend = BLEND_ALPHAADD | BLEND_COLORMUL | BLEND_ZWRITE;
quad.tex = HTEXTURE(nullptr); //Replaced for the sake of brevity (in the engine's code, I used a globally allocated texture array and did some random tile generation here)
for(UINT i = 0; i < 4; i++)
{
quad.v[i].z = 0.5f;
quad.v[i].col = 0xFF7F7F7F;
}
quad.v[0].x = x;
quad.v[0].y = y;
quad.v[0].tx = 0;
quad.v[0].ty = 0;
quad.v[1].x = x + m_tileWidth;
quad.v[1].y = y;
quad.v[1].tx = 1.0;
quad.v[1].ty = 0;
quad.v[2].x = x + m_tileWidth;
quad.v[2].y = y + m_tileHeight;
quad.v[2].tx = 1.0;
quad.v[2].ty = 1.0;
quad.v[3].x = x;
quad.v[3].y = y + m_tileHeight;
quad.v[3].tx = 0;
quad.v[3].ty = 1.0;
memcpy(&m_tiles[l].quad, &quad, sizeof(hgeQuad));
if(++column > m_nColumns - 1) {
column = 0;
row++;
}
}
}
//
// [Render]
// For drawing the entire tile layer
// - X/Y = world position
// - Top/Left = screen 'clipping' position
// - Width/Height = screen 'clipping' dimensions
//
bool CTileLayer::Render(HGE* hge, float cameraX, float cameraY, float cameraTop, float cameraLeft, float cameraWidth, float cameraHeight)
{
// Calculate the current number of tiles
const ULONG nTiles = m_nColumns * m_nRows;
// Calculate min & max X/Y world pixel coordinates
const float scalarX = cameraX / m_layerWidth; // This is how far (from 0 to 1, in world coordinates) along the X-axis we are within the layer
const float scalarY = cameraY / m_layerHeight; // This is how far (from 0 to 1, in world coordinates) along the Y-axis we are within the layer
const float minX = cameraTop + (scalarX * float(m_nColumns) - m_tileWidth); // Leftmost pixel coordinate within the world
const float minY = cameraLeft + (scalarY * float(m_nRows) - m_tileHeight); // Topmost pixel coordinate within the world
const float maxX = minX + cameraWidth + m_tileWidth; // Rightmost pixel coordinate within the world
const float maxY = minY + cameraHeight + m_tileHeight; // Bottommost pixel coordinate within the world
// Loop through all tiles in the map
for(ULONG l = 0; l < nTiles; l++)
{
CTile tile = m_tiles[l];
// Calculate this tile's X/Y world pixel coordinates
float tileX = (float(tile.column) * m_tileWidth) - cameraX;
float tileY = (float(tile.row) * m_tileHeight) - cameraY;
// Check if this tile is within the boundaries of the current camera view
if(tileX > minX && tileY > minY && tileX < maxX && tileY < maxY) {
// It is, so draw it!
hge->Gfx_RenderQuad(&tile.quad, -cameraX, -cameraY);
}
}
return false;
}
//
// [Free]
// Gee, I wonder what this does? lol...
//
void CTileLayer::Free()
{
delete [] m_tiles;
m_tiles = NULL;
}
问题
- 在不严重影响任何其他渲染优化的情况下,可以做些什么来解决这些架构/优化问题?
- 为什么会出现这个错误?如何修复?
感谢您的时间!