提高交错列等距网格上点击检测的性能

10
我正在开发一个等角游戏引擎,并已经创建了像素级别点击检测的算法。请访问项目,注意到点击检测能够检测到哪个瓦片边缘被点击。它还会检查y索引以便点击最前面的瓦片。
我的当前算法解释如下:
等角网格由100*65px的瓦片图像组成。 TileW=100,TileL=50,tileH=15

Sizing of tile

地图由三维数组 map[z][y][x] 表示。
瓦片中心点 (x,y) 的计算方式如下:
//x, y, z are the position of the tile

if(y%2===0) { x-=-0.5; }    //To accommodate the offset found in even rows
this.centerX = (x*tileW) + (tileW/2);
this.centerY = (y*tileL) - y*((tileL)/2) + ((tileL)/2) + (tileH/2) - (z*tileH);

Isometric grid

原型函数,用于确定鼠标是否在瓦片上给定区域内:

Tile.prototype.allContainsMouse = function() {
    var dx = Math.abs(mouse.mapX-this.centerX),
        dy = Math.abs(mouse.mapY-this.centerY);

    if(dx>(tileW/2)) {return false;}    //Refer to image
    return (dx/(tileW*0.5) + (dy/(tileL*0.5)) < (1+tileHLRatio));
}

Tile.prototype.allContainsMouse() 如果鼠标在绿色区域内,则返回true。通过检查dx是否大于瓦片宽度的一半,可以裁剪掉红色区域。

Figure 1


Tile.prototype.topContainsMouse = function() {
    var topFaceCenterY = this.centerY - (tileH/2);
    var dx = Math.abs(mouse.mapX-this.centerX),
        dy = Math.abs(mouse.mapY-topFaceCenterY);

    return ((dx/(tileW*0.5) + dy/(tileL*0.5) <= 1));
};

Returns true if mouse is on top face


Tile.prototype.leftContainsMouse = function() {
    var dx = mouse.mapX-this.centerX;
    if(dx<0) { return true; } else { return false; }
};

(如果鼠标在中心点左侧)


Tile.prototype.rightContainsMouse = function() {
    var dx = mouse.mapX-this.centerX;
    if(dx>0) { return true; } else { return false; }
};

(如果鼠标在中心点右侧)

将所有方法整合为一个工作:

  • Loop Through the entire 3d map[z][y][x] array
  • if allContainsMouse() returns true, map[z][y][x] is the tile our mouse is on.
  • Add this tile to the array tilesUnderneathMouse array.
  • Loop through tilesUnderneathMouse array, and choose the tile with the highest y. It is the most upfront tile.

    if(allContainsMouse && !topContainsMouse)
    

Bottom match

  • if(allContainsMouse && !topContainsMouse && leftContainsMouse)
    

left match

(类似的概念也适用于右侧)

最后,我的问题:

#1 如何实现这一点,使其更加高效(不通过循环遍历所有瓷砖)(伪代码可接受)

#2 如果您无法回答问题#1,您有什么建议来提高我的点击检测效率(区块加载已经考虑过)

我想到的:

我最初尝试解决这个问题的方式是不使用格子的中心点,而是将鼠标(x,y)位置直接转换为格子的x、y。在我的想法中,这是编码最难但最有效率的解决方案。在一个正方形的网格中,很容易将(x,y)位置转换为网格上的一个正方形。然而,在错列列网格中,你要处理偏移量。我尝试使用一个函数来计算偏移量,该函数接受一个x或y值,并返回相应的偏移量y或x。arccos(cosx) 的锯齿图解决了这个问题。

检查鼠标是否在图块内,使用这种方法很困难,我无法弄清楚。我正在检查鼠标(x,y)是否在依赖于tileX,tileY近似值(一个方格网格近似)的y = mx + b线下方。

如果您到达此处,谢谢!


请参阅将2D网格图像值转换为2D数组,您可以直接从鼠标坐标计算地板网格坐标,然后仅循环遍历近邻的Y,Z轴。 - Spektre
一个瓷砖可以高到足以完全遮盖上面/左上方/右上方的瓷砖吗? - David Eisenstat
@DavidEisenstat 不,这是一个游戏引擎,必须支持程序员希望拥有的每个瓦片。 - Max
@MaxMastalerz很好奇,所以我玩了一下我的等距引擎,并成功地实现了我的方法。已编辑我的答案并添加了扫描部分... - Spektre
1个回答

