首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >优化一个简单的2D Tile引擎(+潜在的bugfix)

优化一个简单的2D Tile引擎(+潜在的bugfix)
EN

Stack Overflow用户
提问于 2013-04-18 09:50:34
回答 2查看 1.3K关注 0票数 0

前言

是的,这里有很多东西要覆盖.但是我会尽我最大的努力让这一切井井有条,内容丰富,直截了当。

使用HGE文库中的C++,我创建了一个简单的瓷砖引擎。

到目前为止,我实施了以下设计:

  • CTile类,表示CTileLayer中的单个块,包含行/列信息以及HGE::hgeQuad (存储顶点、颜色和纹理信息,请看这里获取详细信息)。
  • CTileLayer类,表示二维的“平面”(存储为CTile对象的一维数组),包含行/列的#、X/Y世界坐标信息、平铺像素宽度/高度信息以及图层的整体宽度/高度(以像素为单位)。

CTileLayer负责呈现在虚拟相机“视口”边界内完全或部分可见的任何瓷砖,并避免对超出此可见范围的任何瓷砖进行渲染。在创建时,它会预先计算存储在每个CTile对象中的所有信息,这样引擎的核心就有了更大的喘息空间,并且可以严格地集中在渲染循环上。当然,它也处理每一个包含的瓷砖的适当的去分配。

问题

我现在面临的问题基本上归结为以下架构/优化问题:

  1. 在我的呈现循环中,即使我没有呈现任何超出可见范围的块,我仍然在循环遍历所有的瓷砖,这似乎对较大的分片有很大的性能影响(也就是说,超过100x100行/列@ 64x64维度的任何东西仍然会使框架下降50%或更多)。
  2. 最后,我打算创建一个花哨的tilemap编辑器来配合这个引擎。 然而,由于我将所有的二维信息存储在一个或多个一维数组中,我不知道实现某种矩形选择和复制/粘贴功能的可能性有多大,而没有一些主要的性能影响--包括在每个帧中循环两次。然而,如果我使用2D数组,会有一个稍微少,但更普遍的FPS下降!

错误

如前所述..。在我的CTileLayer对象的呈现代码中,我优化了要根据这些块是否在查看范围内绘制的瓷砖。这很好用,对于更大的地图,我只注意到一个3-8的FPS下降(与没有这种优化的100+ FPS下降相比)。

但是我认为我计算这个范围是错误的,因为在半程滚动地图之后,您可以看到一个空白(在最上面和最左边),那里的瓷砖没有被渲染,就好像剪裁范围的增长速度超过了摄像机的移动速度(尽管它们都以相同的速度移动)。

随着X&Y轴的深入,这个缺口逐渐增大,最终吞噬了大地图上屏幕左上角的近一半。我的渲染代码如下所示..。

代码

代码语言:javascript
复制
//
// [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;
}

问题

  1. 如何在不影响任何其他呈现优化的情况下修复这些体系结构/优化问题?
  2. 为什么会发生这种错误?怎么能修好呢?

谢谢你抽出时间!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-04-18 10:26:46

优化地图的迭代是相当直接的。

如果在世界坐标(左、上、右、下)中有一个可见的区域,那么简单地除以瓷砖的大小,计算出瓷砖的位置是相当简单的。

一旦有了这些平铺坐标(tl、tt、tr、tb),就可以很容易地计算出一维数组中的第一个可见块。(从2D坐标计算任何平铺索引的方法是(y*宽度)+x-记住,首先要确保输入坐标是有效的。)然后,您只需要一个double循环来迭代可见的tiles:

代码语言:javascript
复制
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,y,左,右,宽,高?只需存储相机的位置(x,y),并根据屏幕/视图端口的尺寸计算可见的rect,以及您定义的任何缩放因子。

票数 1
EN

Stack Overflow用户

发布于 2015-08-02 17:51:03

这是一个伪codish例子,几何变量在二维向量中。相机物体和地形图都有一个中心位置和一个区域(一半大小).即使你决定坚持纯粹的数字,数学也是一样的。即使你不使用中心坐标和范围,也许你会对数学有一个概念。所有这些代码都在呈现函数中,并且相当简化。另外,这个示例假设您已经有了一个2D数组-like对象,该对象保存了这些瓷砖。

所以,首先是一个完整的例子,我将进一步解释每一部分。

代码语言:javascript
复制
// 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

一点一点地解释。首先,在呈现函数中,我们将使用一些变量。

代码语言:javascript
复制
// 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

为了防止渲染所有的瓷砖,您需要提供一个类似照相机的对象,或者提供有关可见区域开始和停止的信息(如果场景是可移动的,则在worldspace中)。

在本例中,我为呈现函数提供了一个相机对象,该函数有一个中心,一个区域存储为2d向量。

代码语言:javascript
复制
// 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

现在,如果地板在任意轴上都高于0或ceil小于0,这就意味着相机外有同样多的瓷砖。

代码语言:javascript
复制
// 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)

tilemap的正常呈现将从0到该轴的瓷砖数,这使用循环中的一个循环来说明两个轴。但是,由于上面的代码x和y将始终坚持在相机的边界内的空间。

代码语言:javascript
复制
// 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

希望这能有所帮助!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16079715

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档