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
    类,表示二维平铺“平面”(存储为
    CTile
    对象的一维数组),包含行/列、X/Y世界坐标信息、平铺像素宽度/高度信息以及以像素为单位的层总宽度/高度
CTileLayer
负责渲染虚拟摄影机“视口”边界内完全或部分可见的任何平铺,并避免对该可见范围之外的任何平铺进行渲染。创建时,它会预先计算每个
CTile
对象中要存储的所有信息,因此引擎的核心有更多的喘息空间,并且可以严格关注渲染循环。当然,它还处理每个包含的磁贴的正确释放。

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

  • 在我的渲染循环中,即使我没有渲染任何超出可见范围的平铺,我仍然在所有平铺中循环,这似乎对较大的平铺贴图的性能有重大影响(即,任何大于100x100行/列@64x64平铺尺寸的东西仍然会将帧率降低50%或更多)
  • 最后,我打算创建一个奇特的tilemap编辑器来配合这个引擎。
    但是,由于我将所有二维信息存储在一个或多个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