4

以下翻译基于:

下面开始:

  1. conversion between grid and screen

    As I mentioned in comment you should make functions that convert between screen and cell grid positions. something like (in C++):

     //---------------------------------------------------------------------------
     // tile sizes
     const int cxs=100;
     const int cys= 50;
     const int czs= 15;
     const int cxs2=cxs>>1;
     const int cys2=cys>>1;
     // view pan (no zoom)
     int pan_x=0,pan_y=0;
     //---------------------------------------------------------------------------
     void isometric::cell2scr(int &sx,int &sy,int cx,int cy,int cz) // grid -> screen
         {
         sx=pan_x+(cxs*cx)+((cy&1)*cxs2);
         sy=pan_y+(cys*cy/2)-(czs*cz);
         }
     //---------------------------------------------------------------------------
     void isometric::scr2cell(int &cx,int &cy,int &cz,int sx,int sy) // screen -> grid
         {
         // rough cell ground estimation (no z value yet)
         cy=(2*(sy-pan_y))/cys;
         cx=   (sx-pan_x-((cy&1)*cxs2))/cxs;
         cz=0;
         // isometric tile shape crossing correction
         int xx,yy;
         cell2scr(xx,yy,cx,cy,cz);
         xx=sx-xx; mx0=cx;
         yy=sy-yy; my0=cy;
         if (xx<=cxs2) { if (yy>     xx *cys/cxs) { cy++; if (int(cy&1)!=0) cx--; } }
         else          { if (yy>(cxs-xx)*cys/cxs) { cy++; if (int(cy&1)==0) cx++; } }
         }
     //---------------------------------------------------------------------------
    

    I used your layout (took mi while to convert mine to it hopefully I do not made some silly mistake somewhere):

    layout

    • red cross represents coordinates returned by cell2scr(x,y,0,0,0)
    • green cross represents mouse coordinates
    • aqua highlight represents returned cell position

    Beware if you are using integer arithmetics you need to take in mind if you dividing/multiplying by half sizes you can lose precision. Use full size and divide the result by 2 for such cases (spend a lot of time figuring that one out in the past).

    The cell2scr is pretty straightforward. The screen position is pan offset + cell position multiplied by its size (step). The x axis need a correction for even/odd rows (that is what ((cy&1)*cxs2) is for) and y axis is shifted by the z axis (((cy&1)*cxs2)). Mine screen has point (0,0) in upper left corner, +x axis is pointing right and +y is pointing down.

    The scr2cell is done by algebraically solved screen position from the equations of cell2scr while assuming z=0 so selects only the grid ground. On top of that is just the even/odd correction added if mouse position is outside found cell area.

  2. scan neighbors

    the scr2cell(x,y,z,mouse_x,mouse_y) returns just cell where your mouse is on the ground. so if you want to add your current selection functionality you need to scan the top cell on that position and few neighboring cells and select the one with least distance.

    No need to scan the whole grid/map just few cells around returned position. That should speed up thing considerably.

    I do it like this:

    scan pattern

    The number of lines depends on the cell z axis size (czs), max number of z layers (gzs) and the cell size (cys). The C++ code of mine with scan looks like this:

     // grid size
     const int gxs=15;
     const int gys=30;
     const int gzs=8;
     // my map (all the cells)
     int map[gzs][gys][gxs];
    
     void isometric::scr2cell(int &cx,int &cy,int &cz,int sx,int sy)
         {
         // rough cell ground estimation (no z value yet)
         cy=(2*(sy-pan_y))/cys;
         cx=   (sx-pan_x-((cy&1)*cxs2))/cxs;
         cz=0;
         // isometric tile shape crossing correction
         int xx,yy;
         cell2scr(xx,yy,cx,cy,cz);
         xx=sx-xx;
         yy=sy-yy;
         if (xx<=cxs2) { if (yy>     xx *cys/cxs) { cy++; if (int(cy&1)!=0) cx--; } }
         else          { if (yy>(cxs-xx)*cys/cxs) { cy++; if (int(cy&1)==0) cx++; } }
         // scan closest neighbors
         int x0=-1,y0=-1,z0=-1,a,b,i;
    
         #define _scann                                                          \
         if ((cx>=0)&&(cx<gxs))                                                  \
          if ((cy>=0)&&(cy<gys))                                                 \
             {                                                                   \
             for (cz=0;(map[cz+1][cy][cx]!=_cell_type_empty)&&(cz<czs-1);cz++);  \
             cell2scr(xx,yy,cx,cy,cz);                                           \
             if (map[cz][cy][cx]==_cell_type_full) yy-=czs;                      \
             xx=(sx-xx); yy=((sy-yy)*cxs)/cys;                                   \
             a=(xx+yy);  b=(xx-yy);                                              \
             if ((a>=0)&&(a<=cxs)&&(b>=0)&&(b<=cxs))                             \
              if (cz>=z0) { x0=cx; y0=cy; z0=cz; }                               \
             }
                                           _scann;   // scan actual cell
         for (i=gzs*czs;i>=0;i-=cys)                 // scan as many lines bellow actual cell as needed
             {
             cy++; if (int(cy&1)!=0) cx--; _scann;
             cx++;                         _scann;
             cy++; if (int(cy&1)!=0) cx--; _scann;
             }
         cx=x0; cy=y0; cz=z0;                        // return remembered cell coordinate
    
         #undef _scann
         }
    

    This selects always the top cell (highest from all the possible) when playing with mouse it feels correctly (at least to me):

    scan result

