那里没有任何逻辑。
如果您检查页面的源代码,您将看到正文中的最后一个脚本标记具有大量的图块坐标。
该示例并没有什么神奇之处,它演示了一个用于弄清楚如何形成形状的“智能”系统。
话虽如此,确实有这样的事情……但它们并不简单。
What is更简单、更易于管理的是地图编辑器。
平铺编辑器
盒子外面:
有很多方法可以做到这一点...有一些免费或便宜的程序可以让您绘制瓷砖,然后输出 XML 或 JSON 或 CSV 或给定程序支持/导出的任何内容。
Tiled ( http://mapeditor.org http://mapeditor.org)就是这样的一个例子。
还有其他的,但 Tiled 是我能想到的第一个,它是免费的,而且实际上相当不错。
pros:
直接的好处是您可以获得一个应用程序,可以让您加载图像图块并将其绘制到地图中。
这些应用程序甚至可能支持添加碰撞层和实体层(将敌人置于 [2,1],将能量提升置于 [3,5],并在熔岩上方放置“伤害玩家”触发器)。
cons:...缺点是您需要确切地知道这些文件的格式,以便您可以将它们读入游戏引擎。
现在,这些系统的输出相对标准化......因此您可以将地图数据插入不同的游戏引擎(否则有什么意义?),虽然游戏引擎并不都使用完全正确的图块文件同样,大多数优秀的图块编辑器都允许导出为多种格式(有些可以让您定义自己的格式)。
...也就是说,替代方案(或者实际上是相同的解决方案,只是手工制作)是创建您自己的图块编辑器。
DIY
您可以在 Canvas 中创建它,就像创建绘制图块的引擎一样简单。
关键区别在于您拥有图块地图(例如来自 StarCr...erm...示例中的“found-art”的图块地图 .png)。
您不需要循环遍历数组,找到图块的坐标并在与该索引匹配的世界坐标处绘制它们,而是从地图中选择一个图块(就像在 MS Paint 中选择颜色),然后在任何地方您单击(或拖动),找出与之相关的数组点,并将该索引设置为等于该图块。
pros:
天空才是极限;你可以制作任何你想要的东西,让它适合你想要使用的任何文件格式,并让它处理你想要扔给它的任何疯狂的东西......
cons:
...当然,这意味着您必须自己制作它,并定义要使用的文件格式,并编写逻辑来处理所有这些滑稽的想法...
基本实现
虽然我通常会尝试使其变得整洁且 JS 范式友好,但这会导致这里产生大量代码。
因此,我将尝试指出它应该在哪里分解为单独的模块。
// assuming images are already loaded properly
// and have fired onload events, which you've listened for
// so that there are no surprises, when your engine tries to
// paint something that isn't there, yet
// this should all be wrapped in a module that deals with
// loading tile-maps, selecting the tile to "paint" with,
// and generating the data-format for the tile, for you to put into the array
// (or accepting plug-in data-formatters, to do so)
var selected_tile = null,
selected_tile_map = get_tile_map(), // this would be an image with your tiles
tile_width = 64, // in image-pixels, not canvas/screen-pixels
tile_height = 64, // in image-pixels, not canvas/screen-pixels
num_tiles_x = selected_tile_map.width / tile_width,
num_tiles_y = selected_tile_map.height / tile_height,
select_tile_num_from_map = function (map_px_X, map_px_Y) {
// there are *lots* of ways to do this, but keeping it simple
var tile_y = Math.floor(map_px_Y / tile_height), // 4 = floor(280/64)
tile_x = Math.floor(map_px_X / tile_width ),
tile_num = tile_y * num_tiles_x + tile_x;
// 23 = 4 down * 5 per row + 3 over
return tile_num;
};
// won't go into event-handling and coordinate-normalization
selected_tile_map.onclick = function (evt) {
// these are the coordinates of the click,
//as they relate to the actual image at full scale
map_x, map_y;
selected_tile = select_tile_num_from_map(map_x, map_y);
};
现在您有了一个简单的系统来确定单击了哪个图块。
再说一遍,构建这个的方法有很多,你可以让它更加面向对象,
并制作一个适当的“平铺”数据结构,您希望在整个引擎中读取和使用它。
现在,我只是返回图块的从零开始的数字,从左到右、从上到下读取。
如果每行有 5 个图块,并且有人选择了第二行的第一个图块,那就是图块 #5。
然后,对于“绘画”,您只需要听一下画布的点击声,找出 X 和 Y 是什么,
找出它在世界上的哪个位置,以及它等于哪个数组点。
从那里,你只需转储selected_tile
,仅此而已。
// this might be one long array, like I did with the tile-map and the number of the tile
// or it might be an array of arrays: each inner-array would be a "row",
// and the outer array would keep track of how many rows down you are,
// from the top of the world
var world_map = [],
selected_coordinate = 0,
world_tile_width = 64, // these might be in *canvas* pixels, or "world" pixels
world_tile_height = 64, // this is so you can scale the size of tiles,
// or zoom in and out of the map, etc
world_width = 320,
world_height = 320,
num_world_tiles_x = world_width / world_tile_width,
num_world_tiles_y = world_height / world_tile_height,
get_map_coordinates_from_click = function (world_x, world_y) {
var coord_x = Math.floor(world_px_x / num_world_tiles_x),
coord_y = Math.floor(world_px_y / num_world_tiles_y),
array_coord = coord_y * num_world_tiles_x + coord_x;
return array_coord;
},
set_map_tile = function (index, tile) {
world_map[index] = tile;
};
canvas.onclick = function (evt) {
// convert screen x/y to canvas, and canvas to world
world_px_x, world_px_y;
selected_coordinate = get_map_coordinates_from_click(world_px_x, world_px_y);
set_map_tile(selected_coordinate, selected_tile);
};
正如您所看到的,执行一个操作的过程与执行另一个操作的过程几乎相同(因为它是 - 给定一个坐标集中的 x 和 y,将其转换为另一个比例/集合)。
那么,绘制图块的过程几乎完全相反。
给定世界索引和图块编号,反向工作即可找到世界 x/y 和图块地图 x/y。
您也可以在示例代码中看到该部分。
这种瓷砖绘画是制作 2D 地图的传统方式,无论我们谈论的是《星际争霸》、《塞尔达传说》还是《马里奥兄弟》。
并非所有人都拥有“用瓷砖绘制”编辑器(有些是在文本文件甚至电子表格中手动编写的,以获得正确的间距),但如果您加载《星际争霸》甚至《魔兽争霸 III》(这是3D),然后进入他们的编辑器,瓷砖画家就是你所得到的,这正是暴雪制作这些地图的方式。
补充
基本前提完成后,您现在还需要其他“地图”:
你需要一个碰撞地图来知道你可以/不能在哪些瓷砖上行走,一个实体地图来显示哪里有门,或能量提升或矿物,或敌人生成,或事件-过场动画的触发器...
并非所有这些都需要在与世界地图相同的坐标空间中操作,但它可能会有所帮助。
此外,您可能想要一个更智能的“世界”。
例如,能够在一个关卡中使用多个图块地图……
以及图块编辑器中用于交换图块地图的下拉菜单。
...一种保存图块信息(不仅是 X/Y,还包括有关图块的其他信息)并保存已完成的“地图”数组(填充有图块)的方法。
即使只是复制 JSON,并将其粘贴到自己的文件中......
程序生成
另一种方法,即您之前建议的方法(“知道如何连接岩石、草等”)称为程序生成.
这要困难得多,而且涉及得多。
像《暗黑破坏神》这样的游戏就使用了这一点,这样你每次玩时都会处于不同的随机生成的环境中。 Warframe 是一款 FPS,它使用程序生成来完成同样的事情。
premise:
基本上,您从图块开始,图块不仅仅是图像,图块必须是具有图像和位置的对象,而且还具有可能在其周围的事物的列表。
当你放下一块草时,该草旁边就有可能生成更多草。
草可能会说,在它周围的四个方向中的任何一个方向上,有 10% 的可能性有水,20% 的可能性有岩石,30% 的可能性有泥土,40% 的可能性有更多的草。
当然,事情确实没那么简单(或者可能很简单,如果你错了)。
虽然这是这个想法,但程序生成的棘手部分实际上是确保一切正常运行而不会中断。
限制条件
例如,在该示例中,你不能让悬崖墙出现在高地的内部。它只能出现在上方和右侧有高地、下方和左侧有低地的地方(星际争霸编辑器会自动执行此操作,如您所画)。坡道只能连接有意义的瓷砖。你不能用墙隔开门,或者将世界包裹在河流/湖泊中,从而阻止你移动(或更糟糕的是,阻止你完成关卡)。
pros
如果你能让所有的寻路和约束发挥作用,这对长寿来说真的很棒——不仅可以伪随机生成地形和布局,还可以放置敌人、放置战利品等等。
近 14 年后,人们仍在玩《暗黑破坏神 II》。
cons
当你是一个单人团队时(业余时间碰巧不是数学家/数据科学家),真的很难做到正确。
对于保证地图的乐趣/平衡/竞争来说真的很糟糕......
《星际争霸》永远不可能使用 100% 随机生成来实现公平的游戏玩法。
程序生成可以用作“种子”。
你可以点击“随机化”按钮,看看你得到了什么,然后从那里进行调整和修复,但是会有太多的“平衡”修复,或者写了太多的游戏规则来限制传播,你'最终你会花更多的时间来修理发电机,而不是自己画地图。
有一些教程,学习遗传算法、寻路等等,都是很棒的技能……但是,对于学习制作 2D 自上而下的瓷砖游戏来说,这有点过分了,更确切地说,是在你掌握一两个游戏/引擎之后需要研究的东西。