基于网格的游戏中的负坐标

Negative coordinates in a grid based game

我在 javascript 中编写了一个小型 2D 游戏,它使用一个网格,玩家从位置 [0,0] 开始并且可以在任一方向移动几乎无限的距离。

现在我想实现 A* 寻路,但我在寻找最佳方式来存储具有所有不同障碍物、敌人和地形的世界时遇到了一些问题。这是我目前尝试过或想过的。

数组的数组

这里我将世界存储在一个数组数组中[x][y]

var world = [[]];
world[312][11] = 0;
world[312][12] = 0;
world[312][13] = 1;
world[312][14] = 1;
...

这对 A* 寻路非常有用!访问特定坐标并填充世界既简单又快速。在上面的示例中,我只存储可通行 (0) 或不可通行 (1) 的地形,但我可以在那里存储几乎任何我想要的东西。但是,如果我的玩家位于 [-12][-230],这对负坐标不太适用。 javascript 数组中的负键实际上不是数组的一部分,它们不会包含在 world.lengthworld[3].length 中,据我所知,这是总体上不好的做法,可能有对性能也有一定的影响。我在某处读到,如果你在数组中使用负键,那你就做错了。 出于显而易见的原因,我仍然不会将整个世界传递给 A* 函数。只是靠近我的播放器的一小部分,但坐标会对应数组中的位置,这很容易处理。

A*寻路专用数组

这就是我现在所在的位置。我有一个名为 pathMap = [[]] 的单独 50x50 网格,即 用于寻路。

var pathMap = [[]];
pathMap[0][0] = 0;
pathMap[0][1] = 0;
pathMap[0][2] = 1;
pathMap[0][3] = 1;
...

它从 pathMap[0][0] 开始,一直到 pathMap[50][50],作为我当前位置的叠加层,我(作为玩家)将始终处于中心位置。我的 真实 坐标可能类似于 [-5195,323],但它转换为 pathMap[25][25] 并且靠近我的所有东西都放在与我的位置相关的 pathMap 上。 现在这有效,但它是一个巨大的混乱。从一个坐标到另一个坐标的所有来回转换让我的大脑受伤。此外,当我从 A* 获取返回路径时,我必须将它的每一步都转换回我的元素在现实世界中应该移动到的实际位置。我还必须在每次更新时将同一个对象填充到 2 个不同的网格中,这也会对性能造成一些影响。

对象数组

我想这是我想去的地方,但我也有一些问题。

var world = [];
world[0] = { x: -10, y: 3, impassable: 0 };
world[1] = { x: -10, y: 4, impassable: 0 };
world[2] = { x: -10, y: 5, impassable: 1 };
world[3] = { x: -10, y: 6, impassable: 1 };
...

非常适合负 x 或 y 值!但是,要在此数组中找到例如 [10,3] 并不容易。我必须遍历整个数组来查找 x == 10y == 3 的对象,而不是第一个示例中非常简单快速的方法 world[10][3] 。此外,我不能真正依赖使用此版本的坐标以正确的顺序排列,排序变得更难,就像使用数组的数组更容易的其他事情一样。

重建游戏,始终保持积极的一面

我不希望这样做,但我考虑过将玩家的起始位置改为 [1000000,1000000] 之类的位置,并将负坐标设为禁区。如果我不得不消除我对无尽的愿景只是为了用更少的代码进行寻路工作,这似乎是一个失败。我知道无论如何总会有一些上限或下限,但我只想从 [0,0] 开始,而不是出于数组相关原因的任意坐标。

其他?

在javascript中,有没有上面没有描述的更好的选择?我很乐意接受建议!

是否有针对类似案例的最佳实践?

您必须区分三个坐标系:

  • 世界坐标。
  • 世界模型/寻路(阵列)坐标。
  • 屏幕坐标。

屏幕坐标系取决于:

  • 视口 = canvas。 (以像素为单位的宽度、高度)。
  • 相机 = (x,y) 世界坐标中心 + viewWidth(世界坐标)。

为了避免麻烦,构建一个小的抽象层来为您计算。

您可能希望使用 Object.defineProperty 来定义属性,这将提供流畅的界面。

var canvas = ... ;
var canvasWidth = canvas.width;
var canvasHeigth = canvas.heigth;


var world = {
              width  : 1000,  // meters
              height : 1000,  // meters
              tileSize : 0.5,   // height and width of a tile, in meter
              model  : null,  // 2D array sized ( width/tileSize, XtileSize )
             };
// possibles world coordinates range from -width/2 to width/2 ; - height/2 height/2.

 var camera = {
                 x : -1,
                 y : -1,
                 viewWidth : 10, // we see 10 meters wide scene
                 viewHeight : -1 // height is deduced from canvas aspect ratio
              };
camera.viewHeight = camera.viewWidth * canvasWidth / canvasHeight ;

那么你的角色看起来像:

// (x,y) is the center of the character in centered world coordinates
// here (0,0) means (500,500) world coords
//                  (1000,1000) array coords
//                  (320, 240) screen coords in 640X480
function /*Class*/ Character(x, y) {
     var _x=x;
     var _y=y;
     var _col=0;
     var _row=0;
     var _sx=0.0;
     var _sy=0.0;         
     var dirty = true;
     Object.defineProperty(this,'x', 
                              { get : function() {return _x; } 
                                set : function(v) { _x=v; 
                                                    dirty=true; } });
     Object.defineProperty(this,'x', 
                              { get : function() {return _y; } 
                                set : function(v) { _y=v; 
                                                    dirty=true; } });
     Object.defineProperty(this,'col', 
                              { get : function() {
                                               if (dirty) updateCoords();
                                               return _col; }  });
     Object.defineProperty(this,'row', 
                              { get : function() {
                                               if (dirty) updateCoords();
                                               return _row; }  });
     Object.defineProperty(this,'sx', 
                              { get : function() {
                                               if (dirty) updateCoords();
                                               return _sx; }  });
     Object.defineProperty(this,'sy', 
                              { get : function() {
                                               if (dirty) updateCoords();
                                               return _sy; }  });
     function updateCoords() {
        _row = ( ( _x + 0.5 * world.width )/ world.tileSize ) | 0 ;
        _col = ( ( _x + 0.5 * world.height )/ world.tileSize ) | 0 ;
        _sx =  canvasWidth  * ( 0.5 + ( _x - camera.x ) / camera.viewWidth ) ;
        _sy =  canvasHeight * ( 0.5 + ( _y - camera.y ) / camera.viewHeight ) ;
        dirty = false;            
     }
}