这是我今天为我的等角引擎破解的完整VCL/C++源代码:

    //---------------------------------------------------------------------------
    //--- Isometric ver: 1.01 ---------------------------------------------------
    //---------------------------------------------------------------------------
    #ifndef _isometric_h
    #define _isometric_h
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    // colors       0x00BBGGRR
    DWORD col_back =0x00000000;
    DWORD col_grid =0x00202020;
    DWORD col_xside=0x00606060;
    DWORD col_yside=0x00808080;
    DWORD col_zside=0x00A0A0A0;
    DWORD col_sel  =0x00FFFF00;
    //---------------------------------------------------------------------------
    //--- configuration defines -------------------------------------------------
    //---------------------------------------------------------------------------
    //  #define isometric_layout_1  // x axis: righ+down, y axis: left+down
    //  #define isometric_layout_2  // x axis: righ     , y axis: left+down
    //---------------------------------------------------------------------------
    #define isometric_layout_2
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    /*
    // grid size
    const int gxs=4;
    const int gys=16;
    const int gzs=8;
    // cell size
    const int cxs=100;
    const int cys= 50;
    const int czs= 15;
    */
    // grid size
    const int gxs=15;
    const int gys=30;
    const int gzs=8;
    // cell size
    const int cxs=40;
    const int cys=20;
    const int czs=10;
    
    const int cxs2=cxs>>1;
    const int cys2=cys>>1;
    // cell types
    enum _cell_type_enum
        {
        _cell_type_empty=0,
        _cell_type_ground,
        _cell_type_full,
        _cell_types
        };
    //---------------------------------------------------------------------------
    class isometric
        {
    public:
        // screen buffer
        Graphics::TBitmap *bmp;
        DWORD **pyx;
        int xs,ys;
        // isometric map
        int map[gzs][gys][gxs];
        // mouse
        int mx,my,mx0,my0;          // [pixel]
        TShiftState sh,sh0;
        int sel_x,sel_y,sel_z;      // [grid]
        // view
        int pan_x,pan_y;
        // constructors for compiler safety
        isometric();
        isometric(isometric& a) { *this=a; }
        ~isometric();
        isometric* operator = (const isometric *a) { *this=*a; return this; }
        isometric* operator = (const isometric &a);
        // Window API
        void resize(int _xs,int _ys);                       // [pixels]
        void mouse(int x,int y,TShiftState sh);             // [mouse]
        void draw();
        // auxiliary API
        void cell2scr(int &sx,int &sy,int cx,int cy,int cz);
        void scr2cell(int &cx,int &cy,int &cz,int sx,int sy);
        void cell_draw(int x,int y,int tp,bool _sel=false);     // [screen]
        void map_random();
        };
    //---------------------------------------------------------------------------
    //---------------------------------------------------------------------------
    isometric::isometric()
        {
        // init screen buffers
        bmp=new Graphics::TBitmap;
        bmp->HandleType=bmDIB;
        bmp->PixelFormat=pf32bit;
        pyx=NULL; xs=0; ys=0;
        resize(1,1);
        // init map
        int x,y,z,t;
        t=_cell_type_empty;
    //  t=_cell_type_ground;
    //  t=_cell_type_full;
        for (z=0;z<gzs;z++,t=_cell_type_empty)
         for (y=0;y<gys;y++)
          for (x=0;x<gxs;x++)
           map[z][y][x]=t;
        // init mouse
        mx =0; my =0; sh =TShiftState();
        mx0=0; my0=0; sh0=TShiftState();
        sel_x=-1; sel_y=-1; sel_z=-1;
        // init view
        pan_x=0; pan_y=0;
        }
    //---------------------------------------------------------------------------
    isometric::~isometric()
        {
        if (pyx) delete[] pyx; pyx=NULL;
        if (bmp) delete   bmp; bmp=NULL;
        }
    //---------------------------------------------------------------------------
    isometric* isometric::operator = (const isometric &a)
        {
        resize(a.xs,a.ys);
        bmp->Canvas->Draw(0,0,a.bmp);
        int x,y,z;
        for (z=0;z<gzs;z++)
         for (y=0;y<gys;y++)
          for (x=0;x<gxs;x++)
           map[z][y][x]=a.map[z][y][x];
        mx=a.mx; mx0=a.mx0; sel_x=a.sel_x;
        my=a.my; my0=a.my0; sel_y=a.sel_y;
        sh=a.sh; sh0=a.sh0; sel_z=a.sel_z;
        pan_x=a.pan_x;
        pan_y=a.pan_y;
        return this;
        }
    //---------------------------------------------------------------------------
    void isometric::resize(int _xs,int _ys)
        {
        if (_xs<1) _xs=1;
        if (_ys<1) _ys=1;
        if ((xs==_xs)&&(ys==_ys)) return;
        bmp->SetSize(_xs,_ys);
        xs=bmp->Width;
        ys=bmp->Height;
        if (pyx) delete pyx;
        pyx=new DWORD*[ys];
        for (int y=0;y<ys;y++) pyx[y]=(DWORD*) bmp->ScanLine[y];
        // center view
        cell2scr(pan_x,pan_y,gxs>>1,gys>>1,0);
        pan_x=(xs>>1)-pan_x;
        pan_y=(ys>>1)-pan_y;
        }
    //---------------------------------------------------------------------------
    void isometric::mouse(int x,int y,TShiftState shift)
        {
        mx0=mx; mx=x;
        my0=my; my=y;
        sh0=sh; sh=shift;
        scr2cell(sel_x,sel_y,sel_z,mx,my);
        if ((sel_x<0)||(sel_y<0)||(sel_z<0)||(sel_x>=gxs)||(sel_y>=gys)||(sel_z>=gzs)) { sel_x=-1; sel_y=-1; sel_z=-1; }
        }
    //---------------------------------------------------------------------------
    void isometric::draw()
        {
        int x,y,z,xx,yy;
        // clear space
        bmp->Canvas->Brush->Color=col_back;
        bmp->Canvas->FillRect(TRect(0,0,xs,ys));
        // grid
        DWORD c0=col_zside;
        col_zside=col_back;
        for (y=0;y<gys;y++)
         for (x=0;x<gxs;x++)
            {
            cell2scr(xx,yy,x,y,0);
            cell_draw(xx,yy,_cell_type_ground,false);
            }
        col_zside=c0;
        // cells
        for (z=0;z<gzs;z++)
         for (y=0;y<gys;y++)
          for (x=0;x<gxs;x++)
            {
            cell2scr(xx,yy,x,y,z);
            cell_draw(xx,yy,map[z][y][x],(x==sel_x)&&(y==sel_y)&&(z==sel_z));
            }
        // mouse0 cross
        bmp->Canvas->Pen->Color=clBlue;
        bmp->Canvas->MoveTo(mx0-10,my0); bmp->Canvas->LineTo(mx0+10,my0);
        bmp->Canvas->MoveTo(mx0,my0-10); bmp->Canvas->LineTo(mx0,my0+10);
        // mouse cross
        bmp->Canvas->Pen->Color=clGreen;
        bmp->Canvas->MoveTo(mx-10,my); bmp->Canvas->LineTo(mx+10,my);
        bmp->Canvas->MoveTo(mx,my-10); bmp->Canvas->LineTo(mx,my+10);
        // grid origin cross
        bmp->Canvas->Pen->Color=clRed;
        bmp->Canvas->MoveTo(pan_x-10,pan_y); bmp->Canvas->LineTo(pan_x+10,pan_y);
        bmp->Canvas->MoveTo(pan_x,pan_y-10); bmp->Canvas->LineTo(pan_x,pan_y+10);
    
        bmp->Canvas->Font->Charset=OEM_CHARSET;
        bmp->Canvas->Font->Name="System";
        bmp->Canvas->Font->Pitch=fpFixed;
        bmp->Canvas->Font->Color=clAqua;
        bmp->Canvas->Brush->Style=bsClear;
        bmp->Canvas->TextOutA(5, 5,AnsiString().sprintf("Mouse: %i x %i",mx,my));
        bmp->Canvas->TextOutA(5,20,AnsiString().sprintf("Select: %i x %i x %i",sel_x,sel_y,sel_z));
        bmp->Canvas->Brush->Style=bsSolid;
        }
    //---------------------------------------------------------------------------
    void isometric::cell2scr(int &sx,int &sy,int cx,int cy,int cz)
        {
        #ifdef isometric_layout_1
        sx=pan_x+((cxs*(cx-cy))/2);
        sy=pan_y+((cys*(cx+cy))/2)-(czs*cz);
        #endif
        #ifdef isometric_layout_2
        sx=pan_x+(cxs*cx)+((cy&1)*cxs2);
        sy=pan_y+(cys*cy/2)-(czs*cz);
        #endif
        }
    //---------------------------------------------------------------------------
    void isometric::scr2cell(int &cx,int &cy,int &cz,int sx,int sy)
        {
        int x0=-1,y0=-1,z0=-1,a,b,i,xx,yy;
    
        #ifdef isometric_layout_1
        // rough cell ground estimation (no z value yet)
        // translate to (0,0,0) top left corner of the grid
        xx=sx-pan_x-cxs2;
        yy=sy-pan_y+cys2;
        // change aspect to square cells cxs x cxs
        yy=(yy*cxs)/cys;
        // use the dot product with axis vectors to compute grid cell coordinates
        cx=(+xx+yy)/cxs;
        cy=(-xx+yy)/cxs;
        cz=0;
    
        // scan closest neighbors
        #define _scann                                                          \
        if ((cx>=0)&&(cx<gxs))                                                  \
         if ((cy>=0)&&(cy<gys))                                                 \
            {                                                                   \
            for (cz=0;(map[cz+1][cy][cx]!=_cell_type_empty)&&(cz<czs-1);cz++);  \
            cell2scr(xx,yy,cx,cy,cz);                                           \
            if (map[cz][cy][cx]==_cell_type_full) yy-=czs;                      \
            xx=(sx-xx); yy=((sy-yy)*cxs)/cys;                                   \
            a=(xx+yy);  b=(xx-yy);                                              \
            if ((a>=0)&&(a<=cxs)&&(b>=0)&&(b<=cxs))                             \
             if (cz>=z0) { x0=cx; y0=cy; z0=cz; }                               \
            }
                      _scann;           // scan actual cell
        for (i=gzs*czs;i>=0;i-=cys)     // scan as many lines bellow actual cell as needed
            {
            cy++;       _scann;
            cx++; cy--; _scann;
            cy++;       _scann;
            }
        cx=x0; cy=y0; cz=z0;            // return remembered cell coordinate
        #undef _scann
        #endif
    
        #ifdef isometric_layout_2
        // rough cell ground estimation (no z value yet)
        cy=(2*(sy-pan_y))/cys;
        cx=   (sx-pan_x-((cy&1)*cxs2))/cxs;
        cz=0;
        // isometric tile shape crossing correction
        cell2scr(xx,yy,cx,cy,cz);
        xx=sx-xx;
        yy=sy-yy;
        if (xx<=cxs2) { if (yy>     xx *cys/cxs) { cy++; if (int(cy&1)!=0) cx--; } }
        else          { if (yy>(cxs-xx)*cys/cxs) { cy++; if (int(cy&1)==0) cx++; } }
        // scan closest neighbors
        #define _scann                                                          \
        if ((cx>=0)&&(cx<gxs))                                                  \
         if ((cy>=0)&&(cy<gys))                                                 \
            {                                                                   \
            for (cz=0;(map[cz+1][cy][cx]!=_cell_type_empty)&&(cz<czs-1);cz++);  \
            cell2scr(xx,yy,cx,cy,cz);                                           \
            if (map[cz][cy][cx]==_cell_type_full) yy-=czs;                      \
            xx=(sx-xx); yy=((sy-yy)*cxs)/cys;                                   \
            a=(xx+yy);  b=(xx-yy);                                              \
            if ((a>=0)&&(a<=cxs)&&(b>=0)&&(b<=cxs))                             \
             if (cz>=z0) { x0=cx; y0=cy; z0=cz; }                               \
            }
                                          _scann;   // scan actual cell
        for (i=gzs*czs;i>=0;i-=cys)                 // scan as many lines bellow actual cell as needed
            {
            cy++; if (int(cy&1)!=0) cx--; _scann;
            cx++;                         _scann;
            cy++; if (int(cy&1)!=0) cx--; _scann;
            }
        cx=x0; cy=y0; cz=z0;                        // return remembered cell coordinate
        #undef _scann
        #endif
        }
    //---------------------------------------------------------------------------
    void isometric::cell_draw(int x,int y,int tp,bool _sel)
        {
        TPoint pnt[5];
        bmp->Canvas->Pen->Color=col_grid;
        if (tp==_cell_type_empty)
            {
            if (!_sel) return;
            bmp->Canvas->Pen->Color=col_sel;
            pnt[0].x=x;      pnt[0].y=y     ;
            pnt[1].x=x+cxs2; pnt[1].y=y+cys2;
            pnt[2].x=x+cxs;  pnt[2].y=y     ;
            pnt[3].x=x+cxs2; pnt[3].y=y-cys2;
            pnt[4].x=x;      pnt[4].y=y     ;
            bmp->Canvas->Polyline(pnt,4);
            }
        else if (tp==_cell_type_ground)
            {
            if (_sel) bmp->Canvas->Brush->Color=col_sel;
            else      bmp->Canvas->Brush->Color=col_zside;
            pnt[0].x=x;      pnt[0].y=y     ;
            pnt[1].x=x+cxs2; pnt[1].y=y+cys2;
            pnt[2].x=x+cxs;  pnt[2].y=y     ;
            pnt[3].x=x+cxs2; pnt[3].y=y-cys2;
            bmp->Canvas->Polygon(pnt,3);
            }
        else if (tp==_cell_type_full)
            {
            if (_sel) bmp->Canvas->Brush->Color=col_sel;
            else      bmp->Canvas->Brush->Color=col_xside;
            pnt[0].x=x+cxs2; pnt[0].y=y+cys2;
            pnt[1].x=x+cxs;  pnt[1].y=y;
            pnt[2].x=x+cxs;  pnt[2].y=y     -czs;
            pnt[3].x=x+cxs2; pnt[3].y=y+cys2-czs;
            bmp->Canvas->Polygon(pnt,3);
    
            if (_sel) bmp->Canvas->Brush->Color=col_sel;
            else bmp->Canvas->Brush->Color=col_yside;
            pnt[0].x=x;      pnt[0].y=y;
            pnt[1].x=x+cxs2; pnt[1].y=y+cys2;
            pnt[2].x=x+cxs2; pnt[2].y=y+cys2-czs;
            pnt[3].x=x;      pnt[3].y=y     -czs;
            bmp->Canvas->Polygon(pnt,3);
    
            if (_sel) bmp->Canvas->Brush->Color=col_sel;
            else bmp->Canvas->Brush->Color=col_zside;
            pnt[0].x=x;      pnt[0].y=y     -czs;
            pnt[1].x=x+cxs2; pnt[1].y=y+cys2-czs;
            pnt[2].x=x+cxs;  pnt[2].y=y     -czs;
            pnt[3].x=x+cxs2; pnt[3].y=y-cys2-czs;
            bmp->Canvas->Polygon(pnt,3);
            }
        }
    //---------------------------------------------------------------------------
    void isometric::map_random()
        {
        int i,x,y,z,x0,y0,r,h;
        // clear
        for (z=0;z<gzs;z++)
         for (y=0;y<gys;y++)
          for (x=0;x<gxs;x++)
           map[z][y][x]=_cell_type_empty;
        // add pseudo-random bumps
        Randomize();
        for (i=0;i<10;i++)
            {
            x0=Random(gxs);
            y0=Random(gys);
            r=Random((gxs+gys)>>3)+1;
            h=Random(gzs);
            for (z=0;(z<gzs)&&(r);z++,r--)
             for (y=y0-r;y<y0+r;y++)
              if ((y>=0)&&(y<gys))
               for (x=x0-r;x<x0+r;x++)
                if ((x>=0)&&(x<gxs))
                 map[z][y][x]=_cell_type_full;
            }
        }
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------

