管理要在HTML5 Canvas上绘制的2D数组中的文本地图


46

因此,我只是为了好玩而制作HTML5 RPG。地图是<canvas>(宽度512像素,高度352像素|宽16格,上下11格)。我想知道是否有一种更有效的方法来绘制<canvas>

这就是我现在的方式。

如何在地图上加载和绘制图块

正在使用该图块以图块(32x32)绘制地图Image()。图像文件通过一个简单的for循环加载,并放入一个名为的数组tiles[],使用时要进行绘制drawImage()

首先,我们加载瓷砖...

在此处输入图片说明

这是完成的过程:

// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
  var imageObj = new Image(); // new instance for each image
  imageObj.src = "js/tiles/t" + x + ".png";
  imageObj.onload = function () {
    console.log("Added tile ... " + loadedImagesCount);
    loadedImagesCount++;
    if (loadedImagesCount == NUM_OF_TILES) {
      // Onces all tiles are loaded ...
      // We paint the map
      for (y = 0; y <= 15; y++) {
        for (x = 0; x <= 10; x++) {
          theX = x * 32;
          theY = y * 32;
          context.drawImage(tiles[5], theY, theX, 32, 32);
        }
      }
    }
  };
  tiles.push(imageObj);
}

自然地,当玩家开始游戏时,它会加载他们最后一次退出的地图。但是在这里,它是一张全草图。

现在,地图使用2D数组。这是一个示例地图。

[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1], 
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1], 
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];

我使用简单的if结构得到了不同的地图。一旦上面的2d数组是return,每个数组中的对应数字将根据Image()存储在内部的情况进行绘制tile[]。然后drawImage()会发生,并根据油漆xy和时间它通过32画上正确的x-y坐标。

多个地图切换如何发生

随着我的游戏,地图有五件事情来跟踪:currentIDleftIDrightIDupID,和bottomID

  • currentID:您正在使用的地图的当前ID。
  • leftID:currentID在当前地图左侧退出时要加载的ID 。
  • rightID:currentID在当前地图右侧退出时要加载的ID 。
  • downID:currentID在当前地图底部退出时要加载的ID 。
  • upID:currentID退出当前地图顶部时要加载的ID 。

一些需要注意的:如果有一个leftIDrightIDupID,或bottomID不特定的,这意味着他们是一个0。这意味着他们不能离开地图的那一侧。这仅仅是一个看不见的封锁。

因此,一旦一个人离开地图的一侧,具体取决于他们离开的位置...例如,如果他们在底部离开,bottomIDmap要加载的编号并将其绘制在地图上。

这是一个代表性的.GIF,可帮助您更好地可视化:

在此处输入图片说明

如您所见,迟早会有很多地图处理许多 ID。这可能会引起一些混乱和忙碌。

明显的优点是,它一次可加载176个图块,刷新一小块512x352画布,并一次处理一张地图。缺点是,当处理许多地图时,MAP ID有时可能会造成混乱。

我的问题

  • 这是存储地图的一种有效方法(考虑到瓷砖的使用),还是有一种更好的处理地图的方法?

我当时正在考虑一张巨型地图。地图大小很大,并且全都是一个2D数组。但是,视口仍为512x352像素。

这是我为解决此问题而制作的另一个.gif,以帮助可视化:

在此处输入图片说明

对不起,如果您听不懂我的英语。请问任何您难以理解的问题。希望我能说清楚。谢谢。


16
图形方面的问题使所有问题都值得赞扬。:)
Tapio 2012年

Answers:


5

编辑:刚刚看到我的答案是基于您的代码,但实际上并没有回答您的问题。我保留了旧答案,以防您可以使用该信息。

编辑2:我用原始代码修复了2个问题:-在计算x和y端时+1加法错误地放在了方括号内,但需要在除法后添加。-我忘了验证x和y值。

您可以简单地将当前地图存储在内存中并加载周围的地图。想象一下一个世界,其中包含大小为5x5的单个级别的二维数组。玩家从字段1开始。由于关卡的顶部和左侧位于世界的边缘,因此无需加载它们。因此,在这种情况下,1/1级别处于活动状态,而1/2和2/1级别已加载...如果玩家现在向右移动,则所有级别(除了您要移动的级别)都将被卸载,并且新环境已加载。表示级别2/1现在处于活动状态,级别1 / 1、2 / 2和3/1现在已加载。

