C++ 优化一个简单的2D平铺引擎(+;潜在的错误修复) 前言
是的,这里有很多内容。。。但我会尽我最大的努力使这篇文章条理清晰,内容丰富,直截了当C++ 优化一个简单的2D平铺引擎(+;潜在的错误修复) 前言,c++,optimization,graphics,2d,tile-engine,C++,Optimization,Graphics,2d,Tile Engine,是的,这里有很多内容。。。但我会尽我最大的努力使这篇文章条理清晰,内容丰富,直截了当 使用C++,我创建了一个简单的瓦片引擎。 到目前为止,我已经实施了以下设计: 一个CTile类,表示CTileLayer中的单个平铺,包含行/列信息以及HGE::hgeQuad(详细信息,它存储顶点、颜色和纹理信息) 一个CTileLayer类,表示二维平铺“平面”(存储为CTile对象的一维数组),包含行/列、X/Y世界坐标信息、平铺像素宽度/高度信息以及以像素为单位的层总宽度/高度 CTileLayer
使用C++,我创建了一个简单的瓦片引擎。 到目前为止,我已经实施了以下设计:
- 一个
类,表示CTile
中的单个平铺,包含行/列信息以及CTileLayer
(详细信息,它存储顶点、颜色和纹理信息)HGE::hgeQuad
- 一个
类,表示二维平铺“平面”(存储为CTileLayer
对象的一维数组),包含行/列、X/Y世界坐标信息、平铺像素宽度/高度信息以及以像素为单位的层总宽度/高度CTile
CTileLayer
负责渲染虚拟摄影机“视口”边界内完全或部分可见的任何平铺,并避免对该可见范围之外的任何平铺进行渲染。创建时,它会预先计算每个CTile
对象中要存储的所有信息,因此引擎的核心有更多的喘息空间,并且可以严格关注渲染循环。当然,它还处理每个包含的磁贴的正确释放。
问题 我现在面临的问题基本上归结为以下架构/优化问题:
但是,由于我将所有二维信息存储在一个或多个1D数组中,因此我不知道实现某种矩形选择和复制/粘贴功能的可能性有多大,而不会对性能造成重大影响——包括每帧在每个磁贴中循环两次。然而,如果我使用2D阵列,会有一个稍微少,但更普遍的FPS下降李> 缺陷 如前所述。。。在我为一个
CTileLayer
对象绘制的渲染代码中,我已经根据它们是否在可视范围内优化了要绘制的平铺。这非常有效,对于更大的地图,我只注意到了3-8 FPS的下降(相比之下,在没有优化的情况下,100+FPS的下降)。但是我认为我计算的这个范围是错误的,因为在地图滚动到一半后,你可以看到一个间隙(在最上面和最左边),在这个间隙中,瓷砖没有被渲染,好像剪辑范围的增加速度超过了相机可以移动的速度(即使它们都以相同的速度移动)。
沿着X&Y轴越远,这个间隙的大小就越大,最终占据了大地图上屏幕左上方的近一半。 我的渲染代码如下所示。。。
代码
//
//[分配]
//用于预计算磁贴信息
//-行/列=地图尺寸(分幅)
//-宽度/高度=瓷砖尺寸(以像素为单位)
//
void CTileLayer::Allocate(UINT numColumns、UINT numRows、float tileWidth、float tileHeight)
{
m_nColumns=numColumns;
m_nRows=numRows;
浮动x,y;
UINT列=0,行=0;
const-ULONG-nTiles=m_-nColumns*m_-nRows;
hgeQuad;
m_tileWidth=tileWidth;
m_tileHeight=tileHeight;
m_layerWidth=m_tileWidth*m_n列;
m_layerHeight=m_tileHeight*m_nRows;
如果(m_tiles!=NULL)Free();
m_tiles=新的CTile[nTiles];
对于(ULONG l=0;lm_n列-1){
列=0;
行++;
}
}
}
//
//[呈现]
//用于绘制整个瓷砖层
//-X/Y=世界位置
//-顶部/左侧=屏幕“剪裁”位置
//-宽度/高度=屏幕“剪裁”尺寸
//
bool-CTileLayer::Render(HGE*HGE、浮点cameraX、浮点Cameraray、浮点cameraTop、浮点cameraLeft、浮点cameraWidth、浮点cameraHeight)
{
//计算当前的分幅数
const-ULONG-nTiles=m_-nColumns*m_-nRows;
//计算最小和最大X/Y世界像素坐标
const float scalarX=cameraX/m_layerWidth;//这是沿X轴在层内的距离(在世界坐标系中,从0到1)
const float scalarY=cameraY/m_layerHeight;//这是沿Y轴在层内的距离(在世界坐标中从0到1)
常量float minX=cameraTop+(scalarX*float(m_nColumns)-m_tileWidth)/
//
// [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;
}
int visiblewidth = tr - tl + 1;
int visibleheight = tb - tt + 1;
for( int rowidx = ( tt * layerwidth ) + tl; visibleheight--; rowidx += layerwidth )
{
for( int tileidx = rowidx, cx = visiblewidth; cx--; tileidx++ )
{
// render m_Tiles[ tileidx ]...
}
}
// x and y are counters, sx is a placeholder for x start value as x will
// be in the inner loop and need to be reset each iteration.
// mx and my will be the values x and y will count towards too.
x=0,
y=0,
sx=0,
mx=total_number_of_tiles_on_x_axis,
my=total_number_of_tiles_on_y_axis
// calculate the lowest and highest worldspace values of the cam
min = cam.center - cam.extent
max = cam.center + cam.extent
// subtract with tilemap corners and divide by tilesize to get
// the anount of tiles that is outside of the cameras scoop
floor = Math.floor( min - ( tilemap.center - tilemap.extent ) / tilesize)
ceil = Math.ceil( max - ( tilemap.center + tilemap.extent ) / tilesize)
if(floor.x > 0)
sx+=floor.x
if(floor.y > 0)
y+=floor.y
if(ceil.x < 0)
mx+=ceil.x
if(ceil.y < 0)
my+=ceil.y
for(; y<my; y++)
// x need to be reset each y iteration, start value are stored in sx
for(x=sx; x<mx; x++)
// render tile x in tilelayer y
// x and y are counters, sx is a placeholder for x start value as x will
// be in the inner loop and need to be reset each iteration.
// mx and my will be the values x and y will count towards too.
x=0,
y=0,
sx=0,
mx=total_number_of_tiles_on_x_axis,
my=total_number_of_tiles_on_y_axis
// calculate the lowest and highest worldspace values of the cam
min = cam.center - cam.extent
max = cam.center + cam.extent
// subtract with tilemap corners and divide by tilesize to get
// the anount of tiles that is outside of the cameras scoop
floor = Math.floor( min - ( tilemap.center - tilemap.extent ) / tilesize)
ceil = Math.ceil( max - ( tilemap.center + tilemap.extent ) / tilesize)
// floor & ceil is 2D vectors
// check if there is any tiles outside to the left or above of camera
if(floor.x > 0)
sx+=floor.x// set start number of sx to amount of tiles outside of camera
if(floor.y > 0)
y+=floor.y // set startnumber of y to amount of tiles outside of camera
// test if there is any tiles outisde to the right or below the camera
if(ceil.x < 0)
mx+=ceil.x // then add the negative value to mx (max x)
if(ceil.y < 0)
my+=ceil.y // then add the negative value to my (max y)
// will loop through only the visible tiles
for(; y<my; y++)
// x need to be reset each y iteration, start value are stored in sx
for(x=sx; x<mx; x++)
// render tile x in tilelayer y