布局仅定义坐标轴的方向(请使用#define isometric_layout_2)。这里使用 Graphics::TBitmap,如果您不使用,请将其更改为任何位图或覆盖gfx部分以使用您的gfx(仅对draw()resize()有效)。此外,TShiftState是的一部分,它只是鼠标按钮和特殊键(如shiftaltctrl)的状态,因此您可以使用bool或其他替代方法(目前未使用,因为我还没有任何单击功能)。
这是我的窗口代码(一个带有一个计时器的单表应用程序),这样您就可以了解如何使用它:
//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "win_main.h"
#include "isometric.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
isometric iso;
//---------------------------------------------------------------------------
void TMain::draw()
    {
    iso.draw();
    Canvas->Draw(0,0,iso.bmp);
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    Cursor=crNone;
    iso.map_random();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    iso.resize(ClientWidth,ClientHeight);
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseMove(TObject *Sender, TShiftState Shift, int X,int Y)                        { iso.mouse(X,Y,Shift); draw(); }
void __fastcall TMain::FormMouseDown(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)   { iso.mouse(X,Y,Shift); draw(); }
void __fastcall TMain::FormMouseUp(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)     { iso.mouse(X,Y,Shift); draw(); }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDblClick(TObject *Sender)
    {
    iso.map_random();
    }
//---------------------------------------------------------------------------

[编辑1] 图形处理方法

请查看Simple OpenGL GUI Framework User Interaction Advice?

主要思路是创建一个阴影屏幕缓冲区,其中存储了渲染单元的ID。这只需要几行代码就可以实现在O(1)时间内进行像素级别的精确精灵/单元选择。

  1. create shadow screen buffer idx[ys][xs]

    It should have the same resolution as your map view And should be capable of storing the (x,y,z) value of render cell inside single pixel (in map grid cell units). I use 32 bit pixel format so I choose 12 bits for x,y and 8 bits for z

     DWORD color = (x) | (y<<12) | (z<<24)
    
  2. before rendering of map clear this buffer

    I use 0xFFFFFFFF as empty color so it is not colliding with cell (0,0,0).

  3. on map cell sprite rendering

    whenever you render pixel to screen buffer pyx[y][x]=color you also render pixel to shadow screen buffer idx[y][x]=c where c is encoded cell position in map grid units (see #1).

  4. On mouse click (or whatever)

    You got screen position of mouse mx,my so if it is in range just read the shadow buffer and obtain selected cell position.

     c=idx[my][mx]
     if (c!=0xFFFFFFFF)
      {
      x= c     &0x00000FFF;
      y=(c>>12)&0x00000FFF;
      z=(c>>24)&0x000000FF;
      }
     else 
      {
      // here use the grid floor cell position formula from above approach if needed
      // or have empty cell rendered for z=0 with some special sprite to avoid this case.
      }
    

    With above encoding this map (screen):

    screen

    is rendered also to shadow screen like this:

    shadow

    Selection is pixel perfect does not matter if you click on top, side...

    The tiles used are:

     Title: Isometric 64x64 Outside Tileset
     Author: Yar
     URL: http://opengameart.org/content/isometric-64x64-outside-tileset
     License(s): * CC-BY 3.0 http://creativecommons.org/licenses/by/3.0/legalcode
    

    And here Win32 Demo:


很多内容可以用伪代码更清晰地解释,尤其是邻居扫描部分。 - Max
@MaxMastalerz 我认为这并不必要,因为图像本身已经很清楚了,而且文本中也包含了描述。另外,你声称已经实现了扫描部分,但是扫描所有单元格,所以你只需要从中选择要扫描的单元格。 - Spektre
@MaxMastalerz 为什么不呢?这只是选择可能发生点击的区域。判断点是否属于单元格的方式与该区域无关,它只影响选择单元格的方式,而该区域保持不变。因此,如果您使用自己的谓词,您的功能不应更改,只会提高速度(因为您不需要扫描 gxs*gys*gzs 个单元格...而只需要扫描 ~3*gzs*czs/cys 个单元格)。 - Spektre
@MaxMastalerz添加了[edit1],使用基于图形的像素完美的O(1)方法。 - Spektre
@MaxMastalerz 在我的回答末尾添加了一个链接,里面有我的演示(请阅读文本),这样你就可以尝试我所提到的像素完美选择功能。 - Spektre
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接