我希望这可以使您了解如何完成。但是,当必须在游戏中模拟每个关卡时,这种方法将无法很好地发挥作用。但是,如果您可以冻结未使用的级别,则应该可以正常工作。

旧答案:

渲染关卡(也是基于图块)时,我要做的是计算图块数组中的哪些项与视口相交,然后仅渲染那些图块。这样,您可以拥有大地图,但只需要在屏幕上渲染该部分即可。

//Mock values
//camera position x
//viewport.x = 233f;
//camera position y
//viewport.y = 100f;
//viewport.w = 640
//viewport.h = 480
//levelWidth = 10
//levelHeight = 15
//tilesize = 32;

//startX defines the starting index for the x axis.
// 7 = 233 / 32
int startX = viewport.x / tilesize;

//startY defines the starting index
// 3 = 100 / 32
int startY = viewport.y / tilesize;

//End index y
// 28 = (233 + 640) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//End index y
// 19 = (100 + 480) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//Validation
if(startX < 0) startX = 0;
if(startY < 0) startY = 0;
//endX is set to 9 here
if(endX >= levelWidth) endX = levelWidth - 1;
//endX is set to 14 here
if(endY >= levelHeight) endY = levelHeight - 1;

for(int y = startY; y < yEnd; y++)
    for(int x = startX; x < xEnd; x++)
          [...]

注意:上面的代码尚未经过测试,但是应该给出一个思路。

遵循一个视口表示的基本示例:

public class Viewport{
    public float x;
    public float y;
    public float w;
    public float h;
}

一个javascript表示看起来像

<script type="text/javascript">
//Declaration
//Constructor
var Viewport = function(xVal, yVal, wVal, hVal){
    this.x = xVal;
    this.y = yVal;
    this.w = wVal;
    this.h = hVal;
}
//Prototypes
Viewport.prototype.x = null;
Viewport.prototype.y = null;
Viewport.prototype.w = null;
Viewport.prototype.h = null;
Viewport.prototype.toString = function(){
    return ["Position: (", this.x, "/" + this.y + "), Size: (", this.w, "/", this.h, ")"].join("");
}

//Usage
var viewport = new Viewport(23, 111, 640, 480);
//Alerts "Position: (23/111), Size: (640/480)
alert(viewport.toString());
</script>

如果以我的代码示例为例,您只需将int和float声明替换为var。还要确保在分配给基于int的值的那些计算上使用Math.floor来获得相同的行为。

我会考虑的一件事(尽管我不确定在JavaScript中是否重要)是将所有图块(或尽可能多的图块)置于一个大纹理中,而不是使用多个单个文件。

以下是说明操作方法的链接:http : //thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


只是为了澄清... viewport.x将被预先定义为“ 531”,viewport.y将被预先定义为“ 352”,并且tileize =“ 32”。这样的..?
测试

如果您的意思是摄像机的位置是531,则可以。我在上面的代码中添加了一些注释。
汤姆·范·格林

我正在使用JavaScript ...我是Java,几乎与此完全相同。
测试

是的,您只需要制作一个基于js的视口类(或者您可以只创建4个变量x,y,w和h)。另外,您还必须确保对分配给int值的这些计算进行累加(Math.floor)。我添加了一个代码示例,说明了视口类的外观。只要确保将声明放在用法之前即可。
汤姆·范·格林

只是说你放640, 480..但是可以用512, 352对吗?
测试

3

将地图存储为图块对于允许您重新使用资产和重新设计地图很有用。实际上,尽管它并没有真正为玩家带来太多好处。另外,您每次都需要重新绘制每个图块。这就是我要做的:

将整个地图存储为一个大数组。没有地图,子地图以及可能的子子地图(例如,如果您要输入建筑物的话)。无论如何,您都在加载它,这使所有内容保持简洁。

一次渲染所有地图。将屏幕渲染到屏幕外的画布上,然后在游戏中使用它。此页面显示了如何使用简单的javascript函数来完成此操作:

var renderToCanvas = function (width, height, renderFunction) {
  var buffer = document.createElement('canvas');
  buffer.width = width;
  buffer.height = height;
  renderFunction(buffer.getContext('2d'));
  return buffer;
};

