这里面没有任何逻辑。
如果您检查页面源代码,您会看到最后一个脚本标签(在body中)有一个巨大的瓦片坐标数组。
那个例子中没有展示出一个“智能”系统来找出如何形成形状的魔法。
现在,话虽如此,确实存在这样的东西......但它们绝不简单。
更简单、更可管理的是地图编辑器。
瓦片编辑器
开箱即用:
有很多方法可以做到这一点...... 有免费或便宜的程序,可以让您绘制瓦片,然后输出XML、JSON、CSV或其他给定程序支持/导出的格式。
Tiled (http://mapeditor.org) 就是其中之一。
还有其他的,但Tiled是我能想到的第一个,而且是免费的,实际上相当不错。
优点:
直接的好处是你可以得到一个应用程序,让你加载图像瓦片,并将它们绘制到地图上。
这些应用程序甚至可能支持添加碰撞层和实体层(将敌人放在[2,1],将强化道具放在[3,5],并在熔岩上方放置“伤害玩家”触发器)。
缺点:
...缺点是你需要确切地知道这些文件的格式,以便你可以将它们读入游戏引擎。
现在,这些系统的输出是相对标准化的......所以你可以将那个地图数据插入不同的游戏引擎中(否则有什么意义呢?),虽然游戏引擎并不都使用完全相同的瓦片文件,但大多数好的瓦片编辑器允许导出到几种格式(有些甚至允许您定义自己的格式)。
......因此,另一种选择(或者说,同样的解决方案,只是手工制作),就是创建自己的瓦片编辑器。
自己动手:
您可以像创建绘制瓦片的引擎一样轻松地在Canvas中创建它。
关键的区别是您拥有瓦片地图(就像示例中的tilemap.png一样)
您会选择地图上的一个瓦片(就像在MS Paint中选择颜色一样),然后无论您在哪里单击(或拖动),都会找出与之相关的数组点,并将该索引设置为等于该瓦片。
优点:
天空是无限的;你可以制作任何想要的东西,使它适合任何你想使用的文件格式,并使其处理你想要投入其中的任何疯狂的东西...
缺点:
...当然,这意味着你必须自己制作它,并定义你想要使用的文件格式,并编写处理所有这些疯狂想法的逻辑...
基本实现
虽然我通常会尝试使代码整洁,符合JS范式,但在这里,那将导致大量的代码。
因此,我将尝试指出应该将其拆分为单独的模块。
var selected_tile = null,
selected_tile_map = get_tile_map(),
tile_width = 64,
tile_height = 64,
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) {
var tile_y = Math.floor(map_px_Y / tile_height),
tile_x = Math.floor(map_px_X / tile_width ),
tile_num = tile_y * num_tiles_x + tile_x;
return tile_num;
};
selected_tile_map.onclick = function (evt) {
map_x, map_y;
selected_tile = select_tile_num_from_map(map_x, map_y);
};
现在您有一个简单的系统来确定哪个瓷砖被点击了。
再次说明,有许多构建此系统的方法,您可以使它更加面向对象,并创建一个适合在整个引擎中阅读和使用的“tile”数据结构。
目前,我只返回从左到右、从上到下读取的基于零的瓷砖编号。
如果每行有5个瓷砖,某人选择第二行的第一个瓷砖,那就是第5个瓷砖。
然后,对于“绘制”,您只需要监听画布点击事件,找出X和Y的值,
找出这在世界上的位置以及相应的数组位置。
从那里,您只需倒入selected_tile
的值,就完成了。
var world_map = [],
selected_coordinate = 0,
world_tile_width = 64,
world_tile_height = 64,
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) ,
set_map_tile = function (index, tile) ;
canvas.onclick = function (evt) ;
正如您所看到的,执行其中一个的过程与执行另一个的过程几乎相同(因为它是 - 给定一个坐标系中的 x 和 y,将其转换为另一个比例/集合)。绘制瓷砖的过程几乎完全相反。给定世界索引和瓷砖编号,反向查找世界 x/y 和瓷砖地图 x/y。您可以在示例代码中看到该部分。这种瓷砖绘画是制作2D地图的传统方法,无论我们谈论的是星际争霸、塞尔达还是马里奥兄弟。并非所有游戏都有“用瓷砖绘画”编辑器的便利(有些是手工制作文本文件,甚至是电子表格,以获得正确的间距),但如果您加载星际争霸或甚至魔兽争霸III(这是3D游戏),并进入它们的编辑器,您会发现一个瓷砖绘画器正是您所需要的,也是暴雪制作这些地图的方式。
补充:
完成基本前提后,您现在还需要其他“地图”:您需要一个碰撞地图来知道哪些瓷砖可以/不能行走,一个实体地图来显示门、电源或矿物等位置,或敌人生成点或剧情事件触发器...
并非所有这些都需要在与世界地图相同的坐标空间中操作,但是这可能会有所帮助。
此外,您可能需要一个更智能的“世界”。例如,使用多个瓷砖地图在一个级别中,并在瓷砖编辑器中使用下拉列表来交换瓷砖地图...
一种方法是保存瓷砖信息(不仅是X/Y,还包括关于瓷砖的其他信息),并保存填充瓷砖的完成“地图”数组。
即使只是复制JSON,并将其粘贴到自己的文件中...
程序生成:
另一种方法是您之前提到的方法(“知道如何连接岩石、草等”),称为“程序生成”。这要困难得多,涉及的内容也更多。像《暗黑破坏神》这样的游戏使用此功能,因此每次玩游戏时,您都处于不同的随机生成环境中。《星际战甲》是一款FPS游戏,它使用程序生成来做同样的事情。
前提:
基本上,您从瓷砖开始,而不仅仅是瓷砖作为图像,瓷砖必须是一个具有图像和位置的对象,但也必须具有可能在其周围出现的事物列表。当您放置一片草地时,该草地将具有在其旁边生成更多草地的可能性。草地可能会说有10%的水、20%的岩石、30%的土壤和40%的更多草地,在其四个方向中的任何一个方向上。
当然,实际上并不是那么简单的(如果你错了话就会变得简单)。虽然这是个好主意,但程序生成的棘手之处在于确保一切都能正常运作而不会出问题。
限制条件:例如,在上述例子中,悬崖墙不能出现在高地内部。它只能出现在上方和右侧有高地以及下方和左侧有低地的地方(StarCraft编辑器会自动完成此操作,因为你进行了绘画)。坡道只能连接有意义的瓷砖。你不能封锁门,或用河流/湖泊包围世界以防止移动(更糟糕的是,阻止你完成一个级别)。
优点:如果您能让所有寻路和约束条件都正常工作,那么这对于伸缩性来说非常好,不仅可以伪随机生成地形和布局,还可以生成敌人位置、战利品位置等等。人们仍在玩《暗黑破坏神II》,近14年了。
缺点:如果你是一个单人团队(谁碰巧不是一个数学家/数据科学家),那么很难做到正确。对于保证地图有趣/平衡/具有竞争力来说真的很糟糕。StarCraft永远不能使用100%的随机生成来进行公平的游戏。程序生成可以用作“种子”。你可以点击“随机化”按钮,看看你得到了什么,然后进行微调和修复,但是会有很多修复“平衡”的问题,或者写出很多游戏规则来限制传播,这样你最终会花更多的时间来修复生成器,而不是自己画地图。
虽然有一些教程,学习遗传算法、寻路等都是非常好的技能...... ...但是,为了学习制作2D俯视角瓷砖游戏,这些技能都过于繁琐了,而且在做完一个或两个游戏/引擎之后再去学习这些技能可能更好。