那里没有任何逻辑。
如果您检查页面的源代码,您会看到正文中的最后一个脚本标记有一个巨大的磁贴坐标数组。
在那个例子中没有什么魔法可以展示一个“智能”系统来弄清楚如何形成形状。
现在,就是说,有这样的事情......但它们并不简单。
更简单、更易于管理的是地图编辑器。
平铺编辑器
开箱即用:
有很多方法可以做到这一点...有免费或便宜的程序可以让您绘制瓷砖,然后会吐出 XML 或 JSON 或 CSV 或给定程序支持/导出的任何内容。
平铺(http://mapeditor.org)就是这样一个例子。
还有其他的,但 Tiled 是我能想到的第一个,是免费的,而且实际上相当不错。
优点:
直接的好处是您将获得一个应用程序,该应用程序可让您加载图像图块并将它们绘制到地图中。
这些应用程序甚至可能支持添加碰撞层和实体层(在 [2,1] 处放置一个敌人,在 [3,5] 处放置一个加电装置,并在熔岩上方放置一个“伤害玩家”触发器)。
缺点:
...缺点是您需要准确了解这些文件的格式,以便将它们读入游戏引擎。
现在,这些系统的输出是相对标准化的......因此您可以将地图数据插入不同的游戏引擎(否则有什么意义?),虽然游戏引擎并不都使用完全一致的图块文件同样,大多数优秀的平铺编辑器允许导出为多种格式(有些可以让您定义自己的格式)。
...也就是说,替代方案(或者实际上是相同的解决方案,只是手工制作)是创建自己的图块编辑器。
DIY
您可以在 Canvas 中创建它,就像创建绘制图块的引擎一样容易。
主要区别在于您拥有瓷砖地图(例如 StarCr 中的 tilemap .png... erm... 示例中的“found-art”)。
不是循环遍历数组,查找图块的坐标并在与该索引匹配的世界坐标处绘制它们,您要做的是从地图中选择一个图块(如在 MS Paint 中选择一种颜色),然后在任何地方您单击(或拖动),找出与之相关的数组点,并将该索引设置为等于该图块。
优点:
天空才是极限;你可以制作任何你想要的东西,让它适合你想使用的任何文件格式,让它处理你想扔给它的任何疯狂的东西......
缺点:
...当然,这意味着您必须自己制作并定义要使用的文件格式,并编写处理所有这些滑稽想法的逻辑...
基本实现
虽然我通常会尝试使这个整洁,并且对 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);
};
现在您有了一个简单的系统来确定点击了哪个图块。
同样,有很多方法可以构建它,你可以让它更 OO,
并制作一个适当的“平铺”数据结构,您希望在整个引擎中阅读和使用它。
现在,我只是返回从零开始的磁贴编号,从左到右、从上到下读取。
如果每行有 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,将其转换为另一个比例/集) .
因此,绘制瓷砖的过程几乎完全相反。
给定 world-index 和 tile-number,反向工作以找到 world-x/y 和 tilemap-x/y。
您也可以在示例代码中看到该部分。
这种瓷砖绘画是制作 2D 地图的传统方式,无论我们谈论的是《星际争霸》、《塞尔达》还是《马里奥兄弟》。
并非所有人都拥有“用瓷砖绘制”编辑器的奢侈(有些是在文本文件甚至电子表格中手动调整间距),但如果你加载星际争霸甚至魔兽争霸III(这是3D),然后进入他们的编辑器,你得到的正是瓷砖画家,这正是暴雪制作这些地图的方式。
补充
有了基本前提,您现在还需要其他“地图”:
你需要一个碰撞地图来知道你可以/不能在哪些瓷砖上行走,一个实体地图,以显示哪里有门,或能量提升或矿物,或敌人产卵,或事件-过场动画的触发器...
并非所有这些都需要在与世界地图相同的坐标空间中运行,但它可能会有所帮助。
此外,您可能想要一个更智能的“世界”。
例如,在一个级别中使用多个瓦片地图的能力...
以及用于交换瓷砖地图的瓷砖编辑器中的下拉菜单。
...一种保存图块信息(不仅是 X/Y,还包括有关图块的其他信息)和保存完成的“地图”数组的方法,其中填充了图块。
即使只是复制 JSON,并将其粘贴到自己的文件中...
程序生成
另一种方法,您之前建议的方法(“知道如何连接岩石、草等”)称为程序生成。
这要困难得多,涉及的要多得多。
像暗黑破坏神这样的游戏使用这个,所以你每次玩时都处于一个不同的随机生成的环境中。 Warframe 是一个 FPS,它使用程序生成来做同样的事情。
前提:
基本上,您从瓦片开始,瓦片不仅仅是图像,瓦片必须是具有图像和位置的对象,但也有可能在其周围的事物的列表。
当您放下一块草时,该草可能会在其旁边产生更多的草。
草可能会说,在它周围的四个方向中,有 10% 的机会是水,20% 的机会是岩石,30% 的机会是泥土,还有 40% 的机会是更多的草。
当然,它真的没有那么简单(或者它可能是,如果你错了)。
虽然是这样的想法,但程序生成的棘手部分实际上是确保一切正常运行而不会中断。
约束
例如,在那个例子中,你不能让悬崖墙出现在高地的内部。它只能出现在上方和右侧有高地,下方和左侧有低地的地方(星际争霸编辑器会自动执行此操作,就像您绘制的那样)。坡道只能连接有意义的瓷砖。你不能把门围起来,或者把世界包裹在一个阻止你移动(或者更糟的是,阻止你完成一个关卡)的河流/湖泊中。
专业人士
如果您可以使所有寻路和约束都起作用,那么长寿真的很棒-不仅可以伪随机生成地形和布局,还可以用于放置敌人、放置战利品等。
将近 14 年后,人们仍在玩暗黑破坏神 II。
缺点
当你是一个单人团队(在业余时间碰巧不是数学家/数据科学家)时,真的很难做到正确。
确保地图有趣/平衡/有竞争力真的很糟糕......
星际争霸永远不可能使用 100% 随机生成来实现公平的游戏玩法。
程序生成可以用作“种子”。
你可以点击“随机化”按钮,看看你得到了什么,然后从那里进行调整和修复,但是对于“平衡”会有很多修复,或者为了限制传播而编写了很多游戏规则,你最终会花费更多的时间来修复生成器,而不仅仅是自己绘制地图。
那里有一些教程,学习遗传算法、寻路等都是非常棒的技能......方式过度杀伤力,而是在您获得一两个游戏/引擎后需要考虑的事情。