这是假设您的地图不会有太大变化。如果要就地渲染子地图(例如,建筑物的内部),则只需将其渲染到画布上,然后将其绘制在画布上即可。这是如何使用它来渲染地图的示例:

var map = renderToCanvas( map_width*32, map_height*32, renderMap );

var MapSizeX = 512;
var MapSizeY = 352;

var draw = function(canvas, mapCentreX, mapCentreY) {
  var context = canvas.getContext('2d');

  var minX = playerX - MapSizeX/2;
  var minY = playerY - MapSizeY/2;

  context.drawImage(map,minX,minY,MapSizeX,MapSizeY,0,0,MapSizeX,MapSizeY);
}

这将在地图周围绘制一小部分地图mapCentreX, mapCentreY。这将使您可以平滑地滚动整个地图以及子平铺运动-您可以将玩家位置存储为图块的1/32分之一,这样您就可以在整个地图上进行平滑运动(显然,如果要作为样式选择,您可以按32个块递增。

如果您愿意,或者您的世界很大并且无法容纳在目标系统的内存中,您仍然可以分块地图(请记住,即使对于每个像素1字节,512x352的地图也需要176 KB),但是像这样渲染地图,您将在大多数浏览器中看到很大的性能提升

这给您带来的灵活性是可以在全球范围内重复使用图块,而无需为编辑一张大图像而烦恼,而且还允许您快速轻松地运行此图块,并且只考虑一张“地图”(或任意数量的“地图”) 。



1

跟踪ID的一种简单方法是在其中使用另一个2d数组:通过仅在该“元数组”上维护x,y,您可以轻松地查找其他ID。

话虽这么说,以下是Google根据其最近的I / O 2012进行的2D平铺画布游戏(嗯,实际上是HTML5多人游戏,但也包括了平铺引擎):http : //www.youtube.com/watch?v= Prkyd5n0P7k也有可用的源代码。


1

您的想法是先将整个地图加载到数组中,这是一种有效的方法,但是很容易获得JavaScript注入,任何人都可以知道地图,然后再冒险。

我也在基于Web的RPG游戏上工作,我加载地图的方法是通过PHP页面通过Ajax加载,该页面从数据库加载地图,其中显示X,Y,Z位置和vlaue。瓷砖,绘制它,最后玩家移动。

我仍在努力使其效率更高,但是地图加载速度已经足够快,可以保持这种状态。


1
我可以看演示吗?
测试

1

数组是存储图块地图的最有效方法

可能有一些结构需要较少的内存,但是只是以更高的加载和访问数据成本为代价。


但是,当您要加载下一张地图时,需要考虑一下。鉴于地图不是特别大,实际上需要花费最长时间的事情是等待服务器交付地图。实际加载仅需几毫秒。但是这种等待可以异步完成,而不必阻塞游戏流程。因此,最好的方法不是在需要时而是在需要时加载数据。播放器在一张地图中,现在加载所有相邻的地图。玩家在下一张地图中移动,再次加载所有相邻的地图,依此类推,等等。玩家甚至不会注意到您的游戏正在后台加载。

这样,您还可以通过绘制相邻地图来创建一个无边界世界的错觉,在这种情况下,您不仅需要加载相邻地图,还需要加载相邻地图。


0

我不确定为什么会这样:您可以看到,迟早会有很多地图处理许多ID。这可能会引起一些混乱和忙碌。

LeftID将始终为currentID的-1,rightID将始终为currentID的+1。

UpID将始终是当前ID的-(总地图宽度),而downID将始终是当前ID的+(总地图宽度),但零除外,这意味着您已经处于边缘。

单个地图可以分成许多地图,并与mapIDXXXX顺序保存,其中XXXX是ID。这样,没有什么可混淆的。我去过您的网站,看来,可能是错误的,问题出在您的地图编辑器上,它施加了大小限制,这阻碍了您的自动化功能将大型地图在保存时分解成多个小地图,这种限制是可能限制了技术解决方案。

我已经编写了一个Javascript地图编辑器,它可以在所有方向上滚动1000 x 1000(我不建议使用该大小)的图块,并且它仍然可以移动到50 x 50的地图,并且具有3层,包括视差滚动。它保存并以json格式读取。如果您说不介意大约2兆欧元的电子邮件,我会给您发送副本。它本来应该放在github上,但是我还没有任何像样的(合法的)tileset,这就是为什么我还不愿意将其放在那里。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.