Max*_*Max 10 javascript algorithm click collision-detection isometric
我正在研究等距游戏引擎,并且已经为像素完美点击检测创建了算法.访问该项目并注意到点击检测能够检测到点击了哪个边缘.它还会检查y-index以单击最前面的磁贴.
等距网格由100*65px的平铺图像组成.
TileW=100, TileL=50, tileH=15
地图由三维数组表示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);
Run Code Online (Sandbox Code Playgroud)
用于确定鼠标是否位于磁贴上给定区域内的原型函数:
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));
}
Run Code Online (Sandbox Code Playgroud)
Tile.prototype.allContainsMouse()如果鼠标在绿色范围内,则返回true.通过检查dx是瓷砖宽度的一半来裁剪红色区域
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));
};
Run Code Online (Sandbox Code Playgroud)
Tile.prototype.leftContainsMouse = function() {
var dx = mouse.mapX-this.centerX;
if(dx<0) { return true; } else { return false; }
};
Run Code Online (Sandbox Code Playgroud)
(如果鼠标留在中心点)
Tile.prototype.rightContainsMouse = function() {
var dx = mouse.mapX-this.centerX;
if(dx>0) { return true; } else { return false; }
};
Run Code Online (Sandbox Code Playgroud)
(如果鼠标位于中心点右侧)
将所有方法整合在一起工作:
allContainsMouse()返回true,map [z] [y] [x]是我们鼠标所在的图块.tilesUnderneathMouse数组中.循环遍历tilesUnderneathMouse数组,并选择最高的图块y.这是最前沿的瓷砖.
if(allContainsMouse && !topContainsMouse)
Run Code Online (Sandbox Code Playgroud)if(allContainsMouse && !topContainsMouse && leftContainsMouse)
Run Code Online (Sandbox Code Playgroud)(类似的概念适用于权利)
#1您将如何实现这一目标,以便更高效(不循环遍历所有磁贴)(接受pesudo代码)
#2如果你无法回答#1,你有什么建议来提高点击检测的效率(已经考虑了块加载)
我最初试图通过不使用平铺中心点来解决这个问题,而是将鼠标(x,y)位置直接转换为平铺x,y.在我看来,这是最难编码,但最有效的解决方案.在正方形网格上,将(x,y)位置转换为网格上的正方形非常容易.但是,在交错列网格中,您可以处理偏移量.我尝试使用带有x或y值的函数计算偏移量,并返回结果偏移量y或x.arccos(cosx)的Zig-zag图解决了这个问题.
检查鼠标是否在瓷砖内,使用这种方法很困难,我无法弄明白.我正在检查鼠标(x,y)是否在一条y=mx+b依赖于tileX,tileY近似(约为正方形网格)的线下面.
如果你到了这里,谢谢!
这个答案基于:
所以事情是这样的:
网格和屏幕之间的转换
正如我在评论中提到的,您应该创建在屏幕和单元格网格位置之间转换的函数。像(在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++; } }
}
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
我使用了你的布局(花了我一段时间将我的布局转换为它希望我不会在某个地方犯一些愚蠢的错误):
cell2scr(x,y,0,0,0)请注意,如果您使用整数算术,则需要记住,如果除/乘以一半大小,您可能会失去精度。对于这种情况,请使用完整尺寸并将结果除以2(过去要花费大量时间来解决这个问题)。
这cell2scr非常简单。屏幕位置是平移偏移 + 单元格位置乘以其大小(步长)。轴x需要对偶数/奇数行进行校正(这就是目的((cy&1)*cxs2)),并且轴由轴 ( )y移动。我的屏幕有一个点在左上角,轴指向右,指向下。z((cy&1)*cxs2)(0,0)+x+y
这scr2cell是通过从方程中代数求解屏幕位置来完成的,cell2scr同时假设z=0仅选择网格地面。最重要的是,如果鼠标位置位于找到的单元格区域之外,则添加偶数/奇数校正。
扫描邻居
仅返回scr2cell(x,y,z,mouse_x,mouse_y)鼠标位于地面上的单元格。因此,如果您想添加当前的选择功能,您需要扫描该位置的顶部单元格和几个相邻单元格,然后选择距离最小的单元格。
无需扫描整个网格/地图,只需扫描返回位置周围的几个单元格即可。这应该会大大加快速度。
我这样做:
行数取决于单元z轴尺寸 ( czs)、最大层数z( gzs) 和单元尺寸 ( cys)。我的 scan 的C++代码如下所示:
// 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
}
Run Code Online (Sandbox Code Playgroud)
当用鼠标玩时,它总是选择顶部单元格(所有可能的单元格中最高的单元格),感觉正确(至少对我来说):
这是我今天为此找到的等距引擎的完整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